Enhance DTOs and services to support sequence and operational status for coaches and stations

This commit is contained in:
Stephanos A
2026-06-16 16:25:33 +03:00
parent a61130cd51
commit 421e30dc6c
7 changed files with 85 additions and 20 deletions

View File

@@ -338,6 +338,14 @@ export default function CoachesPage() {
// Coaches Columns
const coachColumns = [
{
key: 'sequence',
label: 'Sequence',
sortable: true,
render: (coach: any) => (
<span className="font-mono font-semibold text-sm">{coach.sequence}</span>
),
},
{
key: 'number',
label: 'Number',
@@ -358,15 +366,6 @@ export default function CoachesPage() {
<span className="text-sm">{coach.coachType?.name || 'N/A'}</span>
),
},
{
key: 'visualization',
label: 'Seats/Beds',
render: (coach: any) => (
<div className="bg-gray-50 dark:bg-gray-900/30 rounded p-2 max-w-xs overflow-x-auto">
{renderBedVisualization(coach)}
</div>
),
},
{
key: 'arrangement',
label: 'Arrangement',

View File

@@ -40,6 +40,7 @@ interface Coach {
number: string;
coachNumber?: string;
capacity: number;
sequence?: number;
coachType?: { name: string };
}
@@ -630,7 +631,22 @@ export default function SchedulesPage() {
</div>
<div>
<label className="label">Coaches (Optional)</label>
<div className="flex items-center justify-between mb-2">
<label className="label">Coaches (Optional)</label>
<button
type="button"
onClick={() => {
if (bulkForm.coachIds.length === coaches.length) {
setBulkForm({ ...bulkForm, coachIds: [] });
} else {
setBulkForm({ ...bulkForm, coachIds: coaches.map((c: Coach) => c.id) });
}
}}
className="text-xs text-primary hover:underline"
>
{bulkForm.coachIds.length === coaches.length ? 'Deselect All' : 'Select All'}
</button>
</div>
<div className="border border-border rounded-lg p-3 max-h-64 overflow-y-auto space-y-2">
{coaches.length === 0 ? (
<p className="text-sm text-muted-foreground">No coaches available</p>
@@ -656,7 +672,7 @@ export default function SchedulesPage() {
className="rounded"
/>
<span className="text-sm">
{coach.number || coach.coachNumber} - {coach.coachType?.name} (Cap: {coach.capacity})
Seq {coach.sequence || 'N/A'} - {coach.number || coach.coachNumber} - {coach.coachType?.name} (Cap: {coach.capacity})
</span>
</label>
))
@@ -765,7 +781,22 @@ export default function SchedulesPage() {
</div>
<div>
<label className="label">Coaches</label>
<div className="flex items-center justify-between mb-2">
<label className="label">Coaches (Optional)</label>
<button
type="button"
onClick={() => {
if (editForm.coachIds.length === coaches.length) {
setEditForm({ ...editForm, coachIds: [] });
} else {
setEditForm({ ...editForm, coachIds: coaches.map((c: Coach) => c.id) });
}
}}
className="text-xs text-primary hover:underline"
>
{editForm.coachIds.length === coaches.length ? 'Deselect All' : 'Select All'}
</button>
</div>
<div className="border border-border rounded-lg p-3 max-h-64 overflow-y-auto space-y-2">
{coaches.length === 0 ? (
<p className="text-sm text-muted-foreground">No coaches available</p>
@@ -791,7 +822,7 @@ export default function SchedulesPage() {
className="rounded"
/>
<span className="text-sm">
{coach.number || coach.coachNumber} - {coach.coachType?.name} (Cap: {coach.capacity})
Seq {coach.sequence || 'N/A'} - {coach.number || coach.coachNumber} - {coach.coachType?.name} (Cap: {coach.capacity})
</span>
</label>
))

View File

@@ -152,7 +152,13 @@ export default function SeatsPage() {
};
const isCoachBlocked = (coach: any) => {
return coach.seats?.some((s: any) => s.status === 'BLOCKED' || s.isBlocked);
const seats = (coach.seats || []).filter((s: any) => s.seatNumber && !s.seatNumber.startsWith('-'));
return seats.length > 0 && seats.every((s: any) => s.status === 'BLOCKED' || s.isBlocked);
};
const isCoachUnblocked = (coach: any) => {
const seats = (coach.seats || []).filter((s: any) => s.seatNumber && !s.seatNumber.startsWith('-'));
return seats.length > 0 && seats.every((s: any) => s.status !== 'BLOCKED' && !s.isBlocked);
};
const submitBlockCoach = async () => {
@@ -520,6 +526,7 @@ export default function SeatsPage() {
const seats = (coach.seats || []).filter((s: any) => s.seatNumber);
const isExpanded = expandedCoaches.has(coach.id);
const seatOrBedLabel = isBedCoach ? 'beds' : 'seats';
const sequence = coachData?.sequence ?? coach?.sequence ?? index + 1;
return (
<div key={coach.id} className="border border-gray-200 dark:border-gray-700 rounded-lg overflow-hidden bg-white dark:bg-gray-800/50 shadow-md hover:shadow-lg transition-shadow">
@@ -532,7 +539,7 @@ export default function SeatsPage() {
<ChevronDown className="w-5 h-5 text-[rgb(20,113,76)]" />
</div>
<div className="text-left">
<p className="font-semibold text-foreground">Coach {coach.coachNumber}</p>
<p className="font-semibold text-foreground">{sequence} - {coach.coachNumber}</p>
<p className="text-xs text-muted-foreground">{coachTypeName} {seats.length} {seatOrBedLabel}</p>
</div>
</button>
@@ -541,8 +548,19 @@ export default function SeatsPage() {
size="sm"
onClick={() => isCoachBlocked(coach) ? handleUnblockCoach(coach) : handleBlockCoach(coach)}
className="ml-2"
disabled={!isCoachBlocked(coach) && !isCoachUnblocked(coach)}
>
{isCoachBlocked(coach) ? 'Unblock' : 'Block'}
{isCoachBlocked(coach) ? (
<>
<Unlock className="w-4 h-4" /> Unblock
</>
) : isCoachUnblocked(coach) ? (
<>
<Lock className="w-4 h-4" /> Block
</>
) : (
'Mixed Status'
)}
</ActionButton>
</div>