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

@@ -16,6 +16,7 @@ import { Roles } from '../auth/decorators/roles.decorator';
import { CurrentUser } from '../auth/decorators/current-user.decorator';
import { Role } from '@prisma/client';
import { CreateEventDto, UpdateEventDto, UpdateEventStatusDto } from './dto';
import { ParseBooleanPipe } from '../common/pipes';
@Controller('events')
@UseGuards(JwtAuthGuard, RolesGuard)
@@ -59,10 +60,9 @@ export class EventsController {
@Roles(Role.ADMINISTRATOR, Role.COORDINATOR)
remove(
@Param('id') id: string,
@Query('hard') hard?: string,
@Query('hard', ParseBooleanPipe) hard: boolean,
@CurrentUser() user?: any,
) {
const isHardDelete = hard === 'true';
return this.eventsService.remove(id, isHardDelete, user?.role);
return this.eventsService.remove(id, hard, user?.role);
}
}

View File

@@ -7,11 +7,24 @@ import {
} from '@nestjs/common';
import { PrismaService } from '../prisma/prisma.service';
import { CreateEventDto, UpdateEventDto, UpdateEventStatusDto } from './dto';
import { executeHardDelete } from '../common/utils';
@Injectable()
export class EventsService {
private readonly logger = new Logger(EventsService.name);
private readonly eventInclude = {
driver: true,
vehicle: true,
masterEvent: {
select: { id: true, title: true, type: true, startTime: true, endTime: true },
},
childEvents: {
where: { deletedAt: null },
select: { id: true, title: true, type: true },
},
} as const;
constructor(private prisma: PrismaService) {}
async create(createEventDto: CreateEventDto) {
@@ -69,17 +82,7 @@ export class EventsService {
startTime: new Date(createEventDto.startTime),
endTime: new Date(createEventDto.endTime),
},
include: {
driver: true,
vehicle: true,
masterEvent: {
select: { id: true, title: true, type: true, startTime: true, endTime: true },
},
childEvents: {
where: { deletedAt: null },
select: { id: true, title: true, type: true },
},
},
include: this.eventInclude,
});
return this.enrichEventWithVips(event);
@@ -88,37 +91,46 @@ export class EventsService {
async findAll() {
const events = await this.prisma.scheduleEvent.findMany({
where: { deletedAt: null },
include: {
driver: true,
vehicle: true,
masterEvent: {
select: { id: true, title: true, type: true, startTime: true, endTime: true },
},
childEvents: {
where: { deletedAt: null },
select: { id: true, title: true, type: true },
},
},
include: this.eventInclude,
orderBy: { startTime: 'asc' },
});
return Promise.all(events.map((event) => this.enrichEventWithVips(event)));
// Collect all unique VIP IDs from all events
const allVipIds = new Set<string>();
events.forEach((event) => {
event.vipIds?.forEach((vipId) => allVipIds.add(vipId));
});
// Fetch all VIPs in a single query (eliminates N+1)
const vipsMap = new Map();
if (allVipIds.size > 0) {
const vips = await this.prisma.vIP.findMany({
where: {
id: { in: Array.from(allVipIds) },
deletedAt: null,
},
});
vips.forEach((vip) => vipsMap.set(vip.id, vip));
}
// Enrich each event with its VIPs from the map (no additional queries)
return events.map((event) => {
if (!event.vipIds || event.vipIds.length === 0) {
return { ...event, vips: [], vip: null };
}
const vips = event.vipIds
.map((vipId) => vipsMap.get(vipId))
.filter((vip) => vip !== undefined);
return { ...event, vips, vip: vips[0] || null };
});
}
async findOne(id: string) {
const event = await this.prisma.scheduleEvent.findFirst({
where: { id, deletedAt: null },
include: {
driver: true,
vehicle: true,
masterEvent: {
select: { id: true, title: true, type: true, startTime: true, endTime: true },
},
childEvents: {
where: { deletedAt: null },
select: { id: true, title: true, type: true },
},
},
include: this.eventInclude,
});
if (!event) {
@@ -207,17 +219,7 @@ export class EventsService {
const updatedEvent = await this.prisma.scheduleEvent.update({
where: { id: event.id },
data: updateData,
include: {
driver: true,
vehicle: true,
masterEvent: {
select: { id: true, title: true, type: true, startTime: true, endTime: true },
},
childEvents: {
where: { deletedAt: null },
select: { id: true, title: true, type: true },
},
},
include: this.eventInclude,
});
return this.enrichEventWithVips(updatedEvent);
@@ -233,40 +235,27 @@ export class EventsService {
const updatedEvent = await this.prisma.scheduleEvent.update({
where: { id: event.id },
data: { status: updateEventStatusDto.status },
include: {
driver: true,
vehicle: true,
masterEvent: {
select: { id: true, title: true, type: true, startTime: true, endTime: true },
},
childEvents: {
where: { deletedAt: null },
select: { id: true, title: true, type: true },
},
},
include: this.eventInclude,
});
return this.enrichEventWithVips(updatedEvent);
}
async remove(id: string, hardDelete = false, userRole?: string) {
if (hardDelete && userRole !== 'ADMINISTRATOR') {
throw new ForbiddenException('Only administrators can permanently delete records');
}
const event = await this.findOne(id);
if (hardDelete) {
this.logger.log(`Hard deleting event: ${event.title}`);
return this.prisma.scheduleEvent.delete({
where: { id: event.id },
});
}
this.logger.log(`Soft deleting event: ${event.title}`);
return this.prisma.scheduleEvent.update({
where: { id: event.id },
data: { deletedAt: new Date() },
return executeHardDelete({
id,
hardDelete,
userRole,
findOne: (id) => this.findOne(id),
performHardDelete: (id) =>
this.prisma.scheduleEvent.delete({ where: { id } }),
performSoftDelete: (id) =>
this.prisma.scheduleEvent.update({
where: { id },
data: { deletedAt: new Date() },
}),
entityName: 'Event',
logger: this.logger,
});
}