Files
app/src/admin/utils/dashboardHelpers.ts
BOHA 519edce373 fix: 2026-06-09 full-codebase audit hardening
Validation: shared NaN-guarded Zod coercion helpers in schemas/common.ts replace
the raw number|string transform idiom across every schema (the root-cause NaN bug
class); emailOrEmpty + lenient isoDateString/timeString.

Security: roles privilege-escalation closed; refresh-token family revocation on
reuse; TOTP uses config params; read endpoints permission-guarded; received-invoices
gross VAT on all paths; orders-pdf custom-items authz.

Concurrency: $queryRaw SELECT...FOR UPDATE locks in ascending-id order (warehouse
confirm/cancel, attendance lockUserRow); uniqueness checks moved into create
transactions (TOCTOU -> 409); deterministic id tiebreak on second-precision
timestamp ordering (plan resolveCell/resolveGrid, warehouse FIFO).

Frontend: Rules-of-Hooks fixed across ~14 pages + PlanCellModal; UTC-date persisted
fields; dashboard invalidation gaps; stale-closure confirm bugs.

Tooling/tests: ESLint flat config (react-hooks/rules-of-hooks = error) + Prettier;
tsconfig.test.json so tsc -b type-checks the tests; removed 3 dead deps; npm audit
fix (8 -> 3). Suite 195 -> 247 (happy-path auth, FIFO oldest-first, flakiness fixes),
isolated on app_test via .env.test with a hard-throw setup guard.

Gates: tsc 0 | build 0 | vitest 247/247 | eslint 0 errors.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-09 06:45:26 +02:00

98 lines
2.6 KiB
TypeScript

export const LEAVE_TYPE_LABELS: Record<string, string> = {
vacation: "Dovolená",
sick: "Nemoc",
unpaid: "Neplacené volno",
};
export const STATUS_LABELS: Record<string, string> = {
in: "Přítomen",
away: "Přestávka",
out: "Nepřihlášen",
leave: "Nepřítomen",
absent: "Nepřítomen",
};
// Re-exported from the single source of truth so the dashboard activity feed
// and the Audit Log page can't drift apart. See ../lib/entityTypeLabels.
export { ENTITY_TYPE_LABELS } from "../lib/entityTypeLabels";
export const ACTION_LABELS: Record<string, string> = {
create: "Vytvořil",
update: "Upravil",
delete: "Smazal",
login: "Přihlášení",
};
export function getCzechDate(): string {
const now = new Date();
const days = [
"Neděle",
"Pondělí",
"Úterý",
"Středa",
"Čtvrtek",
"Pátek",
"Sobota",
];
const months = [
"ledna",
"února",
"března",
"dubna",
"května",
"června",
"července",
"srpna",
"září",
"října",
"listopadu",
"prosince",
];
const day = days[now.getDay()];
const oneJan = new Date(now.getFullYear(), 0, 1);
// Naive week-of-year (NOT true ISO-8601 week-numbering): can be off by one
// around the year boundary. Intentionally left as-is — this string is
// display-only in the dashboard header; a correct ISO-week implementation
// (Thursday-anchored, week-year aware) is more than this cosmetic line warrants.
const week = Math.ceil(
((now.getTime() - oneJan.getTime()) / 86400000 + oneJan.getDay() + 1) / 7,
);
return `${day}, ${now.getDate()}. ${months[now.getMonth()]} ${now.getFullYear()} · Týden ${week}`;
}
export function getActivityIconClass(action: string): string {
const map: Record<string, string> = {
create: "success",
update: "info",
delete: "danger",
login: "accent",
};
return map[action] || "muted";
}
export function formatActivityTime(dateString: string): string {
const date = new Date(dateString);
const now = new Date();
const diff = now.getTime() - date.getTime();
if (diff < 60000) return "Právě teď";
if (diff < 3600000) return `${Math.floor(diff / 60000)} min`;
if (date.toDateString() === now.toDateString()) {
return date.toLocaleTimeString("cs-CZ", {
hour: "2-digit",
minute: "2-digit",
});
}
return date.toLocaleDateString("cs-CZ", { day: "2-digit", month: "2-digit" });
}
export function formatSessionDate(dateString: string): string {
const date = new Date(dateString);
return date.toLocaleDateString("cs-CZ", {
day: "2-digit",
month: "2-digit",
year: "numeric",
hour: "2-digit",
minute: "2-digit",
});
}