feat(platform): checkpoint current implementation state
This commit is contained in:
@@ -8,6 +8,17 @@ 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);
|
||||
@@ -21,8 +32,17 @@ export function MyVacationsClient() {
|
||||
|
||||
const resourceId = myResource?.id;
|
||||
|
||||
const { data: vacations, isLoading, refetch } = trpc.vacation.list.useQuery(
|
||||
{ resourceId, limit: 200 },
|
||||
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<unknown>;
|
||||
};
|
||||
|
||||
const { data: vacations, isLoading, refetch } = vacationListQuery(
|
||||
{ limit: 200, ...(resourceId ? { resourceId } : {}) },
|
||||
{ enabled: !!resourceId, staleTime: 15_000 },
|
||||
);
|
||||
|
||||
@@ -33,7 +53,7 @@ export function MyVacationsClient() {
|
||||
},
|
||||
});
|
||||
|
||||
const vacationList = vacations ?? [];
|
||||
const vacationList: VacationListItem[] = vacations ?? [];
|
||||
|
||||
return (
|
||||
<div className="p-6 max-w-4xl mx-auto space-y-6">
|
||||
@@ -102,10 +122,13 @@ export function MyVacationsClient() {
|
||||
{vacationList.map((v) => {
|
||||
const start = new Date(v.startDate);
|
||||
const end = new Date(v.endDate);
|
||||
const days = Math.round((end.getTime() - start.getTime()) / 86_400_000) + 1;
|
||||
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 vWithExtra = v as unknown as { rejectionReason?: string | null; isHalfDay?: boolean };
|
||||
const affectsBalance = type === VacationType.ANNUAL || type === VacationType.OTHER;
|
||||
|
||||
return (
|
||||
<tr key={v.id} className="hover:bg-gray-50 dark:hover:bg-gray-700/50">
|
||||
@@ -116,16 +139,54 @@ export function MyVacationsClient() {
|
||||
</td>
|
||||
<td className="px-4 py-3 text-gray-600 dark:text-gray-400">{start.toLocaleDateString("en-GB")}</td>
|
||||
<td className="px-4 py-3 text-gray-600 dark:text-gray-400">{end.toLocaleDateString("en-GB")}</td>
|
||||
<td className="px-4 py-3 text-gray-600 dark:text-gray-400">{vWithExtra.isHalfDay ? "0.5" : days}</td>
|
||||
<td className="px-4 py-3 text-right text-gray-600 dark:text-gray-400">
|
||||
<div className="space-y-1">
|
||||
<div>{requestedDays}</div>
|
||||
{affectsBalance && deductedDays !== null && (
|
||||
<div className="text-[11px] text-gray-400 dark:text-gray-500">
|
||||
Deducted: {deductedDays}
|
||||
</div>
|
||||
)}
|
||||
{affectsBalance && deductedDays === 0 && holidayBreakdown.length > 0 && (
|
||||
<div className="text-[11px] text-emerald-600 dark:text-emerald-400">
|
||||
Fully covered by holidays
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</td>
|
||||
<td className="px-4 py-3">
|
||||
<span className={`inline-flex px-2 py-0.5 rounded-full text-xs font-medium ${STATUS_BADGE[status] ?? ""}`}>
|
||||
{status}
|
||||
</span>
|
||||
</td>
|
||||
<td className="px-4 py-3 text-gray-400 dark:text-gray-500 text-xs max-w-[200px]">
|
||||
{vWithExtra.rejectionReason ? (
|
||||
<span className="text-red-500">{vWithExtra.rejectionReason}</span>
|
||||
) : (v.note ?? "—")}
|
||||
<td className="px-4 py-3 text-gray-400 dark:text-gray-500 text-xs max-w-[280px]">
|
||||
{v.rejectionReason ? (
|
||||
<span className="text-red-500">{v.rejectionReason}</span>
|
||||
) : (
|
||||
<div className="space-y-1">
|
||||
<div>{v.note ?? "—"}</div>
|
||||
{affectsBalance && holidayBasis.length > 0 && (
|
||||
<div className="text-[11px] text-gray-500 dark:text-gray-400">
|
||||
Holiday basis: {holidayBasis.join(" / ")}
|
||||
</div>
|
||||
)}
|
||||
{affectsBalance && holidayBreakdown.length > 0 && (
|
||||
<div className="text-[11px] text-gray-500 dark:text-gray-400">
|
||||
Excluded holidays: {holidayBreakdown.map((holiday) => `${holiday.date} (${holiday.source})`).join(", ")}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
{!v.rejectionReason && affectsBalance && deductedDays !== null && deductedDays !== requestedDays && holidayBreakdown.length === 0 && (
|
||||
<div className="mt-1 text-[11px] text-gray-500 dark:text-gray-400">
|
||||
Local holiday-adjusted deduction snapshot applied.
|
||||
</div>
|
||||
)}
|
||||
{!v.rejectionReason && !affectsBalance && (
|
||||
<div className="mt-1 text-[11px] text-gray-500 dark:text-gray-400">
|
||||
This leave type does not reduce annual vacation entitlement.
|
||||
</div>
|
||||
)}
|
||||
</td>
|
||||
<td className="px-4 py-3 text-right">
|
||||
{(status === VacationStatus.PENDING || status === VacationStatus.APPROVED) && (
|
||||
|
||||
Reference in New Issue
Block a user