feat(ux): Sprint 1 — quick wins: EmptyState, DateRangePresets, debounce, save feedback, scenarios nav
- EmptyState shared component; replace AllocationsClient inline empty state - DateRangePresets (this month/quarter/3 months/year) integrated into AllocationModal - Debounce conflict-check inputs in AllocationModal (400ms) using existing useDebounce - Dashboard layout save feedback via SuccessToast after DB write completes - Scenarios nav item in Planning sidebar + /scenarios list page Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import { useState, useEffect, useMemo } from "react";
|
||||
import { useDebounce } from "~/hooks/useDebounce.js";
|
||||
import { AnimatedModal } from "~/components/ui/AnimatedModal.js";
|
||||
import { useInvalidatePlanningViews } from "~/hooks/useInvalidatePlanningViews.js";
|
||||
import { AllocationStatus } from "@capakraken/shared";
|
||||
@@ -9,6 +10,7 @@ import { trpc } from "~/lib/trpc/client.js";
|
||||
import { getPlanningEntryMutationId } from "~/lib/planningEntryIds.js";
|
||||
import { toDateInputValue } from "~/lib/format.js";
|
||||
import { DateInput } from "~/components/ui/DateInput.js";
|
||||
import { DateRangePresets } from "~/components/ui/DateRangePresets.js";
|
||||
import { RecurrenceEditor } from "./RecurrenceEditor.js";
|
||||
import { InfoTooltip } from "~/components/ui/InfoTooltip.js";
|
||||
import { ConflictWarningPanel } from "./ConflictWarningPanel.js";
|
||||
@@ -71,22 +73,28 @@ export function AllocationModal({ allocation, onClose, onSuccess }: AllocationMo
|
||||
{ enabled: shouldCheckOverlap, staleTime: 30_000 },
|
||||
);
|
||||
|
||||
// Debounce conflict-check inputs so we don't fire on every keystroke/interaction.
|
||||
const debouncedResourceId = useDebounce(resourceId, 400);
|
||||
const debouncedStartDate = useDebounce(startDate, 400);
|
||||
const debouncedEndDate = useDebounce(endDate, 400);
|
||||
const debouncedHoursPerDay = useDebounce(hoursPerDay, 400);
|
||||
|
||||
// Pre-flight conflict check: overbooking + vacation overlap for this resource/period.
|
||||
const conflictCheckStart = startDate ? new Date(startDate) : null;
|
||||
const conflictCheckEnd = endDate ? new Date(endDate) : null;
|
||||
const conflictCheckStart = debouncedStartDate ? new Date(debouncedStartDate) : null;
|
||||
const conflictCheckEnd = debouncedEndDate ? new Date(debouncedEndDate) : null;
|
||||
const shouldCheckConflicts =
|
||||
!isDemandEntry &&
|
||||
!!resourceId &&
|
||||
!!debouncedResourceId &&
|
||||
conflictCheckStart !== null && !isNaN(conflictCheckStart.getTime()) &&
|
||||
conflictCheckEnd !== null && !isNaN(conflictCheckEnd.getTime()) &&
|
||||
hoursPerDay > 0;
|
||||
debouncedHoursPerDay > 0;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const { data: conflictResult, isFetching: checkingConflicts } = (trpc.allocation.checkConflicts.useQuery as any)(
|
||||
{
|
||||
resourceId,
|
||||
resourceId: debouncedResourceId,
|
||||
startDate: conflictCheckStart,
|
||||
endDate: conflictCheckEnd,
|
||||
hoursPerDay,
|
||||
hoursPerDay: debouncedHoursPerDay,
|
||||
excludeAssignmentId: isEditing && allocation?.id ? allocation.id : undefined,
|
||||
},
|
||||
{ enabled: shouldCheckConflicts, staleTime: 15_000 },
|
||||
@@ -424,10 +432,15 @@ export function AllocationModal({ allocation, onClose, onSuccess }: AllocationMo
|
||||
</div>
|
||||
|
||||
{/* Dates */}
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<div className="flex items-center justify-between mb-1">
|
||||
<span className={labelClass}>Date Range <span className="text-red-500">*</span></span>
|
||||
<DateRangePresets onSelect={(s, e) => { setStartDate(s); setEndDate(e); }} />
|
||||
</div>
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label htmlFor="modal-start" className={labelClass}>
|
||||
Start Date <span className="text-red-500">*</span><InfoTooltip content="First day of this allocation period (inclusive)." />
|
||||
Start Date <InfoTooltip content="First day of this allocation period (inclusive)." />
|
||||
</label>
|
||||
<DateInput
|
||||
id="modal-start"
|
||||
@@ -439,7 +452,7 @@ export function AllocationModal({ allocation, onClose, onSuccess }: AllocationMo
|
||||
</div>
|
||||
<div>
|
||||
<label htmlFor="modal-end" className={labelClass}>
|
||||
End Date <span className="text-red-500">*</span><InfoTooltip content="Last day of this allocation period (inclusive)." />
|
||||
End Date <InfoTooltip content="Last day of this allocation period (inclusive)." />
|
||||
</label>
|
||||
<DateInput
|
||||
id="modal-end"
|
||||
@@ -450,6 +463,7 @@ export function AllocationModal({ allocation, onClose, onSuccess }: AllocationMo
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Hours/Day + Status */}
|
||||
|
||||
Reference in New Issue
Block a user