import React, { useState } from 'react'; import { withForm, T, RichSelect, Modal, Form as FormComponent, FieldSet, Label, ErrorFor, } from 'components'; import { useSettings, useRouter, useEffectOnce } from 'hooks'; import { formatTime, formatDate } from 'helpers/time'; import { hexId } from 'helpers/conversion'; import Form from 'helpers/form'; import t from './translations.yml'; class AppointmentForm extends Form { validate() { const errors = {}; if (this.data.date === undefined) errors.date = this.settings.t( t, 'new-appointment.please-enter-date' ); else if (this.data.time === undefined) errors.time = this.settings.t( t, 'new-appointment.please-enter-time' ); else { this.data.timestamp = new Date( `${this.data.date} ${this.data.time}` ); if (this.data.timestamp < new Date()) errors.date = this.settings.t(t, 'new-appointment.in-the-past'); // we allow appointments max. 30 days in the future if ( this.data.timestamp > new Date(new Date().getTime() + 1000 * 60 * 60 * 24 * 30) ) errors.date = this.settings.t( t, 'new-appointment.too-far-in-the-future' ); } if (this.data.slots > 50) { errors.slots = this.settings.t(t, 'new-appointment.too-many-slots'); } if (this.data.slots < 1) { errors.slots = this.settings.t(t, 'new-appointment.too-few-slots'); } return errors; } } const NewAppointment = withForm( ({ route, action, id, form: { valid, error, data, set, reset } }) => { let actionUrl = ''; const settings = useSettings(); const router = useRouter(); if (action !== undefined) actionUrl = `/${action}`; if (id !== undefined) actionUrl += `/view/${id}`; const [saving, setSaving] = useState(false); const cancel = () => router.navigateToUrl(`/provider/schedule${actionUrl}`); let appointment; if (id !== undefined) appointment = appointments.find((app) => hexId(app.id) === id); const save = () => { let action; setSaving(true); // we remove unnecessary fields like 'time' and 'date' delete data.time; delete data.date; if (appointment !== undefined) action = updateAppointmentAction; else action = createAppointmentAction; const promise = action(data, appointment); promise.finally(() => setSaving(false)); promise.then(() => { // we reload the appointments openAppointmentsAction(); // and we go back to the schedule view router.navigateToUrl(`/provider/schedule${actionUrl}`); }); }; useEffectOnce(() => { if (appointment !== undefined) { const appointmentData = { time: formatTime(appointment.timestamp), date: formatDate(appointment.timestamp), slots: appointment.slots, duration: appointment.duration, }; for (const [_, v] of Object.entries(properties)) { for (const [kk, _] of Object.entries(v.values)) { if (appointment[kk] !== undefined) appointmentData[kk] = true; else delete appointmentData[kk]; } } reset(appointmentData); } else { const newData = { duration: data.duration || 30, slots: data.slots || 1, }; let firstProperty; let found = false; addProps: for (const [_, v] of Object.entries(properties)) { for (const [kk, _] of Object.entries(v.values)) { if (firstProperty === undefined) firstProperty = kk; if (data[kk] !== undefined) { found = true; newData[kk] = true; break addProps; } } } if (!found) newData[firstProperty] = true; if (route.hashParams !== undefined) { if (route.hashParams.timestamp !== undefined) { const date = new Date(route.hashParams.timestamp); newData.time = formatTime(date); newData.date = formatDate(date); } } reset(newData); } }); const properties = settings.get('appointmentProperties'); const apptProperties = Object.entries(properties).map(([k, v]) => { const options = Object.entries(v.values).map(([kv, vv]) => ({ value: kv, key: vv, title: <T t={properties} k={`${k}.values.${kv}`} />, })); let currentOption; for (const [k, v] of Object.entries(data)) { for (const option of options) { if (k === option.value && v === true) currentOption = k; } } const changeTo = (option) => { const newData = { ...data }; for (const option of options) newData[option.value] = undefined; newData[option.value] = true; reset(newData); }; return ( <React.Fragment key={k}> <h2> <T t={properties} k={`${k}.title`} /> </h2> <RichSelect options={options} value={currentOption} onChange={(option) => changeTo(option)} /> </React.Fragment> ); }); const durations = [ 5, 10, 15, 20, 30, 45, 60, 90, 120, 150, 180, 210, 240, ].map((v) => ({ value: v, title: ( <T t={t} k={`schedule.appointment.duration.title`} duration={v} /> ), })); return ( <Modal saveDisabled={!valid || saving} cancelDisabled={saving} closeDisabled={saving} className="kip-new-appointment" onSave={save} waiting={saving} onCancel={cancel} onClose={cancel} title={ <T t={t} k={ appointment !== undefined ? 'edit-appointment.title' : 'new-appointment.title' } /> } > <FormComponent> <FieldSet> <div className="kip-field"> <Label htmlFor="date"> <T t={t} k="new-appointment.date" /> </Label> <ErrorFor error={error} field="date" /> <input value={data.date || ''} type="date" className="bulma-input" onChange={(e) => set('date', e.target.value)} /> </div> <div className="kip-field"> <Label htmlFor="time"> <T t={t} k="new-appointment.time" /> </Label> <ErrorFor error={error} field="time" /> <input type="time" className="bulma-input" value={data.time || ''} onChange={(e) => set('time', e.target.value)} step={60} /> </div> <div className="kip-field kip-is-fullwidth kip-slider"> <Label htmlFor="slots"> <T t={t} k="new-appointment.slots" /> </Label> <ErrorFor error={error} field="slots" /> <input type="number" className="bulma-input" value={data.slots || 1} onChange={(e) => set('slots', parseInt(e.target.value)) } step={1} min={1} max={50} /> </div> <div className="kip-field kip-is-fullwidth"> <RichSelect id="duration" value={data.duration || 30} onChange={(value) => set('duration', value.value) } options={durations} /> </div> <div className="kip-field kip-is-fullwidth"> {apptProperties} </div> </FieldSet> </FormComponent> </Modal> ); }, AppointmentForm, 'form' ); export default NewAppointment;