cleanup
This commit is contained in:
@@ -8,10 +8,17 @@ import type {
|
||||
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, 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();
|
||||
@@ -34,6 +41,7 @@ export function ProjectFormPage({ mode }: { mode: "create" | "edit" }) {
|
||||
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<ProjectPendingConfirmation | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (!token) {
|
||||
@@ -103,6 +111,43 @@ export function ProjectFormPage({ mode }: { mode: "create" | "edit" }) {
|
||||
}));
|
||||
}
|
||||
|
||||
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);
|
||||
@@ -158,13 +203,12 @@ export function ProjectFormPage({ mode }: { mode: "create" | "edit" }) {
|
||||
<label className="block">
|
||||
<span className="mb-2 block text-sm font-semibold text-text">Customer</span>
|
||||
<div className="relative">
|
||||
<input
|
||||
value={customerSearchTerm}
|
||||
onChange={(event) => {
|
||||
setCustomerSearchTerm(event.target.value);
|
||||
updateField("customerId", "");
|
||||
setCustomerPickerOpen(true);
|
||||
}}
|
||||
<input
|
||||
value={customerSearchTerm}
|
||||
onChange={(event) => {
|
||||
setCustomerSearchTerm(event.target.value);
|
||||
setCustomerPickerOpen(true);
|
||||
}}
|
||||
onFocus={() => setCustomerPickerOpen(true)}
|
||||
onBlur={() => window.setTimeout(() => {
|
||||
setCustomerPickerOpen(false);
|
||||
@@ -187,9 +231,7 @@ export function ProjectFormPage({ mode }: { mode: "create" | "edit" }) {
|
||||
.map((customer) => (
|
||||
<button key={customer.id} type="button" onMouseDown={(event) => {
|
||||
event.preventDefault();
|
||||
updateField("customerId", customer.id);
|
||||
setCustomerSearchTerm(customer.name);
|
||||
setCustomerPickerOpen(false);
|
||||
requestCustomerSelection(customer.id, customer.name);
|
||||
}} className="block w-full border-b border-line/50 px-2 py-2 text-left text-sm transition last:border-b-0 hover:bg-page/70">
|
||||
<div className="font-semibold text-text">{customer.name}</div>
|
||||
<div className="mt-1 text-xs text-muted">{customer.email}</div>
|
||||
@@ -274,13 +316,12 @@ export function ProjectFormPage({ mode }: { mode: "create" | "edit" }) {
|
||||
<label className="block">
|
||||
<span className="mb-2 block text-sm font-semibold text-text">Quote</span>
|
||||
<div className="relative">
|
||||
<input
|
||||
value={quoteSearchTerm}
|
||||
onChange={(event) => {
|
||||
setQuoteSearchTerm(event.target.value);
|
||||
updateField("salesQuoteId", null);
|
||||
setQuotePickerOpen(true);
|
||||
}}
|
||||
<input
|
||||
value={quoteSearchTerm}
|
||||
onChange={(event) => {
|
||||
setQuoteSearchTerm(event.target.value);
|
||||
setQuotePickerOpen(true);
|
||||
}}
|
||||
onFocus={() => setQuotePickerOpen(true)}
|
||||
onBlur={() => window.setTimeout(() => {
|
||||
setQuotePickerOpen(false);
|
||||
@@ -293,9 +334,11 @@ export function ProjectFormPage({ mode }: { mode: "create" | "edit" }) {
|
||||
<div className="absolute z-20 mt-2 max-h-64 w-full overflow-y-auto rounded-2xl border border-line/70 bg-surface shadow-panel">
|
||||
<button type="button" onMouseDown={(event) => {
|
||||
event.preventDefault();
|
||||
updateField("salesQuoteId", null);
|
||||
setQuoteSearchTerm("");
|
||||
setQuotePickerOpen(false);
|
||||
if (form.salesQuoteId) {
|
||||
setPendingConfirmation({ kind: "unlink-quote" });
|
||||
} else {
|
||||
unlinkQuote();
|
||||
}
|
||||
}} className="block w-full border-b border-line/50 px-2 py-2 text-left text-sm transition hover:bg-page/70">
|
||||
<div className="font-semibold text-text">No linked quote</div>
|
||||
</button>
|
||||
@@ -326,13 +369,12 @@ export function ProjectFormPage({ mode }: { mode: "create" | "edit" }) {
|
||||
<label className="block">
|
||||
<span className="mb-2 block text-sm font-semibold text-text">Sales order</span>
|
||||
<div className="relative">
|
||||
<input
|
||||
value={orderSearchTerm}
|
||||
onChange={(event) => {
|
||||
setOrderSearchTerm(event.target.value);
|
||||
updateField("salesOrderId", null);
|
||||
setOrderPickerOpen(true);
|
||||
}}
|
||||
<input
|
||||
value={orderSearchTerm}
|
||||
onChange={(event) => {
|
||||
setOrderSearchTerm(event.target.value);
|
||||
setOrderPickerOpen(true);
|
||||
}}
|
||||
onFocus={() => setOrderPickerOpen(true)}
|
||||
onBlur={() => window.setTimeout(() => {
|
||||
setOrderPickerOpen(false);
|
||||
@@ -345,9 +387,11 @@ export function ProjectFormPage({ mode }: { mode: "create" | "edit" }) {
|
||||
<div className="absolute z-20 mt-2 max-h-64 w-full overflow-y-auto rounded-2xl border border-line/70 bg-surface shadow-panel">
|
||||
<button type="button" onMouseDown={(event) => {
|
||||
event.preventDefault();
|
||||
updateField("salesOrderId", null);
|
||||
setOrderSearchTerm("");
|
||||
setOrderPickerOpen(false);
|
||||
if (form.salesOrderId) {
|
||||
setPendingConfirmation({ kind: "unlink-order" });
|
||||
} else {
|
||||
unlinkOrder();
|
||||
}
|
||||
}} className="block w-full border-b border-line/50 px-2 py-2 text-left text-sm transition hover:bg-page/70">
|
||||
<div className="font-semibold text-text">No linked sales order</div>
|
||||
</button>
|
||||
@@ -378,13 +422,12 @@ export function ProjectFormPage({ mode }: { mode: "create" | "edit" }) {
|
||||
<label className="block">
|
||||
<span className="mb-2 block text-sm font-semibold text-text">Shipment</span>
|
||||
<div className="relative">
|
||||
<input
|
||||
value={shipmentSearchTerm}
|
||||
onChange={(event) => {
|
||||
setShipmentSearchTerm(event.target.value);
|
||||
updateField("shipmentId", null);
|
||||
setShipmentPickerOpen(true);
|
||||
}}
|
||||
<input
|
||||
value={shipmentSearchTerm}
|
||||
onChange={(event) => {
|
||||
setShipmentSearchTerm(event.target.value);
|
||||
setShipmentPickerOpen(true);
|
||||
}}
|
||||
onFocus={() => setShipmentPickerOpen(true)}
|
||||
onBlur={() => window.setTimeout(() => {
|
||||
setShipmentPickerOpen(false);
|
||||
@@ -397,9 +440,11 @@ export function ProjectFormPage({ mode }: { mode: "create" | "edit" }) {
|
||||
<div className="absolute z-20 mt-2 max-h-64 w-full overflow-y-auto rounded-2xl border border-line/70 bg-surface shadow-panel">
|
||||
<button type="button" onMouseDown={(event) => {
|
||||
event.preventDefault();
|
||||
updateField("shipmentId", null);
|
||||
setShipmentSearchTerm("");
|
||||
setShipmentPickerOpen(false);
|
||||
if (form.shipmentId) {
|
||||
setPendingConfirmation({ kind: "unlink-shipment" });
|
||||
} else {
|
||||
unlinkShipment();
|
||||
}
|
||||
}} className="block w-full border-b border-line/50 px-2 py-2 text-left text-sm transition hover:bg-page/70">
|
||||
<div className="font-semibold text-text">No linked shipment</div>
|
||||
</button>
|
||||
@@ -439,6 +484,60 @@ export function ProjectFormPage({ mode }: { mode: "create" | "edit" }) {
|
||||
</button>
|
||||
</div>
|
||||
</section>
|
||||
<ConfirmActionDialog
|
||||
open={pendingConfirmation != null}
|
||||
title={
|
||||
pendingConfirmation?.kind === "change-customer"
|
||||
? "Change project customer"
|
||||
: pendingConfirmation?.kind === "unlink-quote"
|
||||
? "Remove linked quote"
|
||||
: pendingConfirmation?.kind === "unlink-order"
|
||||
? "Remove linked sales order"
|
||||
: "Remove linked shipment"
|
||||
}
|
||||
description={
|
||||
pendingConfirmation?.kind === "change-customer"
|
||||
? `Switch this project to ${pendingConfirmation.customerName}. Existing quote, sales order, and shipment links will be cleared.`
|
||||
: pendingConfirmation?.kind === "unlink-quote"
|
||||
? "Remove the currently linked quote from this project draft."
|
||||
: pendingConfirmation?.kind === "unlink-order"
|
||||
? "Remove the currently linked sales order from this project draft."
|
||||
: "Remove the currently linked shipment from this project draft."
|
||||
}
|
||||
impact={
|
||||
pendingConfirmation?.kind === "change-customer"
|
||||
? "Commercial and delivery linkage tied to the previous customer will be cleared immediately from the draft."
|
||||
: "The project will no longer point to that related record after you save this edit."
|
||||
}
|
||||
recovery={
|
||||
pendingConfirmation?.kind === "change-customer"
|
||||
? "Re-link the correct quote, order, and shipment before saving if the customer change was accidental."
|
||||
: "Pick the related record again before saving if this unlink was a mistake."
|
||||
}
|
||||
confirmLabel={
|
||||
pendingConfirmation?.kind === "change-customer"
|
||||
? "Change customer"
|
||||
: "Remove link"
|
||||
}
|
||||
onClose={() => setPendingConfirmation(null)}
|
||||
onConfirm={() => {
|
||||
if (!pendingConfirmation) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (pendingConfirmation.kind === "change-customer") {
|
||||
applyCustomerSelection(pendingConfirmation.customerId, pendingConfirmation.customerName);
|
||||
} else if (pendingConfirmation.kind === "unlink-quote") {
|
||||
unlinkQuote();
|
||||
} else if (pendingConfirmation.kind === "unlink-order") {
|
||||
unlinkOrder();
|
||||
} else if (pendingConfirmation.kind === "unlink-shipment") {
|
||||
unlinkShipment();
|
||||
}
|
||||
|
||||
setPendingConfirmation(null);
|
||||
}}
|
||||
/>
|
||||
</form>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user