fix(plan): readable audit descriptions, project labels, dashboard invalidation, view-only & sticky-column UI
Work-plan fixes from this session: - Czech audit descriptions for plan entries/overrides (was empty '-') - server-embedded project label on grid cells (was '-' past the 100-project cap) - dashboard activity + audit-log cache invalidation on plan mutations - read-only cells: no '+' on empty, keep hover on cells with a record - view modal: Czech category + formatted dates - sticky date column made opaque (no bleed-through on horizontal scroll) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
94
src/utils/planAuditDescription.ts
Normal file
94
src/utils/planAuditDescription.ts
Normal file
@@ -0,0 +1,94 @@
|
||||
/**
|
||||
* 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<string, string> = {
|
||||
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;
|
||||
/** `plan_category` enum value. */
|
||||
category: 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,
|
||||
planCategoryLabel(opts.category),
|
||||
];
|
||||
|
||||
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(" · ");
|
||||
}
|
||||
Reference in New Issue
Block a user