import { useEffect, useMemo, useState } from 'react' import { formatINR, fromRupees } from '@sims/domain' import { Badge, Button, DataTable, Dialog, EmptyState, ErrorState, Field, FormField, FormGrid, Notice, PageHeader, Skeleton, Toolbar, useToast, } from '@sims/ui' import { addPrice, createModule, downloadModuleClientsCsv, getModuleClients, getModules, getPrices, notifyModuleClients, patchModule, previewSample, role, KIND_LABEL, type Kind, type Module, } from '../api' import { LivePreview } from '../components/LivePreview' import { useData } from './Clients' const ALL_KINDS: Kind[] = ['one_time', 'monthly', 'yearly', 'usage'] const today = () => new Date().toISOString().slice(0, 10) /** Textarea ↔ string[]: one "what's included" bullet per row, blanks dropped. */ const splitLines = (s: string): string[] => s.split('\n').map((x) => x.trim()).filter((x) => x !== '') /** Module catalog + dated price book. Owner edits; staff read-only. */ export function Modules() { const isOwner = role() === 'owner' const modules = useData(getModules, []) const [creating, setCreating] = useState(false) const [selected, setSelected] = useState() return (
setCreating(true)}>New module : read-only (staff)} /> {modules.error !== undefined ? : modules.data === undefined ? : modules.data.length === 0 ? No modules yet. : ( setSelected(modules.data![i])} rows={modules.data.map((m) => ({ code: m.code, name: m.name, sac: m.sac, kinds: m.allowedKinds.map((k) => KIND_LABEL[k]).join(' · '), multi: m.multiSubscription ? yes : '—', active: m.active ? active : inactive, }))} /> )} {selected !== undefined && ( <> modules.reload()} /> )} {isOwner && ( setCreating(false)} onCreated={() => { setCreating(false); modules.reload() }} /> )}
) } /** * "Who's on this module" roster (spec §10): every ACTIVE client link with what * it pays and its next renewal, plus Notify-all (owner/manager) and CSV export. */ function ModuleClients(props: { module: Module }) { const managerial = role() === 'owner' || role() === 'manager' const [page, setPage] = useState(1) const roster = useData(() => getModuleClients(props.module.id, page), [props.module.id, page]) const [notifying, setNotifying] = useState(false) const [subject, setSubject] = useState('') const [bodyText, setBodyText] = useState('') const [busy, setBusy] = useState(false) const [result, setResult] = useState() const [err, setErr] = useState() const doNotify = () => { setBusy(true); setErr(undefined); setResult(undefined) notifyModuleClients(props.module.id, { subject, body: bodyText }) .then((out) => { setResult(`Sent ${out.sent}/${out.total}` + (out.failed.length > 0 ? ` — unreached: ${out.failed.map((f) => `${f.client} (${f.error})`).join(', ')}` : '')) setNotifying(false) }) .catch((e: Error) => setErr(e.message)) .finally(() => setBusy(false)) } const data = roster.data const pages = data !== undefined ? Math.max(1, Math.ceil(data.total / data.pageSize)) : 1 return (

Clients on this module

{managerial && ( )} {notifying && managerial && (
setSubject(e.target.value)} />