mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-09-08 04:15:43 +00:00
Coaches, seats, schedules, and pricing related updates
This commit is contained in:
@@ -2,141 +2,251 @@
|
||||
|
||||
import { useState } from 'react';
|
||||
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
import { fleetApi } from '@/lib/api';
|
||||
import { Plus, Search, Grid3x3, Edit, Trash2 } from 'lucide-react';
|
||||
import DataTable from '@/components/ui/DataTable';
|
||||
import ActionButton from '@/components/ui/ActionButton';
|
||||
import Modal from '@/components/ui/Modal';
|
||||
import ConfirmDialog from '@/components/ui/ConfirmDialog';
|
||||
import { Plus, Search, Grid3x3, Train, Edit, Trash2 } from 'lucide-react';
|
||||
import { fleetApi, apiClient } from '@/lib/api';
|
||||
|
||||
type Tab = 'types' | 'coaches';
|
||||
|
||||
export default function CoachesPage() {
|
||||
const [activeTab, setActiveTab] = useState<Tab>('coaches');
|
||||
const [search, setSearch] = useState('');
|
||||
const [showModal, setShowModal] = useState(false);
|
||||
const [editingCoach, setEditingCoach] = useState<any>(null);
|
||||
const [deleteConfirm, setDeleteConfirm] = useState<{ isOpen: boolean; coach: any | null }>({ isOpen: false, coach: null });
|
||||
const [editingItem, setEditingItem] = useState<any>(null);
|
||||
const [deleteConfirm, setDeleteConfirm] = useState<{ isOpen: boolean; item: any | null }>({ isOpen: false, item: null });
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
const { data, isLoading } = useQuery({
|
||||
queryKey: ['coaches', search],
|
||||
queryFn: () => fleetApi.getCoaches({ search }),
|
||||
// Coach Types Queries
|
||||
const { data: coachTypesData, isLoading: typesLoading } = useQuery({
|
||||
queryKey: ['coach-types'],
|
||||
queryFn: () => apiClient.get('/fleet/coach-types'),
|
||||
});
|
||||
|
||||
const createMutation = useMutation({
|
||||
// Coaches Queries
|
||||
const { data: coachesData, isLoading: coachesLoading } = useQuery({
|
||||
queryKey: ['coaches'],
|
||||
queryFn: () => fleetApi.getCoaches({}),
|
||||
});
|
||||
|
||||
// Coach Type Mutations
|
||||
const createCoachTypeMutation = useMutation({
|
||||
mutationFn: (data: any) => apiClient.post('/fleet/coach-types', data),
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ['coach-types'] });
|
||||
setShowModal(false);
|
||||
setEditingItem(null);
|
||||
},
|
||||
});
|
||||
|
||||
const updateCoachTypeMutation = useMutation({
|
||||
mutationFn: ({ id, data }: { id: string; data: any }) => apiClient.patch(`/fleet/coach-types/${id}`, data),
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ['coach-types'] });
|
||||
setShowModal(false);
|
||||
setEditingItem(null);
|
||||
},
|
||||
});
|
||||
|
||||
const deleteCoachTypeMutation = useMutation({
|
||||
mutationFn: (id: string) => apiClient.delete(`/fleet/coach-types/${id}`),
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ['coach-types'] });
|
||||
},
|
||||
});
|
||||
|
||||
// Coach Mutations
|
||||
const createCoachMutation = useMutation({
|
||||
mutationFn: fleetApi.createCoach,
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ['coaches'] });
|
||||
setShowModal(false);
|
||||
setEditingCoach(null);
|
||||
setEditingItem(null);
|
||||
},
|
||||
});
|
||||
|
||||
const updateMutation = useMutation({
|
||||
const updateCoachMutation = useMutation({
|
||||
mutationFn: ({ id, data }: { id: string; data: any }) => fleetApi.updateCoach(id, data),
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ['coaches'] });
|
||||
setShowModal(false);
|
||||
setEditingCoach(null);
|
||||
setEditingItem(null);
|
||||
},
|
||||
});
|
||||
|
||||
const deleteMutation = useMutation({
|
||||
const deleteCoachMutation = useMutation({
|
||||
mutationFn: fleetApi.deleteCoach,
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ['coaches'] });
|
||||
},
|
||||
});
|
||||
|
||||
const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
|
||||
const handleCoachTypeSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
|
||||
e.preventDefault();
|
||||
const formData = new FormData(e.currentTarget);
|
||||
const coachData = {
|
||||
coachNumber: formData.get('coachNumber') as string,
|
||||
label: formData.get('label') as string,
|
||||
seatClassId: formData.get('seatClassId') as string,
|
||||
coachType: formData.get('coachType') as string,
|
||||
mode: formData.get('mode') as string,
|
||||
seatArrangement: formData.get('seatArrangement') as string,
|
||||
totalUnits: parseInt(formData.get('totalUnits') as string),
|
||||
isActive: formData.get('isActive') === 'true',
|
||||
const data = {
|
||||
code: formData.get('code') as string,
|
||||
name: formData.get('name') as string,
|
||||
type: formData.get('type') as string,
|
||||
};
|
||||
|
||||
if (editingCoach) {
|
||||
await updateMutation.mutateAsync({ id: editingCoach.id, data: coachData });
|
||||
if (editingItem?.isCoachType) {
|
||||
await updateCoachTypeMutation.mutateAsync({ id: editingItem.id, data });
|
||||
} else {
|
||||
await createMutation.mutateAsync(coachData);
|
||||
await createCoachTypeMutation.mutateAsync(data);
|
||||
}
|
||||
};
|
||||
|
||||
const handleDelete = (coach: any) => {
|
||||
setDeleteConfirm({ isOpen: true, coach });
|
||||
const handleCoachSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
|
||||
e.preventDefault();
|
||||
const formData = new FormData(e.currentTarget);
|
||||
const data = {
|
||||
number: formData.get('number') as string,
|
||||
coachTypeId: formData.get('coachTypeId') as string,
|
||||
arrangement: formData.get('arrangement') as string,
|
||||
capacity: parseInt(formData.get('capacity') as string),
|
||||
status: formData.get('status') as string,
|
||||
};
|
||||
|
||||
if (editingItem?.isCoach) {
|
||||
await updateCoachMutation.mutateAsync({ id: editingItem.id, data });
|
||||
} else {
|
||||
await createCoachMutation.mutateAsync(data);
|
||||
}
|
||||
};
|
||||
|
||||
const handleDelete = (item: any, isCoachType: boolean) => {
|
||||
setDeleteConfirm({ isOpen: true, item: { ...item, isCoachType } });
|
||||
};
|
||||
|
||||
const confirmDelete = async () => {
|
||||
if (deleteConfirm.coach) {
|
||||
await deleteMutation.mutateAsync(deleteConfirm.coach.id);
|
||||
setDeleteConfirm({ isOpen: false, coach: null });
|
||||
if (deleteConfirm.item?.isCoachType) {
|
||||
await deleteCoachTypeMutation.mutateAsync(deleteConfirm.item.id);
|
||||
} else {
|
||||
await deleteCoachMutation.mutateAsync(deleteConfirm.item.id);
|
||||
}
|
||||
setDeleteConfirm({ isOpen: false, item: null });
|
||||
};
|
||||
|
||||
const coaches = data?.items || data?.data || [];
|
||||
const coachTypesArray = Array.isArray(coachTypesData) ? coachTypesData : (coachTypesData as any)?.items || (coachTypesData as any)?.data || [];
|
||||
const coaches = coachesData?.items || coachesData?.data || [];
|
||||
|
||||
const columns = [
|
||||
const filteredCoachTypes = coachTypesArray.filter((ct: any) => {
|
||||
if (!search) return true;
|
||||
const searchLower = search.toLowerCase();
|
||||
return (
|
||||
ct.code?.toLowerCase().includes(searchLower) ||
|
||||
ct.name?.toLowerCase().includes(searchLower) ||
|
||||
ct.type?.toLowerCase().includes(searchLower)
|
||||
);
|
||||
});
|
||||
|
||||
const filteredCoaches = coaches.filter((coach: any) => {
|
||||
if (!search) return true;
|
||||
const searchLower = search.toLowerCase();
|
||||
return (
|
||||
coach.number?.toLowerCase().includes(searchLower) ||
|
||||
coach.coachNumber?.toLowerCase().includes(searchLower) ||
|
||||
coach.coachType?.name?.toLowerCase().includes(searchLower) ||
|
||||
coach.arrangement?.toLowerCase().includes(searchLower)
|
||||
);
|
||||
});
|
||||
|
||||
const typeColorMap: Record<string, string> = {
|
||||
passenger: 'edr-badge-info',
|
||||
sleeper: 'edr-badge-warning',
|
||||
dining: 'edr-badge-success',
|
||||
baggage: 'edr-badge-danger',
|
||||
};
|
||||
|
||||
const statusMap: Record<string, string> = {
|
||||
ACTIVE: 'edr-badge-success',
|
||||
MAINTENANCE: 'edr-badge-warning',
|
||||
INACTIVE: 'edr-badge-danger',
|
||||
};
|
||||
|
||||
// Coach Types Columns
|
||||
const coachTypeColumns = [
|
||||
{
|
||||
key: 'coachNumber',
|
||||
label: 'Coach Number',
|
||||
key: 'code',
|
||||
label: 'Code',
|
||||
sortable: true,
|
||||
render: (ct: any) => (
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="flex h-8 w-8 items-center justify-center rounded-lg bg-[rgb(20,113,76)]">
|
||||
<Grid3x3 className="h-4 w-4 text-white" />
|
||||
</div>
|
||||
<span className="font-mono font-medium">{ct.code}</span>
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: 'name',
|
||||
label: 'Name',
|
||||
sortable: true,
|
||||
render: (ct: any) => <span className="font-medium">{ct.name}</span>,
|
||||
},
|
||||
{
|
||||
key: 'type',
|
||||
label: 'Type',
|
||||
render: (ct: any) => (
|
||||
<span className={`edr-badge ${typeColorMap[ct.type] || 'edr-badge-info'}`}>
|
||||
{ct.type}
|
||||
</span>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: 'coaches',
|
||||
label: 'Coaches',
|
||||
render: (ct: any) => (
|
||||
<span className="text-sm font-medium">{ct.coaches?.length || 0}</span>
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
// Coaches Columns
|
||||
const coachColumns = [
|
||||
{
|
||||
key: 'number',
|
||||
label: 'Number',
|
||||
sortable: true,
|
||||
render: (coach: any) => (
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="flex h-8 w-8 items-center justify-center rounded-lg bg-[rgb(20,113,76)]">
|
||||
<Grid3x3 className="h-4 w-4 text-white" />
|
||||
</div>
|
||||
<span className="font-medium">{coach.coachNumber}</span>
|
||||
<span className="font-medium">{coach.number || coach.coachNumber}</span>
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: 'seatClass',
|
||||
label: 'Seat Class',
|
||||
render: (coach: any) => {
|
||||
const seatClass = coach.seatClass?.name || coach.serviceClass || 'N/A';
|
||||
const colorMap: Record<string, string> = {
|
||||
'ECONOMY_REGULAR': 'edr-badge-info',
|
||||
'ECONOMY_BED': 'edr-badge-warning',
|
||||
'VIP_BED': 'edr-badge-success',
|
||||
};
|
||||
return (
|
||||
<span className={`edr-badge ${colorMap[seatClass] || 'edr-badge-info'}`}>
|
||||
{seatClass.replace(/_/g, ' ')}
|
||||
</span>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
key: 'totalSeats',
|
||||
label: 'Total Seats',
|
||||
key: 'coachType',
|
||||
label: 'Coach Type',
|
||||
render: (coach: any) => (
|
||||
<span className="font-mono text-sm">{coach.totalSeats || coach.totalUnits || 0}</span>
|
||||
<span className="text-sm">{coach.coachType?.name || 'N/A'}</span>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: 'layout',
|
||||
label: 'Layout',
|
||||
key: 'arrangement',
|
||||
label: 'Arrangement',
|
||||
render: (coach: any) => (
|
||||
<span className="text-sm text-muted-foreground">
|
||||
{coach.layout || coach.seatLayout || coach.seatArrangement || 'N/A'}
|
||||
</span>
|
||||
<span className="text-sm font-mono">{coach.arrangement || 'N/A'}</span>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: 'capacity',
|
||||
label: 'Capacity',
|
||||
render: (coach: any) => (
|
||||
<span className="font-mono text-sm font-medium">{coach.capacity || 0}</span>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: 'status',
|
||||
label: 'Status',
|
||||
render: (coach: any) => {
|
||||
const status = coach.isActive ? 'ACTIVE' : 'INACTIVE';
|
||||
const statusMap: Record<string, string> = {
|
||||
ACTIVE: 'edr-badge-success',
|
||||
MAINTENANCE: 'edr-badge-warning',
|
||||
INACTIVE: 'edr-badge-danger',
|
||||
};
|
||||
const status = coach.status || 'ACTIVE';
|
||||
return (
|
||||
<span className={`edr-badge ${statusMap[status] || 'edr-badge-info'}`}>
|
||||
{status}
|
||||
@@ -146,11 +256,11 @@ export default function CoachesPage() {
|
||||
},
|
||||
];
|
||||
|
||||
const actions = [
|
||||
const coachTypeActions = [
|
||||
{
|
||||
label: 'Edit',
|
||||
onClick: (coach: any) => {
|
||||
setEditingCoach(coach);
|
||||
onClick: (item: any) => {
|
||||
setEditingItem({ ...item, isCoachType: true });
|
||||
setShowModal(true);
|
||||
},
|
||||
variant: 'secondary' as const,
|
||||
@@ -158,7 +268,25 @@ export default function CoachesPage() {
|
||||
},
|
||||
{
|
||||
label: 'Delete',
|
||||
onClick: handleDelete,
|
||||
onClick: (item: any) => handleDelete(item, true),
|
||||
variant: 'danger' as const,
|
||||
icon: Trash2,
|
||||
},
|
||||
];
|
||||
|
||||
const coachActions = [
|
||||
{
|
||||
label: 'Edit',
|
||||
onClick: (item: any) => {
|
||||
setEditingItem({ ...item, isCoach: true });
|
||||
setShowModal(true);
|
||||
},
|
||||
variant: 'secondary' as const,
|
||||
icon: Edit,
|
||||
},
|
||||
{
|
||||
label: 'Delete',
|
||||
onClick: (item: any) => handleDelete(item, false),
|
||||
variant: 'danger' as const,
|
||||
icon: Trash2,
|
||||
},
|
||||
@@ -169,51 +297,112 @@ export default function CoachesPage() {
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<h1 className="text-3xl font-bold text-foreground">Coach Management</h1>
|
||||
<p className="text-muted-foreground mt-1">Manage train coaches and configurations</p>
|
||||
<p className="text-muted-foreground mt-1">Manage coach types and train coaches</p>
|
||||
</div>
|
||||
<ActionButton
|
||||
icon={Plus}
|
||||
onClick={() => {
|
||||
setEditingCoach(null);
|
||||
setEditingItem(null);
|
||||
setSearch('');
|
||||
setShowModal(true);
|
||||
}}
|
||||
>
|
||||
Add Coach
|
||||
{activeTab === 'types' ? 'Add Coach Type' : 'Add Coach'}
|
||||
</ActionButton>
|
||||
</div>
|
||||
|
||||
{/* Tabs */}
|
||||
<div className="card">
|
||||
<div className="flex items-center gap-4 mb-6">
|
||||
<div className="relative flex-1">
|
||||
<Search className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground" />
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Search coaches..."
|
||||
value={search}
|
||||
onChange={(e) => setSearch(e.target.value)}
|
||||
className="input pl-10"
|
||||
/>
|
||||
</div>
|
||||
<div className="flex border-b border-border">
|
||||
<button
|
||||
onClick={() => {
|
||||
setActiveTab('types');
|
||||
setSearch('');
|
||||
}}
|
||||
className={`px-4 py-3 font-medium transition-colors ${
|
||||
activeTab === 'types'
|
||||
? 'border-b-2 border-primary text-primary'
|
||||
: 'text-muted-foreground hover:text-foreground'
|
||||
}`}
|
||||
>
|
||||
Coach Types
|
||||
</button>
|
||||
<button
|
||||
onClick={() => {
|
||||
setActiveTab('coaches');
|
||||
setSearch('');
|
||||
}}
|
||||
className={`px-4 py-3 font-medium transition-colors ${
|
||||
activeTab === 'coaches'
|
||||
? 'border-b-2 border-primary text-primary'
|
||||
: 'text-muted-foreground hover:text-foreground'
|
||||
}`}
|
||||
>
|
||||
Coaches
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<DataTable
|
||||
columns={columns}
|
||||
data={coaches}
|
||||
actions={actions}
|
||||
loading={isLoading}
|
||||
/>
|
||||
{/* Coach Types Tab */}
|
||||
{activeTab === 'types' && (
|
||||
<div className="pt-6 space-y-4">
|
||||
<div className="relative">
|
||||
<Search className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground" />
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Search by code, name, or type..."
|
||||
value={search}
|
||||
onChange={(e) => setSearch(e.target.value)}
|
||||
className="input pl-10 w-full"
|
||||
/>
|
||||
</div>
|
||||
<DataTable
|
||||
columns={coachTypeColumns}
|
||||
data={filteredCoachTypes}
|
||||
actions={coachTypeActions}
|
||||
loading={typesLoading}
|
||||
emptyMessage={search ? "No coach types match your search" : "No coach types found. Create one to get started."}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Coaches Tab */}
|
||||
{activeTab === 'coaches' && (
|
||||
<div className="pt-6 space-y-4">
|
||||
<div className="relative">
|
||||
<Search className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground" />
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Search by coach number, type, or arrangement..."
|
||||
value={search}
|
||||
onChange={(e) => setSearch(e.target.value)}
|
||||
className="input pl-10 w-full"
|
||||
/>
|
||||
</div>
|
||||
<DataTable
|
||||
columns={coachColumns}
|
||||
data={filteredCoaches}
|
||||
actions={coachActions}
|
||||
loading={coachesLoading}
|
||||
emptyMessage={search ? "No coaches match your search" : "No coaches found. Create one to get started."}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Delete Confirmation */}
|
||||
<ConfirmDialog
|
||||
isOpen={deleteConfirm.isOpen}
|
||||
onClose={() => setDeleteConfirm({ isOpen: false, coach: null })}
|
||||
onClose={() => setDeleteConfirm({ isOpen: false, item: null })}
|
||||
onConfirm={confirmDelete}
|
||||
title="Delete Coach"
|
||||
message={`Are you sure you want to delete coach ${deleteConfirm.coach?.coachNumber}?`}
|
||||
title={`Delete ${deleteConfirm.item?.isCoachType ? 'Coach Type' : 'Coach'}`}
|
||||
message={`Are you sure you want to delete ${deleteConfirm.item?.name || deleteConfirm.item?.number}?`}
|
||||
confirmText="Delete"
|
||||
isDanger={true}
|
||||
warning="This coach may be assigned to schedules and trips. Deleting it may impact these systems."
|
||||
warning={
|
||||
deleteConfirm.item?.isCoachType
|
||||
? 'This coach type may have coaches assigned. Deleting it may impact these systems.'
|
||||
: 'This coach may be assigned to schedules. Deleting it may impact these systems.'
|
||||
}
|
||||
/>
|
||||
|
||||
{/* Add/Edit Modal */}
|
||||
@@ -221,106 +410,170 @@ export default function CoachesPage() {
|
||||
isOpen={showModal}
|
||||
onClose={() => {
|
||||
setShowModal(false);
|
||||
setEditingCoach(null);
|
||||
setEditingItem(null);
|
||||
}}
|
||||
title={`${editingCoach ? 'Edit' : 'Add'} Coach`}
|
||||
size="lg"
|
||||
title={
|
||||
activeTab === 'types'
|
||||
? `${editingItem?.isCoachType ? 'Edit' : 'Add'} Coach Type`
|
||||
: `${editingItem?.isCoach ? 'Edit' : 'Add'} Coach`
|
||||
}
|
||||
size={activeTab === 'types' ? 'md' : 'lg'}
|
||||
>
|
||||
<form onSubmit={handleSubmit} className="space-y-4">
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label className="label">Coach Number *</label>
|
||||
<input
|
||||
type="text"
|
||||
name="coachNumber"
|
||||
className="input"
|
||||
defaultValue={editingCoach?.coachNumber}
|
||||
required
|
||||
placeholder="e.g., C001"
|
||||
/>
|
||||
{/* Coach Type Form */}
|
||||
{activeTab === 'types' && (
|
||||
<form onSubmit={handleCoachTypeSubmit} className="space-y-4">
|
||||
<div className="grid grid-cols-1 gap-4">
|
||||
<div>
|
||||
<label className="label">Code</label>
|
||||
<input
|
||||
type="text"
|
||||
name="code"
|
||||
className="input"
|
||||
defaultValue={editingItem?.code || ''}
|
||||
required
|
||||
placeholder="e.g., HSC"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="label">Name</label>
|
||||
<input
|
||||
type="text"
|
||||
name="name"
|
||||
className="input"
|
||||
defaultValue={editingItem?.name || ''}
|
||||
required
|
||||
placeholder="e.g., Hard Seat Coach"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="label">Type</label>
|
||||
<input
|
||||
type="text"
|
||||
name="type"
|
||||
className="input"
|
||||
defaultValue={editingItem?.type || ''}
|
||||
required
|
||||
placeholder="e.g., Regular Seat"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label className="label">Label *</label>
|
||||
<input
|
||||
type="text"
|
||||
name="label"
|
||||
className="input"
|
||||
defaultValue={editingCoach?.label}
|
||||
required
|
||||
placeholder="e.g., Coach 1"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="label">Coach Type</label>
|
||||
<select name="coachType" className="input" defaultValue={editingCoach?.coachType}>
|
||||
<option value="passenger">Passenger</option>
|
||||
<option value="sleeper">Sleeper</option>
|
||||
<option value="dining">Dining</option>
|
||||
<option value="baggage">Baggage</option>
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label className="label">Mode *</label>
|
||||
<select name="mode" className="input" defaultValue={editingCoach?.mode || 'seat'}>
|
||||
<option value="seat">Seat</option>
|
||||
<option value="bed">Bed</option>
|
||||
<option value="convertible">Convertible</option>
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label className="label">Seat Arrangement</label>
|
||||
<input
|
||||
type="text"
|
||||
name="seatArrangement"
|
||||
className="input"
|
||||
defaultValue={editingCoach?.seatArrangement}
|
||||
placeholder="e.g., 2+2"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="label">Total Units *</label>
|
||||
<input
|
||||
type="number"
|
||||
name="totalUnits"
|
||||
className="input"
|
||||
defaultValue={editingCoach?.totalUnits}
|
||||
required
|
||||
min="1"
|
||||
placeholder="60"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="label">Status</label>
|
||||
<select
|
||||
name="isActive"
|
||||
className="input"
|
||||
defaultValue={editingCoach?.isActive?.toString() || 'true'}
|
||||
|
||||
<div className="flex justify-end gap-2 pt-4">
|
||||
<ActionButton
|
||||
type="button"
|
||||
variant="secondary"
|
||||
onClick={() => {
|
||||
setShowModal(false);
|
||||
setEditingItem(null);
|
||||
}}
|
||||
>
|
||||
<option value="true">Active</option>
|
||||
<option value="false">Inactive</option>
|
||||
</select>
|
||||
Cancel
|
||||
</ActionButton>
|
||||
<ActionButton
|
||||
type="submit"
|
||||
loading={createCoachTypeMutation.isPending || updateCoachTypeMutation.isPending}
|
||||
>
|
||||
{editingItem?.isCoachType ? 'Update' : 'Create'} Coach Type
|
||||
</ActionButton>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-end gap-2 pt-4">
|
||||
<ActionButton
|
||||
type="button"
|
||||
variant="secondary"
|
||||
onClick={() => {
|
||||
setShowModal(false);
|
||||
setEditingCoach(null);
|
||||
}}
|
||||
>
|
||||
Cancel
|
||||
</ActionButton>
|
||||
<ActionButton
|
||||
type="submit"
|
||||
loading={createMutation.isPending || updateMutation.isPending}
|
||||
>
|
||||
{editingCoach ? 'Update' : 'Create'} Coach
|
||||
</ActionButton>
|
||||
</div>
|
||||
</form>
|
||||
</form>
|
||||
)}
|
||||
|
||||
{/* Coach Form */}
|
||||
{activeTab === 'coaches' && (
|
||||
<form onSubmit={handleCoachSubmit} className="space-y-4">
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label className="label">Coach Type</label>
|
||||
<select
|
||||
name="coachTypeId"
|
||||
className="input"
|
||||
defaultValue={editingItem?.coachTypeId || ''}
|
||||
required
|
||||
>
|
||||
<option value="">Select Coach Type</option>
|
||||
{coachTypesArray.map((ct: any) => (
|
||||
<option key={ct.id} value={ct.id}>
|
||||
{ct.code} - {ct.name} - {ct.type}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="label">Number</label>
|
||||
<input
|
||||
type="text"
|
||||
name="number"
|
||||
className="input"
|
||||
defaultValue={editingItem?.number || editingItem?.coachNumber || ''}
|
||||
required
|
||||
placeholder="e.g., A-001"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="label">Arrangement</label>
|
||||
<input
|
||||
type="text"
|
||||
name="arrangement"
|
||||
className="input"
|
||||
defaultValue={editingItem?.arrangement || editingItem?.seatArrangement || '2+2'}
|
||||
required
|
||||
placeholder="e.g., 2+2, 3+2"
|
||||
/>
|
||||
<p className="text-xs text-muted-foreground mt-1">Format: separate columns with +</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="label">Capacity</label>
|
||||
<input
|
||||
type="number"
|
||||
name="capacity"
|
||||
className="input"
|
||||
defaultValue={editingItem?.capacity || editingItem?.totalUnits || ''}
|
||||
required
|
||||
min="1"
|
||||
placeholder="60"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="md:col-span-2">
|
||||
<label className="label">Status</label>
|
||||
<select
|
||||
name="status"
|
||||
className="input"
|
||||
defaultValue={editingItem?.status || 'ACTIVE'}
|
||||
>
|
||||
<option value="ACTIVE">Active</option>
|
||||
<option value="MAINTENANCE">Maintenance</option>
|
||||
<option value="INACTIVE">Inactive</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-end gap-2 pt-4">
|
||||
<ActionButton
|
||||
type="button"
|
||||
variant="secondary"
|
||||
onClick={() => {
|
||||
setShowModal(false);
|
||||
setEditingItem(null);
|
||||
}}
|
||||
>
|
||||
Cancel
|
||||
</ActionButton>
|
||||
<ActionButton
|
||||
type="submit"
|
||||
loading={createCoachMutation.isPending || updateCoachMutation.isPending}
|
||||
>
|
||||
{editingItem?.isCoach ? 'Update' : 'Create'} Coach
|
||||
</ActionButton>
|
||||
</div>
|
||||
</form>
|
||||
)}
|
||||
</Modal>
|
||||
</div>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user