ac845d72b7
Modal Overlay (Finding 1 — 6 admin files): - Migrated CountriesClient, ManagementLevelsClient, OrgUnitsClient, CalculationRulesClient, UtilizationCategoriesClient, RoleModal from inline fixed-overlay to AnimatedModal component - Gains: animated transitions, backdrop blur, escape key for free Notification Helper (Finding 9 — 9 API files, 14 call sites): - New createNotification() + createNotificationsForUsers() in packages/api/src/lib/create-notification.ts - Handles exactOptionalPropertyTypes spread + SSE emit internally - Simplified: budget-alerts, estimate-reminders, auto-staffing, vacation-conflicts, chargeability-alerts, comment, vacation, notification ConfirmDialog (Finding 3 — 11 files): - Replaced all window.confirm() calls with ConfirmDialog component - Files: CommentThread, EffortRules, ExperienceMultipliers, ManagementLevels, CalculationRules, Countries, RateCards, ApplyEffortRules, ApplyExperienceMultipliers, NotificationCenter, ReminderModal EntityCombobox (Finding 4 — 3 files): - New generic EntityCombobox<T> with customization hooks - ResourceCombobox + ProjectCombobox rewritten as thin wrappers - All consumers unchanged (backwards-compatible props) Proficiency Constants (Finding 2 — 2 files): - SkillsAnalytics + SkillMarketplace now import from skills/shared.tsx - Deleted ~70 LOC of local duplicate definitions Regression: 283 engine + 37 staffing tests pass. TypeScript clean. AI Assistant: all 87 tools verified accessible. Co-Authored-By: claude-flow <ruv@ruv.net>
65 lines
1.7 KiB
TypeScript
65 lines
1.7 KiB
TypeScript
"use client";
|
|
|
|
import { useCallback } from "react";
|
|
import { trpc } from "~/lib/trpc/client.js";
|
|
import type { ProjectStatus } from "@planarchy/shared";
|
|
import { EntityCombobox } from "./EntityCombobox.js";
|
|
|
|
type ProjectItem = { id: string; shortCode: string; name: string };
|
|
|
|
interface ProjectComboboxProps {
|
|
value: string | null;
|
|
onChange: (id: string | null) => void;
|
|
placeholder?: string;
|
|
disabled?: boolean;
|
|
status?: ProjectStatus;
|
|
className?: string;
|
|
}
|
|
|
|
export function ProjectCombobox({
|
|
status,
|
|
...props
|
|
}: ProjectComboboxProps) {
|
|
const useSearchQuery = (search: string, enabled: boolean) => {
|
|
const { data } = trpc.project.list.useQuery(
|
|
{ search: search || undefined, limit: 15, ...(status ? { status } : {}) },
|
|
{ enabled, staleTime: 30_000 },
|
|
);
|
|
return { data: (data?.projects ?? []) as ProjectItem[] };
|
|
};
|
|
|
|
const useSelectedQuery = (_id: string | null, enabled: boolean) => {
|
|
const { data } = trpc.project.list.useQuery(
|
|
{ limit: 500 },
|
|
{ enabled, staleTime: 60_000 },
|
|
);
|
|
return { data: (data?.projects ?? []) as ProjectItem[] };
|
|
};
|
|
|
|
const getLabel = useCallback(
|
|
(p: ProjectItem) => `${p.shortCode} \u2014 ${p.name}`,
|
|
[],
|
|
);
|
|
|
|
const renderItem = useCallback(
|
|
(p: ProjectItem) => (
|
|
<>
|
|
<span className="font-medium text-xs text-gray-400 dark:text-gray-500 mr-1.5">{p.shortCode}</span>
|
|
<span>{p.name}</span>
|
|
</>
|
|
),
|
|
[],
|
|
);
|
|
|
|
return (
|
|
<EntityCombobox<ProjectItem>
|
|
{...props}
|
|
placeholder={props.placeholder ?? "Search project\u2026"}
|
|
useSearchQuery={useSearchQuery}
|
|
useSelectedQuery={useSelectedQuery}
|
|
getLabel={getLabel}
|
|
renderItem={renderItem}
|
|
/>
|
|
);
|
|
}
|