From 7f740a7e2ed40266074f49f241c60f28deafd967 Mon Sep 17 00:00:00 2001 From: Thomas Joise Date: Sun, 19 Jul 2026 18:46:44 +0530 Subject: [PATCH] feat(ui): split module screen into Clients | Setup tabs + prominent Add-client MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A module's detail screen was one long stack mixing day-to-day (who's on it) with owner config (details/fields/quote/prices). Now two tabs: - Clients (default): the roster + install details, with a prominent primary '+ Add client to this module' button (was a quiet 'Assign client…'). Tab shows the client count. - Setup (owner only): Details, Fields, Quote content, Price book. Opening a module always lands on Clients. typecheck + build clean. Co-Authored-By: Claude Fable 5 --- apps/hq-web/src/pages/Modules.tsx | 36 +++++++++++++++++++++---------- 1 file changed, 25 insertions(+), 11 deletions(-) diff --git a/apps/hq-web/src/pages/Modules.tsx b/apps/hq-web/src/pages/Modules.tsx index 6aa80e1..b8fee61 100644 --- a/apps/hq-web/src/pages/Modules.tsx +++ b/apps/hq-web/src/pages/Modules.tsx @@ -2,7 +2,7 @@ import { useEffect, useMemo, useState } from 'react' import { formatINR, fromRupees } from '@sims/domain' import { Badge, Button, ConfirmDialog, DataTable, Dialog, EmptyState, ErrorState, Field, FormField, - FormGrid, Notice, PageHeader, Skeleton, Toolbar, useToast, + FormGrid, Notice, PageHeader, Skeleton, Tabs, Toolbar, useToast, } from '@sims/ui' import { addPrice, assignClientModule, createModule, downloadModuleClientsCsv, getClients, @@ -29,7 +29,9 @@ export function Modules() { // Master → detail: selecting a module opens its own screen (derive from fresh data so the // header + fields reflect edits immediately after a reload). const [selectedId, setSelectedId] = useState() + const [detailTab, setDetailTab] = useState<'clients' | 'setup'>('clients') const selected = modules.data?.find((m) => m.id === selectedId) + const openModule = (mid: string) => { setSelectedId(mid); setDetailTab('clients') } // How many clients sit on each module — one lightweight roster count per module, // best-effort so a slow/failed one never blocks the table (cell shows '…' until then). const [clientCounts, setClientCounts] = useState>({}) @@ -59,11 +61,24 @@ export function Modules() { desc={`SAC ${selected.sac} · ${selected.allowedKinds.map((k) => KIND_LABEL[k]).join(' · ')} · ${selected.active ? 'active' : 'inactive'}`} actions={} /> - modules.reload()} /> - - modules.reload()} /> - modules.reload()} /> - + setDetailTab(k as 'clients' | 'setup')} + /> + {detailTab === 'clients' ? ( + + ) : ( + <> + modules.reload()} /> + modules.reload()} /> + modules.reload()} /> + + + )} ) } @@ -88,14 +103,14 @@ export function Modules() { { key: 'multi', label: 'Multi-sub' }, { key: 'active', label: 'Active' }, { key: 'act', label: '' }, ]} - onRowClick={(_row, i) => setSelectedId(modules.data![i]!.id)} + onRowClick={(_row, i) => openModule(modules.data![i]!.id)} rows={modules.data.map((m) => ({ code: m.code, name: m.name, sac: m.sac, clients: clientCounts[m.id] !== undefined ? clientCounts[m.id] : '…', kinds: m.allowedKinds.map((k) => KIND_LABEL[k]).join(' · '), multi: m.multiSubscription ? yes : '—', active: m.active ? active : inactive, - act: e.stopPropagation()}>, + act: e.stopPropagation()}>, }))} /> )} @@ -182,10 +197,9 @@ function ModuleClients(props: { module: Module }) { const pages = data !== undefined ? Math.max(1, Math.ceil(data.total / data.pageSize)) : 1 return (
-

Clients on this module

- {managerial && (