CRM
This commit is contained in:
124
client/src/modules/crm/CrmFormPage.tsx
Normal file
124
client/src/modules/crm/CrmFormPage.tsx
Normal file
@@ -0,0 +1,124 @@
|
||||
import type { CrmRecordInput } from "@mrp/shared/dist/crm/types.js";
|
||||
import { useEffect, useState } from "react";
|
||||
import { Link, useNavigate, useParams } from "react-router-dom";
|
||||
|
||||
import { useAuth } from "../../auth/AuthProvider";
|
||||
import { api, ApiError } from "../../lib/api";
|
||||
import { CrmRecordForm } from "./CrmRecordForm";
|
||||
import { type CrmEntity, crmConfigs, emptyCrmRecordInput } from "./config";
|
||||
|
||||
interface CrmFormPageProps {
|
||||
entity: CrmEntity;
|
||||
mode: "create" | "edit";
|
||||
}
|
||||
|
||||
export function CrmFormPage({ entity, mode }: CrmFormPageProps) {
|
||||
const navigate = useNavigate();
|
||||
const { token } = useAuth();
|
||||
const { customerId, vendorId } = useParams();
|
||||
const recordId = entity === "customer" ? customerId : vendorId;
|
||||
const config = crmConfigs[entity];
|
||||
const [form, setForm] = useState<CrmRecordInput>(emptyCrmRecordInput);
|
||||
const [status, setStatus] = useState(
|
||||
mode === "create" ? `Create a new ${config.singularLabel.toLowerCase()} record.` : `Loading ${config.singularLabel.toLowerCase()}...`
|
||||
);
|
||||
const [isSaving, setIsSaving] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (mode !== "edit" || !token || !recordId) {
|
||||
return;
|
||||
}
|
||||
|
||||
const loadRecord = entity === "customer" ? api.getCustomer(token, recordId) : api.getVendor(token, recordId);
|
||||
|
||||
loadRecord
|
||||
.then((record) => {
|
||||
setForm({
|
||||
name: record.name,
|
||||
email: record.email,
|
||||
phone: record.phone,
|
||||
addressLine1: record.addressLine1,
|
||||
addressLine2: record.addressLine2,
|
||||
city: record.city,
|
||||
state: record.state,
|
||||
postalCode: record.postalCode,
|
||||
country: record.country,
|
||||
status: record.status,
|
||||
notes: record.notes,
|
||||
});
|
||||
setStatus(`${config.singularLabel} record loaded.`);
|
||||
})
|
||||
.catch((error: unknown) => {
|
||||
const message = error instanceof ApiError ? error.message : `Unable to load ${config.singularLabel.toLowerCase()}.`;
|
||||
setStatus(message);
|
||||
});
|
||||
}, [config.singularLabel, entity, mode, recordId, token]);
|
||||
|
||||
function updateField<Key extends keyof CrmRecordInput>(key: Key, value: CrmRecordInput[Key]) {
|
||||
setForm((current: CrmRecordInput) => ({ ...current, [key]: value }));
|
||||
}
|
||||
|
||||
async function handleSubmit(event: React.FormEvent<HTMLFormElement>) {
|
||||
event.preventDefault();
|
||||
if (!token) {
|
||||
return;
|
||||
}
|
||||
|
||||
setIsSaving(true);
|
||||
setStatus(`Saving ${config.singularLabel.toLowerCase()}...`);
|
||||
|
||||
try {
|
||||
const savedRecord =
|
||||
entity === "customer"
|
||||
? mode === "create"
|
||||
? await api.createCustomer(token, form)
|
||||
: await api.updateCustomer(token, recordId ?? "", form)
|
||||
: mode === "create"
|
||||
? await api.createVendor(token, form)
|
||||
: await api.updateVendor(token, recordId ?? "", form);
|
||||
|
||||
navigate(`${config.routeBase}/${savedRecord.id}`);
|
||||
} catch (error: unknown) {
|
||||
const message = error instanceof ApiError ? error.message : `Unable to save ${config.singularLabel.toLowerCase()}.`;
|
||||
setStatus(message);
|
||||
setIsSaving(false);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<form className="space-y-6" onSubmit={handleSubmit}>
|
||||
<section className="rounded-[28px] border border-line/70 bg-surface/90 p-8 shadow-panel">
|
||||
<div className="flex flex-col gap-4 lg:flex-row lg:items-start lg:justify-between">
|
||||
<div>
|
||||
<p className="text-xs font-semibold uppercase tracking-[0.24em] text-muted">CRM Editor</p>
|
||||
<h3 className="mt-3 text-2xl font-bold text-text">
|
||||
{mode === "create" ? `New ${config.singularLabel}` : `Edit ${config.singularLabel}`}
|
||||
</h3>
|
||||
<p className="mt-2 max-w-2xl text-sm text-muted">
|
||||
Capture the operational contact and address details needed for quoting, purchasing, and shipping workflows.
|
||||
</p>
|
||||
</div>
|
||||
<Link
|
||||
to={mode === "create" ? config.routeBase : `${config.routeBase}/${recordId}`}
|
||||
className="inline-flex items-center justify-center rounded-2xl border border-line/70 px-4 py-3 text-sm font-semibold text-text"
|
||||
>
|
||||
Cancel
|
||||
</Link>
|
||||
</div>
|
||||
</section>
|
||||
<section className="space-y-6 rounded-[28px] border border-line/70 bg-surface/90 p-8 shadow-panel">
|
||||
<CrmRecordForm form={form} onChange={updateField} />
|
||||
<div className="flex items-center justify-between rounded-2xl border border-line/70 bg-page/70 px-4 py-4">
|
||||
<span className="text-sm text-muted">{status}</span>
|
||||
<button
|
||||
type="submit"
|
||||
disabled={isSaving}
|
||||
className="rounded-2xl bg-brand px-5 py-3 text-sm font-semibold text-white disabled:cursor-not-allowed disabled:opacity-60"
|
||||
>
|
||||
{isSaving ? "Saving..." : mode === "create" ? `Create ${config.singularLabel}` : "Save changes"}
|
||||
</button>
|
||||
</div>
|
||||
</section>
|
||||
</form>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user