import type { ZinoClient } from "./client"; import type { ActivityLogEntry, Column, DetailViewResponse, InstanceReport, ReportSection, StateTransition, TabularViewParams, TabularViewResponse, } from "./types"; import { mockDelay, mockTabularResponse, mockInstanceReport, mockAuditLog, } from "./mock"; /** * View and reporting methods — tabular record views, detail views, * and audit trails. Maps to view-service endpoints. */ export class ViewService { private client: ZinoClient; constructor(client: ZinoClient) { this.client = client; } /** * Fetch a paginated, sortable, filterable record view. * Maps to `GET /recordview?rv_id=…`. */ async getTabularView( viewId: string, params: TabularViewParams = {}, ): Promise { if (this.client.isMock) { await mockDelay(); return mockTabularResponse(params); } const qs = new URLSearchParams(); qs.set("rv_id", viewId); if (params.page) qs.set("page", String(params.page)); if (params.pageSize) qs.set("limit", String(params.pageSize)); if (params.sortBy) qs.set("sort_by", params.sortBy); if (params.sortDir) qs.set("sort_dir", params.sortDir); if (params.search) qs.set("search", params.search); if (params.filters) { for (const [k, v] of Object.entries(params.filters)) { qs.set(`filter.${k}`, v); } } const raw = await this.client.request<{ config: { fields: Array<{ field_key: string; output_label: string; data_type: string; is_filter: boolean; is_search: boolean }> }; data: Record[]; pagination?: { page: number; limit: number; total_count: number; total_pages: number }; }>("GET", `/recordview?${qs.toString()}`); const columns: Column[] = raw.config.fields .filter((f) => f.field_key !== "") .map((f) => ({ key: f.field_key, label: f.output_label, data_type: f.data_type, sortable: true, filterable: f.is_filter, searchable: f.is_search, })); const rows = Array.isArray(raw.data) ? raw.data : []; return { rows, total: raw.pagination?.total_count ?? rows.length, totalPages: raw.pagination?.total_pages, page: raw.pagination?.page ?? params.page ?? 1, columns, }; } /** * Fetch a detail view for a specific workflow instance. * Maps to `GET /detailview?dv_id=…&instance_id=…`. */ async getDetailView(viewId: string, instanceId: string): Promise { if (this.client.isMock) { await mockDelay(); const data: Record = { instance_id: instanceId, view_id: viewId, status: "Open", created_at: new Date().toISOString(), }; return { data, sections: [ { title: "Details", fields: Object.entries(data).map(([k, v]) => ({ label: k, value: v })), }, ], }; } const raw = await this.client.request<{ config: { fields: Array<{ field_key: string; output_label: string; data_type: string }> }; data: Record; }>("GET", `/detailview?dv_id=${encodeURIComponent(viewId)}&instance_id=${encodeURIComponent(instanceId)}`); const data = raw.data ?? {}; const fields = (raw.config?.fields ?? []).filter((f) => f.field_key !== ""); const sections: ReportSection[] = [ { title: "Details", fields: fields.length > 0 ? fields.map((f) => ({ label: f.output_label, value: data[f.field_key] })) : Object.entries(data).map(([k, v]) => ({ label: k, value: v })), }, ]; return { data, sections }; } /** * Fetch a full instance report — global data, activity history, * and state transitions. */ async getInstanceReport( workflowId: string, instanceId: string, ): Promise<{ report: InstanceReport; sections: ReportSection[]; history: StateTransition[] }> { if (this.client.isMock) { await mockDelay(); return mockInstanceReport(instanceId); } const [instance, audit] = await Promise.all([ this.client.request("POST", "/instance", { workflow_id: workflowId, instance_id: instanceId, }), this.client.request( "GET", `/audit?instance_id=${encodeURIComponent(instanceId)}`, ), ]); const sections: ReportSection[] = [ { title: "Instance Details", fields: [ { label: "Instance ID", value: instance.instance_id }, { label: "Workflow", value: instance.workflow_id }, { label: "Current State", value: instance.current_state_name }, { label: "Created", value: instance.created_at }, { label: "Updated", value: instance.updated_at }, ], }, { title: "Data", fields: Object.entries((instance as any).data ?? instance.global_data ?? {}).map(([k, v]) => ({ label: k, value: v, })), }, ]; const history: StateTransition[] = audit.map((entry) => ({ from_state: "", to_state: entry.execution_state, activity_name: entry.activity_id, performed_by: entry.user_id, timestamp: entry.created_at, data: entry.data, })); return { report: instance, sections, history }; } /** * Fetch the raw audit log for an instance. */ async getAuditLog(instanceId: string): Promise { if (this.client.isMock) { await mockDelay(); return mockAuditLog(); } return this.client.request( "GET", `/audit?instance_id=${encodeURIComponent(instanceId)}`, ); } }