import { compareColonyCodes } from "@/lib/colonies/sort"; import type { Suggestion, SuggestionIntent } from "./types"; export type SuggestionRow = | { kind: "single"; suggestion: Suggestion } | { kind: "combined"; intent: SuggestionIntent; suggestions: Suggestion[] }; export interface SuggestionGroupModel { key: string; label: string; apiaryId: string | null; rows: SuggestionRow[]; } const OPERATION_KEY = "operation"; const OPERATION_LABEL = "Betrieb"; function compareText(a: string, b: string): number { return a.localeCompare(b, "de", { numeric: true }); } function rowsOf(list: readonly Suggestion[]): SuggestionRow[] { const byIntent = new Map(); for (const suggestion of list) { byIntent.set(suggestion.intent, [...(byIntent.get(suggestion.intent) ?? []), suggestion]); } const rows: SuggestionRow[] = []; const done = new Set(); for (const suggestion of list) { if (done.has(suggestion.intent)) continue; done.add(suggestion.intent); const same = byIntent.get(suggestion.intent) ?? []; rows.push( same.length > 1 ? { kind: "combined", intent: suggestion.intent, suggestions: same } : { kind: "single", suggestion }, ); } return rows; } /** * One group per stand (by name), then „Betrieb“ for everything without a * stand. Inside a group the API's order (rank) is kept; the same intent is * one row, placed where its first suggestion is. */ export function groupSuggestions(list: readonly Suggestion[]): SuggestionGroupModel[] { const stands = new Map(); const operation: Suggestion[] = []; for (const suggestion of list) { if (suggestion.apiaryId === null) { operation.push(suggestion); } else { stands.set(suggestion.apiaryId, [...(stands.get(suggestion.apiaryId) ?? []), suggestion]); } } const groups: SuggestionGroupModel[] = [...stands.entries()].map(([apiaryId, members]) => ({ key: apiaryId, label: members[0].apiaryName ?? "Stand", apiaryId, rows: rowsOf(members), })); groups.sort((a, b) => compareText(a.label, b.label)); if (operation.length > 0) { groups.push({ key: OPERATION_KEY, label: OPERATION_LABEL, apiaryId: null, rows: rowsOf(operation) }); } return groups; } export interface RoundTarget { apiary: { id: string; name: string }; colonies: { id: string; code: string }[]; /** The first inspection of the round. */ href: string; /** The first inspection without round parameters (no signed-in user to keep the round). */ plainHref: string; } /** * The stand round for due inspections of one stand: the colonies once each, * in code order. Null without a colony at a stand. */ export function roundTarget(suggestions: readonly Suggestion[]): RoundTarget | null { const colonies = new Map(); let apiary: RoundTarget["apiary"] | null = null; for (const suggestion of suggestions) { if (suggestion.apiaryId === null || suggestion.colonyId === null) continue; apiary ??= { id: suggestion.apiaryId, name: suggestion.apiaryName ?? "Stand" }; if (suggestion.apiaryId === apiary.id) { colonies.set(suggestion.colonyId, suggestion.colonyCode ?? suggestion.colonyId); } } if (apiary === null) return null; const sorted = [...colonies.entries()] .map(([id, code]) => ({ id, code })) .sort((a, b) => compareColonyCodes(a.code, b.code)); const href = `/inspections/new?colonyId=${encodeURIComponent(sorted[0].id)}&round=${encodeURIComponent(apiary.id)}`; const plainHref = `/inspections/new?colonyId=${encodeURIComponent(sorted[0].id)}`; return { apiary, colonies: sorted, href, plainHref }; }