refactor: code efficiency improvements (Issues #9-13, #15, #17-20)

Backend:
- Extract shared hard-delete authorization utility (#9)
- Extract Prisma include constants per entity (#11)
- Fix N+1 query pattern in events findAll (#12)
- Extract shared date utility functions (#13)
- Move vehicle utilization filtering to DB query (#15)
- Add ParseBooleanPipe for query params
- Add CurrentDriver decorator + ResolveDriverInterceptor (#20)

Frontend:
- Extract shared form utilities (toDatetimeLocal) and enum labels (#17)
- Replace browser confirm() with styled ConfirmModal (#18)
- Add centralized query-keys.ts constants (#19)
- Clean up unused imports, add useMemo where needed (#19)
- Standardize filter button styling across list pages

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-08 16:07:19 +01:00
parent 806b67954e
commit f2b3f34a72
38 changed files with 1042 additions and 463 deletions

View File

@@ -1,11 +1,21 @@
import { Injectable, NotFoundException, ForbiddenException, Logger } from '@nestjs/common';
import { Injectable, NotFoundException, Logger } from '@nestjs/common';
import { PrismaService } from '../prisma/prisma.service';
import { CreateVehicleDto, UpdateVehicleDto } from './dto';
import { executeHardDelete } from '../common/utils';
@Injectable()
export class VehiclesService {
private readonly logger = new Logger(VehiclesService.name);
private readonly vehicleInclude = {
currentDriver: true,
events: {
where: { deletedAt: null },
include: { driver: true, vehicle: true },
orderBy: { startTime: 'asc' as const },
},
} as const;
constructor(private prisma: PrismaService) {}
async create(createVehicleDto: CreateVehicleDto) {
@@ -13,27 +23,14 @@ export class VehiclesService {
return this.prisma.vehicle.create({
data: createVehicleDto,
include: {
currentDriver: true,
events: {
where: { deletedAt: null },
include: { driver: true, vehicle: true },
},
},
include: this.vehicleInclude,
});
}
async findAll() {
return this.prisma.vehicle.findMany({
where: { deletedAt: null },
include: {
currentDriver: true,
events: {
where: { deletedAt: null },
include: { driver: true, vehicle: true },
orderBy: { startTime: 'asc' },
},
},
include: this.vehicleInclude,
orderBy: { name: 'asc' },
});
}
@@ -54,14 +51,7 @@ export class VehiclesService {
async findOne(id: string) {
const vehicle = await this.prisma.vehicle.findFirst({
where: { id, deletedAt: null },
include: {
currentDriver: true,
events: {
where: { deletedAt: null },
include: { driver: true, vehicle: true },
orderBy: { startTime: 'asc' },
},
},
include: this.vehicleInclude,
});
if (!vehicle) {
@@ -79,34 +69,24 @@ export class VehiclesService {
return this.prisma.vehicle.update({
where: { id: vehicle.id },
data: updateVehicleDto,
include: {
currentDriver: true,
events: {
where: { deletedAt: null },
include: { driver: true, vehicle: true },
},
},
include: this.vehicleInclude,
});
}
async remove(id: string, hardDelete = false, userRole?: string) {
if (hardDelete && userRole !== 'ADMINISTRATOR') {
throw new ForbiddenException('Only administrators can permanently delete records');
}
const vehicle = await this.findOne(id);
if (hardDelete) {
this.logger.log(`Hard deleting vehicle: ${vehicle.name}`);
return this.prisma.vehicle.delete({
where: { id: vehicle.id },
});
}
this.logger.log(`Soft deleting vehicle: ${vehicle.name}`);
return this.prisma.vehicle.update({
where: { id: vehicle.id },
data: { deletedAt: new Date() },
return executeHardDelete({
id,
hardDelete,
userRole,
findOne: (id) => this.findOne(id),
performHardDelete: (id) => this.prisma.vehicle.delete({ where: { id } }),
performSoftDelete: (id) =>
this.prisma.vehicle.update({
where: { id },
data: { deletedAt: new Date() },
}),
entityName: 'Vehicle',
logger: this.logger,
});
}
@@ -114,24 +94,35 @@ export class VehiclesService {
* Get vehicle utilization statistics
*/
async getUtilization() {
const vehicles = await this.findAll();
const now = new Date();
const stats = vehicles.map((vehicle) => {
const upcomingEvents = vehicle.events.filter(
(event) => new Date(event.startTime) > new Date(),
);
return {
id: vehicle.id,
name: vehicle.name,
type: vehicle.type,
seatCapacity: vehicle.seatCapacity,
status: vehicle.status,
upcomingTrips: upcomingEvents.length,
currentDriver: vehicle.currentDriver?.name,
};
// Fetch vehicles with only upcoming events (filtered at database level)
const vehicles = await this.prisma.vehicle.findMany({
where: { deletedAt: null },
include: {
currentDriver: true,
events: {
where: {
deletedAt: null,
startTime: { gt: now }, // Only fetch upcoming events
},
include: { driver: true, vehicle: true },
orderBy: { startTime: 'asc' },
},
},
orderBy: { name: 'asc' },
});
const stats = vehicles.map((vehicle) => ({
id: vehicle.id,
name: vehicle.name,
type: vehicle.type,
seatCapacity: vehicle.seatCapacity,
status: vehicle.status,
upcomingTrips: vehicle.events.length, // Already filtered at DB level
currentDriver: vehicle.currentDriver?.name,
}));
return {
totalVehicles: vehicles.length,
available: vehicles.filter((v) => v.status === 'AVAILABLE').length,