update reservation
This commit is contained in:
@@ -13,9 +13,13 @@ type Customer = {
|
||||
email: string
|
||||
phone?: string | null
|
||||
driverLicense?: string | null
|
||||
dateOfBirth?: string | null
|
||||
nationality?: string | null
|
||||
address?: Record<string, unknown> | null
|
||||
licenseExpiry?: string | null
|
||||
licenseIssuedAt?: string | null
|
||||
licenseCountry?: string | null
|
||||
licenseNumber?: string | null
|
||||
licenseCategory?: string | null
|
||||
licenseImageUrl?: string | null
|
||||
}
|
||||
@@ -62,6 +66,11 @@ export default function NewReservationPage() {
|
||||
const [newCustomerEmail, setNewCustomerEmail] = useState('')
|
||||
const [newCustomerPhone, setNewCustomerPhone] = useState('')
|
||||
const [driverLicense, setDriverLicense] = useState('')
|
||||
const [customerDateOfBirth, setCustomerDateOfBirth] = useState('')
|
||||
const [customerNationality, setCustomerNationality] = useState('')
|
||||
const [customerFullAddress, setCustomerFullAddress] = useState('')
|
||||
const [customerIdentityDocumentNumber, setCustomerIdentityDocumentNumber] = useState('')
|
||||
const [customerInternationalLicenseNumber, setCustomerInternationalLicenseNumber] = useState('')
|
||||
const [licenseExpiry, setLicenseExpiry] = useState('')
|
||||
const [licenseIssuedAt, setLicenseIssuedAt] = useState('')
|
||||
const [licenseCountry, setLicenseCountry] = useState('')
|
||||
@@ -94,12 +103,16 @@ export default function NewReservationPage() {
|
||||
return: 'Return location',
|
||||
deposit: 'Deposit amount (MAD)',
|
||||
paymentMode: 'Payment mode',
|
||||
renterIdentity: 'Renter identity',
|
||||
driverLicenseInfo: 'Driver license information',
|
||||
driverLicense: 'Driver license number',
|
||||
licenseExpiry: 'License expiry',
|
||||
licenseIssuedAt: 'License issued at',
|
||||
licenseCountry: 'License country',
|
||||
licenseCategory: 'License category',
|
||||
fullAddress: 'Full address',
|
||||
identityDocumentNumber: 'CIN / Passport number',
|
||||
internationalLicenseNumber: 'International permit number',
|
||||
licenseImage: 'Driver license image',
|
||||
licenseImageHint: 'Upload a photo or scan of the primary driver license.',
|
||||
licenseImageSelected: 'Selected file',
|
||||
@@ -108,7 +121,7 @@ export default function NewReservationPage() {
|
||||
addAdditionalDriver: 'Add additional driver',
|
||||
additionalDriverInfo: 'Additional driver',
|
||||
dateOfBirth: 'Date of birth',
|
||||
nationality: 'License country',
|
||||
nationality: 'Nationality',
|
||||
notes: 'Notes',
|
||||
notesPlaceholder: 'Optional notes…',
|
||||
create: 'Create booking',
|
||||
@@ -148,12 +161,16 @@ export default function NewReservationPage() {
|
||||
return: 'Lieu de retour',
|
||||
deposit: 'Montant du dépôt (MAD)',
|
||||
paymentMode: 'Mode de paiement',
|
||||
renterIdentity: 'Identité du locataire',
|
||||
driverLicenseInfo: 'Informations du permis',
|
||||
driverLicense: 'Numéro du permis',
|
||||
licenseExpiry: 'Expiration du permis',
|
||||
licenseIssuedAt: 'Permis délivré le',
|
||||
licenseCountry: 'Pays du permis',
|
||||
licenseCategory: 'Catégorie du permis',
|
||||
fullAddress: 'Adresse complète',
|
||||
identityDocumentNumber: 'N° CIN / passeport',
|
||||
internationalLicenseNumber: 'N° de permis international',
|
||||
licenseImage: 'Image du permis',
|
||||
licenseImageHint: 'Téléversez une photo ou un scan du permis du conducteur principal.',
|
||||
licenseImageSelected: 'Fichier sélectionné',
|
||||
@@ -162,7 +179,7 @@ export default function NewReservationPage() {
|
||||
addAdditionalDriver: 'Ajouter un conducteur supplémentaire',
|
||||
additionalDriverInfo: 'Conducteur supplémentaire',
|
||||
dateOfBirth: 'Date de naissance',
|
||||
nationality: 'Pays du permis',
|
||||
nationality: 'Nationalité',
|
||||
notes: 'Notes',
|
||||
notesPlaceholder: 'Notes optionnelles…',
|
||||
create: 'Créer la réservation',
|
||||
@@ -202,12 +219,16 @@ export default function NewReservationPage() {
|
||||
return: 'موقع التسليم',
|
||||
deposit: 'مبلغ العربون (MAD)',
|
||||
paymentMode: 'طريقة الدفع',
|
||||
renterIdentity: 'بيانات المستأجر',
|
||||
driverLicenseInfo: 'معلومات رخصة القيادة',
|
||||
driverLicense: 'رقم رخصة القيادة',
|
||||
licenseExpiry: 'تاريخ انتهاء الرخصة',
|
||||
licenseIssuedAt: 'تاريخ إصدار الرخصة',
|
||||
licenseCountry: 'بلد الرخصة',
|
||||
licenseCategory: 'فئة الرخصة',
|
||||
fullAddress: 'العنوان الكامل',
|
||||
identityDocumentNumber: 'رقم البطاقة الوطنية / جواز السفر',
|
||||
internationalLicenseNumber: 'رقم الرخصة الدولية',
|
||||
licenseImage: 'صورة رخصة القيادة',
|
||||
licenseImageHint: 'ارفع صورة أو مسحًا لرخصة السائق الأساسي.',
|
||||
licenseImageSelected: 'الملف المحدد',
|
||||
@@ -216,7 +237,7 @@ export default function NewReservationPage() {
|
||||
addAdditionalDriver: 'إضافة سائق إضافي',
|
||||
additionalDriverInfo: 'السائق الإضافي',
|
||||
dateOfBirth: 'تاريخ الميلاد',
|
||||
nationality: 'بلد الرخصة',
|
||||
nationality: 'الجنسية',
|
||||
notes: 'ملاحظات',
|
||||
notesPlaceholder: 'ملاحظات اختيارية…',
|
||||
create: 'إنشاء الحجز',
|
||||
@@ -272,10 +293,22 @@ export default function NewReservationPage() {
|
||||
|
||||
const canSubmit = !!customerId && !!vehicleId && !!startDate && !!endDate
|
||||
|
||||
function readCustomerAddressValue(customer: Customer | undefined, key: string) {
|
||||
const address = customer?.address
|
||||
if (!address || typeof address !== 'object' || Array.isArray(address)) return ''
|
||||
const value = address[key]
|
||||
return typeof value === 'string' ? value : ''
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
const selected = customers.find((customer) => customer.id === customerId)
|
||||
if (!selected) return
|
||||
setDriverLicense(selected.driverLicense ?? '')
|
||||
setDriverLicense(selected.driverLicense ?? selected.licenseNumber ?? '')
|
||||
setCustomerDateOfBirth(selected.dateOfBirth ? selected.dateOfBirth.slice(0, 10) : '')
|
||||
setCustomerNationality(selected.nationality ?? '')
|
||||
setCustomerFullAddress(readCustomerAddressValue(selected, 'fullAddress'))
|
||||
setCustomerIdentityDocumentNumber(readCustomerAddressValue(selected, 'identityDocumentNumber'))
|
||||
setCustomerInternationalLicenseNumber(readCustomerAddressValue(selected, 'internationalLicenseNumber'))
|
||||
setLicenseExpiry(selected.licenseExpiry ? selected.licenseExpiry.slice(0, 10) : '')
|
||||
setLicenseIssuedAt(selected.licenseIssuedAt ? selected.licenseIssuedAt.slice(0, 10) : '')
|
||||
setLicenseCountry(selected.licenseCountry ?? '')
|
||||
@@ -318,6 +351,20 @@ export default function NewReservationPage() {
|
||||
setError(copy.required)
|
||||
return
|
||||
}
|
||||
if (
|
||||
!driverLicense.trim() ||
|
||||
!customerDateOfBirth ||
|
||||
!customerNationality.trim() ||
|
||||
!customerFullAddress.trim() ||
|
||||
!customerIdentityDocumentNumber.trim() ||
|
||||
!licenseExpiry ||
|
||||
!licenseIssuedAt ||
|
||||
!licenseCountry.trim() ||
|
||||
!licenseCategory.trim()
|
||||
) {
|
||||
setError(copy.required)
|
||||
return
|
||||
}
|
||||
setSavingCustomer(true)
|
||||
try {
|
||||
const created = await apiFetch<Customer>('/customers', {
|
||||
@@ -329,11 +376,19 @@ export default function NewReservationPage() {
|
||||
lastNameAr: newCustomerLastName.ar.trim() || undefined,
|
||||
email: newCustomerEmail.trim(),
|
||||
phone: newCustomerPhone.trim(),
|
||||
driverLicense: driverLicense.trim() || undefined,
|
||||
licenseExpiry: licenseExpiry ? new Date(licenseExpiry).toISOString() : undefined,
|
||||
licenseIssuedAt: licenseIssuedAt ? new Date(licenseIssuedAt).toISOString() : undefined,
|
||||
licenseCountry: licenseCountry.trim() || undefined,
|
||||
licenseCategory: licenseCategory.trim() || undefined,
|
||||
driverLicense: driverLicense.trim(),
|
||||
dateOfBirth: new Date(customerDateOfBirth).toISOString(),
|
||||
nationality: customerNationality.trim(),
|
||||
address: {
|
||||
fullAddress: customerFullAddress.trim(),
|
||||
identityDocumentNumber: customerIdentityDocumentNumber.trim(),
|
||||
internationalLicenseNumber: customerInternationalLicenseNumber.trim() || undefined,
|
||||
},
|
||||
licenseExpiry: new Date(licenseExpiry).toISOString(),
|
||||
licenseIssuedAt: new Date(licenseIssuedAt).toISOString(),
|
||||
licenseCountry: licenseCountry.trim(),
|
||||
licenseNumber: driverLicense.trim(),
|
||||
licenseCategory: licenseCategory.trim(),
|
||||
}),
|
||||
})
|
||||
setCustomers((prev) => [created, ...prev])
|
||||
@@ -368,6 +423,20 @@ export default function NewReservationPage() {
|
||||
setError(copy.invalidDates)
|
||||
return
|
||||
}
|
||||
if (
|
||||
!driverLicense.trim() ||
|
||||
!customerDateOfBirth ||
|
||||
!customerNationality.trim() ||
|
||||
!customerFullAddress.trim() ||
|
||||
!customerIdentityDocumentNumber.trim() ||
|
||||
!licenseExpiry ||
|
||||
!licenseIssuedAt ||
|
||||
!licenseCountry.trim() ||
|
||||
!licenseCategory.trim()
|
||||
) {
|
||||
setError(copy.required)
|
||||
return
|
||||
}
|
||||
if (includeAdditionalDriver && (!bilingualPrimary(additionalDriver.firstName).trim() || !bilingualPrimary(additionalDriver.lastName).trim() || !additionalDriver.driverLicense.trim())) {
|
||||
setError(copy.required)
|
||||
return
|
||||
@@ -378,11 +447,19 @@ export default function NewReservationPage() {
|
||||
await apiFetch(`/customers/${customerId}`, {
|
||||
method: 'PATCH',
|
||||
body: JSON.stringify({
|
||||
driverLicense: driverLicense.trim() || undefined,
|
||||
licenseExpiry: licenseExpiry ? new Date(licenseExpiry).toISOString() : undefined,
|
||||
licenseIssuedAt: licenseIssuedAt ? new Date(licenseIssuedAt).toISOString() : undefined,
|
||||
licenseCountry: licenseCountry.trim() || undefined,
|
||||
licenseCategory: licenseCategory.trim() || undefined,
|
||||
driverLicense: driverLicense.trim(),
|
||||
dateOfBirth: new Date(customerDateOfBirth).toISOString(),
|
||||
nationality: customerNationality.trim(),
|
||||
address: {
|
||||
fullAddress: customerFullAddress.trim(),
|
||||
identityDocumentNumber: customerIdentityDocumentNumber.trim(),
|
||||
internationalLicenseNumber: customerInternationalLicenseNumber.trim() || undefined,
|
||||
},
|
||||
licenseExpiry: new Date(licenseExpiry).toISOString(),
|
||||
licenseIssuedAt: new Date(licenseIssuedAt).toISOString(),
|
||||
licenseCountry: licenseCountry.trim(),
|
||||
licenseNumber: driverLicense.trim(),
|
||||
licenseCategory: licenseCategory.trim(),
|
||||
}),
|
||||
})
|
||||
|
||||
@@ -509,29 +586,57 @@ export default function NewReservationPage() {
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div className="rounded-xl border border-slate-200 p-4 space-y-4">
|
||||
<p className="text-sm font-semibold text-slate-900">{copy.renterIdentity}</p>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<label className="space-y-1">
|
||||
<span className="text-sm font-medium text-slate-700">{copy.dateOfBirth} <span className="text-red-600">*</span></span>
|
||||
<input type="date" value={customerDateOfBirth} onChange={(e) => setCustomerDateOfBirth(e.target.value)} className="input-field" />
|
||||
</label>
|
||||
<label className="space-y-1">
|
||||
<span className="text-sm font-medium text-slate-700">{copy.nationality} <span className="text-red-600">*</span></span>
|
||||
<input value={customerNationality} onChange={(e) => setCustomerNationality(e.target.value)} className="input-field" />
|
||||
</label>
|
||||
</div>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<label className="space-y-1">
|
||||
<span className="text-sm font-medium text-slate-700">{copy.identityDocumentNumber} <span className="text-red-600">*</span></span>
|
||||
<input value={customerIdentityDocumentNumber} onChange={(e) => setCustomerIdentityDocumentNumber(e.target.value)} className="input-field" />
|
||||
</label>
|
||||
<label className="space-y-1">
|
||||
<span className="text-sm font-medium text-slate-700">{copy.internationalLicenseNumber}</span>
|
||||
<input value={customerInternationalLicenseNumber} onChange={(e) => setCustomerInternationalLicenseNumber(e.target.value)} className="input-field" />
|
||||
</label>
|
||||
</div>
|
||||
<label className="space-y-1 block">
|
||||
<span className="text-sm font-medium text-slate-700">{copy.fullAddress} <span className="text-red-600">*</span></span>
|
||||
<textarea value={customerFullAddress} onChange={(e) => setCustomerFullAddress(e.target.value)} className="input-field min-h-[88px]" />
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div className="rounded-xl border border-slate-200 p-4 space-y-4">
|
||||
<p className="text-sm font-semibold text-slate-900">{copy.driverLicenseInfo}</p>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<label className="space-y-1">
|
||||
<span className="text-sm font-medium text-slate-700">{copy.driverLicense}</span>
|
||||
<span className="text-sm font-medium text-slate-700">{copy.driverLicense} <span className="text-red-600">*</span></span>
|
||||
<input value={driverLicense} onChange={(e) => setDriverLicense(e.target.value)} className="input-field" />
|
||||
</label>
|
||||
<label className="space-y-1">
|
||||
<span className="text-sm font-medium text-slate-700">{copy.licenseCountry}</span>
|
||||
<span className="text-sm font-medium text-slate-700">{copy.licenseCountry} <span className="text-red-600">*</span></span>
|
||||
<input value={licenseCountry} onChange={(e) => setLicenseCountry(e.target.value)} className="input-field" />
|
||||
</label>
|
||||
</div>
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
|
||||
<label className="space-y-1">
|
||||
<span className="text-sm font-medium text-slate-700">{copy.licenseIssuedAt}</span>
|
||||
<span className="text-sm font-medium text-slate-700">{copy.licenseIssuedAt} <span className="text-red-600">*</span></span>
|
||||
<input type="date" value={licenseIssuedAt} onChange={(e) => setLicenseIssuedAt(e.target.value)} className="input-field" />
|
||||
</label>
|
||||
<label className="space-y-1">
|
||||
<span className="text-sm font-medium text-slate-700">{copy.licenseExpiry}</span>
|
||||
<span className="text-sm font-medium text-slate-700">{copy.licenseExpiry} <span className="text-red-600">*</span></span>
|
||||
<input type="date" value={licenseExpiry} onChange={(e) => setLicenseExpiry(e.target.value)} className="input-field" />
|
||||
</label>
|
||||
<label className="space-y-1">
|
||||
<span className="text-sm font-medium text-slate-700">{copy.licenseCategory}</span>
|
||||
<span className="text-sm font-medium text-slate-700">{copy.licenseCategory} <span className="text-red-600">*</span></span>
|
||||
<input value={licenseCategory} onChange={(e) => setLicenseCategory(e.target.value)} className="input-field" />
|
||||
</label>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user