diff --git a/apps/web/src/components/timeline/DemandPopover.tsx b/apps/web/src/components/timeline/DemandPopover.tsx index b656ac4..399761f 100644 --- a/apps/web/src/components/timeline/DemandPopover.tsx +++ b/apps/web/src/components/timeline/DemandPopover.tsx @@ -1,10 +1,12 @@ "use client"; +import { MILLISECONDS_PER_DAY } from "@capakraken/shared"; import type { RefObject } from "react"; import { createPortal } from "react-dom"; import type { TimelineDemandEntry } from "./TimelineContext.js"; import { formatCents, formatDateLong } from "~/lib/format.js"; import { useViewportPopover } from "~/hooks/useViewportPopover.js"; +import { trpc } from "~/lib/trpc/client.js"; interface DemandPopoverProps { demand: TimelineDemandEntry; @@ -37,10 +39,23 @@ export function DemandPopover({ const roleColor = demand.roleEntity?.color ?? "#f59e0b"; const startDate = new Date(demand.startDate); const endDate = new Date(demand.endDate); - const days = Math.max(1, Math.round((endDate.getTime() - startDate.getTime()) / 86_400_000) + 1); + const days = Math.max(1, Math.round((endDate.getTime() - startDate.getTime()) / MILLISECONDS_PER_DAY) + 1); const totalHours = demand.hoursPerDay * days; const budgetCents = demand.dailyCostCents * days; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const { data: suggestionData, isLoading: loadingSuggestions } = (trpc.staffing.getProjectStaffingSuggestions.useQuery as any)( + { + projectId: demand.projectId, + roleName: demand.role ?? undefined, + startDate, + endDate, + limit: 3, + }, + { staleTime: 60_000, retry: false }, + ) as { data: { suggestions: Array<{ id: string; name: string; eid: string; availableHoursPerDay: number; utilization: number }> } | undefined; isLoading: boolean }; + const suggestions = suggestionData?.suggestions ?? []; + const popover = (
+ {/* AI staffing suggestions */} + {(loadingSuggestions || suggestions.length > 0) && ( +
+
+ + + + + Suggested Resources + +
+ {loadingSuggestions ? ( +
+ {[0, 1, 2].map((i) => ( +
+
+
+
+
+
+
+
+ ))} +
+ ) : ( +
+ {suggestions.map((s) => ( +
+
+ + {s.name.slice(0, 2).toUpperCase()} + +
+
+
{s.name}
+
+ {Math.round(s.utilization)}% utilized ยท {s.availableHoursPerDay.toFixed(1)}h/d free +
+
+ +
+ ))} +
+ )} +
+ )} + {/* Actions */}
{demand.unfilledHeadcount > 0 && (