mirror of
https://github.com/tomru/pfadi-bussle.git
synced 2026-03-03 06:27:11 +01:00
It also brings the problem of consolidating bookers over multiple bookings. The amount of data is not justifying having it in an own entity
286 lines
9.0 KiB
TypeScript
286 lines
9.0 KiB
TypeScript
import React, { useEffect, useState } from 'react'
|
|
import Footer from '../../../../components/footer'
|
|
import Header from '../../../../components/header'
|
|
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 withSession, {
|
|
isAdminSession,
|
|
redirectToLogin,
|
|
} from '../../../../lib/session'
|
|
import { getServerSideBooking } from '../../../../lib/getServerSideProps'
|
|
|
|
export const getServerSideProps = withSession(async (context) => {
|
|
const { req, res } = context
|
|
|
|
const adminUser = isAdminSession(req)
|
|
|
|
if (!adminUser) {
|
|
redirectToLogin(req, res)
|
|
return { props: {} }
|
|
}
|
|
|
|
const milageMax = await getMilageMax()
|
|
const serverSideBookingProps = await getServerSideBooking(context)
|
|
return {
|
|
props: {
|
|
...serverSideBookingProps.props,
|
|
milageMax,
|
|
user: adminUser,
|
|
},
|
|
}
|
|
})
|
|
|
|
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 (
|
|
<>
|
|
<Header />
|
|
<main className="main">
|
|
{booking && (
|
|
<form className="form" 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>
|
|
)}
|
|
</main>
|
|
|
|
<Footer />
|
|
</>
|
|
)
|
|
}
|