This commit is contained in:
2026-03-14 18:46:06 -05:00
parent f1fd2ed979
commit c0cc546e33
15 changed files with 979 additions and 27 deletions

View File

@@ -1,11 +1,12 @@
import { permissions } from "@mrp/shared";
import type { CrmContactEntryInput, CrmRecordDetailDto } from "@mrp/shared/dist/crm/types.js";
import type { CrmContactDto, CrmContactEntryInput, CrmRecordDetailDto } from "@mrp/shared/dist/crm/types.js";
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 { CrmContactTypeBadge } from "./CrmContactTypeBadge";
import { CrmStatusBadge } from "./CrmStatusBadge";
@@ -145,6 +146,15 @@ export function CrmDetailPage({ entity }: CrmDetailPageProps) {
.join("\n")}
</dd>
</div>
<div className="md:col-span-2">
<dt className="text-xs font-semibold uppercase tracking-[0.18em] text-muted">Commercial terms</dt>
<dd className="mt-2 grid gap-3 text-sm text-text md:grid-cols-2">
<div>Payment terms: {record.paymentTerms ?? "Not set"}</div>
<div>Currency: {record.currencyCode ?? "USD"}</div>
<div>Tax exempt: {record.taxExempt ? "Yes" : "No"}</div>
<div>Credit hold: {record.creditHold ? "Yes" : "No"}</div>
</dd>
</div>
</dl>
</article>
<article className="min-w-0 rounded-[28px] border border-line/70 bg-surface/90 p-6 shadow-panel 2xl:p-7">
@@ -155,8 +165,56 @@ export function CrmDetailPage({ entity }: CrmDetailPageProps) {
<div className="mt-8 rounded-2xl border border-line/70 bg-page/70 px-4 py-4 text-sm text-muted">
Created {new Date(record.createdAt).toLocaleDateString()}
</div>
{entity === "customer" ? (
<div className="mt-4 rounded-2xl border border-line/70 bg-page/70 px-4 py-4">
<p className="text-xs font-semibold uppercase tracking-[0.18em] text-muted">Reseller Profile</p>
<div className="mt-3 grid gap-3 text-sm text-text">
<div>
<span className="font-semibold">Account type:</span>{" "}
{record.isReseller ? "Reseller" : record.parentCustomerName ? "End customer" : "Direct customer"}
</div>
<div>
<span className="font-semibold">Discount:</span> {(record.resellerDiscountPercent ?? 0).toFixed(2)}%
</div>
<div>
<span className="font-semibold">Parent reseller:</span> {record.parentCustomerName ?? "None"}
</div>
<div>
<span className="font-semibold">Child accounts:</span> {record.childCustomers?.length ?? 0}
</div>
</div>
</div>
) : null}
</article>
</div>
{entity === "customer" && (record.childCustomers?.length ?? 0) > 0 ? (
<section className="rounded-[28px] border border-line/70 bg-surface/90 p-6 shadow-panel 2xl:p-7">
<p className="text-xs font-semibold uppercase tracking-[0.24em] text-muted">Hierarchy</p>
<h4 className="mt-3 text-xl font-bold text-text">End customers under this reseller</h4>
<div className="mt-5 grid gap-3 xl:grid-cols-2 2xl:grid-cols-3">
{record.childCustomers?.map((child) => (
<Link
key={child.id}
to={`/crm/customers/${child.id}`}
className="rounded-3xl border border-line/70 bg-page/60 px-4 py-4 transition hover:border-brand/50 hover:bg-page/80"
>
<div className="text-sm font-semibold text-text">{child.name}</div>
<div className="mt-2">
<CrmStatusBadge status={child.status} />
</div>
</Link>
))}
</div>
</section>
) : null}
<CrmContactsPanel
entity={entity}
ownerId={record.id}
contacts={record.contacts ?? []}
onContactsChange={(contacts: CrmContactDto[]) =>
setRecord((current) => (current ? { ...current, contacts } : current))
}
/>
<section className="grid gap-4 2xl:grid-cols-[minmax(360px,0.88fr)_minmax(0,1.12fr)]">
{canManage ? (
<article className="min-w-0 rounded-[28px] border border-line/70 bg-surface/90 p-6 shadow-panel 2xl:p-7">