diff --git a/apps/edr-passenger-api/src/modules/fleet/fleet.dto.ts b/apps/edr-passenger-api/src/modules/fleet/fleet.dto.ts index d0875d79e..609afb4e5 100644 --- a/apps/edr-passenger-api/src/modules/fleet/fleet.dto.ts +++ b/apps/edr-passenger-api/src/modules/fleet/fleet.dto.ts @@ -17,7 +17,10 @@ export class CreateCoachDto { @ApiPropertyOptional({ example: 'ACTIVE', description: 'Status: ACTIVE, INACTIVE' }) @IsOptional() @IsString() status?: string; } -export class UpdateCoachDto extends PartialType(OmitType(CreateCoachDto, ['number'] as const)) {} +export class UpdateCoachDto extends PartialType(OmitType(CreateCoachDto, ['number'] as const)) { + @ApiPropertyOptional({ example: 1, description: 'Sequence number for ordering' }) + @IsOptional() @IsInt() sequence?: number; +} export class AssignCoachDto { @ApiProperty({ example: 'schedule-uuid', description: 'TrainSchedule UUID' }) @IsString() scheduleId: string; diff --git a/apps/edr-passenger-api/src/modules/fleet/fleet.service.ts b/apps/edr-passenger-api/src/modules/fleet/fleet.service.ts index 84c0ac2a3..0662da595 100644 --- a/apps/edr-passenger-api/src/modules/fleet/fleet.service.ts +++ b/apps/edr-passenger-api/src/modules/fleet/fleet.service.ts @@ -298,6 +298,7 @@ export class FleetService { arrangement: dto.arrangement, capacity: dto.capacity, status: dto.status, + sequence: dto.sequence, }, include: { coachType: true }, }); @@ -306,6 +307,10 @@ export class FleetService { async deleteCoach(id: string) { const coach = await this.prisma.coach.findUnique({ where: { id } }); if (!coach) throw new NotFoundException('Coach not found'); + + // Delete related seats first + await this.prisma.seat.deleteMany({ where: { coachId: id } }); + return this.prisma.coach.delete({ where: { id } }); } diff --git a/apps/edr-passenger-api/src/modules/stations/stations.dto.ts b/apps/edr-passenger-api/src/modules/stations/stations.dto.ts index 05973bfe4..fc350dbcf 100644 --- a/apps/edr-passenger-api/src/modules/stations/stations.dto.ts +++ b/apps/edr-passenger-api/src/modules/stations/stations.dto.ts @@ -1,4 +1,4 @@ -import { IsString, IsNumber, IsOptional } from 'class-validator'; +import { IsString, IsNumber, IsOptional, IsInt, IsBoolean } from 'class-validator'; import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; export class CreateStationDto { @@ -6,6 +6,9 @@ export class CreateStationDto { @ApiProperty({ example: 'Addis Ababa' }) @IsString() name: string; @ApiProperty({ example: 'Addis Ababa' }) @IsString() city: string; @ApiPropertyOptional() @IsOptional() @IsString() timezone?: string; + @ApiPropertyOptional() @IsOptional() @IsString() countryCode?: string; @ApiProperty({ example: 9.0054 }) @IsNumber() lat: number; @ApiProperty({ example: 38.7636 }) @IsNumber() lng: number; + @ApiPropertyOptional({ example: 1 }) @IsOptional() @IsInt() sequence?: number; + @ApiPropertyOptional({ example: true }) @IsOptional() @IsBoolean() isOperational?: boolean; } diff --git a/apps/edr-passenger-api/src/modules/stations/stations.service.ts b/apps/edr-passenger-api/src/modules/stations/stations.service.ts index bfa0ab7bb..5dd1aa77d 100644 --- a/apps/edr-passenger-api/src/modules/stations/stations.service.ts +++ b/apps/edr-passenger-api/src/modules/stations/stations.service.ts @@ -1,4 +1,4 @@ -import { Injectable, NotFoundException, Inject, Optional } from '@nestjs/common'; +import { Injectable, NotFoundException, Inject, Optional, BadRequestException } from '@nestjs/common'; import { REQUEST } from '@nestjs/core'; import { PrismaService } from '../../common/prisma.service'; import { AuditService } from '../../common/audit.service'; @@ -65,9 +65,15 @@ export class StationsService { async update(id: string, dto: Partial) { const oldStation = await this.findOne(id); + const { code, name, city, timezone, lat, lng } = dto; + const data: any = { code, name, city, timezone, lat, lng }; + if ('countryCode' in dto) data.countryCode = (dto as any).countryCode; + if ('sequence' in dto) data.sequence = (dto as any).sequence; + if ('isOperational' in dto) data.isOperational = (dto as any).isOperational; + const updatedStation = await this.prisma.station.update({ where: { id }, - data: dto, + data, }); await this.auditService.log({ diff --git a/apps/edr-passenger-web/backoffice/src/app/coaches/page.tsx b/apps/edr-passenger-web/backoffice/src/app/coaches/page.tsx index a30d32b72..bf4f88a98 100644 --- a/apps/edr-passenger-web/backoffice/src/app/coaches/page.tsx +++ b/apps/edr-passenger-web/backoffice/src/app/coaches/page.tsx @@ -338,6 +338,14 @@ export default function CoachesPage() { // Coaches Columns const coachColumns = [ + { + key: 'sequence', + label: 'Sequence', + sortable: true, + render: (coach: any) => ( + {coach.sequence} + ), + }, { key: 'number', label: 'Number', @@ -358,15 +366,6 @@ export default function CoachesPage() { {coach.coachType?.name || 'N/A'} ), }, - { - key: 'visualization', - label: 'Seats/Beds', - render: (coach: any) => ( -
- {renderBedVisualization(coach)} -
- ), - }, { key: 'arrangement', label: 'Arrangement', diff --git a/apps/edr-passenger-web/backoffice/src/app/schedules/page.tsx b/apps/edr-passenger-web/backoffice/src/app/schedules/page.tsx index a40fbbc02..e60de15c2 100644 --- a/apps/edr-passenger-web/backoffice/src/app/schedules/page.tsx +++ b/apps/edr-passenger-web/backoffice/src/app/schedules/page.tsx @@ -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() {
- +
+ + +
{coaches.length === 0 ? (

No coaches available

@@ -656,7 +672,7 @@ export default function SchedulesPage() { className="rounded" /> - {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}) )) @@ -765,7 +781,22 @@ export default function SchedulesPage() {
- +
+ + +
{coaches.length === 0 ? (

No coaches available

@@ -791,7 +822,7 @@ export default function SchedulesPage() { className="rounded" /> - {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}) )) diff --git a/apps/edr-passenger-web/backoffice/src/app/seats/page.tsx b/apps/edr-passenger-web/backoffice/src/app/seats/page.tsx index ba4130b81..ba561b24e 100644 --- a/apps/edr-passenger-web/backoffice/src/app/seats/page.tsx +++ b/apps/edr-passenger-web/backoffice/src/app/seats/page.tsx @@ -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 (
@@ -532,7 +539,7 @@ export default function SeatsPage() {
-

Coach {coach.coachNumber}

+

{sequence} - {coach.coachNumber}

{coachTypeName} • {seats.length} {seatOrBedLabel}

@@ -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) ? ( + <> + Unblock + + ) : isCoachUnblocked(coach) ? ( + <> + Block + + ) : ( + 'Mixed Status' + )}