diff --git a/apps/hq-web/src/api.ts b/apps/hq-web/src/api.ts index f8181a8..aa7f6bc 100644 --- a/apps/hq-web/src/api.ts +++ b/apps/hq-web/src/api.ts @@ -429,6 +429,43 @@ export const putTemplateSettings = ( export const uploadTemplateLogo = (dataUri: string): Promise => apiFetch<{ logo: string }>('/settings/template/logo', { method: 'POST', body: JSON.stringify({ dataUri }) }).then((r) => r.logo) +// ---------- module → client roster (spec §10) ---------- + +export interface ModuleClientRow { + clientId: string; clientName: string; clientCode: string + status: string; kind: Kind; edition: string + nextRenewal: string | null; pricePaise: number | null +} +export interface ModuleClientsPage { + clients: ModuleClientRow[]; total: number; page: number; pageSize: number; totalPricePaise: number +} +export const getModuleClients = (moduleId: string, page = 1): Promise => + apiFetch(`/modules/${moduleId}/clients?page=${page}`) +export const notifyModuleClients = ( + moduleId: string, body: { subject: string; body: string }, +): Promise<{ sent: number; total: number; failed: { client: string; error: string }[] }> => + apiFetch(`/modules/${moduleId}/notify`, { method: 'POST', body: JSON.stringify(body) }) +/** CSV export needs the bearer header an can't carry — blob + transient anchor. */ +export async function downloadModuleClientsCsv(moduleId: string, moduleCode: string): Promise { + const token = localStorage.getItem(TOKEN_KEY) ?? '' + const res = await fetch(`/api/modules/${moduleId}/clients.csv`, { + headers: token !== '' ? { authorization: `Bearer ${token}` } : {}, + }) + if (!res.ok) { + const body = (await res.json().catch(() => ({}))) as { error?: string } + throw new Error(body.error ?? `CSV failed: HTTP ${res.status}`) + } + const blob = await res.blob() + const url = URL.createObjectURL(blob) + const a = document.createElement('a') + a.href = url + a.download = `module-${moduleCode}-clients.csv` + document.body.appendChild(a) + a.click() + a.remove() + URL.revokeObjectURL(url) +} + /** The PDF route needs the bearer header, which an