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>
98 lines
2.6 KiB
TypeScript
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",
|
|
});
|
|
}
|