"use client"; import { useState } from "react"; import { VacationStatus, VacationType } from "@capakraken/shared"; import { trpc } from "~/lib/trpc/client.js"; import { VacationModal } from "./VacationModal.js"; import { InfoTooltip } from "~/components/ui/InfoTooltip.js"; import { BalanceCard } from "./BalanceCard.js"; import { VacationCalendar } from "./VacationCalendar.js"; import { VACATION_STATUS_BADGE as STATUS_BADGE, VACATION_TYPE_LABELS as TYPE_LABELS, VACATION_TYPE_BADGE } from "~/lib/status-styles.js"; import { getHolidayBasis, getHolidayBreakdown, getRequestedDays, type VacationExplainabilityEntry } from "./vacationExplainability.js"; type VacationListItem = VacationExplainabilityEntry & { id: string; startDate: Date | string; endDate: Date | string; status: string; type: string; note?: string | null; rejectionReason?: string | null; }; export function MyVacationsClient() { const [showModal, setShowModal] = useState(false); const utils = trpc.useUtils(); // Find resource linked to current user const { data: myResource } = trpc.resource.getMyResource.useQuery(undefined, { staleTime: 60_000, }); const resourceId = myResource?.id; const vacationListQuery = trpc.vacation.list.useQuery as unknown as ( input: { limit: number; resourceId?: string | undefined }, options: { enabled: boolean; staleTime: number }, ) => { data: VacationListItem[] | undefined; isLoading: boolean; refetch: () => Promise; }; const { data: vacations, isLoading, refetch } = vacationListQuery( { limit: 200, ...(resourceId ? { resourceId } : {}) }, { enabled: !!resourceId, staleTime: 15_000 }, ); const cancelMutation = trpc.vacation.cancel.useMutation({ onSuccess: async () => { await utils.vacation.list.invalidate(); await utils.entitlement.getBalance.invalidate(); }, }); const vacationList: VacationListItem[] = vacations ?? []; return (
{/* Header */}

My Vacations

Manage your personal vacation requests

{!resourceId && (
Your account is not linked to a resource. Please contact an administrator.
)} {/* Balance card */} {resourceId && ( )} {/* Calendar */} {resourceId && vacationList.length > 0 && ( )} {/* Vacation list */}
{isLoading ? (
Loading…
) : vacationList.length === 0 ? (
No vacation requests yet.
) : ( {vacationList.map((v) => { const start = new Date(v.startDate); const end = new Date(v.endDate); const requestedDays = getRequestedDays(v); const deductedDays = typeof v.deductedDays === "number" ? v.deductedDays : null; const holidayBasis = getHolidayBasis(v); const holidayBreakdown = getHolidayBreakdown(v); const status = v.status as string; const type = v.type as string; const affectsBalance = type === VacationType.ANNUAL || type === VacationType.OTHER; return ( ); })}
Type Start End Days Status Note
{TYPE_LABELS[type] ?? type} {start.toLocaleDateString("en-GB")} {end.toLocaleDateString("en-GB")}
{requestedDays}
{affectsBalance && deductedDays !== null && (
Deducted: {deductedDays}
)} {affectsBalance && deductedDays === 0 && holidayBreakdown.length > 0 && (
Fully covered by holidays
)}
{status} {v.rejectionReason ? ( {v.rejectionReason} ) : (
{v.note ?? "—"}
{affectsBalance && holidayBasis.length > 0 && (
Holiday basis: {holidayBasis.join(" / ")}
)} {affectsBalance && holidayBreakdown.length > 0 && (
Excluded holidays: {holidayBreakdown.map((holiday) => `${holiday.date} (${holiday.source})`).join(", ")}
)}
)} {!v.rejectionReason && affectsBalance && deductedDays !== null && deductedDays !== requestedDays && holidayBreakdown.length === 0 && (
Local holiday-adjusted deduction snapshot applied.
)} {!v.rejectionReason && !affectsBalance && (
This leave type does not reduce annual vacation entitlement.
)}
{(status === VacationStatus.PENDING || status === VacationStatus.APPROVED) && ( )}
)}
{showModal && resourceId && ( setShowModal(false)} onSuccess={() => { setShowModal(false); void refetch(); void utils.entitlement.getBalance.invalidate(); }} /> )}
); }