diff --git a/components/header.tsx b/components/header.tsx index d6161b6..676a1a0 100644 --- a/components/header.tsx +++ b/components/header.tsx @@ -1,10 +1,6 @@ import React from 'react' -export default function Header({ - label = 'Pfadi Bussle Buchen', -}: { - label?: string -}) { +export default function Header({ label = 'Pfadi Bussle' }: { label?: string }) { return (

{label}

diff --git a/db/bill.ts b/db/bill.ts index b4d8f70..468f1dc 100644 --- a/db/bill.ts +++ b/db/bill.ts @@ -1,7 +1,8 @@ import * as mongoose from 'mongoose' -import { BILL_STATUS, MILAGE_RATES, getMilageRateValue } from './enums' +import { BILL_STATUS, MILAGE_TARIFS } from './enums' +import { getBillTotal } from '../helpers/bill' -export interface AdditionalCosts { +export interface AdditionalCost { name: string value: number } @@ -12,9 +13,9 @@ export interface BillDocument milageStart: number milageEnd: number milage?: number - rate: MILAGE_RATES + tarif: MILAGE_TARIFS status: BILL_STATUS - additionalCosts: AdditionalCosts[] + additionalCosts: AdditionalCost[] } export interface BillModel extends mongoose.Model {} @@ -46,10 +47,10 @@ const BillSchema = new mongoose.Schema( message: (props) => `${props.value} is smaller than milageStart!`, }, }, - rate: { + tarif: { type: String, - enum: Object.values(MILAGE_RATES), - default: MILAGE_RATES.EXTERN_LTE_200, + enum: Object.values(MILAGE_TARIFS), + default: MILAGE_TARIFS.EXTERN, required: true, }, additionalCosts: [ @@ -79,13 +80,7 @@ BillSchema.virtual('milage').get(function () { BillSchema.virtual('total').get(function () { const bill = this as BillDocument - const milageCosts = - Math.round(bill.milage * getMilageRateValue(bill.rate) * 100) / 100 - const additionalCostSum = bill.additionalCosts - .map(({ value }) => value) - .reduce((acc, value) => acc + value, 0) - - return milageCosts + additionalCostSum + return getBillTotal(bill) }) export default mongoose.models.Bill || diff --git a/db/enums.ts b/db/enums.ts index 5868e48..da6040c 100644 --- a/db/enums.ts +++ b/db/enums.ts @@ -11,30 +11,8 @@ export enum BILL_STATUS { PAID = 'paid', } -export enum MILAGE_RATES { - INTERN_LTE_200 = 'intern_lte_200_km', - INTERN_201_1000 = 'intern_201_1000_km', - INTERN_1001_2000 = 'intern_1001_2000_km', - INTERN_GTE_2001 = 'intern_gte_2001_km', - EXTERN_LTE_200 = 'extern_lte_200_km', - EXTERN_201_1000 = 'extern_201_1000_km', - EXTERN_1001_2000 = 'extern_1001_2000_km', - EXTERN_GTE_2001 = 'extern_gte_2001_km', - FREE_OF_CHARGE = 'free_of_charge', -} - -const rates = { - [MILAGE_RATES.INTERN_LTE_200]: 0.37, - [MILAGE_RATES.INTERN_201_1000]: 0.22, - [MILAGE_RATES.INTERN_1001_2000]: 0.15, - [MILAGE_RATES.INTERN_GTE_2001]: 0.13, - [MILAGE_RATES.EXTERN_LTE_200]: 0.42, - [MILAGE_RATES.EXTERN_201_1000]: 0.25, - [MILAGE_RATES.EXTERN_1001_2000]: 0.2, - [MILAGE_RATES.EXTERN_GTE_2001]: 0.18, - [MILAGE_RATES.FREE_OF_CHARGE]: 0, -} - -export function getMilageRateValue(milageRate: MILAGE_RATES): number { - return rates[milageRate] +export enum MILAGE_TARIFS { + INTERN = 'intern', + EXTERN = 'extern', + NOCHARGE = 'nocharge', } diff --git a/helpers/bill.ts b/helpers/bill.ts new file mode 100644 index 0000000..d809072 --- /dev/null +++ b/helpers/bill.ts @@ -0,0 +1,65 @@ +import { MILAGE_TARIFS } from '../db/enums' +import { AdditionalCost } from '../db/bill' + +function roundToCent(amount: number) { + return Math.round(amount * 100) / 100 +} + +export function getMilageCosts(tarif: MILAGE_TARIFS, km: number): number { + if (tarif === MILAGE_TARIFS.NOCHARGE) { + return 0 + } + + if (km <= 0) { + return 0 + } + + let rate: number + + if (tarif === MILAGE_TARIFS.EXTERN) { + if (km <= 200) { + rate = 0.42 + } else if (km <= 1000) { + rate = 0.25 + } else if (km <= 2000) { + rate = 0.2 + } else { + rate = 0.18 + } + } + + if (tarif === MILAGE_TARIFS.INTERN) { + if (km <= 200) { + rate = 0.37 + } else if (km <= 1000) { + rate = 0.22 + } else if (km <= 2000) { + rate = 0.15 + } else { + rate = 0.13 + } + } + + if (rate === undefined) { + throw Error('Unable to determine rate') + } + + return roundToCent(km * rate) +} + +export function getBillTotal({ + tarif, + milage, + additionalCosts, +}: { + tarif: MILAGE_TARIFS + milage?: number + additionalCosts: AdditionalCost[] +}): number { + const milageCosts = getMilageCosts(tarif, milage) + const additionalCostsSum = additionalCosts + .map(({ value }) => value) + .reduce((acc: number, value: number) => acc + value, 0) + + return roundToCent(milageCosts + additionalCostsSum) +} diff --git a/pages/booking/[uuid]/bill.tsx b/pages/booking/[uuid]/bill.tsx index 2ac9d0a..1400ef7 100644 --- a/pages/booking/[uuid]/bill.tsx +++ b/pages/booking/[uuid]/bill.tsx @@ -4,20 +4,17 @@ import Footer from '../../../components/footer' import Header from '../../../components/header' import Input from '../../../components/input' import Select from '../../../components/select' -import { AdditionalCosts, BillDocument } from '../../../db/bill' +import { AdditionalCost, BillDocument } from '../../../db/bill' import { BookingDocument } from '../../../db/booking' -import { - BILL_STATUS, - MILAGE_RATES, - getMilageRateValue, -} from '../../../db/enums' +import { BILL_STATUS, MILAGE_TARIFS } from '../../../db/enums' import { getBookingByUUID, getMilageMax } from '../../../db/index' import { dateFormatFrontend } from '../../../helpers/date' +import { getBillTotal } from '../../../helpers/bill' -const milageRateOptions = Object.values(MILAGE_RATES).map((rate) => { +const milageTarifOptions = Object.values(MILAGE_TARIFS).map((tarif) => { return ( - ) }) @@ -54,26 +51,14 @@ export const getServerSideProps: GetServerSideProps = async (context) => { } } -function getRateLabel(rate: MILAGE_RATES) { - switch (rate) { - case MILAGE_RATES.INTERN_LTE_200: - return 'Intern bis zu 200km' - case MILAGE_RATES.INTERN_201_1000: - return 'Intern 201-1.000km' - case MILAGE_RATES.INTERN_1001_2000: - return 'Intern 1.001-2.000km' - case MILAGE_RATES.INTERN_GTE_2001: - return 'Intern ab 2.001km' - case MILAGE_RATES.EXTERN_LTE_200: - return 'Extern bis zu 200km' - case MILAGE_RATES.EXTERN_201_1000: - return 'Extern 201-1.000km' - case MILAGE_RATES.EXTERN_1001_2000: - return 'Extern 1.001-2.000km' - case MILAGE_RATES.EXTERN_GTE_2001: - return 'Extern ab 2.001km' - case MILAGE_RATES.FREE_OF_CHARGE: - return 'Frei' +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' } @@ -98,8 +83,8 @@ async function saveBill( milageStart: number milageEnd: number milage?: number - rate: MILAGE_RATES - additionalCosts?: AdditionalCosts[] + tarif: MILAGE_TARIFS + additionalCosts: AdditionalCost[] status: BILL_STATUS } ): Promise { @@ -129,17 +114,16 @@ export default function BillPage({ booking.bill?.milageStart || milageMax ) const [milageEnd, setMilageEnd] = useState(booking.bill?.milageEnd) - const [rate, setRate] = useState( - booking.bill?.rate || MILAGE_RATES.EXTERN_LTE_200 + 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 = - Math.round(milage && rate && milage * getMilageRateValue(rate) * 100) / - 100 || 0 + const total = getBillTotal({ tarif, milage, additionalCosts }) // in case the props change, update the internal state useEffect(() => setBooking(bookingProp), [bookingProp]) @@ -154,8 +138,9 @@ export default function BillPage({ milageStart, milageEnd, milage, - rate, + tarif, status, + additionalCosts, }) booking.bill = bill @@ -167,11 +152,29 @@ export default function BillPage({ setStoringInProgress(false) } + const onAddAdditionalCost = function ( + event: React.MouseEvent + ) { + event.preventDefault() + setAdditionalCosts([...additionalCosts, { name: '', value: 0 }]) + } + + const onRemoveAdditionalCost = function ( + event: React.MouseEvent, + index: number + ) { + event.preventDefault() + setAdditionalCosts([ + ...additionalCosts.slice(0, index), + ...additionalCosts.slice(index + 1), + ]) + } + return (
-

Pfadi Bussle Buchung

+

Abrechnung

Buchungszeitraum:{' '} @@ -205,10 +208,10 @@ export default function BillPage({ - +
+ + +
+ {additionalCosts.map((_, index) => { + return ( + <> +
+ + +
+
+ { + const newAdditonalCosts = [...additionalCosts] + newAdditonalCosts[index] = { + value: newAdditonalCosts[index].value, + name: event.target.value, + } + setAdditionalCosts(newAdditonalCosts) + }} + /> + { + const newAdditonalCosts = [...additionalCosts] + newAdditonalCosts[index] = { + name: newAdditonalCosts[index].name, + value: Number(event.target.value), + } + setAdditionalCosts(newAdditonalCosts) + }} + /> +
+ + ) + })} +
{storingError && (
{storingError}
diff --git a/pages/booking/[uuid]/index.tsx b/pages/booking/[uuid]/index.tsx index a018bf3..d8c2a81 100644 --- a/pages/booking/[uuid]/index.tsx +++ b/pages/booking/[uuid]/index.tsx @@ -94,7 +94,7 @@ export default function ShowBooking({
-

Ihre Pfadi Bussle Buchung

+

Ihre Buchung

Buchungsstatus: {getBookingStatus(booking)}
diff --git a/styles/index.css b/styles/index.css index 6aa7a31..12490f1 100644 --- a/styles/index.css +++ b/styles/index.css @@ -60,7 +60,7 @@ } .ibtn { - @apply py-2 px-2 text-gray-400 w-10 h-10; + @apply p-1 text-gray-400 text-xs w-6 h-6 rounded; } .ibtn:hover {