feat: warn on the dashboard when a swarm check is overdue

Schwarmkontrolle already captures nextCheckDate per check, but it was
only ever visible by opening that one colony's tab. During swarm
season a missed follow-up check can mean losing half the colony, so
this now surfaces as a dashboard alert (OVERDUE_SWARM_CHECK) like
overdue tasks already do - only the most recent check per colony is
considered, so a completed follow-up clears the warning.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
This commit is contained in:
Nick SzaunigandClaude Sonnet 5 committed 2026-09-06 02:35:17 +02:00
1 parent acbf5f16ce
commit 0881a38800
4 files changed
+101 -16

No files matched your search

@@ -11,7 +11,8 @@ export interface DashboardSummary {
}
export interface DashboardAlert {
type: 'OVERDUE_TASK' | 'COLONY_ATTENTION' | 'LOW_STOCK';
type:
'OVERDUE_TASK' | 'COLONY_ATTENTION' | 'LOW_STOCK' | 'OVERDUE_SWARM_CHECK';
message: string;
relatedType: string;
relatedId: string;
@@ -60,20 +61,30 @@ export class DashboardService {
}
async alerts(): Promise<DashboardAlert[]> {
const [overdueTasks, attentionColonies, stockItems] = await Promise.all([
this.prisma.task.findMany({
where: {
status: { in: ['OPEN', 'IN_PROGRESS'] },
dueDate: { lt: new Date() },
},
}),
this.prisma.colony.findMany({
where: { status: { in: [...ATTENTION_COLONY_STATUSES] } },
}),
this.prisma.stockItem.findMany({
where: { minThreshold: { not: null } },
}),
]);
const [overdueTasks, attentionColonies, stockItems, swarmChecks] =
await Promise.all([
this.prisma.task.findMany({
where: {
status: { in: ['OPEN', 'IN_PROGRESS'] },
dueDate: { lt: new Date() },
},
}),
this.prisma.colony.findMany({
where: { status: { in: [...ATTENTION_COLONY_STATUSES] } },
}),
this.prisma.stockItem.findMany({
where: { minThreshold: { not: null } },
}),
this.prisma.swarmControlCheck.findMany({
where: { colony: { status: 'ACTIVE' } },
orderBy: [{ colonyId: 'asc' }, { date: 'desc' }],
select: {
colonyId: true,
nextCheckDate: true,
colony: { select: { code: true } },
},
}),
]);
const alerts: DashboardAlert[] = [];
for (const task of overdueTasks) {
@@ -102,6 +113,26 @@ export class DashboardService {
});
}
}
// swarmChecks is ordered by colonyId then date desc, so the first entry
// seen per colonyId is that colony's most recent check - only the most
// recent nextCheckDate matters, an older overdue one has been superseded.
const now = new Date();
const latestCheckByColony = new Map<string, (typeof swarmChecks)[number]>();
for (const check of swarmChecks) {
if (!latestCheckByColony.has(check.colonyId)) {
latestCheckByColony.set(check.colonyId, check);
}
}
for (const check of latestCheckByColony.values()) {
if (check.nextCheckDate && check.nextCheckDate < now) {
alerts.push({
type: 'OVERDUE_SWARM_CHECK',
message: `Schwarmkontrolle für Volk ${check.colony.code} überfällig`,
relatedType: 'Colony',
relatedId: check.colonyId,
});
}
}
return alerts;
}
}