refactor: deduplicate modals, notifications, confirms, comboboxes, proficiency

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>
This commit is contained in:
2026-03-22 21:50:39 +01:00
parent c7b76e086d
commit ac845d72b7
29 changed files with 737 additions and 607 deletions
@@ -2,6 +2,7 @@
import { useState } from "react";
import { useSearchParams } from "next/navigation";
import { ConfirmDialog } from "~/components/ui/ConfirmDialog.js";
import { trpc } from "~/lib/trpc/client.js";
import { usePermissions } from "~/hooks/usePermissions.js";
import { TaskCard } from "./TaskCard.js";
@@ -31,6 +32,7 @@ export function NotificationCenterClient() {
const [activeTab, setActiveTab] = useState<TabKey>(initialTab);
const { canEdit } = usePermissions();
const [showTaskModal, setShowTaskModal] = useState(false);
const [confirmDeleteReminder, setConfirmDeleteReminder] = useState<string | null>(null);
const [reminderModal, setReminderModal] = useState<{
open: boolean;
reminder: {
@@ -374,11 +376,7 @@ export function NotificationCenterClient() {
</button>
<button
type="button"
onClick={() => {
if (window.confirm("Delete this reminder?")) {
deleteReminder.mutate({ id: r.id });
}
}}
onClick={() => setConfirmDeleteReminder(r.id)}
disabled={deleteReminder.isPending}
className="p-1 text-gray-400 hover:text-red-600 dark:hover:text-red-400 hover:bg-gray-100 dark:hover:bg-gray-800 rounded transition-colors"
title="Delete"
@@ -413,6 +411,20 @@ export function NotificationCenterClient() {
onSuccess={() => setShowTaskModal(false)}
/>
)}
{confirmDeleteReminder && (
<ConfirmDialog
title="Delete reminder"
message="Are you sure you want to delete this reminder?"
confirmLabel="Delete"
variant="danger"
onConfirm={() => {
deleteReminder.mutate({ id: confirmDeleteReminder });
setConfirmDeleteReminder(null);
}}
onCancel={() => setConfirmDeleteReminder(null)}
/>
)}
</div>
);
}