import { useState } from 'react' import { Link, useNavigate, useParams, useSearchParams } from 'react-router-dom' import { formatINR } from '@sims/domain' import { Badge, Button, DataTable, EmptyState, ErrorState, RecordHeader, Skeleton, StatCard, Stats, Tabs, Toolbar, } from '@sims/ui' import { assignClientModule, getClient, getClientModules, getLedger, getModules, patchClient, role, isManagerial, getEmployees, setClientOwner, getRecurringPlans, deactivateRecurringPlan, getAmc, deactivateAmc, generateAmcRenewalInvoice, getInteractions, getInteractionTypes, updateInteraction, getClientAwsUsage, CLIENT_STATUSES, KIND_LABEL, type AmcPaidStatus, type ClientStatus, type Outcome, } from '../api' import { CLIENT_TONE, DOC_TONE, useData } from './Clients' import { AccountOwnerSelect, AssignModuleForm, ClientModuleStatusSelect, LogInteractionForm, NewAmcForm, NewRecurringForm, PaymentForm, RowDate, } from './client-forms' const inr = (p: number) => formatINR(p, { symbol: false }) const AMC_TONE: Record = { paid: 'ok', unpaid: 'err', unbilled: 'warn' } const OUTCOME_TONE: Record = { positive: 'ok', neutral: 'warn', negative: 'err' } const TAB_KEYS = ['overview', 'modules', 'documents', 'payments', 'amc', 'interactions', 'aws'] as const type TabKey = (typeof TAB_KEYS)[number] /** Client 360° — record header, KPIs, pulse ribbon (Task 10), tabbed sections. */ export function ClientDetail() { const id = useParams()['id'] ?? '' const nav = useNavigate() const [sp, setSp] = useSearchParams() const client = useData(() => getClient(id), [id]) const modules = useData(getModules, []) const cms = useData(() => getClientModules(id), [id]) const ledger = useData(() => getLedger(id), [id]) const plans = useData(() => getRecurringPlans(id), [id]) const amc = useData(() => getAmc(id), [id]) const interactions = useData(() => getInteractions(id), [id]) const types = useData(getInteractionTypes, []) const awsUsage = useData(() => getClientAwsUsage(id), [id]) const employees = useData(() => getEmployees(), []) const isOwner = role() === 'owner' const canRoute = isManagerial() const [actionErr, setActionErr] = useState() const rawTab = sp.get('tab') ?? 'overview' const tab: TabKey = (TAB_KEYS as readonly string[]).includes(rawTab) ? (rawTab as TabKey) : 'overview' const setTab = (k: string) => setSp(k === 'overview' ? {} : { tab: k }, { replace: true }) const c = client.data if (client.error !== undefined) return if (c === undefined) return
const moduleName = (moduleId: string) => modules.data?.find((m) => m.id === moduleId)?.name ?? moduleId const docs = ledger.data?.documents ?? [] const openDocs = docs.filter((d) => d.status === 'draft' || d.status === 'sent' || d.status === 'part_paid') const activeModules = (cms.data ?? []).filter((m) => m.active) const lastInteraction = interactions.data?.[0]?.onDate return (
Clients/{c.code}
{c.status}} meta={ <> {c.code} · State {c.stateCode} {c.gstin !== undefined && c.gstin !== '' && · {c.gstin}} {c.contacts[0] !== undefined && ( · {[c.contacts[0].name, c.contacts[0].email, c.contacts[0].phone].filter((x) => x !== undefined && x !== '').join(' · ')} )} } actions={ <> } /> {canRoute && employees.data !== undefined && ( { setActionErr(undefined) setClientOwner(id, ownerId) .then(client.reload).catch((err: Error) => setActionErr(err.message)) }} /> )} {actionErr !== undefined && } {tab === 'overview' && ( <> {/* Pulse ribbon lands here in the next task. */}

Recent documents

{docs.length === 0 ? No documents yet. : ( nav(`/documents/${docs[i]!.id}`)} rows={docs.slice(0, 5).map((d) => ({ no: d.docNo ?? 'draft', type: d.docType, date: d.docDate, payable: inr(d.payablePaise), status: {d.status}, }))} /> )} {docs.length > 5 && ( )} )} {tab === 'modules' && ( <> {modules.data !== undefined && ( { setActionErr(undefined) assignClientModule(id, { moduleId, kind }) .then(() => { cms.reload(); ledger.reload() }) .catch((err: Error) => setActionErr(err.message)) }} /> )} {cms.error !== undefined && } {cms.data === undefined && cms.error === undefined ? : (cms.data ?? []).length === 0 ? No modules assigned yet. : ( { const paid = ledger.data?.modulePaid.find((mp) => mp.moduleId === cm.moduleId) return { module: moduleName(cm.moduleId), kind: KIND_LABEL[cm.kind], status: cms.reload()} onError={setActionErr} />, installed: cms.reload()} onError={setActionErr} />, completed: cms.reload()} onError={setActionErr} />, trained: cms.reload()} onError={setActionErr} />, billed: paid !== undefined ? inr(paid.billedPaise) : '—', settled: paid !== undefined ? inr(paid.settledPaise) : '—', } })} /> )} )} {tab === 'documents' && ( <> {ledger.error !== undefined && } {ledger.data === undefined && ledger.error === undefined ? : docs.length === 0 ? No documents yet — compose one from New Document. : ( nav(`/documents/${docs[i]!.id}`)} rows={docs.map((d) => ({ no: d.docNo ?? draft, type: d.docType, date: d.docDate, payable: inr(d.payablePaise), status: {d.status}, }))} /> )} )} {tab === 'payments' && ( <> {ledger.data !== undefined && ( )} { ledger.reload(); cms.reload(); amc.reload() }} /> {ledger.data !== undefined && ledger.data.payments.length > 0 && ( ({ on: p.receivedOn, mode: p.mode, ref: p.reference !== '' ? p.reference : '—', amount: inr(p.amountPaise), tds: p.tdsPaise > 0 ? inr(p.tdsPaise) : '—', }))} /> )}

