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:
@@ -2,6 +2,7 @@
|
||||
|
||||
import { useState } from "react";
|
||||
import { clsx } from "clsx";
|
||||
import { ConfirmDialog } from "~/components/ui/ConfirmDialog.js";
|
||||
import { trpc } from "~/lib/trpc/client.js";
|
||||
|
||||
interface ApplyEffortRulesProps {
|
||||
@@ -17,6 +18,7 @@ export function ApplyEffortRules({ estimateId, canEdit, onApplied }: ApplyEffort
|
||||
const [selectedRuleSetId, setSelectedRuleSetId] = useState<string>("");
|
||||
const [mode, setMode] = useState<"replace" | "append">("replace");
|
||||
const [showPreview, setShowPreview] = useState(false);
|
||||
const [confirmApply, setConfirmApply] = useState(false);
|
||||
|
||||
const previewQuery = trpc.effortRule.preview.useQuery(
|
||||
{ estimateId, ruleSetId: selectedRuleSetId },
|
||||
@@ -106,10 +108,7 @@ export function ApplyEffortRules({ estimateId, canEdit, onApplied }: ApplyEffort
|
||||
<button
|
||||
onClick={() => {
|
||||
if (!selectedRuleSetId) return;
|
||||
const action = mode === "replace" ? "replace all existing demand lines" : "append new demand lines";
|
||||
if (confirm(`This will ${action}. Continue?`)) {
|
||||
applyMutation.mutate({ estimateId, ruleSetId: selectedRuleSetId, mode });
|
||||
}
|
||||
setConfirmApply(true);
|
||||
}}
|
||||
disabled={!selectedRuleSetId || applyMutation.isPending}
|
||||
className="rounded-2xl bg-brand-600 px-4 py-2 text-sm font-medium text-white hover:bg-brand-700 disabled:opacity-50"
|
||||
@@ -210,6 +209,19 @@ export function ApplyEffortRules({ estimateId, canEdit, onApplied }: ApplyEffort
|
||||
{showPreview && previewQuery.isLoading && (
|
||||
<p className="mt-3 text-sm text-gray-400">Computing preview...</p>
|
||||
)}
|
||||
|
||||
{confirmApply && (
|
||||
<ConfirmDialog
|
||||
title="Apply effort rules"
|
||||
message={`This will ${mode === "replace" ? "replace all existing demand lines" : "append new demand lines"}. Continue?`}
|
||||
confirmLabel="Apply"
|
||||
onConfirm={() => {
|
||||
applyMutation.mutate({ estimateId, ruleSetId: selectedRuleSetId, mode });
|
||||
setConfirmApply(false);
|
||||
}}
|
||||
onCancel={() => setConfirmApply(false)}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
import { useState } from "react";
|
||||
import { clsx } from "clsx";
|
||||
import { ConfirmDialog } from "~/components/ui/ConfirmDialog.js";
|
||||
import { InfoTooltip } from "~/components/ui/InfoTooltip.js";
|
||||
import { formatCents } from "~/lib/format.js";
|
||||
import { trpc } from "~/lib/trpc/client.js";
|
||||
@@ -18,6 +19,7 @@ export function ApplyExperienceMultipliers({ estimateId, canEdit, onApplied }: A
|
||||
|
||||
const [selectedSetId, setSelectedSetId] = useState<string>("");
|
||||
const [showPreview, setShowPreview] = useState(false);
|
||||
const [confirmApply, setConfirmApply] = useState(false);
|
||||
|
||||
const previewQuery = trpc.experienceMultiplier.preview.useQuery(
|
||||
{ estimateId, multiplierSetId: selectedSetId },
|
||||
@@ -96,9 +98,7 @@ export function ApplyExperienceMultipliers({ estimateId, canEdit, onApplied }: A
|
||||
<button
|
||||
onClick={() => {
|
||||
if (!selectedSetId) return;
|
||||
if (confirm("This will update cost/bill rates and hours on matching demand lines. Continue?")) {
|
||||
applyMutation.mutate({ estimateId, multiplierSetId: selectedSetId });
|
||||
}
|
||||
setConfirmApply(true);
|
||||
}}
|
||||
disabled={!selectedSetId || applyMutation.isPending}
|
||||
className="rounded-2xl bg-brand-600 px-4 py-2 text-sm font-medium text-white hover:bg-brand-700 disabled:opacity-50"
|
||||
@@ -204,6 +204,19 @@ export function ApplyExperienceMultipliers({ estimateId, canEdit, onApplied }: A
|
||||
{showPreview && previewQuery.isLoading && (
|
||||
<p className="mt-3 text-sm text-gray-400">Computing preview...</p>
|
||||
)}
|
||||
|
||||
{confirmApply && (
|
||||
<ConfirmDialog
|
||||
title="Apply experience multipliers"
|
||||
message="This will update cost/bill rates and hours on matching demand lines. Continue?"
|
||||
confirmLabel="Apply"
|
||||
onConfirm={() => {
|
||||
applyMutation.mutate({ estimateId, multiplierSetId: selectedSetId });
|
||||
setConfirmApply(false);
|
||||
}}
|
||||
onCancel={() => setConfirmApply(false)}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user