diff --git a/apps/hq-web/src/api.ts b/apps/hq-web/src/api.ts index b7428fb..0d6c606 100644 --- a/apps/hq-web/src/api.ts +++ b/apps/hq-web/src/api.ts @@ -88,6 +88,9 @@ export interface Client { address: string; contacts: ClientContact[]; status: ClientStatus; notes: string /** Account/enquiry owner (→ employee id); absent = unassigned. */ ownerId?: string + /** D18 WS-F support-access data; the DB password itself is reveal-only. */ + anydesk?: string; os?: string; district?: string; sector?: string + hasDbPassword: boolean } export type Kind = 'one_time' | 'monthly' | 'yearly' | 'usage' @@ -173,9 +176,20 @@ export interface Ledger { // ---------- typed calls ---------- -export const getClients = (q?: string): Promise => - apiFetch<{ clients: Client[] }>(`/clients${q !== undefined && q !== '' ? `?q=${encodeURIComponent(q)}` : ''}`) - .then((r) => r.clients) +export const getClients = ( + q?: string, filters?: { district?: string; sector?: string }, +): Promise => { + const params = new URLSearchParams() + if (q !== undefined && q !== '') params.set('q', q) + if (filters?.district !== undefined && filters.district !== '') params.set('district', filters.district) + if (filters?.sector !== undefined && filters.sector !== '') params.set('sector', filters.sector) + const qs = params.toString() + return apiFetch<{ clients: Client[] }>(`/clients${qs !== '' ? `?${qs}` : ''}`).then((r) => r.clients) +} +/** Decrypt-and-return the client's DB password — owner/manager; every call is audited. */ +export const revealDbPassword = (id: string): Promise => + apiFetch<{ password: string }>(`/clients/${id}/reveal-db-password`, { method: 'POST', body: '{}' }) + .then((r) => r.password) export const getClient = (id: string): Promise => apiFetch<{ client: Client }>(`/clients/${id}`).then((r) => r.client) export const createClient = (body: Record): Promise => diff --git a/apps/hq-web/src/components/ClientFormDialog.tsx b/apps/hq-web/src/components/ClientFormDialog.tsx index 525f57d..90483c5 100644 --- a/apps/hq-web/src/components/ClientFormDialog.tsx +++ b/apps/hq-web/src/components/ClientFormDialog.tsx @@ -14,7 +14,10 @@ export function ClientFormDialog(props: { }) { const toast = useToast() const editing = props.initial !== undefined - const [f, setF] = useState({ name: '', gstin: '', stateCode: '32', address: '', notes: '' }) + const [f, setF] = useState({ + name: '', gstin: '', stateCode: '32', address: '', notes: '', + anydesk: '', os: '', district: '', sector: '', + }) const [contacts, setContacts] = useState([]) const [error, setError] = useState() const [saving, setSaving] = useState(false) @@ -26,6 +29,7 @@ export function ClientFormDialog(props: { setF({ name: c?.name ?? '', gstin: c?.gstin ?? '', stateCode: c?.stateCode ?? '32', address: c?.address ?? '', notes: c?.notes ?? '', + anydesk: c?.anydesk ?? '', os: c?.os ?? '', district: c?.district ?? '', sector: c?.sector ?? '', }) setContacts((c?.contacts ?? []).map((ct) => ({ name: ct.name, email: ct.email ?? '', phone: ct.phone ?? '', role: ct.role ?? '', @@ -53,6 +57,15 @@ export function ClientFormDialog(props: { const body = { name: f.name.trim(), stateCode: f.stateCode.trim(), address: f.address, notes: f.notes, contacts: cleaned, + // Support-access fields (D18 WS-F). PATCH clears on ''; POST omits blanks. + ...(editing + ? { anydesk: f.anydesk, os: f.os, district: f.district, sector: f.sector } + : { + ...(f.anydesk.trim() !== '' ? { anydesk: f.anydesk.trim() } : {}), + ...(f.os.trim() !== '' ? { os: f.os.trim() } : {}), + ...(f.district.trim() !== '' ? { district: f.district.trim() } : {}), + ...(f.sector.trim() !== '' ? { sector: f.sector.trim() } : {}), + }), // Server quirk: PATCH accepts gstin: null to clear (repos-clients skips the checksum // assert on null), but POST's ClientInput.gstin is a bare optional string — sending // null there crashes assertGstin's .toUpperCase() on null. So: editing may clear via @@ -88,6 +101,10 @@ export function ClientFormDialog(props: {