Recurring plans

{isOwner && cms.data !== undefined && ( cm.active).map((cm) => ({ id: cm.id, label: `${moduleName(cm.moduleId)} · ${KIND_LABEL[cm.kind]}`, }))} onDone={() => plans.reload()} /> )} {plans.error !== undefined && } {plans.data === undefined && plans.error === undefined ? : (plans.data ?? []).length === 0 ? No recurring plans. : ( { const cm = cms.data?.find((x) => x.id === plan.clientModuleId) return { module: cm !== undefined ? moduleName(cm.moduleId) : '—', cadence: plan.cadence, amount: plan.amountPaise !== null ? inr(plan.amountPaise) : 'price book', next: plan.nextRun, policy: {plan.policy}, active: plan.active ? 'yes' : 'no', act: plan.active ? ( ) : '—', } })} /> )} )} {tab === 'amc' && ( <> {isOwner && amc.reload()} />} {amc.error !== undefined && } {amc.data === undefined && amc.error === undefined ? : (amc.data ?? []).length === 0 ? No AMC contracts. : ( ({ coverage: a.coverage !== '' ? a.coverage : '—', period: `${a.periodFrom} → ${a.periodTo}`, amount: inr(a.amountPaise), days: a.renewalReminderDays, paid: {a.paidStatus}, act: (
{a.paidStatus !== 'unpaid' && ( )} {a.active && ( )}
), }))} /> )} )} {tab === 'interactions' && ( <> {types.data !== undefined && ( interactions.reload()} /> )} {interactions.error !== undefined && } {interactions.data === undefined && interactions.error === undefined ? : (interactions.data ?? []).length === 0 ? No interactions logged yet. : ( ({ on: i.onDate, type: types.data?.find((t) => t.code === i.typeCode)?.label ?? i.typeCode, notes: i.notes !== '' ? i.notes : '—', outcome: i.outcome !== null ? {i.outcome} : '—', follow: ( { setActionErr(undefined) updateInteraction(i.id, { followUpOn: e.target.value !== '' ? e.target.value : null }) .then(() => interactions.reload()).catch((err: Error) => setActionErr(err.message)) }} /> ), }))} /> )} )} {tab === 'aws' && ( <> {awsUsage.error !== undefined && } {awsUsage.data === undefined && awsUsage.error === undefined ? : (awsUsage.data ?? []).length === 0 ? No AWS usage recorded for this client. : ( ({ month: u.month, storage: u.storageGb, transfer: u.transferGb, cost: inr(u.costPaise), source: u.source, }))} /> )} )}
) }