'use client' import { useEffect, useState } from 'react' import dayjs from 'dayjs' import { formatCurrency, getCurrencyLabel } from '@rentaldrivego/types' import { apiFetch } from '@/lib/api' import { useDashboardI18n } from '@/components/I18nProvider' const CATEGORIES = ['ECONOMY', 'COMPACT', 'MIDSIZE', 'FULLSIZE', 'SUV', 'LUXURY', 'VAN', 'TRUCK'] as const type Category = typeof CATEGORIES[number] interface Offer { id: string title: string description?: string | null termsAndConds?: string | null type: 'PERCENTAGE' | 'FIXED_AMOUNT' | 'FREE_DAY' | 'SPECIAL_RATE' discountValue: number specialRate?: number | null appliesToAll: boolean categories: Category[] minRentalDays?: number | null maxRentalDays?: number | null promoCode?: string | null maxRedemptions?: number | null validFrom: string validUntil: string isActive: boolean isPublic: boolean isFeatured: boolean redemptionCount?: number vehicles?: Array<{ vehicleId: string }> } interface FleetVehicle { id: string make: string model: string year: number category: string licensePlate: string } interface FormState { title: string description: string termsAndConds: string type: Offer['type'] discountValue: string specialRate: string appliesToAll: boolean categories: Category[] vehicleIds: string[] minRentalDays: string maxRentalDays: string promoCode: string maxRedemptions: string validFrom: string validUntil: string isActive: boolean isPublic: boolean isFeatured: boolean } function emptyForm(): FormState { const today = dayjs().format('YYYY-MM-DD') const nextMonth = dayjs().add(30, 'day').format('YYYY-MM-DD') return { title: '', description: '', termsAndConds: '', type: 'PERCENTAGE', discountValue: '', specialRate: '', appliesToAll: true, categories: [], vehicleIds: [], minRentalDays: '', maxRentalDays: '', promoCode: '', maxRedemptions: '', validFrom: today, validUntil: nextMonth, isActive: true, isPublic: true, isFeatured: false, } } function offerToForm(o: Offer): FormState { return { title: o.title, description: o.description ?? '', termsAndConds: o.termsAndConds ?? '', type: o.type, discountValue: String(o.discountValue), specialRate: o.specialRate != null ? String(o.specialRate) : '', appliesToAll: o.appliesToAll, categories: o.categories ?? [], vehicleIds: o.vehicles?.map((v) => v.vehicleId) ?? [], minRentalDays: o.minRentalDays != null ? String(o.minRentalDays) : '', maxRentalDays: o.maxRentalDays != null ? String(o.maxRentalDays) : '', promoCode: o.promoCode ?? '', maxRedemptions: o.maxRedemptions != null ? String(o.maxRedemptions) : '', validFrom: dayjs(o.validFrom).format('YYYY-MM-DD'), validUntil: dayjs(o.validUntil).format('YYYY-MM-DD'), isActive: o.isActive, isPublic: o.isPublic, isFeatured: o.isFeatured, } } const copy = { en: { title: 'Offers', subtitle: 'Promotions shown on your site and optionally on the storefront.', createOffer: 'Create Offer', editOffer: 'Edit Offer', deleteOffer: 'Delete Offer', confirmDelete: 'Are you sure you want to delete this offer? This action cannot be undone.', cancel: 'Cancel', save: 'Save', saving: 'Saving…', delete: 'Delete', deleting: 'Deleting…', ends: 'ends', active: 'Active', inactive: 'Inactive', storefront: 'Storefront', featured: 'Featured', empty: 'No offers yet. Create your first offer to attract customers.', labelTitle: 'Title *', labelDescription: 'Description', labelTerms: 'Terms & Conditions', labelType: 'Discount Type *', labelDiscount: 'Discount Value *', labelSpecialRate: 'Special Rate (MAD/day) *', labelAppliesToAll: 'Applies to all vehicles', labelCategories: 'Vehicle Categories', labelVehicles: 'Specific Vehicles', labelVehicleSearch: 'Search vehicles…', labelMinDays: 'Min Rental Days', labelMaxDays: 'Max Rental Days', labelPromoCode: 'Promo Code', labelMaxRedemptions: 'Max Redemptions', labelValidFrom: 'Valid From *', labelValidUntil: 'Valid Until *', labelIsActive: 'Active', labelIsPublic: 'Show on storefront', labelIsFeatured: 'Featured', typePERCENTAGE: 'Percentage (%)', typeFIXED_AMOUNT: 'Fixed Amount (MAD)', typeFREE_DAY: 'Free Day', typeSPECIAL_RATE: 'Special Daily Rate', placeholderTitle: 'e.g. Summer Promotion', placeholderDescription: 'Optional description…', placeholderTerms: 'Terms and conditions…', placeholderPromoCode: 'e.g. SUMMER20', errorTitle: 'Title is required.', errorDiscount: 'Please enter a valid discount value.', errorDates: 'Valid From and Valid Until are required.', errorDateOrder: 'Valid Until must be after Valid From.', failedSave: 'Failed to save offer.', failedDelete: 'Failed to delete offer.', redemptions: 'redemptions', freeDay: 'Free day', loadingVehicles: 'Loading fleet…', noVehiclesFound: 'No vehicles found.', selected: (n: number) => `${n} selected`, categoryLabels: { ECONOMY: 'Economy', COMPACT: 'Compact', MIDSIZE: 'Midsize', FULLSIZE: 'Full-size', SUV: 'SUV', LUXURY: 'Luxury', VAN: 'Van', TRUCK: 'Truck', }, }, fr: { title: 'Offres', subtitle: 'Promotions affichées sur votre site et éventuellement sur la storefront.', createOffer: 'Créer une offre', editOffer: "Modifier l'offre", deleteOffer: "Supprimer l'offre", confirmDelete: 'Êtes-vous sûr de vouloir supprimer cette offre ? Cette action est irréversible.', cancel: 'Annuler', save: 'Enregistrer', saving: 'Enregistrement…', delete: 'Supprimer', deleting: 'Suppression…', ends: 'expire le', active: 'Actif', inactive: 'Inactif', storefront: 'Storefront', featured: 'Mise en avant', empty: 'Aucune offre. Créez votre première offre pour attirer des clients.', labelTitle: 'Titre *', labelDescription: 'Description', labelTerms: 'Conditions générales', labelType: 'Type de remise *', labelDiscount: 'Valeur de la remise *', labelSpecialRate: 'Tarif spécial (MAD/jour) *', labelAppliesToAll: 'Applicable à tous les véhicules', labelCategories: 'Catégories de véhicules', labelVehicles: 'Véhicules spécifiques', labelVehicleSearch: 'Rechercher des véhicules…', labelMinDays: 'Jours de location minimum', labelMaxDays: 'Jours de location maximum', labelPromoCode: 'Code promo', labelMaxRedemptions: 'Nombre max de rédemptions', labelValidFrom: 'Valable du *', labelValidUntil: "Valable jusqu'au *", labelIsActive: 'Actif', labelIsPublic: 'Afficher sur la storefront', labelIsFeatured: 'Mise en avant', typePERCENTAGE: 'Pourcentage (%)', typeFIXED_AMOUNT: 'Montant fixe (MAD)', typeFREE_DAY: 'Jour gratuit', typeSPECIAL_RATE: 'Tarif journalier spécial', placeholderTitle: 'ex. Promotion estivale', placeholderDescription: 'Description optionnelle…', placeholderTerms: 'Conditions générales…', placeholderPromoCode: 'ex. ETE20', errorTitle: 'Le titre est requis.', errorDiscount: 'Veuillez saisir une valeur de remise valide.', errorDates: 'Les dates de début et de fin sont requises.', errorDateOrder: 'La date de fin doit être après la date de début.', failedSave: "Échec de l'enregistrement de l'offre.", failedDelete: "Échec de la suppression de l'offre.", redemptions: 'rédemptions', freeDay: 'Jour gratuit', loadingVehicles: 'Chargement de la flotte…', noVehiclesFound: 'Aucun véhicule trouvé.', selected: (n: number) => `${n} sélectionné${n > 1 ? 's' : ''}`, categoryLabels: { ECONOMY: 'Économique', COMPACT: 'Compacte', MIDSIZE: 'Intermédiaire', FULLSIZE: 'Grande berline', SUV: 'SUV', LUXURY: 'Luxe', VAN: 'Van', TRUCK: 'Camionnette', }, }, ar: { title: 'العروض', subtitle: 'عروض ترويجية تظهر في موقعك ويمكن عرضها أيضاً في السوق.', createOffer: 'إنشاء عرض', editOffer: 'تعديل العرض', deleteOffer: 'حذف العرض', confirmDelete: 'هل أنت متأكد من حذف هذا العرض؟ لا يمكن التراجع عن هذا الإجراء.', cancel: 'إلغاء', save: 'حفظ', saving: 'جارٍ الحفظ…', delete: 'حذف', deleting: 'جارٍ الحذف…', ends: 'تنتهي في', active: 'نشط', inactive: 'غير نشط', storefront: 'السوق', featured: 'مميز', empty: 'لا توجد عروض بعد. أنشئ أول عرض لجذب العملاء.', labelTitle: 'العنوان *', labelDescription: 'الوصف', labelTerms: 'الشروط والأحكام', labelType: 'نوع الخصم *', labelDiscount: 'قيمة الخصم *', labelSpecialRate: 'السعر الخاص (درهم/يوم) *', labelAppliesToAll: 'ينطبق على جميع المركبات', labelCategories: 'فئات المركبات', labelVehicles: 'مركبات محددة', labelVehicleSearch: 'ابحث عن مركبة…', labelMinDays: 'الحد الأدنى لأيام الإيجار', labelMaxDays: 'الحد الأقصى لأيام الإيجار', labelPromoCode: 'رمز الترويج', labelMaxRedemptions: 'الحد الأقصى للاسترداد', labelValidFrom: 'صالح من *', labelValidUntil: 'صالح حتى *', labelIsActive: 'نشط', labelIsPublic: 'عرض في السوق', labelIsFeatured: 'مميز', typePERCENTAGE: 'نسبة مئوية (%)', typeFIXED_AMOUNT: 'مبلغ ثابت (درهم)', typeFREE_DAY: 'يوم مجاني', typeSPECIAL_RATE: 'سعر يومي خاص', placeholderTitle: 'مثال: عرض الصيف', placeholderDescription: 'وصف اختياري…', placeholderTerms: 'الشروط والأحكام…', placeholderPromoCode: 'مثال: SUMMER20', errorTitle: 'العنوان مطلوب.', errorDiscount: 'يرجى إدخال قيمة خصم صحيحة.', errorDates: 'تاريخ البداية والنهاية مطلوبان.', errorDateOrder: 'يجب أن يكون تاريخ الانتهاء بعد تاريخ البداية.', failedSave: 'فشل حفظ العرض.', failedDelete: 'فشل حذف العرض.', redemptions: 'استرداد', freeDay: 'يوم مجاني', loadingVehicles: 'جارٍ تحميل الأسطول…', noVehiclesFound: 'لم يتم العثور على مركبات.', selected: (n: number) => `${n} محدد`, categoryLabels: { ECONOMY: 'اقتصادية', COMPACT: 'مدمجة', MIDSIZE: 'متوسطة', FULLSIZE: 'كبيرة', SUV: 'دفع رباعي', LUXURY: 'فاخرة', VAN: 'فان', TRUCK: 'شاحنة', }, }, } function discountLabel(offer: Offer, t: typeof copy['en'], language: 'en' | 'fr' | 'ar'): string { if (offer.type === 'PERCENTAGE') return `${offer.discountValue}%` if (offer.type === 'FIXED_AMOUNT') return formatCurrency(offer.discountValue, 'MAD', language) if (offer.type === 'FREE_DAY') return t.freeDay if (offer.type === 'SPECIAL_RATE') return `${offer.specialRate ?? offer.discountValue} ${getCurrencyLabel(language)}${language === 'fr' ? '/jour' : language === 'ar' ? '/يوم' : '/day'}` return String(offer.discountValue) } export default function OffersPage() { const { language } = useDashboardI18n() const t = copy[language] const [offers, setOffers] = useState([]) const [error, setError] = useState(null) const [modalOpen, setModalOpen] = useState(false) const [editing, setEditing] = useState(null) const [form, setForm] = useState(emptyForm) const [formError, setFormError] = useState(null) const [saving, setSaving] = useState(false) const [deleteTarget, setDeleteTarget] = useState(null) const [deleting, setDeleting] = useState(false) const [fleet, setFleet] = useState([]) const [loadingFleet, setLoadingFleet] = useState(false) const [vehicleSearch, setVehicleSearch] = useState('') function load() { apiFetch('/offers') .then((data) => { setOffers(Array.isArray(data) ? data : []) setError(null) }) .catch((err) => setError(err.message)) } useEffect(() => { load() }, []) async function loadFleet() { setLoadingFleet(true) try { const data = await apiFetch('/vehicles?pageSize=200') setFleet(Array.isArray(data) ? data : []) } catch { // non-fatal; vehicle picker just stays empty } finally { setLoadingFleet(false) } } function openCreate() { setEditing(null) setForm(emptyForm()) setFormError(null) setVehicleSearch('') setModalOpen(true) loadFleet() } async function openEdit(offer: Offer) { setEditing(offer) setForm(offerToForm(offer)) setFormError(null) setVehicleSearch('') setModalOpen(true) // Load fleet and the offer's current vehicle IDs in parallel loadFleet() try { const detail = await apiFetch(`/offers/${offer.id}`) setForm((prev) => ({ ...prev, vehicleIds: Array.isArray(detail?.vehicles) ? detail.vehicles.map((v) => v.vehicleId) : [], })) } catch { // leave vehicleIds as empty if fetch fails } } function closeModal() { setModalOpen(false) setEditing(null) setFormError(null) } function toggleCategory(cat: Category) { setForm((f) => ({ ...f, categories: f.categories.includes(cat) ? f.categories.filter((c) => c !== cat) : [...f.categories, cat], })) } function toggleVehicle(id: string) { setForm((f) => ({ ...f, vehicleIds: f.vehicleIds.includes(id) ? f.vehicleIds.filter((v) => v !== id) : [...f.vehicleIds, id], })) } async function handleSubmit(e: React.FormEvent) { e.preventDefault() setFormError(null) if (!form.title.trim()) return setFormError(t.errorTitle) if (!form.validFrom || !form.validUntil) return setFormError(t.errorDates) if (form.validUntil <= form.validFrom) return setFormError(t.errorDateOrder) if (form.type !== 'FREE_DAY') { const dv = Number(form.discountValue) if (isNaN(dv) || dv < 0) return setFormError(t.errorDiscount) } setSaving(true) try { const payload: Record = { title: form.title.trim(), description: form.description.trim() || undefined, termsAndConds: form.termsAndConds.trim() || undefined, type: form.type, discountValue: form.type === 'FREE_DAY' ? 0 : Number(form.discountValue), specialRate: form.specialRate ? Number(form.specialRate) : undefined, appliesToAll: form.appliesToAll, categories: form.appliesToAll ? [] : form.categories, minRentalDays: form.minRentalDays ? Number(form.minRentalDays) : undefined, maxRentalDays: form.maxRentalDays ? Number(form.maxRentalDays) : undefined, promoCode: form.promoCode.trim() || undefined, maxRedemptions: form.maxRedemptions ? Number(form.maxRedemptions) : undefined, validFrom: new Date(form.validFrom).toISOString(), validUntil: new Date(form.validUntil).toISOString(), isActive: form.isActive, isPublic: form.isPublic, isFeatured: form.isFeatured, vehicleIds: form.appliesToAll ? [] : form.vehicleIds, } if (editing) { await apiFetch(`/offers/${editing.id}`, { method: 'PATCH', body: JSON.stringify(payload) }) } else { await apiFetch('/offers', { method: 'POST', body: JSON.stringify(payload) }) } closeModal() load() } catch (err: any) { setFormError(err.message ?? t.failedSave) } finally { setSaving(false) } } async function handleDelete() { if (!deleteTarget) return setDeleting(true) try { await apiFetch(`/offers/${deleteTarget.id}`, { method: 'DELETE' }) setDeleteTarget(null) load() } catch (err: any) { setFormError(err.message ?? t.failedDelete) setDeleteTarget(null) } finally { setDeleting(false) } } const isRtl = language === 'ar' const filteredFleet = fleet.filter((v) => { const q = vehicleSearch.toLowerCase() return ( !q || v.make.toLowerCase().includes(q) || v.model.toLowerCase().includes(q) || v.licensePlate.toLowerCase().includes(q) ) }) return (
{/* Header */}

{t.title}

{t.subtitle}

{error &&
{error}
} {/* Offer cards */}
{offers.map((offer) => (

{offer.title}

{t[`type${offer.type}` as keyof typeof t] as string} · {t.ends} {dayjs(offer.validUntil).format('MMM D, YYYY')}

{offer.isActive ? t.active : t.inactive}

{discountLabel(offer, t, language)}

{offer.description && (

{offer.description}

)}
{offer.isPublic && {t.storefront}} {offer.isFeatured && {t.featured}} {offer.promoCode && {offer.promoCode}} {typeof offer.redemptionCount === 'number' && ( {offer.redemptionCount} {t.redemptions} )}
))} {offers.length === 0 && !error && (
{t.empty}
)}
{/* Create / Edit Modal */} {modalOpen && (

{editing ? t.editOffer : t.createOffer}

{/* Title */}
setForm((f) => ({ ...f, title: e.target.value }))} placeholder={t.placeholderTitle} />
{/* Description */}