mirror of
https://github.com/tomru/pfadi-bussle.git
synced 2026-03-03 06:27:11 +01:00
265 lines
8.6 KiB
TypeScript
265 lines
8.6 KiB
TypeScript
import React, { useEffect, useState } from 'react'
|
|
import Layout from '../../../../components/layout'
|
|
import Input from '../../../../components/input'
|
|
import Select from '../../../../components/select'
|
|
import { Booking } from '../../../../db/booking'
|
|
import { BILL_STATUS, MILAGE_TARIFS } from '../../../../db/enums'
|
|
import { getMilageMax } from '../../../../db/index'
|
|
import { daysFormatFrontend } from '../../../../helpers/date'
|
|
import { getBillTotal, createBill, patchBill } from '../../../../helpers/bill'
|
|
import { getBookingStatus } from '../../../../helpers/booking'
|
|
import { getServerSideBooking } from '../../../../lib/getServerSideProps'
|
|
|
|
export const getServerSideProps = async (context) => {
|
|
const milageMax = await getMilageMax()
|
|
const serverSideBookingProps = await getServerSideBooking(context)
|
|
return {
|
|
props: {
|
|
...serverSideBookingProps.props,
|
|
milageMax,
|
|
},
|
|
};
|
|
}
|
|
|
|
const milageTarifOptions = Object.values(MILAGE_TARIFS).map((tarif) => {
|
|
return (
|
|
<option value={tarif} key={tarif}>
|
|
{getTarifLabel(tarif)}
|
|
</option>
|
|
)
|
|
})
|
|
|
|
const billStatusOptions = Object.values(BILL_STATUS).map((status) => {
|
|
return (
|
|
<option value={status} key={status}>
|
|
{getBillStatusLabel(status)}
|
|
</option>
|
|
)
|
|
})
|
|
|
|
function getTarifLabel(tarif: MILAGE_TARIFS) {
|
|
switch (tarif) {
|
|
case MILAGE_TARIFS.EXTERN:
|
|
return 'Extern'
|
|
case MILAGE_TARIFS.INTERN:
|
|
return 'Intern'
|
|
case MILAGE_TARIFS.NOCHARGE:
|
|
return 'Frei von Kosten'
|
|
default:
|
|
return 'Keine'
|
|
}
|
|
}
|
|
|
|
function getBillStatusLabel(status: BILL_STATUS) {
|
|
switch (status) {
|
|
case BILL_STATUS.UNINVOICED:
|
|
return 'Nicht gestellt'
|
|
case BILL_STATUS.INVOICED:
|
|
return 'Gestellt'
|
|
case BILL_STATUS.PAID:
|
|
return 'Bezahlt'
|
|
default:
|
|
return 'Unbekannt!!!'
|
|
}
|
|
}
|
|
|
|
export default function BookingBillPage({
|
|
booking: bookingProp,
|
|
milageMax,
|
|
}: {
|
|
booking: Booking
|
|
milageMax: number
|
|
}) {
|
|
const [booking, setBooking] = useState(bookingProp)
|
|
const [milageStart, setMilageStart] = useState(
|
|
booking?.bill?.milageStart || milageMax
|
|
)
|
|
const [milageEnd, setMilageEnd] = useState(booking?.bill?.milageEnd)
|
|
const [tarif, setTarif] = useState(
|
|
booking?.bill?.tarif || MILAGE_TARIFS.EXTERN
|
|
)
|
|
const [status, setStatus] = useState(booking?.bill?.status)
|
|
const [additionalCosts, setAdditionalCosts] = useState([])
|
|
const [storingInProgress, setStoringInProgress] = useState(false)
|
|
const [storingError, setStoringError] = useState(null)
|
|
const milage =
|
|
(0 < milageStart && milageStart < milageEnd && milageEnd - milageStart) || 0
|
|
const total = getBillTotal({ tarif, milage, additionalCosts })
|
|
|
|
// in case the props change, update the internal state
|
|
useEffect(() => setBooking(bookingProp), [bookingProp])
|
|
|
|
const onSubmit = async (event: React.FormEvent<HTMLFormElement>) => {
|
|
event.preventDefault()
|
|
setStoringInProgress(true)
|
|
setStoringError(null)
|
|
|
|
try {
|
|
const saveBill = !!booking.bill ? createBill : patchBill
|
|
const bill = await saveBill(booking.uuid, {
|
|
milageStart,
|
|
milageEnd,
|
|
milage,
|
|
tarif,
|
|
status,
|
|
additionalCosts,
|
|
})
|
|
|
|
booking.bill = bill
|
|
setBooking(booking)
|
|
} catch (error) {
|
|
setStoringError('Buchung konnte nicht gespeichert werden!')
|
|
console.error('Failed to store booking', error)
|
|
}
|
|
setStoringInProgress(false)
|
|
}
|
|
|
|
const onAddAdditionalCost = function (
|
|
event: React.MouseEvent<HTMLButtonElement>
|
|
) {
|
|
event.preventDefault()
|
|
setAdditionalCosts([...additionalCosts, { name: '', value: 0 }])
|
|
}
|
|
|
|
const onRemoveAdditionalCost = function (
|
|
event: React.MouseEvent<HTMLButtonElement>,
|
|
index: number
|
|
) {
|
|
event.preventDefault()
|
|
setAdditionalCosts([
|
|
...additionalCosts.slice(0, index),
|
|
...additionalCosts.slice(index + 1),
|
|
])
|
|
}
|
|
|
|
return (
|
|
<Layout>
|
|
{booking && (
|
|
<form className="w-full" onSubmit={onSubmit}>
|
|
<div>
|
|
<strong>Buchungszeitraum:</strong>{' '}
|
|
{daysFormatFrontend(booking.days)}
|
|
</div>
|
|
<div>
|
|
<strong>Bucher:</strong> {booking.name}
|
|
</div>
|
|
<div>
|
|
<strong>Buchungsstatus:</strong>{' '}
|
|
{getBookingStatus(booking.status)}
|
|
</div>
|
|
<div>
|
|
<Input
|
|
label="Anfangskilometer"
|
|
name="milageStart"
|
|
required
|
|
value={milageStart}
|
|
type="number"
|
|
onChange={(e: React.ChangeEvent<React.ElementRef<'input'>>) =>
|
|
setMilageStart(Number(e.target.value))
|
|
}
|
|
/>
|
|
<Input
|
|
label="Endkilometer"
|
|
name="milageEnd"
|
|
required
|
|
value={milageEnd}
|
|
type="number"
|
|
onChange={(e: React.ChangeEvent<React.ElementRef<'input'>>) =>
|
|
setMilageEnd(Number(e.target.value))
|
|
}
|
|
/>
|
|
<Input label="Gefahren" name="milage" readOnly value={milage} />
|
|
<Select
|
|
label="Rate"
|
|
name="tarif"
|
|
value={tarif}
|
|
onChange={(e) => setTarif(e.target.value as MILAGE_TARIFS)}
|
|
>
|
|
{milageTarifOptions}
|
|
</Select>
|
|
<div className="mb-3">
|
|
<button
|
|
className="ibtn btn-gray mr-3"
|
|
onClick={onAddAdditionalCost}
|
|
title="Zusätzliche Kosten hinzufügen"
|
|
>
|
|
+
|
|
</button>
|
|
<label className="flabel inline">Zusätzliche Kosten</label>
|
|
</div>
|
|
{additionalCosts.map((_, index) => {
|
|
return (
|
|
<>
|
|
<div className="mb-3" key={`label${index}`}>
|
|
<button
|
|
className="ibtn btn-gray mr-3"
|
|
onClick={(event) =>
|
|
onRemoveAdditionalCost(event, index)
|
|
}
|
|
title="Entfernen"
|
|
>
|
|
-
|
|
</button>
|
|
<label className="flabel inline">{`Kostenpunkt ${index + 1
|
|
}`}</label>
|
|
</div>
|
|
<div className="ml-10 mb-3" key={`input{index}`}>
|
|
<Input
|
|
label={`Name`}
|
|
name={`additionalCostName${index}`}
|
|
key={`additionalCostName${index}`}
|
|
value={additionalCosts[index].name}
|
|
onChange={(event) => {
|
|
const newAdditonalCosts = [...additionalCosts]
|
|
newAdditonalCosts[index] = {
|
|
value: newAdditonalCosts[index].value,
|
|
name: event.target.value,
|
|
}
|
|
setAdditionalCosts(newAdditonalCosts)
|
|
}}
|
|
/>
|
|
<Input
|
|
label={`Betrag`}
|
|
name={`additionalCostValue${index}`}
|
|
key={`additionalCostValue${index}`}
|
|
value={additionalCosts[index].value}
|
|
type="number"
|
|
onChange={(event) => {
|
|
const newAdditonalCosts = [...additionalCosts]
|
|
newAdditonalCosts[index] = {
|
|
name: newAdditonalCosts[index].name,
|
|
value: Number(event.target.value),
|
|
}
|
|
setAdditionalCosts(newAdditonalCosts)
|
|
}}
|
|
/>
|
|
</div>
|
|
</>
|
|
)
|
|
})}
|
|
<Input label="Summe" name="total" readOnly value={total} />
|
|
</div>
|
|
<Select
|
|
label="Status"
|
|
name={status}
|
|
value={status}
|
|
onChange={(e) => setStatus(e.target.value as BILL_STATUS)}
|
|
>
|
|
{billStatusOptions}
|
|
</Select>
|
|
{storingError && (
|
|
<div className="error-message flex-grow mt-6">{storingError}</div>
|
|
)}
|
|
<button
|
|
type="submit"
|
|
className="btn btn-blue mt-3"
|
|
disabled={storingInProgress}
|
|
>
|
|
Rechnung {!!booking.bill ? 'Updaten' : 'Erstellen'}
|
|
</button>
|
|
</form>
|
|
)}
|
|
</Layout>
|
|
)
|
|
}
|