/** * Human-readable Czech descriptions for work-plan audit-log rows. * * The audit log table ("Popis" column) shows only the `description` string, * so it must be self-contained: who, when, what. Previously the plan routes * left `description` undefined for normal edits, which rendered as "-" and * made the audit rows useless. These helpers build a readable subject line * such as: `Jan Novák · 08.06.2026 – 12.06.2026 · Práce · Projekt ABC`. * * Everything here is pure (no Prisma, no Date.now) so it is trivially unit * testable. The service layer resolves user/project names and feeds plain * strings in. */ /** Czech labels for the `plan_category` enum. Mirrors the frontend * PlanCellModal options so the audit log reads the same as the UI. */ export const PLAN_CATEGORY_LABELS_CS: Record = { work: "Práce", preparation: "Příprava", travel: "Cesta / Montáž", leave: "Dovolená", sick: "Nemoc", training: "Školení", other: "Jiné", }; /** Translate a plan category to its Czech label, falling back to the raw * value for unknown categories so nothing is ever silently dropped. */ export function planCategoryLabel(category: string): string { return PLAN_CATEGORY_LABELS_CS[category] ?? category; } /** * Format a `YYYY-MM-DD` string as Czech `DD.MM.YYYY`. * * Takes a string (not a Date) on purpose: work-plan dates live in `@db.Date` * columns and the service derives the ISO day via `toISOString().slice(0,10)`, * so there is no timezone ambiguity to reintroduce here. Matches the * `localDateCzStr` style in `src/utils/date.ts`. */ export function formatPlanDate(iso: string): string { const [y, m, d] = iso.split("-"); if (!y || !m || !d) return iso; return `${d}.${m}.${y}`; } export interface PlanAuditDescriptionInput { /** Employee the plan row belongs to (display name). */ userName: string; /** Already-resolved Czech category label (resolved from plan_categories). */ categoryLabel: string; /** Resolved project name, or null when the row has no project. */ projectName?: string | null; /** Range start (YYYY-MM-DD). For overrides, pass the shift date here. */ dateFrom: string; /** Range end (YYYY-MM-DD). For overrides/single days, equals `dateFrom`. */ dateTo: string; /** Append "nouzová úprava" when the change bypassed the past-date lock. */ force?: boolean; /** Extra Czech note appended at the end (e.g. "nahrazeno novým záznamem"). */ suffix?: string; } /** * Build a Czech audit description: a `·`-separated subject line. The action * (create/update/delete) and entity type are shown in their own audit-table * columns, so the description carries only the subject — never the verb. */ export function buildPlanAuditDescription( opts: PlanAuditDescriptionInput, ): string { const dateLabel = opts.dateFrom === opts.dateTo ? formatPlanDate(opts.dateFrom) : `${formatPlanDate(opts.dateFrom)} – ${formatPlanDate(opts.dateTo)}`; const parts: string[] = [opts.userName, dateLabel, opts.categoryLabel]; if (opts.projectName && opts.projectName.trim()) { parts.push(opts.projectName.trim()); } if (opts.suffix && opts.suffix.trim()) { parts.push(opts.suffix.trim()); } if (opts.force) { parts.push("nouzová úprava"); } return parts.join(" · "); }