import type { ProjectCustomerOptionDto, ProjectDocumentOptionDto, ProjectInput, ProjectMilestoneInput, ProjectOwnerOptionDto, ProjectShipmentOptionDto, } from "@mrp/shared/dist/projects/types.js"; import { useEffect, useState } from "react"; import { Link, useNavigate, useParams } from "react-router-dom"; import { ConfirmActionDialog } from "../../components/ConfirmActionDialog"; import { useAuth } from "../../auth/AuthProvider"; import { api, ApiError } from "../../lib/api"; import { emptyProjectInput, projectMilestoneStatusOptions, projectPriorityOptions, projectStatusOptions } from "./config"; type ProjectPendingConfirmation = | { kind: "change-customer"; customerId: string; customerName: string } | { kind: "unlink-quote" } | { kind: "unlink-order" } | { kind: "unlink-shipment" }; export function ProjectFormPage({ mode }: { mode: "create" | "edit" }) { const { token, user } = useAuth(); const navigate = useNavigate(); const { projectId } = useParams(); const [form, setForm] = useState(() => ({ ...emptyProjectInput, ownerId: user?.id ?? null })); const [customerOptions, setCustomerOptions] = useState([]); const [ownerOptions, setOwnerOptions] = useState([]); const [quoteOptions, setQuoteOptions] = useState([]); const [orderOptions, setOrderOptions] = useState([]); const [shipmentOptions, setShipmentOptions] = useState([]); const [customerSearchTerm, setCustomerSearchTerm] = useState(""); const [ownerSearchTerm, setOwnerSearchTerm] = useState(""); const [quoteSearchTerm, setQuoteSearchTerm] = useState(""); const [orderSearchTerm, setOrderSearchTerm] = useState(""); const [shipmentSearchTerm, setShipmentSearchTerm] = useState(""); const [customerPickerOpen, setCustomerPickerOpen] = useState(false); const [ownerPickerOpen, setOwnerPickerOpen] = useState(false); const [quotePickerOpen, setQuotePickerOpen] = useState(false); const [orderPickerOpen, setOrderPickerOpen] = useState(false); const [shipmentPickerOpen, setShipmentPickerOpen] = useState(false); const [status, setStatus] = useState(mode === "create" ? "Create a new project." : "Loading project..."); const [isSaving, setIsSaving] = useState(false); const [pendingConfirmation, setPendingConfirmation] = useState(null); function reindexMilestones(milestones: ProjectMilestoneInput[]) { return milestones.map((milestone, index) => ({ ...milestone, sortOrder: index * 10, })); } useEffect(() => { if (!token) { return; } api.getProjectCustomerOptions(token).then(setCustomerOptions).catch(() => setCustomerOptions([])); api.getProjectOwnerOptions(token).then(setOwnerOptions).catch(() => setOwnerOptions([])); }, [token]); useEffect(() => { if (!token || !form.customerId) { setQuoteOptions([]); setOrderOptions([]); setShipmentOptions([]); return; } api.getProjectQuoteOptions(token, form.customerId).then(setQuoteOptions).catch(() => setQuoteOptions([])); api.getProjectOrderOptions(token, form.customerId).then(setOrderOptions).catch(() => setOrderOptions([])); api.getProjectShipmentOptions(token, form.customerId).then(setShipmentOptions).catch(() => setShipmentOptions([])); }, [form.customerId, token]); useEffect(() => { if (!token || mode !== "edit" || !projectId) { return; } api.getProject(token, projectId) .then((project) => { setForm({ name: project.name, status: project.status, priority: project.priority, customerId: project.customerId, salesQuoteId: project.salesQuoteId, salesOrderId: project.salesOrderId, shipmentId: project.shipmentId, ownerId: project.ownerId, dueDate: project.dueDate, notes: project.notes, milestones: project.milestones.map((milestone) => ({ id: milestone.id, title: milestone.title, status: milestone.status, dueDate: milestone.dueDate, notes: milestone.notes, sortOrder: milestone.sortOrder, })), }); setCustomerSearchTerm(project.customerName); setOwnerSearchTerm(project.ownerName ?? ""); setQuoteSearchTerm(project.salesQuoteNumber ?? ""); setOrderSearchTerm(project.salesOrderNumber ?? ""); setShipmentSearchTerm(project.shipmentNumber ?? ""); setStatus("Project loaded."); }) .catch((error: unknown) => { const message = error instanceof ApiError ? error.message : "Unable to load project."; setStatus(message); }); }, [mode, projectId, token]); function updateField(key: Key, value: ProjectInput[Key]) { setForm((current: ProjectInput) => ({ ...current, [key]: value, ...(key === "customerId" ? { salesQuoteId: null, salesOrderId: null, shipmentId: null, } : {}), })); } function hasLinkedCommercialRecords() { return Boolean(form.salesQuoteId || form.salesOrderId || form.shipmentId); } function applyCustomerSelection(customerId: string, customerName: string) { updateField("customerId", customerId); setCustomerSearchTerm(customerName); setCustomerPickerOpen(false); } function requestCustomerSelection(customerId: string, customerName: string) { if (form.customerId && form.customerId !== customerId && hasLinkedCommercialRecords()) { setPendingConfirmation({ kind: "change-customer", customerId, customerName }); return; } applyCustomerSelection(customerId, customerName); } function unlinkQuote() { updateField("salesQuoteId", null); setQuoteSearchTerm(""); setQuotePickerOpen(false); } function unlinkOrder() { updateField("salesOrderId", null); setOrderSearchTerm(""); setOrderPickerOpen(false); } function unlinkShipment() { updateField("shipmentId", null); setShipmentSearchTerm(""); setShipmentPickerOpen(false); } function restoreSearchTerms() { const selectedCustomer = customerOptions.find((customer) => customer.id === form.customerId); const selectedOwner = ownerOptions.find((owner) => owner.id === form.ownerId); const selectedQuote = quoteOptions.find((quote) => quote.id === form.salesQuoteId); const selectedOrder = orderOptions.find((order) => order.id === form.salesOrderId); const selectedShipment = shipmentOptions.find((shipment) => shipment.id === form.shipmentId); setCustomerSearchTerm(selectedCustomer?.name ?? ""); setOwnerSearchTerm(selectedOwner?.fullName ?? ""); setQuoteSearchTerm(selectedQuote?.documentNumber ?? ""); setOrderSearchTerm(selectedOrder?.documentNumber ?? ""); setShipmentSearchTerm(selectedShipment?.shipmentNumber ?? ""); } function addMilestone() { setForm((current) => ({ ...current, milestones: reindexMilestones([ ...current.milestones, { id: null, title: "", status: "PLANNED", dueDate: current.dueDate, notes: "", sortOrder: current.milestones.length * 10, }, ]), })); } function updateMilestone(index: number, key: Key, value: ProjectMilestoneInput[Key]) { setForm((current) => ({ ...current, milestones: current.milestones.map((milestone, milestoneIndex) => milestoneIndex === index ? { ...milestone, [key]: value, } : milestone ), })); } function removeMilestone(index: number) { setForm((current) => ({ ...current, milestones: reindexMilestones(current.milestones.filter((_, milestoneIndex) => milestoneIndex !== index)), })); } async function handleSubmit(event: React.FormEvent) { event.preventDefault(); if (!token) { return; } setIsSaving(true); setStatus("Saving project..."); try { const saved = mode === "create" ? await api.createProject(token, form) : await api.updateProject(token, projectId ?? "", form); navigate(`/projects/${saved.id}`); } catch (error: unknown) { const message = error instanceof ApiError ? error.message : "Unable to save project."; setStatus(message); setIsSaving(false); } } return (

PROJECTS EDITOR

{mode === "create" ? "New Project" : "Edit Project"}

Cancel