From ad20f58b415fac65854a9d27b76f677186ac7714 Mon Sep 17 00:00:00 2001 From: kmiola <kmiola@ethz.ch> Date: Sat, 16 Nov 2024 00:46:29 +0000 Subject: [PATCH] implement bill, credit and reimbursement form on one page, connection to backend works, kst and ledgers are added as dropdown --- src/pages/Belegformular.tsx | 341 +++--------------------------- src/pages/Bills.tsx | 411 ++++++++++++++++++++++++++++++++++++ src/pages/CreditPayment.tsx | 322 ++++++++++++++++++++++++++++ src/pages/Reimbursement.tsx | 322 ++++++++++++++++++++++++++++ 4 files changed, 1089 insertions(+), 307 deletions(-) create mode 100644 src/pages/Bills.tsx create mode 100644 src/pages/CreditPayment.tsx create mode 100644 src/pages/Reimbursement.tsx diff --git a/src/pages/Belegformular.tsx b/src/pages/Belegformular.tsx index 45c64dc..8846d49 100644 --- a/src/pages/Belegformular.tsx +++ b/src/pages/Belegformular.tsx @@ -1,322 +1,49 @@ -import React, { useState, useEffect } from 'react'; -import { Container, CircularProgress, Typography, Select, MenuItem, FormControl, InputLabel, Stack, Button, TextField, Alert } from '@mui/material'; -import { kstsReadKsts, reimbursementsCreateReimbursement, ledgersReadLedgers } from '../client/services.gen'; -import { KstsPublic, KstPublic, ReimbursementsCreateReimbursementData, LedgersPublic, LedgerPublic } from '../client/types.gen'; -import { SelectChangeEvent } from '@mui/material/Select'; +import React, { useState } from 'react'; +import { Container, FormControl, InputLabel, Select, MenuItem, Typography, SelectChangeEvent } from '@mui/material'; +import Reimbursement from './Reimbursement'; +import Bills from './Bills'; +import CreditPayment from './CreditPayment'; -import { client } from '../client/services.gen'; +const MainForm: React.FC = () => { + const [selectedType, setSelectedType] = useState<string>(''); -// Configure the client -client.setConfig({ - baseUrl: import.meta.env.VITE_API_BASE_URL, - headers: { - Origin: `localhost`, - }, -}); - -const Belegformular: React.FC = () => { - const [kstItems, setKstItems] = useState<KstPublic[]>([]); - const [ledgerItems, setLedgerItems] = useState<LedgerPublic[]>([]); - const [loadingKst, setLoadingKst] = useState(true); - const [loadingLedger, setLoadingLedger] = useState(true); - const [errorKst, setErrorKst] = useState<string | null>(null); - const [errorLedger, setErrorLedger] = useState<string | null>(null); - - const [formData, setFormData] = useState<ReimbursementsCreateReimbursementData>({ - body: { - creditor: { - kst_id: '', - ledger_id: '', - amount: 0, - accounting_year: 2024, - currency: 'CHF', - comment: '', - qcomment: '', - }, - recipt: '', - creator: '', - recipient: '', - }, - }); - - const [error, setError] = useState<string | null>(null); - const [success, setSuccess] = useState<string | null>(null); - const [validationErrors, setValidationErrors] = useState<Record<string, string>>({}); - - const fetchKsts = async () => { - setErrorKst(null); - setLoadingKst(true); - - try { - const responseKst = await kstsReadKsts(); - - // Check if response is HTML, indicating a possible error - if (typeof responseKst.data !== 'object' || !('items' in responseKst.data)) { - console.error('Unexpected response format:', responseKst.data); - setErrorKst('Received unexpected response format from the server.'); - return; - } - - const KstList = responseKst.data as KstsPublic; - if (!KstList.items) { - setErrorKst('No KSTs found'); - return; - } - setKstItems(KstList.items); - } catch (err) { - setErrorKst('Failed to load KSTs'); - console.error('Error fetching KSTs:', err); - } finally { - setLoadingKst(false); - } + const handleTypeChange = (event: SelectChangeEvent<string>) => { + setSelectedType(event.target.value as string); }; - const fetchLedgers = async () => { - setErrorLedger(null); - setLoadingLedger(true); - - try { - const responseLedger = await ledgersReadLedgers(); - - // Check if response is HTML, indicating a possible error - if (typeof responseLedger.data !== 'object' || !('items' in responseLedger.data)) { - console.error('Unexpected response format:', responseLedger.data); - setErrorLedger('Received unexpected response format from the server.'); - return; - } - - const LedgerList = responseLedger.data as LedgersPublic; - if (!LedgerList.items) { - setErrorLedger('No Ledgers found'); - return; - } - setLedgerItems(LedgerList.items); - } catch (err) { - setErrorLedger('Failed to load Ledgers'); - console.error('Error fetching Ledgers:', err); - } finally { - setLoadingLedger(false); - } - }; - - useEffect(() => { - fetchKsts(); - fetchLedgers(); - }, []); - - const handleChange = (e: React.ChangeEvent<{ name?: string; value: unknown }>) => { - const { name, value } = e.target as HTMLInputElement; - - const keys = name.split('.'); // Handle nested keys like creditor.kst_id - const updatedFormData = { ...formData }; - - let current = updatedFormData.body as any; // Use `any` for dynamic nested updates - for (let i = 0; i < keys.length - 1; i++) { - current = current[keys[i]]; - } - current[keys[keys.length - 1]] = value; - - setFormData(updatedFormData); - }; - - const handleSelectChange = (event: SelectChangeEvent<string>) => { - const { name, value } = event.target; - setFormData((prevFormData) => ({ - ...prevFormData, - body: { - ...prevFormData.body, - creditor: { - ...prevFormData.body.creditor, - [name]: value, - }, - }, - })); - }; - - const validateForm = (): boolean => { - let isValid = true; - const errors: Record<string, string> = {}; - - if (!formData.body.creditor.kst_id.trim()) { - errors['creditor.kst_id'] = 'KST-ID is required'; - isValid = false; - } - if (!formData.body.creditor.ledger_id.trim()) { - errors['creditor.ledger_id'] = 'Ledger-ID is required'; - isValid = false; - } - if (!formData.body.creditor.amount || formData.body.creditor.amount <= 0) { - errors['creditor.amount'] = 'Amount must be greater than zero'; - } - - // Set validation errors if any - setValidationErrors(errors); - return isValid; - }; - - const handleSubmit = async () => { - setError(null); - setSuccess(null); - if (!validateForm()) { - return; - } - try { - await reimbursementsCreateReimbursement(formData); - setSuccess('Reimbursement created successfully!'); - setFormData({ - body: { - creditor: { - kst_id: '', - ledger_id: '', - amount: 0, - accounting_year: 2024, - currency: 'CHF', - comment: '', - qcomment: '', - }, - recipt: '', - creator: '', - recipient: '', - }, - }); - } catch (err) { - setError('Failed to create reimbursement.'); - console.error('Error creating reimbursement:', err); + const renderForm = () => { + switch (selectedType) { + case 'beleg': + return <Reimbursement />; + case 'bill': + return <Bills /> + case 'creditPayment': + return <CreditPayment /> + default: + return null; } }; return ( <Container> <Typography variant="h4" component="h2" gutterBottom> - Create New Reimbursement + Select Form Type </Typography> - - {success && <Alert severity="success">{success}</Alert>} - {error && <Alert severity="error">{error}</Alert>} - - <Stack spacing={2} sx={{ mt: 2 }}> - <FormControl fullWidth required error={!!validationErrors['creditor.kst_id']}> - <InputLabel id="kst-id-label">KST ID</InputLabel> - <Select - labelId="kst-id-label" - name="kst_id" - value={formData.body.creditor.kst_id} - onChange={handleSelectChange} - > - {kstItems.map((item) => ( - <MenuItem key={item.id} value={item.id}> - {item.name_de} - </MenuItem> - ))} - </Select> - {validationErrors['creditor.kst_id'] && ( - <Typography color="error">{validationErrors['creditor.kst_id']}</Typography> - )} - </FormControl> - <FormControl fullWidth required error={!!validationErrors['creditor.ledger_id']}> - <InputLabel id="ledger-id-label">Ledger ID</InputLabel> - <Select - labelId="ledger-id-label" - name="ledger_id" - value={formData.body.creditor.ledger_id} - onChange={handleSelectChange} - > - {ledgerItems.map((item) => ( - <MenuItem key={item.id} value={item.id}> - {item.namede} - </MenuItem> - ))} - </Select> - {validationErrors['creditor.ledger_id'] && ( - <Typography color="error">{validationErrors['creditor.ledger_id']}</Typography> - )} - </FormControl> - <TextField - label="Amount" - name="creditor.amount" - type="number" - value={formData.body.creditor.amount} - onChange={handleChange} - fullWidth - required - error={!!validationErrors['creditor.amount']} - helperText={validationErrors['creditor.amount']} - /> - <TextField - label="Accounting Year" - name="creditor.accounting_year" - type="number" - value={formData.body.creditor.accounting_year} - onChange={handleChange} - fullWidth - required - error={!!validationErrors['creditor.accounting_year']} - helperText={validationErrors['creditor.accounting_year']} - /> - <TextField - label="Currency" - name="creditor.currency" - value={formData.body.creditor.currency} - onChange={handleChange} - fullWidth - required - error={!!validationErrors['creditor.currency']} - helperText={validationErrors['creditor.currency']} - /> - <TextField - label="Comment" - name="creditor.comment" - value={formData.body.creditor.comment} - onChange={handleChange} - fullWidth - error={!!validationErrors['creditor.comment']} - helperText={validationErrors['creditor.comment']} - /> - <TextField - label="QComment" - name="creditor.qcomment" - value={formData.body.creditor.qcomment} - onChange={handleChange} - fullWidth - error={!!validationErrors['creditor.qcomment']} - helperText={validationErrors['creditor.qcomment']} - /> - <TextField - label="Recipt" - name="recipt" - value={formData.body.recipt} - onChange={handleChange} - fullWidth - required - error={!!validationErrors['recipt']} - helperText={validationErrors['recipt']} - /> - <TextField - label="Creator" - name="creator" - value={formData.body.creator} - onChange={handleChange} - fullWidth - required - error={!!validationErrors['creator']} - helperText={validationErrors['creator']} - /> - <TextField - label="Recipient" - name="recipient" - value={formData.body.recipient} - onChange={handleChange} - fullWidth - required - error={!!validationErrors['recipient']} - helperText={validationErrors['recipient']} - /> - <Button onClick={handleSubmit} variant="contained" color="primary"> - Create Reimbursement - </Button> - </Stack> + <FormControl fullWidth> + <InputLabel id="form-type-label">Form Type</InputLabel> + <Select + labelId="form-type-label" + value={selectedType} + onChange={handleTypeChange} + > + <MenuItem value="beleg">Beleg</MenuItem> + <MenuItem value="bill">Bill</MenuItem> + <MenuItem value="creditPayment">Credit Payment</MenuItem> + </Select> + </FormControl> + {renderForm()} </Container> ); }; -export default Belegformular; \ No newline at end of file +export default MainForm; diff --git a/src/pages/Bills.tsx b/src/pages/Bills.tsx new file mode 100644 index 0000000..481133b --- /dev/null +++ b/src/pages/Bills.tsx @@ -0,0 +1,411 @@ +import React, { useState, useEffect } from 'react'; +import { Container, CircularProgress, Typography, Select, MenuItem, FormControl, InputLabel, Stack, Button, TextField, Alert } from '@mui/material'; +import { kstsReadKsts, billsCreateBill, ledgersReadLedgers } from '../client/services.gen'; // Adjust the import path as needed +import { KstsPublic, KstPublic, BillsCreateBillData, LedgersPublic, LedgerPublic } from '../client/types.gen'; + +import { SelectChangeEvent } from '@mui/material/Select'; + +import { client } from '../client/services.gen'; + +// Configure the client +client.setConfig({ + baseUrl: import.meta.env.VITE_API_BASE_URL, + headers: { + Origin: `localhost`, + }, +}); + + + + +const CreateBillForm: React.FC = () => { + + const [kstItems, setKstItems] = useState<KstPublic[]>([]); + const [ledgerItems, setLedgerItems] = useState<LedgerPublic[]>([]); + const [loadingKst, setLoadingKst] = useState(true); + const [loadingLedger, setLoadingLedger] = useState(true); + const [errorKst, setErrorKst] = useState<string | null>(null); + const [errorLedger, setErrorLedger] = useState<string | null>(null); + + + const [formData, setFormData] = useState<BillsCreateBillData>({ + body:{ + creditor: { + kst_id: '', + ledger_id: '', + amount: 0, + accounting_year: 2024, + currency: 'CHF', // Default value, adjust as needed + comment: '', + qcomment: '', + }, + address: { + name: '', + address1: '', + address2: '', + address3: '', + plz: 0, + city: '', + country: '', + }, + reference: 0, + iban: '', + recipt: '', + comment: '', + } + }); + + const [error, setError] = useState<string | null>(null); + const [success, setSuccess] = useState<string | null>(null); + const [validationErrors, setValidationErrors] = useState<Record<string, string>>({}); + + + const fetchKsts = async () => { + setErrorKst(null); + setLoadingKst(true); + + try { + const responseKst = await kstsReadKsts(); + + // Check if response is HTML, indicating a possible error + if (typeof responseKst.data !== 'object' || !('items' in responseKst.data)) { + console.error('Unexpected response format:', responseKst.data); + setErrorKst('Received unexpected response format from the server.'); + return; + } + + const KstList = responseKst.data as KstsPublic; + if (!KstList.items) { + setErrorKst('No KSTs found'); + return; + } + setKstItems(KstList.items); + } catch (err) { + setErrorKst('Failed to load KSTs'); + console.error('Error fetching KSTs:', err); + } finally { + setLoadingKst(false); + } + }; + + const fetchLedgers = async () => { + setErrorLedger(null); + setLoadingLedger(true); + + try { + const responseLedger = await ledgersReadLedgers(); + + // Check if response is HTML, indicating a possible error + if (typeof responseLedger.data !== 'object' || !('items' in responseLedger.data)) { + console.error('Unexpected response format:', responseLedger.data); + setErrorLedger('Received unexpected response format from the server.'); + return; + } + + const LedgerList = responseLedger.data as LedgersPublic; + if (!LedgerList.items) { + setErrorLedger('No Ledgers found'); + return; + } + setLedgerItems(LedgerList.items); + } catch (err) { + setErrorLedger('Failed to load Ledgers'); + console.error('Error fetching Ledgers:', err); + } finally { + setLoadingLedger(false); + } + }; + + useEffect(() => { + fetchKsts(); + fetchLedgers(); + }, []); + + const handleChange = (e: React.ChangeEvent<{ name?: string; value: unknown }>) => { + const { name, value } = e.target as HTMLInputElement; + + const keys = name.split('.'); // Handle nested keys like creditor.kst_id + const updatedFormData = { ...formData }; + + let current = updatedFormData.body as any; // Use `any` for dynamic nested updates + for (let i = 0; i < keys.length - 1; i++) { + current = current[keys[i]]; + } + current[keys[keys.length - 1]] = value; + + setFormData(updatedFormData); + }; + + const handleSelectChange = (event: SelectChangeEvent<string>) => { + const { name, value } = event.target; + setFormData((prevFormData) => ({ + ...prevFormData, + body: { + ...prevFormData.body, + creditor: { + ...prevFormData.body.creditor, + [name]: value, + }, + }, + })); + }; + + const validateForm = (): boolean => { + let isValid = true; + const errors: Record<string, string> = {}; + + if (!formData.body.creditor.kst_id.trim()) { + errors['creditor.kst_id'] = 'KST-ID is required'; + isValid = false; + } + if (!formData.body.creditor.ledger_id.trim()) { + errors['creditor.ledger_id'] = 'Ledger-ID is required'; + isValid = false; + } + if (!formData.body.creditor.amount || formData.body.creditor.amount <= 0) { + errors['creditor.amount'] = 'Amount must be greater than zero'; + } + + // Set validation errors if any + setValidationErrors(errors); + return isValid; + }; + + const handleSubmit = async () => { + setError(null); + setSuccess(null); + if (!validateForm()) { + return; + } + try { + await billsCreateBill(formData); + setSuccess('Reimbursement created successfully!'); + setFormData({ + body:{ + creditor: { + kst_id: '', + ledger_id: '', + amount: 0, + accounting_year: 2024, + currency: 'CHF', // Default value, adjust as needed + comment: '', + qcomment: '', + }, + address: { + name: '', + address1: '', + address2: '', + address3: '', + plz: 0, + city: '', + country: '', + }, + reference: 0, + iban: '', + recipt: '', + comment: '', + } + }); + } catch (err) { + setError('Failed to create bill.'); + console.error('Error creating bill:', err); + } + }; + + return ( + <Container> + <Typography variant="h4" component="h2" gutterBottom> + Create Bill + </Typography> + {success && <Alert severity="success">{success}</Alert>} + {error && <Alert severity="error">{error}</Alert>} + + <Stack spacing={2} sx={{ mt: 2 }}> + <FormControl fullWidth required error={!!validationErrors['creditor.kst_id']}> + <InputLabel id="kst-id-label">KST ID</InputLabel> + <Select + labelId="kst-id-label" + name="kst_id" + value={formData.body.creditor.kst_id} + onChange={handleSelectChange} + > + {kstItems.map((item) => ( + <MenuItem key={item.id} value={item.id}> + {item.name_de} + </MenuItem> + ))} + </Select> + {validationErrors['creditor.kst_id'] && ( + <Typography color="error">{validationErrors['creditor.kst_id']}</Typography> + )} + </FormControl> + <FormControl fullWidth required error={!!validationErrors['creditor.ledger_id']}> + <InputLabel id="ledger-id-label">Ledger ID</InputLabel> + <Select + labelId="ledger-id-label" + name="ledger_id" + value={formData.body.creditor.ledger_id} + onChange={handleSelectChange} + > + {ledgerItems.map((item) => ( + <MenuItem key={item.id} value={item.id}> + {item.namede} + </MenuItem> + ))} + </Select> + {validationErrors['creditor.ledger_id'] && ( + <Typography color="error">{validationErrors['creditor.ledger_id']}</Typography> + )} + </FormControl> + <TextField + label="Amount" + name="creditor.amount" + type="number" + value={formData.body.creditor.amount} + onChange={handleChange} + fullWidth + required + error={!!validationErrors['creditor.amount']} + helperText={validationErrors['creditor.amount']} + /> + <TextField + label="Accounting Year" + name="creditor.accounting_year" + type="number" + value={formData.body.creditor.accounting_year} + onChange={handleChange} + fullWidth + required + error={!!validationErrors['creditor.accounting_year']} + helperText={validationErrors['creditor.accounting_year']} + /> + <TextField + label="Currency" + name="creditor.currency" + value={formData.body.creditor.currency} + onChange={handleChange} + fullWidth + required + error={!!validationErrors['creditor.currency']} + helperText={validationErrors['creditor.currency']} + /> + <TextField + label="Comment" + name="creditor.comment" + value={formData.body.creditor.comment} + onChange={handleChange} + fullWidth + error={!!validationErrors['creditor.comment']} + helperText={validationErrors['creditor.comment']} + /> + <TextField + label="QComment" + name="creditor.qcomment" + value={formData.body.creditor.qcomment} + onChange={handleChange} + fullWidth + margin="normal" + /> + <TextField + label="Address Name" + name="address.name" + value={formData.body.address.name} + onChange={handleChange} + fullWidth + margin="normal" + /> + <TextField + label="Address Line 1" + name="address.address1" + value={formData.body.address.address1} + onChange={handleChange} + fullWidth + margin="normal" + /> + <TextField + label="Address Line 2" + name="address.address2" + value={formData.body.address.address2} + onChange={handleChange} + fullWidth + margin="normal" + /> + <TextField + label="Address Line 3" + name="address.address3" + value={formData.body.address.address3} + onChange={handleChange} + fullWidth + margin="normal" + /> + <TextField + label="Postal Code" + name="address.plz" + type="number" + value={formData.body.address.plz} + onChange={handleChange} + fullWidth + margin="normal" + /> + <TextField + label="City" + name="address.city" + value={formData.body.address.city} + onChange={handleChange} + fullWidth + margin="normal" + /> + <TextField + label="Country" + name="address.country" + value={formData.body.address.country} + onChange={handleChange} + fullWidth + margin="normal" + /> + <TextField + label="Reference" + name="reference" + type="number" + value={formData.body.reference} + onChange={handleChange} + fullWidth + margin="normal" + /> + <TextField + label="IBAN" + name="iban" + value={formData.body.iban} + onChange={handleChange} + fullWidth + margin="normal" + /> + <TextField + label="Recipt" + name="recipt" + value={formData.body.recipt} + onChange={handleChange} + fullWidth + required + error={!!validationErrors['recipt']} + helperText={validationErrors['recipt']} + /> + <TextField + label="Comment" + name="comment" + value={formData.body.comment} + onChange={handleChange} + fullWidth + required + error={!!validationErrors['recipient']} + helperText={validationErrors['recipient']} + /> + <Button onClick={handleSubmit} variant="contained" color="primary"> + Create Bill + </Button> + </Stack> + </Container> + ); +}; + +export default CreateBillForm; \ No newline at end of file diff --git a/src/pages/CreditPayment.tsx b/src/pages/CreditPayment.tsx new file mode 100644 index 0000000..a4862f2 --- /dev/null +++ b/src/pages/CreditPayment.tsx @@ -0,0 +1,322 @@ +import React, { useState, useEffect } from 'react'; +import { Container, CircularProgress, Typography, Select, MenuItem, FormControl, InputLabel, Stack, Button, TextField, Alert } from '@mui/material'; +import { kstsReadKsts, creditPaymentsCreateCreditPayment, ledgersReadLedgers } from '../client/services.gen'; +import { KstsPublic, KstPublic, CreditPaymentsCreateCreditPaymentData, LedgersPublic, LedgerPublic } from '../client/types.gen'; +import { SelectChangeEvent } from '@mui/material/Select'; + +import { client } from '../client/services.gen'; + +// Configure the client +client.setConfig({ + baseUrl: import.meta.env.VITE_API_BASE_URL, + headers: { + Origin: `localhost`, + }, +}); + +const CreditPayment: React.FC = () => { + const [kstItems, setKstItems] = useState<KstPublic[]>([]); + const [ledgerItems, setLedgerItems] = useState<LedgerPublic[]>([]); + const [loadingKst, setLoadingKst] = useState(true); + const [loadingLedger, setLoadingLedger] = useState(true); + const [errorKst, setErrorKst] = useState<string | null>(null); + const [errorLedger, setErrorLedger] = useState<string | null>(null); + + const [formData, setFormData] = useState<CreditPaymentsCreateCreditPaymentData>({ + body: { + creditor: { + kst_id: '', + ledger_id: '', + amount: 0, + accounting_year: 2024, + currency: 'CHF', + comment: '', + qcomment: '', + }, + recipt: '', + card: 'President', + creator: '', + }, + }); + + const [error, setError] = useState<string | null>(null); + const [success, setSuccess] = useState<string | null>(null); + const [validationErrors, setValidationErrors] = useState<Record<string, string>>({}); + + const fetchKsts = async () => { + setErrorKst(null); + setLoadingKst(true); + + try { + const responseKst = await kstsReadKsts(); + + // Check if response is HTML, indicating a possible error + if (typeof responseKst.data !== 'object' || !('items' in responseKst.data)) { + console.error('Unexpected response format:', responseKst.data); + setErrorKst('Received unexpected response format from the server.'); + return; + } + + const KstList = responseKst.data as KstsPublic; + if (!KstList.items) { + setErrorKst('No KSTs found'); + return; + } + setKstItems(KstList.items); + } catch (err) { + setErrorKst('Failed to load KSTs'); + console.error('Error fetching KSTs:', err); + } finally { + setLoadingKst(false); + } + }; + + const fetchLedgers = async () => { + setErrorLedger(null); + setLoadingLedger(true); + + try { + const responseLedger = await ledgersReadLedgers(); + + // Check if response is HTML, indicating a possible error + if (typeof responseLedger.data !== 'object' || !('items' in responseLedger.data)) { + console.error('Unexpected response format:', responseLedger.data); + setErrorLedger('Received unexpected response format from the server.'); + return; + } + + const LedgerList = responseLedger.data as LedgersPublic; + if (!LedgerList.items) { + setErrorLedger('No Ledgers found'); + return; + } + setLedgerItems(LedgerList.items); + } catch (err) { + setErrorLedger('Failed to load Ledgers'); + console.error('Error fetching Ledgers:', err); + } finally { + setLoadingLedger(false); + } + }; + + useEffect(() => { + fetchKsts(); + fetchLedgers(); + }, []); + + const handleChange = (e: React.ChangeEvent<{ name?: string; value: unknown }>) => { + const { name, value } = e.target as HTMLInputElement; + + const keys = name.split('.'); // Handle nested keys like creditor.kst_id + const updatedFormData = { ...formData }; + + let current = updatedFormData.body as any; // Use `any` for dynamic nested updates + for (let i = 0; i < keys.length - 1; i++) { + current = current[keys[i]]; + } + current[keys[keys.length - 1]] = value; + + setFormData(updatedFormData); + }; + + const handleSelectChange = (event: SelectChangeEvent<string>) => { + const { name, value } = event.target; + setFormData((prevFormData) => ({ + ...prevFormData, + body: { + ...prevFormData.body, + creditor: { + ...prevFormData.body.creditor, + [name]: value, + }, + }, + })); + }; + + const validateForm = (): boolean => { + let isValid = true; + const errors: Record<string, string> = {}; + + if (!formData.body.creditor.kst_id.trim()) { + errors['creditor.kst_id'] = 'KST-ID is required'; + isValid = false; + } + if (!formData.body.creditor.ledger_id.trim()) { + errors['creditor.ledger_id'] = 'Ledger-ID is required'; + isValid = false; + } + if (!formData.body.creditor.amount || formData.body.creditor.amount <= 0) { + errors['creditor.amount'] = 'Amount must be greater than zero'; + } + + // Set validation errors if any + setValidationErrors(errors); + return isValid; + }; + + const handleSubmit = async () => { + setError(null); + setSuccess(null); + if (!validateForm()) { + return; + } + try { + await creditPaymentsCreateCreditPayment(formData); + setSuccess('CreditPayment created successfully!'); + setFormData({ + body: { + creditor: { + kst_id: '', + ledger_id: '', + amount: 0, + accounting_year: 2024, + currency: 'CHF', + comment: '', + qcomment: '', + }, + recipt: '', + card: 'President', + creator: '', + }, + }); + } catch (err) { + setError('Failed to create reimbursement.'); + console.error('Error creating reimbursement:', err); + } + }; + + return ( + <Container> + <Typography variant="h4" component="h2" gutterBottom> + Create New CreditPayment + </Typography> + + {success && <Alert severity="success">{success}</Alert>} + {error && <Alert severity="error">{error}</Alert>} + + <Stack spacing={2} sx={{ mt: 2 }}> + <FormControl fullWidth required error={!!validationErrors['creditor.kst_id']}> + <InputLabel id="kst-id-label">KST ID</InputLabel> + <Select + labelId="kst-id-label" + name="kst_id" + value={formData.body.creditor.kst_id} + onChange={handleSelectChange} + > + {kstItems.map((item) => ( + <MenuItem key={item.id} value={item.id}> + {item.name_de} + </MenuItem> + ))} + </Select> + {validationErrors['creditor.kst_id'] && ( + <Typography color="error">{validationErrors['creditor.kst_id']}</Typography> + )} + </FormControl> + <FormControl fullWidth required error={!!validationErrors['creditor.ledger_id']}> + <InputLabel id="ledger-id-label">Ledger ID</InputLabel> + <Select + labelId="ledger-id-label" + name="ledger_id" + value={formData.body.creditor.ledger_id} + onChange={handleSelectChange} + > + {ledgerItems.map((item) => ( + <MenuItem key={item.id} value={item.id}> + {item.namede} + </MenuItem> + ))} + </Select> + {validationErrors['creditor.ledger_id'] && ( + <Typography color="error">{validationErrors['creditor.ledger_id']}</Typography> + )} + </FormControl> + <TextField + label="Amount" + name="creditor.amount" + type="number" + value={formData.body.creditor.amount} + onChange={handleChange} + fullWidth + required + error={!!validationErrors['creditor.amount']} + helperText={validationErrors['creditor.amount']} + /> + <TextField + label="Accounting Year" + name="creditor.accounting_year" + type="number" + value={formData.body.creditor.accounting_year} + onChange={handleChange} + fullWidth + required + error={!!validationErrors['creditor.accounting_year']} + helperText={validationErrors['creditor.accounting_year']} + /> + <TextField + label="Currency" + name="creditor.currency" + value={formData.body.creditor.currency} + onChange={handleChange} + fullWidth + required + error={!!validationErrors['creditor.currency']} + helperText={validationErrors['creditor.currency']} + /> + <TextField + label="Comment" + name="creditor.comment" + value={formData.body.creditor.comment} + onChange={handleChange} + fullWidth + error={!!validationErrors['creditor.comment']} + helperText={validationErrors['creditor.comment']} + /> + <TextField + label="QComment" + name="creditor.qcomment" + value={formData.body.creditor.qcomment} + onChange={handleChange} + fullWidth + error={!!validationErrors['creditor.qcomment']} + helperText={validationErrors['creditor.qcomment']} + /> + <TextField + label="Recipt" + name="recipt" + value={formData.body.recipt} + onChange={handleChange} + fullWidth + required + error={!!validationErrors['recipt']} + helperText={validationErrors['recipt']} + /> + <TextField + label="Creator" + name="creator" + value={formData.body.creator} + onChange={handleChange} + fullWidth + required + error={!!validationErrors['creator']} + helperText={validationErrors['creator']} + /> + <TextField + label="Recipient" + name="recipient" + value={formData.body.card} + onChange={handleChange} + fullWidth + required + error={!!validationErrors['recipient']} + helperText={validationErrors['recipient']} + /> + <Button onClick={handleSubmit} variant="contained" color="primary"> + Create CreditPayment + </Button> + </Stack> + </Container> + ); +}; + +export default CreditPayment; \ No newline at end of file diff --git a/src/pages/Reimbursement.tsx b/src/pages/Reimbursement.tsx new file mode 100644 index 0000000..c75ee7b --- /dev/null +++ b/src/pages/Reimbursement.tsx @@ -0,0 +1,322 @@ +import React, { useState, useEffect } from 'react'; +import { Container, CircularProgress, Typography, Select, MenuItem, FormControl, InputLabel, Stack, Button, TextField, Alert } from '@mui/material'; +import { kstsReadKsts, reimbursementsCreateReimbursement, ledgersReadLedgers } from '../client/services.gen'; +import { KstsPublic, KstPublic, ReimbursementsCreateReimbursementData, LedgersPublic, LedgerPublic } from '../client/types.gen'; +import { SelectChangeEvent } from '@mui/material/Select'; + +import { client } from '../client/services.gen'; + +// Configure the client +client.setConfig({ + baseUrl: import.meta.env.VITE_API_BASE_URL, + headers: { + Origin: `localhost`, + }, +}); + +const Reimbursement: React.FC = () => { + const [kstItems, setKstItems] = useState<KstPublic[]>([]); + const [ledgerItems, setLedgerItems] = useState<LedgerPublic[]>([]); + const [loadingKst, setLoadingKst] = useState(true); + const [loadingLedger, setLoadingLedger] = useState(true); + const [errorKst, setErrorKst] = useState<string | null>(null); + const [errorLedger, setErrorLedger] = useState<string | null>(null); + + const [formData, setFormData] = useState<ReimbursementsCreateReimbursementData>({ + body: { + creditor: { + kst_id: '', + ledger_id: '', + amount: 0, + accounting_year: 2024, + currency: 'CHF', + comment: '', + qcomment: '', + }, + recipt: '', + creator: '', + recipient: '', + }, + }); + + const [error, setError] = useState<string | null>(null); + const [success, setSuccess] = useState<string | null>(null); + const [validationErrors, setValidationErrors] = useState<Record<string, string>>({}); + + const fetchKsts = async () => { + setErrorKst(null); + setLoadingKst(true); + + try { + const responseKst = await kstsReadKsts(); + + // Check if response is HTML, indicating a possible error + if (typeof responseKst.data !== 'object' || !('items' in responseKst.data)) { + console.error('Unexpected response format:', responseKst.data); + setErrorKst('Received unexpected response format from the server.'); + return; + } + + const KstList = responseKst.data as KstsPublic; + if (!KstList.items) { + setErrorKst('No KSTs found'); + return; + } + setKstItems(KstList.items); + } catch (err) { + setErrorKst('Failed to load KSTs'); + console.error('Error fetching KSTs:', err); + } finally { + setLoadingKst(false); + } + }; + + const fetchLedgers = async () => { + setErrorLedger(null); + setLoadingLedger(true); + + try { + const responseLedger = await ledgersReadLedgers(); + + // Check if response is HTML, indicating a possible error + if (typeof responseLedger.data !== 'object' || !('items' in responseLedger.data)) { + console.error('Unexpected response format:', responseLedger.data); + setErrorLedger('Received unexpected response format from the server.'); + return; + } + + const LedgerList = responseLedger.data as LedgersPublic; + if (!LedgerList.items) { + setErrorLedger('No Ledgers found'); + return; + } + setLedgerItems(LedgerList.items); + } catch (err) { + setErrorLedger('Failed to load Ledgers'); + console.error('Error fetching Ledgers:', err); + } finally { + setLoadingLedger(false); + } + }; + + useEffect(() => { + fetchKsts(); + fetchLedgers(); + }, []); + + const handleChange = (e: React.ChangeEvent<{ name?: string; value: unknown }>) => { + const { name, value } = e.target as HTMLInputElement; + + const keys = name.split('.'); // Handle nested keys like creditor.kst_id + const updatedFormData = { ...formData }; + + let current = updatedFormData.body as any; // Use `any` for dynamic nested updates + for (let i = 0; i < keys.length - 1; i++) { + current = current[keys[i]]; + } + current[keys[keys.length - 1]] = value; + + setFormData(updatedFormData); + }; + + const handleSelectChange = (event: SelectChangeEvent<string>) => { + const { name, value } = event.target; + setFormData((prevFormData) => ({ + ...prevFormData, + body: { + ...prevFormData.body, + creditor: { + ...prevFormData.body.creditor, + [name]: value, + }, + }, + })); + }; + + const validateForm = (): boolean => { + let isValid = true; + const errors: Record<string, string> = {}; + + if (!formData.body.creditor.kst_id.trim()) { + errors['creditor.kst_id'] = 'KST-ID is required'; + isValid = false; + } + if (!formData.body.creditor.ledger_id.trim()) { + errors['creditor.ledger_id'] = 'Ledger-ID is required'; + isValid = false; + } + if (!formData.body.creditor.amount || formData.body.creditor.amount <= 0) { + errors['creditor.amount'] = 'Amount must be greater than zero'; + } + + // Set validation errors if any + setValidationErrors(errors); + return isValid; + }; + + const handleSubmit = async () => { + setError(null); + setSuccess(null); + if (!validateForm()) { + return; + } + try { + await reimbursementsCreateReimbursement(formData); + setSuccess('Reimbursement created successfully!'); + setFormData({ + body: { + creditor: { + kst_id: '', + ledger_id: '', + amount: 0, + accounting_year: 2024, + currency: 'CHF', + comment: '', + qcomment: '', + }, + recipt: '', + creator: '', + recipient: '', + }, + }); + } catch (err) { + setError('Failed to create reimbursement.'); + console.error('Error creating reimbursement:', err); + } + }; + + return ( + <Container> + <Typography variant="h4" component="h2" gutterBottom> + Create New Reimbursement + </Typography> + + {success && <Alert severity="success">{success}</Alert>} + {error && <Alert severity="error">{error}</Alert>} + + <Stack spacing={2} sx={{ mt: 2 }}> + <FormControl fullWidth required error={!!validationErrors['creditor.kst_id']}> + <InputLabel id="kst-id-label">KST ID</InputLabel> + <Select + labelId="kst-id-label" + name="kst_id" + value={formData.body.creditor.kst_id} + onChange={handleSelectChange} + > + {kstItems.map((item) => ( + <MenuItem key={item.id} value={item.id}> + {item.name_de} + </MenuItem> + ))} + </Select> + {validationErrors['creditor.kst_id'] && ( + <Typography color="error">{validationErrors['creditor.kst_id']}</Typography> + )} + </FormControl> + <FormControl fullWidth required error={!!validationErrors['creditor.ledger_id']}> + <InputLabel id="ledger-id-label">Ledger ID</InputLabel> + <Select + labelId="ledger-id-label" + name="ledger_id" + value={formData.body.creditor.ledger_id} + onChange={handleSelectChange} + > + {ledgerItems.map((item) => ( + <MenuItem key={item.id} value={item.id}> + {item.namede} + </MenuItem> + ))} + </Select> + {validationErrors['creditor.ledger_id'] && ( + <Typography color="error">{validationErrors['creditor.ledger_id']}</Typography> + )} + </FormControl> + <TextField + label="Amount" + name="creditor.amount" + type="number" + value={formData.body.creditor.amount} + onChange={handleChange} + fullWidth + required + error={!!validationErrors['creditor.amount']} + helperText={validationErrors['creditor.amount']} + /> + <TextField + label="Accounting Year" + name="creditor.accounting_year" + type="number" + value={formData.body.creditor.accounting_year} + onChange={handleChange} + fullWidth + required + error={!!validationErrors['creditor.accounting_year']} + helperText={validationErrors['creditor.accounting_year']} + /> + <TextField + label="Currency" + name="creditor.currency" + value={formData.body.creditor.currency} + onChange={handleChange} + fullWidth + required + error={!!validationErrors['creditor.currency']} + helperText={validationErrors['creditor.currency']} + /> + <TextField + label="Comment" + name="creditor.comment" + value={formData.body.creditor.comment} + onChange={handleChange} + fullWidth + error={!!validationErrors['creditor.comment']} + helperText={validationErrors['creditor.comment']} + /> + <TextField + label="QComment" + name="creditor.qcomment" + value={formData.body.creditor.qcomment} + onChange={handleChange} + fullWidth + error={!!validationErrors['creditor.qcomment']} + helperText={validationErrors['creditor.qcomment']} + /> + <TextField + label="Recipt" + name="recipt" + value={formData.body.recipt} + onChange={handleChange} + fullWidth + required + error={!!validationErrors['recipt']} + helperText={validationErrors['recipt']} + /> + <TextField + label="Creator" + name="creator" + value={formData.body.creator} + onChange={handleChange} + fullWidth + required + error={!!validationErrors['creator']} + helperText={validationErrors['creator']} + /> + <TextField + label="Recipient" + name="recipient" + value={formData.body.recipient} + onChange={handleChange} + fullWidth + required + error={!!validationErrors['recipient']} + helperText={validationErrors['recipient']} + /> + <Button onClick={handleSubmit} variant="contained" color="primary"> + Create Reimbursement + </Button> + </Stack> + </Container> + ); +}; + +export default Reimbursement; \ No newline at end of file -- GitLab