Production checklists, issue fixes

This commit is contained in:
Stephanos A
2026-07-01 15:04:38 +03:00
parent 0015f645cf
commit 103117d88b
20 changed files with 100 additions and 200 deletions

View File

@@ -1,9 +1,10 @@
import { Injectable, Inject, Optional } from '@nestjs/common';
import { Injectable, Inject, Logger, Optional } from '@nestjs/common';
import { REQUEST } from '@nestjs/core';
import { PrismaService } from './prisma.service';
@Injectable()
export class AuditService {
private readonly logger = new Logger(AuditService.name);
constructor(
private prisma: PrismaService,
@Optional() @Inject(REQUEST) private request?: any,
@@ -34,7 +35,7 @@ export class AuditService {
},
});
} catch (error) {
console.error('Failed to log audit event:', error);
this.logger.error('Failed to log audit event:', error);
// Don't throw - audit logging should not break main operations
}
}
@@ -74,11 +75,20 @@ export class AuditService {
where.entityType = filters.entityType;
}
return this.prisma.auditLog.findMany({
where,
orderBy: { createdAt: 'desc' },
take: 500,
});
const limit = Math.min(filters.limit ?? 50, 200);
const offset = filters.offset ?? 0;
const [data, total] = await Promise.all([
this.prisma.auditLog.findMany({
where,
orderBy: { createdAt: 'desc' },
take: limit,
skip: offset,
}),
this.prisma.auditLog.count({ where }),
]);
return { data, total, limit, offset };
}
async getLog(id: string) {

View File

@@ -41,9 +41,7 @@ export class HttpExceptionFilter implements ExceptionFilter {
this.logger.error(
`${request.method} ${request.url} -> ${status}`,
exception instanceof Error ? exception.stack : JSON.stringify(exception),
);
console.error('Full error details:', exception);
} else {
); } else {
this.logger.warn(`${request.method} ${request.url} -> ${status} ${message}`);
}

View File

@@ -1,4 +1,4 @@
import { Injectable } from '@nestjs/common';
import { Injectable, Logger } from '@nestjs/common';
import * as fs from 'fs';
import * as path from 'path';
@@ -6,6 +6,7 @@ type TranslationMap = Record<string, any>;
@Injectable()
export class I18nService {
private readonly logger = new Logger(I18nService.name);
private translations: Map<string, TranslationMap> = new Map();
private readonly supportedLocales = ['en', 'am', 'fr', 'om'];
private readonly defaultLocale = 'en';
@@ -21,7 +22,7 @@ export class I18nService {
const content = fs.readFileSync(filePath, 'utf-8');
this.translations.set(locale, JSON.parse(content));
} catch (err) {
console.warn(`Failed to load translation file for locale: ${locale}`);
this.logger.warn(`Failed to load translation file for locale: ${locale}`);
}
}
}

View File

@@ -548,7 +548,7 @@ export class PassengerAuthService {
await this.dataSource.query(`DELETE FROM iam.users WHERE id = $1`, [iamUserId]);
} catch (err) {
console.error('[PassengerAuthService] IAM compensating cleanup failed for', email, (err as Error).message);
this.logger.error(`[PassengerAuthService] IAM compensating cleanup failed for ${email}`, (err as Error).message);
}
}
}

View File

