import { permissions } from "@mrp/shared"; import type { CrmContactDto, CrmContactEntryInput, CrmRecordDetailDto } from "@mrp/shared/dist/crm/types.js"; import type { PurchaseOrderSummaryDto } from "@mrp/shared"; import { useEffect, useState } from "react"; import { Link, useParams } from "react-router-dom"; import { useAuth } from "../../auth/AuthProvider"; import { api, ApiError } from "../../lib/api"; import { CrmAttachmentsPanel } from "./CrmAttachmentsPanel"; import { CrmContactsPanel } from "./CrmContactsPanel"; import { CrmContactEntryForm } from "./CrmContactEntryForm"; import { CrmLifecycleBadge } from "./CrmLifecycleBadge"; import { CrmContactTypeBadge } from "./CrmContactTypeBadge"; import { CrmStatusBadge } from "./CrmStatusBadge"; import { type CrmEntity, crmConfigs, emptyCrmContactEntryInput } from "./config"; interface CrmDetailPageProps { entity: CrmEntity; } export function CrmDetailPage({ entity }: CrmDetailPageProps) { const { token, user } = useAuth(); const { customerId, vendorId } = useParams(); const recordId = entity === "customer" ? customerId : vendorId; const config = crmConfigs[entity]; const [record, setRecord] = useState(null); const [relatedPurchaseOrders, setRelatedPurchaseOrders] = useState([]); const [status, setStatus] = useState(`Loading ${config.singularLabel.toLowerCase()}...`); const [contactEntryForm, setContactEntryForm] = useState(emptyCrmContactEntryInput); const [contactEntryStatus, setContactEntryStatus] = useState("Add a timeline entry for this account."); const [isSavingContactEntry, setIsSavingContactEntry] = useState(false); const canManage = user?.permissions.includes(permissions.crmWrite) ?? false; useEffect(() => { if (!token || !recordId) { return; } const loadRecord = entity === "customer" ? api.getCustomer(token, recordId) : api.getVendor(token, recordId); loadRecord .then((nextRecord) => { setRecord(nextRecord); setStatus(`${config.singularLabel} record loaded.`); setContactEntryStatus("Add a timeline entry for this account."); if (entity === "vendor") { return api.getPurchaseOrders(token, { vendorId: nextRecord.id }); } return []; }) .then((purchaseOrders) => setRelatedPurchaseOrders(purchaseOrders)) .catch((error: unknown) => { const message = error instanceof ApiError ? error.message : `Unable to load ${config.singularLabel.toLowerCase()}.`; setStatus(message); }); }, [config.singularLabel, entity, recordId, token]); if (!record) { return
{status}
; } function updateContactEntryField(key: Key, value: CrmContactEntryInput[Key]) { setContactEntryForm((current) => ({ ...current, [key]: value })); } async function handleContactEntrySubmit(event: React.FormEvent) { event.preventDefault(); if (!token || !recordId) { return; } setIsSavingContactEntry(true); setContactEntryStatus("Saving timeline entry..."); try { const nextEntry = entity === "customer" ? await api.createCustomerContactEntry(token, recordId, contactEntryForm) : await api.createVendorContactEntry(token, recordId, contactEntryForm); setRecord((current) => current ? { ...current, contactHistory: [nextEntry, ...current.contactHistory].sort( (left, right) => new Date(right.contactAt).getTime() - new Date(left.contactAt).getTime() ), rollups: { lastContactAt: nextEntry.contactAt, contactHistoryCount: (current.rollups?.contactHistoryCount ?? current.contactHistory.length) + 1, contactCount: current.rollups?.contactCount ?? current.contacts?.length ?? 0, attachmentCount: current.rollups?.attachmentCount ?? 0, childCustomerCount: current.rollups?.childCustomerCount, }, } : current ); setContactEntryForm({ ...emptyCrmContactEntryInput, contactAt: new Date().toISOString(), }); setContactEntryStatus("Timeline entry added."); } catch (error: unknown) { const message = error instanceof ApiError ? error.message : "Unable to save timeline entry."; setContactEntryStatus(message); } finally { setIsSavingContactEntry(false); } } return (

CRM Detail

{record.name}

{record.lifecycleStage ? : null}

{config.singularLabel} record last updated {new Date(record.updatedAt).toLocaleString()}.

Back to {config.collectionLabel.toLowerCase()} {canManage ? ( Edit {config.singularLabel.toLowerCase()} ) : null}

Contact

Email
{record.email}
Phone
{record.phone}
Address
{[record.addressLine1, record.addressLine2, `${record.city}, ${record.state} ${record.postalCode}`, record.country] .filter(Boolean) .join("\n")}
Commercial terms
Payment terms: {record.paymentTerms ?? "Not set"}
Currency: {record.currencyCode ?? "USD"}
Tax exempt: {record.taxExempt ? "Yes" : "No"}
Credit hold: {record.creditHold ? "Yes" : "No"}

Internal Notes

{record.notes || "No internal notes recorded for this account yet."}

Created {new Date(record.createdAt).toLocaleDateString()}

Operational Flags

{record.preferredAccount ? Preferred : null} {record.strategicAccount ? Strategic : null} {record.requiresApproval ? Requires Approval : null} {record.blockedAccount ? Blocked : null} {!record.preferredAccount && !record.strategicAccount && !record.requiresApproval && !record.blockedAccount ? ( Standard ) : null}
{entity === "customer" ? (

Reseller Profile

Account type:{" "} {record.isReseller ? "Reseller" : record.parentCustomerName ? "End customer" : "Direct customer"}
Discount: {(record.resellerDiscountPercent ?? 0).toFixed(2)}%
Parent reseller: {record.parentCustomerName ?? "None"}
Child accounts: {record.childCustomers?.length ?? 0}
) : null}

Last Contact

{record.rollups?.lastContactAt ? new Date(record.rollups.lastContactAt).toLocaleDateString() : "None"}

Timeline Entries

{record.rollups?.contactHistoryCount ?? record.contactHistory.length}

Account Contacts

{record.rollups?.contactCount ?? record.contacts?.length ?? 0}

Attachments

{record.rollups?.attachmentCount ?? 0}
{entity === "customer" && (record.childCustomers?.length ?? 0) > 0 ? (

Hierarchy

End customers under this reseller

{record.childCustomers?.map((child) => (
{child.name}
))}
) : null} {entity === "vendor" ? (

Purchasing Activity

Recent purchase orders

{canManage ? ( New purchase order ) : null} Open purchasing
{relatedPurchaseOrders.length === 0 ? (
No purchase orders exist for this vendor yet.
) : (
{relatedPurchaseOrders.slice(0, 8).map((order) => (
{order.documentNumber}
{new Date(order.issueDate).toLocaleDateString()} ยท {order.lineCount} lines
${order.total.toFixed(2)}
))}
)}
) : null} setRecord((current) => current ? { ...current, contacts, rollups: { lastContactAt: current.rollups?.lastContactAt ?? null, contactHistoryCount: current.rollups?.contactHistoryCount ?? current.contactHistory.length, contactCount: contacts.length, attachmentCount: current.rollups?.attachmentCount ?? 0, childCustomerCount: current.rollups?.childCustomerCount, }, } : current ) } />
{canManage ? (

Contact History

Add timeline entry

Record calls, emails, meetings, and follow-up notes directly against this account.

) : null}

Timeline

Recent interactions

{record.contactHistory.length === 0 ? (
No contact history has been recorded for this account yet.
) : (
{record.contactHistory.map((entry) => (
{entry.summary}

{entry.body}

{new Date(entry.contactAt).toLocaleString()}
{entry.createdBy.name}
))}
)}
setRecord((current) => current ? { ...current, rollups: { lastContactAt: current.rollups?.lastContactAt ?? null, contactHistoryCount: current.rollups?.contactHistoryCount ?? current.contactHistory.length, contactCount: current.rollups?.contactCount ?? current.contacts?.length ?? 0, attachmentCount, childCustomerCount: current.rollups?.childCustomerCount, }, } : current ) } />
); }