From 2da29c8191b80488bc8b5b6dd84042e60ce85100 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hartmut=20N=C3=B6renberg?= Date: Fri, 3 Apr 2026 16:05:29 +0200 Subject: [PATCH] =?UTF-8?q?fix(ux):=20resolve=20tickets=20#59=20#66=20#67?= =?UTF-8?q?=20=E2=80=94=20project=20feedback=20and=20demand=20summary?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit #66: Project detail "Open Demands" summary incorrectly counted COMPLETED demands as open. Fix: add `status !== "COMPLETED"` to the activeDemands filter in /projects/[id]/page.tsx. #59/#67: Project creation and edit had two bugs: 1. Both invalidated `project.list` but the page queries `project.listWithCosts` — the list never refreshed after a save. 2. Success toasts were either absent (ProjectModal) or mounted inside the wizard component that unmounts before the toast finishes. Fix: correct invalidation key to listWithCosts; add optional onSuccess prop to both ProjectWizard and ProjectModal; ProjectsClient wires onSuccess to a persistent SuccessToast rendered outside the modals. Co-Authored-By: claude-flow --- .../src/app/(app)/projects/ProjectsClient.tsx | 30 +++++++++++++++++-- apps/web/src/app/(app)/projects/[id]/page.tsx | 2 +- .../src/components/projects/ProjectModal.tsx | 9 ++++-- .../src/components/projects/ProjectWizard.tsx | 6 ++-- 4 files changed, 39 insertions(+), 8 deletions(-) diff --git a/apps/web/src/app/(app)/projects/ProjectsClient.tsx b/apps/web/src/app/(app)/projects/ProjectsClient.tsx index 9476ac6..0c45eb8 100644 --- a/apps/web/src/app/(app)/projects/ProjectsClient.tsx +++ b/apps/web/src/app/(app)/projects/ProjectsClient.tsx @@ -28,6 +28,7 @@ import { useRowOrder } from "~/hooks/useRowOrder.js"; import { SortableColumnHeader } from "~/components/ui/SortableColumnHeader.js"; import { DraggableTableRow } from "~/components/ui/DraggableTableRow.js"; import { ShoringBadge } from "~/components/projects/ShoringIndicator.js"; +import { SuccessToast } from "~/components/ui/SuccessToast.js"; import { PROJECT_STATUS_BADGE as STATUS_COLORS, ORDER_TYPE_BADGE as ORDER_TYPE_COLORS } from "~/lib/status-styles.js"; @@ -182,6 +183,7 @@ export function ProjectsClient() { const [modalOpen, setModalOpen] = useState(false); const [wizardOpen, setWizardOpen] = useState(false); const [editingProject, setEditingProject] = useState(null); + const [successToast, setSuccessToast] = useState(null); const [openStatusProjectId, setOpenStatusProjectId] = useState(null); const [batchStatusPicker, setBatchStatusPicker] = useState(false); const [confirmBatchStatus, setConfirmBatchStatus] = useState<{ ids: string[]; status: string } | null>(null); @@ -749,10 +751,34 @@ export function ProjectsClient() { /> {/* Modal */} - {modalOpen && } + {modalOpen && ( + + setSuccessToast( + editingProject + ? `Project "${name}" updated successfully.` + : `Project "${name}" created successfully.`, + ) + } + /> + )} {/* Wizard */} - setWizardOpen(false)} /> + setWizardOpen(false)} + onSuccess={(shortCode, name) => + setSuccessToast(`Project "${shortCode} — ${name}" created successfully.`) + } + /> + + setSuccessToast(null)} + /> ); } diff --git a/apps/web/src/app/(app)/projects/[id]/page.tsx b/apps/web/src/app/(app)/projects/[id]/page.tsx index e1be42d..88bda71 100644 --- a/apps/web/src/app/(app)/projects/[id]/page.tsx +++ b/apps/web/src/app/(app)/projects/[id]/page.tsx @@ -32,7 +32,7 @@ export default async function ProjectDetailPage({ params }: ProjectDetailPagePro } const activeAssignments = project.assignments.filter((assignment) => assignment.status !== "CANCELLED"); - const activeDemands = project.demands.filter((demand) => demand.status !== "CANCELLED"); + const activeDemands = project.demands.filter((demand) => demand.status !== "CANCELLED" && demand.status !== "COMPLETED"); const requestedSeats = activeDemands.reduce((sum, demand) => sum + demand.requestedHeadcount, 0); const unfilledSeats = activeDemands.reduce((sum, demand) => sum + demand.unfilledHeadcount, 0); diff --git a/apps/web/src/components/projects/ProjectModal.tsx b/apps/web/src/components/projects/ProjectModal.tsx index 9738d03..0312f24 100644 --- a/apps/web/src/components/projects/ProjectModal.tsx +++ b/apps/web/src/components/projects/ProjectModal.tsx @@ -95,9 +95,10 @@ function projectToForm(project: Project): FormState { interface ProjectModalProps { project?: Project | null; onClose: () => void; + onSuccess?: (name: string) => void; } -export function ProjectModal({ project, onClose }: ProjectModalProps) { +export function ProjectModal({ project, onClose, onSuccess }: ProjectModalProps) { const isEdit = !!project; const utils = trpc.useUtils(); @@ -116,7 +117,8 @@ export function ProjectModal({ project, onClose }: ProjectModalProps) { // @ts-ignore TS2589: tRPC infers union type too deeply for CreateProjectSchema with .refine() const createMutation = trpc.project.create.useMutation({ onSuccess: async () => { - await utils.project.list.invalidate(); + await utils.project.listWithCosts.invalidate(); + onSuccess?.(form.name.trim()); onClose(); }, onError: (err) => { @@ -126,7 +128,8 @@ export function ProjectModal({ project, onClose }: ProjectModalProps) { const updateMutation = trpc.project.update.useMutation({ onSuccess: async () => { - await utils.project.list.invalidate(); + await utils.project.listWithCosts.invalidate(); + onSuccess?.(form.name.trim()); onClose(); }, onError: (err) => { diff --git a/apps/web/src/components/projects/ProjectWizard.tsx b/apps/web/src/components/projects/ProjectWizard.tsx index 598b6ea..43c2c06 100644 --- a/apps/web/src/components/projects/ProjectWizard.tsx +++ b/apps/web/src/components/projects/ProjectWizard.tsx @@ -1041,9 +1041,10 @@ function Step5({ state, onChange, onSubmit, isSubmitting, submitError }: Step5Pr interface ProjectWizardProps { open: boolean; onClose: () => void; + onSuccess?: (shortCode: string, name: string) => void; } -export function ProjectWizard({ open, onClose }: ProjectWizardProps) { +export function ProjectWizard({ open, onClose, onSuccess }: ProjectWizardProps) { const utils = trpc.useUtils(); const [step, setStep] = useState(0); const [state, setState] = useState(makeDefaultState); @@ -1164,13 +1165,14 @@ export function ProjectWizard({ open, onClose }: ProjectWizardProps) { } } - await utils.project.list.invalidate(); + await utils.project.listWithCosts.invalidate(); await utils.timeline.getEntries.invalidate(); await utils.timeline.getEntriesView.invalidate(); setShowConfetti(true); setShowSuccessToast(true); setTimeout(() => { setShowConfetti(false); + onSuccess?.(project.shortCode, project.name); handleClose(); }, 1200); } catch (err) {