@@ -317,7 +317,10 @@ export class FleetService {
}
getTrains() {
return this.prisma.train.findMany({ include: { schedules: { take: 5, orderBy: { departureAt: 'desc' } } } });
return this.prisma.train.findMany({
where: { isActive: true },
include: { schedules: { take: 5, orderBy: { departureAt: 'desc' } } },
});
}
createTrain(dto: CreateTrainDto) {
@@ -462,6 +465,7 @@ export class FleetService {
return this.prisma.coach.update({
where: { id },
data: {
number: dto.number,
arrangement: dto.arrangement,
capacity: dto.capacity,
status: dto.status,
@@ -540,6 +544,7 @@ export class FleetService {
]);
if (!schedule) throw new NotFoundException('Schedule not found');
if (!coach) throw new NotFoundException('Coach not found');
if (coach.status !== 'ACTIVE') throw new BadRequestException('Coach is not active');
return this.prisma.coachAssignment.create({ data: dto });
}

View File

@@ -1,8 +1,9 @@
import { Controller, Get } from '@nestjs/common';
import { Controller, Get, HttpStatus, Res } from '@nestjs/common';
import { ApiTags, ApiOperation } from '@nestjs/swagger';
import { SkipThrottle } from '@nestjs/throttler';
import { IsPublic } from '@tria-plc/api-common/modules/auth/decorators/public.decorator';
import { PrismaService } from '../../common/prisma.service';
import { Response } from 'express';
@ApiTags('Health')
@Controller('health')
@@ -20,17 +21,17 @@ export class HealthController {
@Get('ready')
@IsPublic()
@ApiOperation({ summary: 'Readiness probe — checks database connectivity' })
async readiness() {
async readiness(@Res() res: Response) {
const start = Date.now();
try {
await this.prisma.$queryRaw`SELECT 1`;
return {
return res.status(HttpStatus.OK).json({
status: 'ok',
timestamp: new Date().toISOString(),
checks: { database: { status: 'ok', latencyMs: Date.now() - start } },
};
});
} catch (err) {
return {
return res.status(HttpStatus.SERVICE_UNAVAILABLE).json({
status: 'error',
timestamp: new Date().toISOString(),
checks: {
@@ -40,7 +41,7 @@ export class HealthController {
error: err instanceof Error ? err.message : 'Unknown error',
},
},
};
});
}
}

View File

@@ -1,4 +1,4 @@
import { Injectable, NotFoundException, BadRequestException } from '@nestjs/common';
import { Injectable, Logger, NotFoundException, BadRequestException } from '@nestjs/common';
import { InjectDataSource } from '@nestjs/typeorm';
import { DataSource } from 'typeorm';
import { PrismaService } from '../../common/prisma.service';
@@ -25,6 +25,7 @@ type IamUserRow = {
@Injectable()
export class PassengersService {
private readonly logger = new Logger(PassengersService.name);
constructor(
private readonly prisma: PrismaService,
@InjectDataSource() private readonly dataSource: DataSource,
@@ -373,7 +374,7 @@ export class PassengersService {
verifiedData = verification.passengerData;
}
} catch (error) {
console.warn('Fayda verification failed, using manual data:', error);
this.logger.warn('Fayda verification failed, using manual data:', error);
}
}

View File

@@ -1,4 +1,4 @@
import { Injectable } from '@nestjs/common';
import { Injectable, Logger } from '@nestjs/common';
import { InjectDataSource } from '@nestjs/typeorm';
import { DataSource } from 'typeorm';
import { PrismaService } from '../../common/prisma.service';
@@ -6,6 +6,7 @@ import { GenerateReportDto, ReportType } from './reports.dto';
@Injectable()
export class ReportsService {
private readonly logger = new Logger(ReportsService.name);
constructor(
private prisma: PrismaService,
@InjectDataSource() private dataSource: DataSource,
@@ -60,8 +61,6 @@ export class ReportsService {
include: { paymentIntent: true }
});
console.log(`[Reports] Revenue Report: Found ${bookings.length} bookings between ${dateFrom} and ${dateTo}`);
const totalRevenue = bookings.reduce((sum, b) => sum + b.totalMinor, 0);
const byPaymentMethod = bookings.reduce((acc, b) => {
const method = b.paymentIntent?.method ?? 'UNKNOWN';

View File

@@ -100,10 +100,15 @@ export class SchedulesService {
const arr = parseEthiopianTime(dto.arrivalAt);
if (arr <= dep) throw new BadRequestException('arrivalAt must be after departureAt');
const route = await this.prisma.route.findUnique({
where: { id: dto.routeId },
include: { stops: { orderBy: { sequence: 'asc' } } },
});
const [train, route] = await Promise.all([
this.prisma.train.findUnique({ where: { id: dto.trainId } }),
this.prisma.route.findUnique({
where: { id: dto.routeId },
include: { stops: { orderBy: { sequence: 'asc' } } },
}),
]);
if (!train) throw new NotFoundException('Train not found');
if (!train.isActive) throw new BadRequestException('Train is not active');
if (!route) throw new NotFoundException('Route not found');
if (!route.active) throw new BadRequestException('Route is not active');
if (route.stops.length < 2) throw new BadRequestException('Route must have at least 2 stops');
@@ -247,10 +252,15 @@ export class SchedulesService {
const arr = parseEthiopianTime(dto.arrivalAt);
if (arr <= dep) throw new BadRequestException('arrivalAt must be after departureAt');
const route = await this.prisma.route.findUnique({
where: { id: dto.routeId },
include: { stops: { orderBy: { sequence: 'asc' } } },
});
const [train, route] = await Promise.all([
this.prisma.train.findUnique({ where: { id: dto.trainId } }),
this.prisma.route.findUnique({
where: { id: dto.routeId },
include: { stops: { orderBy: { sequence: 'asc' } } },
}),
]);
if (!train) throw new NotFoundException('Train not found');
if (!train.isActive) throw new BadRequestException('Train is not active');
if (!route) throw new NotFoundException('Route not found');
if (!route.active) throw new BadRequestException('Route is not active');
if (route.stops.length < 2) throw new BadRequestException('Route must have at least 2 stops');
@@ -540,6 +550,8 @@ export class SchedulesService {
const coachIds = coaches.map(c => c.coachId);
const existingCoaches = await this.prisma.coach.findMany({ where: { id: { in: coachIds } } });
if (existingCoaches.length !== coachIds.length) throw new NotFoundException('One or more coaches not found');
const inactiveCoach = existingCoaches.find(c => c.status !== 'ACTIVE');
if (inactiveCoach) throw new BadRequestException(`Coach ${inactiveCoach.number} is not active`);
await this.prisma.coachAssignment.deleteMany({ where: { scheduleId } });

View File

@@ -6,12 +6,16 @@ export class SeatClassesService {
constructor(private prisma: PrismaService) {}
listSeatClasses() {
return this.prisma.seatClass.findMany({ orderBy: { createdAt: 'asc' } });
return this.prisma.seatClass.findMany({
where: { isActive: true },
orderBy: { createdAt: 'asc' },
});
}
async getSeatClass(id: string) {
const sc = await this.prisma.seatClass.findUnique({ where: { id } });
if (!sc) throw new NotFoundException('SeatClass not found');
if (!sc.isActive) throw new NotFoundException('SeatClass is not active');
return sc;
}

View File

@@ -33,8 +33,11 @@ export class StationsService {
where.countryCode = filters.country;
}
// Default to operational stations only; allow explicit override (e.g. back-office)
if (filters.operational !== undefined && filters.operational !== '') {
where.isOperational = filters.operational === 'true';
} else {
where.isOperational = true;
}
return this.prisma.station.findMany({
@@ -46,6 +49,7 @@ export class StationsService {
async findOne(id: string) {
const s = await this.prisma.station.findUnique({ where: { id } });
if (!s) throw new NotFoundException('Station not found');
if (!s.isOperational) throw new NotFoundException('Station is not operational');
return s;
}

View File

@@ -99,18 +99,11 @@ export class TicketsService {
let guestEmail = null;
const matchingProfile = t.booking?.passenger?.travelerProfiles?.find((tp: any) => tp.fullName === t.passengerName);
// DEBUG: Log to see what we're getting
this.logger.debug(`Ticket ${t.id}: passengerName=${t.passengerName}, profiles count=${t.booking?.passenger?.travelerProfiles?.length || 0}, matchingProfile=${!!matchingProfile}`);
if (matchingProfile) {
this.logger.debug(`Matching profile notes: ${matchingProfile.notes}`);
}
if (matchingProfile?.notes) {
try {
const notesData = JSON.parse(matchingProfile.notes);
guestPhone = notesData.phone || null;
guestEmail = notesData.email || null;
this.logger.debug(`Extracted from notes: phone=${guestPhone}, email=${guestEmail}`);
} catch (err) {
this.logger.error(`Failed to parse notes JSON: ${err}`);
}
@@ -120,13 +113,11 @@ export class TicketsService {
if (!guestPhone) guestPhone = t.booking?.contactPhone;
if (!guestEmail) guestEmail = t.booking?.contactEmail;
this.logger.debug(`Final values: phone=${guestPhone}, email=${guestEmail}`);
const passengerInfo = iam
? { fullName: iam.name?.en ?? iam.name?.am ?? null, email: iam.email, phone: iam.phone_number }
: { fullName: 'Guest', email: guestEmail, phone: guestPhone };
this.logger.debug(`Final passenger info: ${JSON.stringify(passengerInfo)}`);
return {
id: t.id,
@@ -596,8 +587,7 @@ export class TicketsService {
}
private async fireBoardingPassNotification(booking: any, ticket: any, leg: string | null) {
// TODO: Implement notification logic
console.log(`Boarding pass notification for booking ${booking.bookingRef}, leg: ${leg}`);
this.logger.log(`Boarding pass notification for booking ${booking.bookingRef}, leg: ${leg}`);
}
async getValidationLogs(ticketId: string) {
@@ -687,4 +677,4 @@ export class TicketsService {
if (!ticket) throw new NotFoundException('Ticket not found');
return this.prisma.ticket.update({ where: { id }, data: { status: 'ACTIVE' } });
}
}
}

View File

@@ -144,11 +144,10 @@ export default function CoachesPage() {
const [activeTab, setActiveTab] = useState<Tab>('coaches');
const [search, setSearch] = useState('');
const [showModal, setShowModal] = useState(false);
const [showPreviewModal, setShowPreviewModal] = useState(false);
const [seatMapPreview, setSeatMapPreview] = useState<any>(null);
const [editingItem, setEditingItem] = useState<any>(null);
const [deleteConfirm, setDeleteConfirm] = useState<{ isOpen: boolean; item: any | null; error?: string }>({ isOpen: false, item: null });
const [selectedCoachTypeId, setSelectedCoachTypeId] = useState<string>('');
const queryClient = useQueryClient();
// Coach Types Queries
@@ -221,14 +220,6 @@ export default function CoachesPage() {
},
});
const generateSeatMapMutation = useMutation({
mutationFn: fleetApi.generateSeatMap,
onSuccess: (data) => {
setSeatMapPreview(data);
setShowPreviewModal(true);
},
});
const handleCoachTypeSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
const formData = new FormData(e.currentTarget);
@@ -275,27 +266,6 @@ export default function CoachesPage() {
}
};
const handlePreviewSeatMap = async () => {
const form = document.querySelector('form') as HTMLFormElement;
const formData = new FormData(form);
const bedCategory = formData.get('bedCategory') as string;
const capacity = parseInt(formData.get('capacity') as string);
if (!bedCategory || !capacity) {
alert('Please select a bed category and enter capacity to preview seat map');
return;
}
const bedsPerRoom = bedCategory === 'VIP_BED' ? 4 : 6;
const roomsPerCoach = Math.ceil(capacity / bedsPerRoom);
await generateSeatMapMutation.mutateAsync({
coachCount: 1,
roomsPerCoach,
roomType: bedCategory,
});
};
const handleDelete = (item: any, isCoachType: boolean) => {
setDeleteConfirm({ isOpen: true, item: { ...item, isCoachType } });
};
@@ -779,23 +749,25 @@ export default function CoachesPage() {
/>
</div>
{/* Conditionally show bed fields only for Economy and Regular coach types */}
{(() => {
const selectedCoachType = coachTypesArray.find((ct: any) => ct.id === (selectedCoachTypeId || editingItem?.coachTypeId));
const isEconomyOrRegular = selectedCoachType &&
(selectedCoachType.name?.toLowerCase().includes('economy') ||
selectedCoachType.name?.toLowerCase().includes('regular') ||
selectedCoachType.type?.toLowerCase().includes('economy') ||
selectedCoachType.type?.toLowerCase().includes('regular'));
return isEconomyOrRegular ? (
const isBedType = selectedCoachType &&
(selectedCoachType.name?.toLowerCase().includes('bed') ||
selectedCoachType.name?.toLowerCase().includes('sleeper') ||
selectedCoachType.type?.toLowerCase().includes('sleeper'));
const derivedBedCategory = editingItem?.isCoach && selectedCoachType
? (selectedCoachType.name?.toLowerCase().includes('vip') ? 'VIP_BED' : 'ECONOMY_BED')
: (editingItem?.bedCategory || '');
return isBedType ? (
<>
<div>
<label className="label">Bed Category</label>
<select
name="bedCategory"
className="input"
defaultValue={editingItem?.bedCategory || ''}
defaultValue={derivedBedCategory}
>
<option value="">Select bed category</option>
<option value="ECONOMY_BED">Economy Bed</option>
@@ -832,9 +804,9 @@ export default function CoachesPage() {
type="text"
name="arrangement"
className="input"
defaultValue={editingItem?.arrangement || editingItem?.seatArrangement || '2+2'}
defaultValue={editingItem?.arrangement || editingItem?.seatArrangement }
required
placeholder="e.g., 2+2, 3+2"
placeholder="e.g., 3+2"
/>
<p className="text-xs text-muted-foreground mt-1">
For regular seats: columns separated by +
@@ -850,7 +822,7 @@ export default function CoachesPage() {
defaultValue={editingItem?.capacity || editingItem?.totalUnits || ''}
required
min="1"
placeholder="60"
placeholder="e.g., 60"
/>
</div>
@@ -860,10 +832,10 @@ export default function CoachesPage() {
type="number"
name="sequence"
className="input"
defaultValue={editingItem?.sequence || 1}
defaultValue={editingItem?.sequence }
min="1"
required
placeholder="1"
placeholder="e.g., 1"
/>
<p className="text-xs text-muted-foreground mt-1">
Position in train consist
@@ -885,14 +857,6 @@ export default function CoachesPage() {
</div>
<div className="flex justify-end gap-2 pt-4">
<ActionButton
type="button"
variant="secondary"
onClick={handlePreviewSeatMap}
loading={generateSeatMapMutation.isPending}
>
Preview Bed Layout
</ActionButton>
<ActionButton
type="button"
variant="secondary"
@@ -915,57 +879,7 @@ export default function CoachesPage() {
)}
</Modal>
{/* Seat Map Preview Modal */}
<Modal
isOpen={showPreviewModal}
onClose={() => {
setShowPreviewModal(false);
setSeatMapPreview(null);
}}
title="Bed Layout Preview"
size="lg"
>
{seatMapPreview && (
<div className="space-y-4">
<div className="bg-muted/50 p-4 rounded-lg">
<h4 className="font-semibold mb-2">Configuration</h4>
<div className="grid grid-cols-2 gap-4 text-sm">
<div>Room Type: <span className="font-medium">{seatMapPreview.roomType}</span></div>
<div>Rooms per Coach: <span className="font-medium">{seatMapPreview.roomsPerCoach}</span></div>
<div>Beds per Room: <span className="font-medium">{seatMapPreview.bedsPerRoom}</span></div>
<div>Total Beds: <span className="font-medium">{seatMapPreview.totalBeds}</span></div>
</div>
</div>
<div className="space-y-2">
<h4 className="font-semibold">Bed Layout Sample (First Few Rooms)</h4>
<div className="bg-gray-50 p-4 rounded border max-h-64 overflow-y-auto">
{seatMapPreview.seats?.slice(0, 24).map((seat: any, idx: number) => (
<div key={idx} className="text-xs mb-1 font-mono">
{seat.seat_id} - Room: {seat.room_id} - {seat.position} {seat.bed_type}
</div>
))}
{seatMapPreview.seats?.length > 24 && (
<div className="text-xs text-muted-foreground mt-2">
... and {seatMapPreview.seats.length - 24} more beds
</div>
)}
</div>
</div>
<div className="flex justify-end">
<ActionButton
onClick={() => {
setShowPreviewModal(false);
setSeatMapPreview(null);
}}
>
Close
</ActionButton>
</div>
</div>
)}
</Modal>
</div>
);
}

View File

@@ -55,12 +55,6 @@ function DashboardPageContent() {
retry: 1,
});
const { data: topAgents, isLoading: agentsLoading } = useQuery({
queryKey: ['top-agents'],
queryFn: () => dashboardApi.getTopAgents(5),
retry: 1,
});
const { data: upcomingTrips, isLoading: tripsLoading } = useQuery({
queryKey: ['upcoming-trips'],
queryFn: () => dashboardApi.getUpcomingTrips(5),
@@ -109,13 +103,6 @@ function DashboardPageContent() {
{ key: 'createdAt', label: 'Created', render: (item: any) => formatDateTime(item.createdAt) },
];
const agentColumns = [
{ key: 'name', label: 'Agent Name', render: (item: any) => item.name || item.fullName },
{ key: 'bookings', label: 'Bookings', render: (item: any) => item.bookingsCount || item.bookings || 0 },
{ key: 'revenue', label: 'Revenue', render: (item: any) => formatCurrency(item.totalRevenue || item.revenue || 0, 'ETB') },
{ key: 'commission', label: 'Commission', render: (item: any) => formatCurrency(item.commission || 0, 'ETB') },
];
const tripColumns = [
{ key: 'trainName', label: 'Train', render: (item: any) => item.trainName || item.train?.name },
{ key: 'route', label: 'Route', render: (item: any) => `${item.originStation?.name || item.origin?.name}${item.destinationStation?.name || item.destination?.name}` },
@@ -231,19 +218,6 @@ function DashboardPageContent() {
/>
</div>
{/* Top Agents */}
<div className="card">
<h2 className="mb-4 text-lg font-semibold text-foreground flex items-center gap-2">
<Users className="h-5 w-5" />
Top Performing Agents
</h2>
<DataTable
data={topAgents || []}
columns={agentColumns}
loading={agentsLoading}
emptyMessage="No agent performance data available"
/>
</div>
</div>
);
}

View File

@@ -32,7 +32,6 @@ export default function OperationalReportsPage() {
refetch();
setShowGenerateModal(false);
} catch (error) {
console.error('Error generating report:', error);
}
};

View File

@@ -64,7 +64,6 @@ export default function PaymentMethodsPage() {
setTimeout(() => setSuccessMessage(''), 3000);
},
onError: (error) => {
console.error('Update failed:', error);
setSuccessMessage('Failed to update payment method');
setTimeout(() => setSuccessMessage(''), 3000);
},
@@ -131,8 +130,6 @@ export default function PaymentMethodsPage() {
processingTime: formData.processingTime,
};
console.log('Submitting data:', submitData);
if (selectedMethod) {
updateMutation.mutate({ id: selectedMethod.id, ...submitData });
} else {

View File

@@ -49,8 +49,7 @@ export default function ActionButton({
try {
setIsLoading(true);
await onClick();
} catch (error) {
console.error('Action failed:', error);
} catch (error) {
} finally {
setIsLoading(false);
}

View File

@@ -13,10 +13,14 @@ export const dashboardApi = {
const bookingsTotal = bookingsRes?.meta?.total || 0;
const passengersTotal = passengersRes?.meta?.total || 0;
// Calculate revenue from bookings
const allBookingsRes = await apiClient.get<any>('/bookings?pageSize=100');
const allBookings = Array.isArray(allBookingsRes) ? allBookingsRes : allBookingsRes?.items || [];
const totalRevenue = allBookings.reduce((sum: number, b: any) => sum + (b.totalMinor || 0), 0);
// Calculate revenue from confirmed bookings only
const confirmedRes = await apiClient.get<any>('/bookings?pageSize=1000&status=CONFIRMED');
const boardedRes = await apiClient.get<any>('/bookings?pageSize=1000&status=BOARDED');
const paidBookings = [
...(Array.isArray(confirmedRes) ? confirmedRes : confirmedRes?.items || []),
...(Array.isArray(boardedRes) ? boardedRes : boardedRes?.items || []),
];
const totalRevenue = paidBookings.reduce((sum: number, b: any) => sum + (b.totalMinor || 0), 0);
// Calculate average occupancy (placeholder - would need dedicated endpoint)
const occupancyRate = Math.floor(Math.random() * 100); // Replace with actual data
@@ -29,10 +33,9 @@ export const dashboardApi = {
totalTripsToday: 0,
activeTrips: 0,
cancelledBookings: 0,
averageTicketPrice: allBookings.length > 0 ? totalRevenue / allBookings.length : 0,
averageTicketPrice: paidBookings.length > 0 ? Math.round(totalRevenue / paidBookings.length) : 0,
};
} catch (error) {
console.error('Failed to fetch dashboard stats:', error);
return {
totalBookings: 0,
totalRevenue: 0,
@@ -51,7 +54,6 @@ export const dashboardApi = {
const response = await apiClient.get<RevenueData[]>(`/dashboard/revenue?days=${days}`);
return response;
} catch (error) {
console.error('Failed to fetch revenue chart:', error);
return [];
}
},
@@ -82,7 +84,6 @@ export const dashboardApi = {
paymentIntent: booking.paymentIntent,
}));
} catch (error) {
console.error('Failed to fetch recent bookings:', error);
return [];
}
},
@@ -92,7 +93,6 @@ export const dashboardApi = {
const response = await apiClient.get<any[]>(`/agents/top?limit=${limit}`);
return response || [];
} catch (error) {
console.error('Failed to fetch top agents:', error);
return [];
}
},
@@ -102,7 +102,6 @@ export const dashboardApi = {
const response = await apiClient.get<any[]>(`/dashboard/occupancy?days=${days}`);
return response || [];
} catch (error) {
console.error('Failed to fetch occupancy trend:', error);
return [];
}
},
@@ -112,7 +111,6 @@ export const dashboardApi = {
const response = await apiClient.get<any[]>(`/schedules/upcoming?limit=${limit}`);
return response || [];
} catch (error) {
console.error('Failed to fetch upcoming trips:', error);
return [];
}
},
@@ -122,7 +120,6 @@ export const dashboardApi = {
const response = await apiClient.get<any[]>('/dashboard/payment-methods');
return response || [];
} catch (error) {
console.error('Failed to fetch payment methods:', error);
return [];
}
},
@@ -137,7 +134,6 @@ export const dashboardApi = {
loyaltyPoints: 0,
};
} catch (error) {
console.error('Failed to fetch passenger stats:', error);
return {
totalPassengers: 0,
newPassengersToday: 0,
@@ -157,7 +153,6 @@ export const dashboardApi = {
totalAmount: 0,
};
} catch (error) {
console.error('Failed to fetch transaction summary:', error);
return {
totalTransactions: 0,
successfulTransactions: 0,
@@ -176,7 +171,6 @@ export const dashboardApi = {
activePayments: 0,
};
} catch (error) {
console.error('Failed to fetch live metrics:', error);
return {
onlineUsers: 0,
activeBookings: 0,

View File

@@ -5,7 +5,6 @@ import { PaginatedResponse } from '@edr/types';
export const routesApi = {
getAll: async () => {
const response = await apiClient.get<any>('/routes');
console.log('Routes API response:', response);
// Handle both direct array and wrapped response
if (Array.isArray(response)) {
return { items: response };
@@ -24,7 +23,6 @@ export const routesApi = {
},
create: (data: any) => {
console.log('Creating route with data:', JSON.stringify(data, null, 2));
return apiClient.post<Route>('/routes', data);
},

View File

@@ -681,7 +681,7 @@ export default function PassengersPage() {
try {
const response: any = await apiClient.post('/fayda/verification/start', {
purpose: 'PURCHASE',
purpose: 'VERIFY',
platform: 'WEB',
saveToAccount: index === 0 && isAuthenticated,
});