Scaffolds the Next.js 16 / TypeScript strict / Tailwind v3 app per NEXTJS_REBUILD_SUPERPROMPT.md, and implements the Foundation slice from the Phase 1 plan: - Corrected Supabase schema (supabase/schema.sql): org units, employees, history, positions, hire drafts, saved reports, audit log, reorg scenarios, role-based profiles, salary-masking view, RLS policies, auto-derivation triggers, position-number generator. - Seed script (supabase/seed.ts): ~800 realistic Austrian employees across 9 divisions / 16 departments / 35 teams, history, 8 open positions, and hr_admin/manager test accounts. - Supabase clients (lib/supabase/*), design tokens (tailwind.config.ts), format/color helpers (lib/format.ts, lib/colors.ts). - Shared UI kit (components/ui): Avatar, StatusChip, Toast, Modal, SlideOver, SegmentedControl, Lookup. - Auth (login page, Server Actions) and proxy.ts (Next 16's replacement for middleware) guarding the authenticated route group. - Shell (Sidebar, Topbar, NewHireButton stub) and the Dashboard page, reading live data via employees_directory. Employees list/detail, hire wizard, action panels, org chart, positions, reports, and audit log are deferred to later phases per the plan.
65 lines
2.3 KiB
TypeScript
65 lines
2.3 KiB
TypeScript
const dateFormatter = new Intl.DateTimeFormat("de-AT", {
|
||
day: "2-digit",
|
||
month: "2-digit",
|
||
year: "numeric",
|
||
});
|
||
|
||
const eurFormatter = new Intl.NumberFormat("de-AT", {
|
||
style: "currency",
|
||
currency: "EUR",
|
||
});
|
||
|
||
export function fmtDate(date: string | Date | null | undefined): string {
|
||
if (!date) return "–";
|
||
const d = typeof date === "string" ? new Date(date) : date;
|
||
if (Number.isNaN(d.getTime())) return "–";
|
||
return dateFormatter.format(d);
|
||
}
|
||
|
||
export function fmtEUR(amount: number | null | undefined): string {
|
||
if (amount === null || amount === undefined) return "••• (ausgeblendet)";
|
||
return eurFormatter.format(amount);
|
||
}
|
||
|
||
export function initials(firstName: string, lastName: string): string {
|
||
const a = firstName.trim().charAt(0).toUpperCase();
|
||
const b = lastName.trim().charAt(0).toUpperCase();
|
||
return `${a}${b}`;
|
||
}
|
||
|
||
export function fmtAge(birthDate: string | Date): number {
|
||
const d = typeof birthDate === "string" ? new Date(birthDate) : birthDate;
|
||
const today = new Date();
|
||
let age = today.getFullYear() - d.getFullYear();
|
||
const hasHadBirthdayThisYear =
|
||
today.getMonth() > d.getMonth() ||
|
||
(today.getMonth() === d.getMonth() && today.getDate() >= d.getDate());
|
||
if (!hasHadBirthdayThisYear) age -= 1;
|
||
return age;
|
||
}
|
||
|
||
export function tenure(entryDate: string | Date, endDate?: string | Date | null): string {
|
||
const start = typeof entryDate === "string" ? new Date(entryDate) : entryDate;
|
||
const end = endDate ? (typeof endDate === "string" ? new Date(endDate) : endDate) : new Date();
|
||
|
||
let years = end.getFullYear() - start.getFullYear();
|
||
let months = end.getMonth() - start.getMonth();
|
||
if (end.getDate() < start.getDate()) months -= 1;
|
||
if (months < 0) {
|
||
years -= 1;
|
||
months += 12;
|
||
}
|
||
if (years < 0) return "0 Monate";
|
||
|
||
const yearPart = years > 0 ? `${years} ${years === 1 ? "Jahr" : "Jahre"}` : "";
|
||
const monthPart = months > 0 ? `${months} ${months === 1 ? "Monat" : "Monate"}` : "";
|
||
if (yearPart && monthPart) return `${yearPart}, ${monthPart}`;
|
||
return yearPart || monthPart || "unter 1 Monat";
|
||
}
|
||
|
||
export function daysBetween(a: string | Date, b: string | Date = new Date()): number {
|
||
const start = typeof a === "string" ? new Date(a) : a;
|
||
const end = typeof b === "string" ? new Date(b) : b;
|
||
return Math.round((end.getTime() - start.getTime()) / (1000 * 60 * 60 * 24));
|
||
}
|