'use client'; import { useState, useEffect } from 'react'; import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; import { Plus, Edit, Trash2, Search } from 'lucide-react'; import DataTable from '@/components/ui/DataTable'; import Badge from '@/components/ui/Badge'; import ActionButton from '@/components/ui/ActionButton'; import Modal from '@/components/ui/Modal'; import ConfirmDialog from '@/components/ui/ConfirmDialog'; import { seatClassesApi, apiClient } from '@/lib/api'; import { formatCurrency } from '@/lib/utils'; export default function ClassesPage() { const [filters, setFilters] = useState({ search: '' }); const [showModal, setShowModal] = useState(false); const [editingClass, setEditingClass] = useState(null); const [deleteConfirm, setDeleteConfirm] = useState<{ isOpen: boolean; class: any | null; error?: string; cascade?: boolean; cascadeChecked?: boolean }>({ isOpen: false, class: null }); const [selectedCoachTypeId, setSelectedCoachTypeId] = useState(''); const queryClient = useQueryClient(); const { data, isLoading } = useQuery({ queryKey: ['classes', filters], queryFn: () => seatClassesApi.getAll(), }); const { data: coachTypes } = useQuery({ queryKey: ['coach-types'], queryFn: () => apiClient.get('/fleet/coach-types'), }); useEffect(() => { if (showModal && editingClass) { setSelectedCoachTypeId(editingClass.coachTypeId || ''); } }, [showModal, editingClass]); const createMutation = useMutation({ mutationFn: seatClassesApi.create, onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['classes'] }); setShowModal(false); setEditingClass(null); setSelectedCoachTypeId(''); }, }); const updateMutation = useMutation({ mutationFn: ({ id, data }: { id: string; data: any }) => seatClassesApi.update(id, data), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['classes'] }); setShowModal(false); setEditingClass(null); setSelectedCoachTypeId(''); }, }); const deleteMutation = useMutation({ mutationFn: ({ id, cascade }: { id: string; cascade?: boolean }) => seatClassesApi.delete(id, cascade), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['classes'] }); }, onError: (e: any) => { const msg = e?.response?.data?.message || e?.message || 'Failed to delete class'; const isFkError = msg?.includes('Cannot delete') || e?.response?.status === 400; if (isFkError && !deleteConfirm.cascade) { setDeleteConfirm(prev => ({ ...prev, cascade: true, cascadeChecked: false, error: Array.isArray(msg) ? msg.join(' ') : msg })); } else { setDeleteConfirm(prev => ({ ...prev, error: Array.isArray(msg) ? msg.join(' ') : msg })); } }, }); const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); if (!selectedCoachTypeId) { alert('Please select a coach type'); return; } const formData = new FormData(e.currentTarget); const classData = { coachTypeId: selectedCoachTypeId, name: formData.get('name') as string, description: formData.get('description') as string, baseFareMinor: Math.round(Number((parseFloat(formData.get('baseFareMinor') as string) * 100).toFixed(10))) || 0, premiumMinor: Math.round(Number((parseFloat(formData.get('premiumMinor') as string) * 100).toFixed(10))) || 0, insuranceFeeMinor: Math.round(Number((parseFloat(formData.get('insuranceFeeMinor') as string) * 100).toFixed(10))) || 0, isActive: formData.get('isActive') === 'true', }; if (editingClass) { await updateMutation.mutateAsync({ id: editingClass.id, data: classData }); } else { await createMutation.mutateAsync(classData); } }; const handleDelete = (cls: any) => { setDeleteConfirm({ isOpen: true, class: cls, error: undefined }); }; const confirmDelete = async () => { if (!deleteConfirm.class) return; try { await deleteMutation.mutateAsync({ id: deleteConfirm.class.id, cascade: deleteConfirm.cascade && deleteConfirm.cascadeChecked }); setDeleteConfirm({ isOpen: false, class: null }); } catch { // error is set by onError handler } }; const coachTypesArray = Array.isArray(coachTypes) ? coachTypes : ((coachTypes as any)?.data || (coachTypes as any)?.items || []); const coachTypeMap = coachTypesArray.reduce((map: any, ct: any) => { map[ct.id] = ct.name; return map; }, {}); const filteredClasses = (data as any)?.items || (Array.isArray(data) ? data : []); const displayedClasses = filteredClasses.filter((cls: any) => { if (!filters.search) return true; const searchLower = filters.search.toLowerCase(); return ( cls.name?.toLowerCase().includes(searchLower) || cls.coachType?.name?.toLowerCase().includes(searchLower) || cls.description?.toLowerCase().includes(searchLower) ); }); const columns = [ { key: 'coachType', label: 'Coach Type', render: (cls: any) => ( {cls.coachType?.name || coachTypeMap[cls.coachTypeId] || 'N/A'} ), }, { key: 'name', label: 'Class Name', render: (cls: any) => {cls.name}, }, { key: 'baseFareMinor', label: 'Base Fare', render: (cls: any) => ( {(cls.baseFareMinor / 100).toFixed(2)} ETB ), }, { key: 'premiumMinor', label: 'Premium', render: (cls: any) => ( {cls.premiumMinor ? (cls.premiumMinor / 100).toFixed(2) : '0.00'} ETB ), }, { key: 'insuranceFeeMinor', label: 'Insurance', render: (cls: any) => ( {cls.insuranceFeeMinor ? (cls.insuranceFeeMinor / 100).toFixed(2) : '0.00'} ETB ), }, { key: 'isActive', label: 'Status', render: (cls: any) => ( {cls.isActive ? 'Active' : 'Inactive'} ), }, ]; const handleOpenModal = (cls?: any) => { if (cls) { setEditingClass(cls); } else { setEditingClass(null); } setShowModal(true); }; const actions = [ { label: 'Edit', onClick: (cls: any) => handleOpenModal(cls), variant: 'secondary' as const, icon: Edit, }, { label: 'Delete', onClick: handleDelete, variant: 'danger' as const, icon: Trash2, }, ]; return (

Classes

Manage class configurations with pricing by coach type

handleOpenModal()} > Add Class
setFilters({ ...filters, search: e.target.value })} />
{/* Delete Confirmation */} setDeleteConfirm({ isOpen: false, class: null })} onConfirm={confirmDelete} title="Delete Class" message={`Are you sure you want to delete ${deleteConfirm.class?.name}?`} confirmText="Delete" isDanger={true} isLoading={deleteMutation.isPending} error={deleteConfirm.error} warning={!deleteConfirm.cascade ? "This class may be used by coaches and fare rules. Deleting it may impact seat assignments and pricing." : undefined} cascadeWarning={deleteConfirm.cascade ? "This class has related fare rules that will also be permanently deleted." : undefined} cascadeChecked={deleteConfirm.cascadeChecked} onCascadeChange={(checked) => setDeleteConfirm(prev => ({ ...prev, cascadeChecked: checked }))} /> {/* Add/Edit Modal */} { setShowModal(false); setEditingClass(null); setSelectedCoachTypeId(''); }} title={`${editingClass ? 'Edit' : 'Add'} Class`} size="lg" >

Pricing Configuration

Per-km distance-based fare rate

Flat fee per passenger (e.g., travel insurance)

{ setShowModal(false); setEditingClass(null); setSelectedCoachTypeId(''); }} > Cancel {editingClass ? 'Update' : 'Create'} Class
); }