@ -27,6 +27,8 @@ export function Modules() {
const module s = useData ( getModules , [ ] )
const [ creating , setCreating ] = useState ( false )
const [ selected , setSelected ] = useState < Module | undefined > ( )
const [ editingId , setEditingId ] = useState < string | undefined > ( ) // inline "Edit module" form
const editingModule = module s.data?.find ( ( m ) = > m . id === editingId )
// 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 < Record < string , number > > ( { } )
@ -46,7 +48,7 @@ export function Modules() {
< div className = "wf-page" >
< PageHeader
title = "Modules"
desc = "What we sell — each with a dated price book. Click a module to edit its detail s, fields, quote content and prices."
desc = "What we sell — each with a dated price book. Use Edit to change a module's details; click a row to see its client s, fields, quote content and prices."
actions = { isOwner
? < Button tone = "primary" onClick = { ( ) = > setCreating ( true ) } > New module < / Button >
: < Badge > read - only ( staff ) < / Badge > }
@ -60,6 +62,7 @@ export function Modules() {
{ key : 'clients' , label : 'Clients' , numeric : true } ,
{ key : 'sac' , label : 'SAC' } , { key : 'kinds' , label : 'Billing kinds' } ,
{ key : 'multi' , label : 'Multi-sub' } , { key : 'active' , label : 'Active' } ,
{ key : 'act' , label : '' } ,
] }
onRowClick = { ( _row , i ) = > setSelected ( module s.data ! [ i ] ) }
rows = { module s.data.map ( ( m ) = > ( {
@ -68,12 +71,18 @@ export function Modules() {
kinds : m.allowedKinds.map ( ( k ) = > KIND_LABEL [ k ] ) . join ( ' · ' ) ,
multi : m.multiSubscription ? < Badge tone = "accent" > yes < / Badge > : '—' ,
active : m.active ? < Badge tone = "ok" > active < / Badge > : < Badge > inactive < / Badge > ,
act : isOwner
? < span onClick = { ( e ) = > e . stopPropagation ( ) } > < Button onClick = { ( ) = > setEditingId ( m . id ) } > Edit < / Button > < / span >
: null ,
} ) ) }
/ >
) }
{ editingModule !== undefined && (
< ModuleDetails key = { ` md- ${ editingModule . id } ` } module = { editingModule } isOwner = { isOwner }
onSaved = { ( ) = > module s.reload ( ) } onClose = { ( ) = > setEditingId ( undefined ) } / >
) }
{ selected !== undefined && (
< >
< ModuleDetails key = { ` md- ${ selected . id } ` } module = { selected } isOwner = { isOwner } onSaved = { ( ) = > module s.reload ( ) } / >
< ModuleClients key = { ` mc- ${ selected . id } ` } module = { selected } / >
< QuoteContent key = { selected . id } module = { selected } isOwner = { isOwner } onSaved = { ( ) = > module s.reload ( ) } / >
< FieldSpecEditor key = { ` fs- ${ selected . id } ` } module = { selected } isOwner = { isOwner } onSaved = { ( ) = > module s.reload ( ) } / >
@ -319,7 +328,7 @@ function ModuleClients(props: { module: Module }) {
/ * * E d i t a m o d u l e ' s c o r e d e t a i l s ( o w n e r o n l y ) : n a m e , S A C , b i l l i n g k i n d s , m u l t i - s u b , a c t i v e .
* Code is the identity and is not editable . Field - spec / quote - content / prices have their
* own panels below . * /
function ModuleDetails ( props : { module : Module ; isOwner : boolean ; onSaved : ( ) = > void } ) {
function ModuleDetails ( props : { module : Module ; isOwner : boolean ; onSaved : ( ) = > void ; onClose : ( ) = > void } ) {
const toast = useToast ( )
const m = props . module
const [ name , setName ] = useState ( m . name )
@ -339,7 +348,7 @@ function ModuleDetails(props: { module: Module; isOwner: boolean; onSaved: () =>
if ( kinds . length === 0 ) { setError ( 'Pick at least one billing kind' ) ; return }
setError ( undefined ) ; setBusy ( true )
patchModule ( m . id , { name : name.trim ( ) , sac : sac.trim ( ) , allowedKinds : kinds , multiSubscription : multi , active } )
. then ( ( ) = > { toast . ok ( 'Module saved' ) ; props . onSaved ( ) } )
. then ( ( ) = > { toast . ok ( 'Module saved' ) ; props . onSaved ( ) ; props . onClose ( ) } )
. catch ( ( e : Error ) = > setError ( e . message ) )
. finally ( ( ) = > setBusy ( false ) )
}
@ -369,6 +378,7 @@ function ModuleDetails(props: { module: Module; isOwner: boolean; onSaved: () =>
< / div >
< Toolbar >
< Button tone = "primary" disabled = { busy } onClick = { save } > { busy ? 'Saving…' : 'Save module' } < / Button >
< Button onClick = { props . onClose } > Cancel < / Button >
< / Toolbar >
{ error !== undefined && < Notice tone = "err" > { error } < / Notice > }
< / div >