feat(platform): checkpoint current implementation state
This commit is contained in:
@@ -3,7 +3,6 @@
|
||||
import { trpc } from "~/lib/trpc/client.js";
|
||||
import { InfoTooltip } from "~/components/ui/InfoTooltip.js";
|
||||
import { ProgressRing } from "~/components/ui/ProgressRing.js";
|
||||
|
||||
interface BalanceCardProps {
|
||||
resourceId: string;
|
||||
year?: number;
|
||||
@@ -11,7 +10,7 @@ interface BalanceCardProps {
|
||||
}
|
||||
|
||||
export function BalanceCard({ resourceId, year = new Date().getFullYear(), compact = false }: BalanceCardProps) {
|
||||
const { data: balance, isLoading } = trpc.entitlement.getBalance.useQuery(
|
||||
const { data: balance, isLoading } = trpc.entitlement.getBalanceDetail.useQuery(
|
||||
{ resourceId, year },
|
||||
{ staleTime: 30_000 },
|
||||
);
|
||||
@@ -27,20 +26,20 @@ export function BalanceCard({ resourceId, year = new Date().getFullYear(), compa
|
||||
|
||||
if (!balance) return null;
|
||||
|
||||
const pct = balance.entitledDays > 0
|
||||
? Math.round((balance.usedDays / balance.entitledDays) * 100)
|
||||
const pct = balance.entitlement > 0
|
||||
? Math.round((balance.taken / balance.entitlement) * 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="font-medium text-gray-900 dark:text-gray-100">{balance.remaining}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-500 dark:text-gray-400">{balance.taken}d used of {balance.entitlement}d</span>
|
||||
{balance.pending > 0 && (
|
||||
<>
|
||||
<span className="text-gray-400 dark:text-gray-600">·</span>
|
||||
<span className="text-amber-600">{balance.pendingDays}d pending</span>
|
||||
<span className="text-amber-600">{balance.pending}d pending</span>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
@@ -52,31 +51,36 @@ export function BalanceCard({ resourceId, year = new Date().getFullYear(), compa
|
||||
: pct >= 70
|
||||
? "var(--color-amber-500, #f59e0b)"
|
||||
: "var(--color-emerald-500, #10b981)";
|
||||
const holidayBasisVariants = balance.deductionSummary?.holidayBasisVariants ?? [];
|
||||
const excludedHolidayCount = balance.deductionSummary?.excludedHolidayDates.length ?? 0;
|
||||
const excludedHolidayTooltip = (balance.vacations ?? [])
|
||||
.flatMap((vacation) => vacation.holidayDetails.map((detail) => `${detail.date} · ${detail.source}`))
|
||||
.join("\n");
|
||||
|
||||
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">
|
||||
<div className="flex items-center gap-3">
|
||||
<ProgressRing value={pct} size={52} strokeWidth={4} color={ringColor}>
|
||||
<span className="text-sm font-bold text-gray-900 dark:text-gray-100">{balance.remainingDays}d</span>
|
||||
<span className="text-sm font-bold text-gray-900 dark:text-gray-100">{balance.remaining}d</span>
|
||||
</ProgressRing>
|
||||
<div>
|
||||
<h3 className="text-sm font-semibold text-gray-700 dark:text-gray-300">
|
||||
Vacation Balance {year}
|
||||
</h3>
|
||||
<p className="text-xs text-gray-400 dark:text-gray-500">{balance.usedDays} of {balance.entitledDays} days used</p>
|
||||
<p className="text-xs text-gray-400 dark:text-gray-500">{balance.taken} of {balance.entitlement} days used</p>
|
||||
</div>
|
||||
</div>
|
||||
{balance.carryoverDays > 0 && (
|
||||
<span className="text-xs text-gray-400 dark:text-gray-500 inline-flex items-center">+{balance.carryoverDays}d carried over<InfoTooltip content="Unused days from the previous year. Automatically calculated on first access." /></span>
|
||||
{balance.carryOver > 0 && (
|
||||
<span className="text-xs text-gray-400 dark:text-gray-500 inline-flex items-center">+{balance.carryOver}d carried over<InfoTooltip content="Unused days from the previous year. Automatically calculated on first access." /></span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-4 gap-3">
|
||||
<Stat label="Entitled" value={balance.entitledDays} color="text-gray-900 dark:text-gray-100" tooltip="Total vacation days granted for this year, including carryover from previous year." />
|
||||
<Stat label="Used" value={balance.usedDays} color="text-gray-600 dark:text-gray-400" tooltip="Days already consumed by approved vacations that have passed." />
|
||||
<Stat label="Pending" value={balance.pendingDays} color="text-amber-600" tooltip="Days reserved by approved future vacations not yet started." />
|
||||
<Stat label="Remaining" value={balance.remainingDays} color={balance.remainingDays < 5 ? "text-red-600" : "text-emerald-600"} tooltip="Entitled - Used - Pending. Red if fewer than 5 days remain." />
|
||||
<Stat label="Entitled" value={balance.entitlement} color="text-gray-900 dark:text-gray-100" tooltip="Total vacation days granted for this year, including carryover from previous year." />
|
||||
<Stat label="Used" value={balance.taken} color="text-gray-600 dark:text-gray-400" tooltip="Days already consumed by approved vacations that have passed." />
|
||||
<Stat label="Pending" value={balance.pending} color="text-amber-600" tooltip="Days reserved by approved future vacations not yet started." />
|
||||
<Stat label="Remaining" value={balance.remaining} color={balance.remaining < 5 ? "text-red-600" : "text-emerald-600"} tooltip="Entitled - Used - Pending. Red if fewer than 5 days remain." />
|
||||
</div>
|
||||
|
||||
{/* Progress bar */}
|
||||
@@ -85,12 +89,12 @@ export function BalanceCard({ resourceId, year = new Date().getFullYear(), compa
|
||||
className="absolute inset-y-0 left-0 bg-emerald-500 rounded-full transition-all"
|
||||
style={{ width: `${Math.min(100, pct)}%` }}
|
||||
/>
|
||||
{balance.pendingDays > 0 && (
|
||||
{balance.pending > 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))}%`,
|
||||
width: `${Math.min(100 - pct, Math.round((balance.pending / balance.entitlement) * 100))}%`,
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
@@ -101,6 +105,35 @@ export function BalanceCard({ resourceId, year = new Date().getFullYear(), compa
|
||||
{balance.sickDays} sick day{balance.sickDays !== 1 ? "s" : ""} recorded (not deducted from annual leave)
|
||||
</p>
|
||||
)}
|
||||
|
||||
{!!balance.deductionSummary && (balance.deductionSummary.approvedVacationCount > 0 || balance.deductionSummary.pendingVacationCount > 0) && (
|
||||
<div className="rounded-lg border border-gray-100 bg-gray-50 px-3 py-2 text-xs text-gray-500 dark:border-gray-700 dark:bg-gray-900/60 dark:text-gray-400">
|
||||
<div className="flex flex-wrap gap-x-3 gap-y-1">
|
||||
<span className="inline-flex items-center gap-1">
|
||||
Formula
|
||||
<InfoTooltip content={balance.deductionSummary.formula} />
|
||||
</span>
|
||||
<span>
|
||||
Vacation deductions: {balance.deductionSummary.approvedDeductedDays}d approved
|
||||
{balance.deductionSummary.pendingDeductedDays > 0 ? ` · ${balance.deductionSummary.pendingDeductedDays}d pending` : ""}
|
||||
</span>
|
||||
<span>
|
||||
Requested: {balance.deductionSummary.approvedRequestedDays + balance.deductionSummary.pendingRequestedDays}d
|
||||
</span>
|
||||
{excludedHolidayCount > 0 && (
|
||||
<span className="inline-flex items-center gap-1">
|
||||
Excluded holidays: {excludedHolidayCount}
|
||||
{excludedHolidayTooltip.length > 0 && <InfoTooltip content={excludedHolidayTooltip} />}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
{holidayBasisVariants.length > 0 && (
|
||||
<div className="mt-1">
|
||||
Holiday basis: {holidayBasisVariants.join(" · ")}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user