chore(repo): initialize planarchy workspace

This commit is contained in:
2026-03-14 14:31:09 +01:00
commit dd55d0e78b
769 changed files with 166461 additions and 0 deletions
@@ -0,0 +1,99 @@
"use client";
import { trpc } from "~/lib/trpc/client.js";
interface BalanceCardProps {
resourceId: string;
year?: number;
compact?: boolean;
}
export function BalanceCard({ resourceId, year = new Date().getFullYear(), compact = false }: BalanceCardProps) {
const { data: balance, isLoading } = trpc.entitlement.getBalance.useQuery(
{ resourceId, year },
{ staleTime: 30_000 },
);
if (isLoading) {
return (
<div className="rounded-xl border border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-800 p-4 animate-pulse">
<div className="h-4 bg-gray-100 dark:bg-gray-700 rounded w-1/3 mb-3" />
<div className="h-8 bg-gray-100 dark:bg-gray-700 rounded w-1/2" />
</div>
);
}
if (!balance) return null;
const pct = balance.entitledDays > 0
? Math.round((balance.usedDays / balance.entitledDays) * 100)
: 0;
if (compact) {
return (
<div className="flex items-center gap-3 text-sm">
<span className="font-medium text-gray-900 dark:text-gray-100">{balance.remainingDays}d remaining</span>
<span className="text-gray-400 dark:text-gray-600">·</span>
<span className="text-gray-500 dark:text-gray-400">{balance.usedDays}d used of {balance.entitledDays}d</span>
{balance.pendingDays > 0 && (
<>
<span className="text-gray-400 dark:text-gray-600">·</span>
<span className="text-amber-600">{balance.pendingDays}d pending</span>
</>
)}
</div>
);
}
return (
<div className="rounded-xl border border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-800 p-5 space-y-3">
<div className="flex items-center justify-between">
<h3 className="text-sm font-semibold text-gray-700 dark:text-gray-300">
Vacation Balance {year}
</h3>
{balance.carryoverDays > 0 && (
<span className="text-xs text-gray-400 dark:text-gray-500">+{balance.carryoverDays}d carried over</span>
)}
</div>
<div className="grid grid-cols-4 gap-3">
<Stat label="Entitled" value={balance.entitledDays} color="text-gray-900" />
<Stat label="Used" value={balance.usedDays} color="text-gray-600" />
<Stat label="Pending" value={balance.pendingDays} color="text-amber-600" />
<Stat label="Remaining" value={balance.remainingDays} color={balance.remainingDays < 5 ? "text-red-600" : "text-emerald-600"} />
</div>
{/* Progress bar */}
<div className="relative h-2 bg-gray-100 dark:bg-gray-700 rounded-full overflow-hidden">
<div
className="absolute inset-y-0 left-0 bg-emerald-500 rounded-full transition-all"
style={{ width: `${Math.min(100, pct)}%` }}
/>
{balance.pendingDays > 0 && (
<div
className="absolute inset-y-0 bg-amber-400 rounded-full"
style={{
left: `${Math.min(100, pct)}%`,
width: `${Math.min(100 - pct, Math.round((balance.pendingDays / balance.entitledDays) * 100))}%`,
}}
/>
)}
</div>
{balance.sickDays > 0 && (
<p className="text-xs text-gray-400 dark:text-gray-500">
{balance.sickDays} sick day{balance.sickDays !== 1 ? "s" : ""} recorded (not deducted from annual leave)
</p>
)}
</div>
);
}
function Stat({ label, value, color }: { label: string; value: number; color: string }) {
return (
<div className="text-center">
<p className={`text-xl font-bold ${color}`}>{value}</p>
<p className="text-xs text-gray-400 dark:text-gray-500 mt-0.5">{label}</p>
</div>
);
}