// [DEF:frontend.src.lib.api.reports:Module] // @TIER: CRITICAL // @SEMANTICS: frontend, api_client, reports, wrapper // @PURPOSE: Wrapper-based reports API client for list/detail retrieval without direct native fetch usage. // @LAYER: Infra // @RELATION: DEPENDS_ON -> [DEF:api_module] // @INVARIANT: Uses existing api wrapper methods and returns structured errors for UI-state mapping. import { api } from '../api.js'; // [DEF:buildReportQueryString:Function] // @PURPOSE: Build query string for reports list endpoint from filter options. // @PRE: options is an object with optional report query fields. // @POST: Returns URL query string without leading '?'. export function buildReportQueryString(options = {}) { console.log("[reports][api][buildReportQueryString:START]"); const params = new URLSearchParams(); if (options.page != null) params.append('page', String(options.page)); if (options.page_size != null) params.append('page_size', String(options.page_size)); if (Array.isArray(options.task_types) && options.task_types.length > 0) { params.append('task_types', options.task_types.join(',')); } if (Array.isArray(options.statuses) && options.statuses.length > 0) { params.append('statuses', options.statuses.join(',')); } if (options.time_from) params.append('time_from', options.time_from); if (options.time_to) params.append('time_to', options.time_to); if (options.search) params.append('search', options.search); if (options.sort_by) params.append('sort_by', options.sort_by); if (options.sort_order) params.append('sort_order', options.sort_order); return params.toString(); } // [/DEF:buildReportQueryString:Function] // [DEF:normalizeApiError:Function] // @PURPOSE: Convert unknown API exceptions into deterministic UI-consumable error objects. // @PRE: error may be Error/string/object. // @POST: Returns structured error object. export function normalizeApiError(error) { console.log("[reports][api][normalizeApiError:START]"); const message = (error && typeof error.message === 'string' && error.message) || (typeof error === 'string' && error) || 'Failed to load reports'; return { message, code: 'REPORTS_API_ERROR', retryable: true }; } // [/DEF:normalizeApiError:Function] // [DEF:getReports:Function] // @PURPOSE: Fetch unified report list using existing request wrapper. // @PRE: valid auth context for protected endpoint. // @POST: Returns parsed payload or structured error for UI-state mapping. // // @TEST_CONTRACT: GetReportsApi -> // { // required_fields: {}, // optional_fields: {options: Object}, // invariants: [ // "Fetches from /reports with built query string", // "Returns response payload on success", // "Catches and normalizes errors using normalizeApiError" // ] // } // @TEST_FIXTURE: valid_get_reports -> {"options": {"page": 1}} // @TEST_EDGE: api_fetch_failure -> api.fetchApi throws error // @TEST_INVARIANT: error_normalization -> verifies: [api_fetch_failure] export async function getReports(options = {}) { try { console.log("[reports][api][getReports:STARTED]", options); const query = buildReportQueryString(options); const res = await api.fetchApi(`/reports${query ? `?${query}` : ''}`); console.log("[reports][api][getReports:SUCCESS]", res); return res; } catch (error) { console.error("[reports][api][getReports:FAILED]", error); throw normalizeApiError(error); } } // [/DEF:getReports:Function] // [DEF:getReportDetail:Function] // @PURPOSE: Fetch one report detail by report_id. // @PRE: reportId is non-empty string; valid auth context. // @POST: Returns parsed detail payload or structured error object. export async function getReportDetail(reportId) { try { console.log(`[reports][api][getReportDetail:STARTED] id=${reportId}`); const res = await api.fetchApi(`/reports/${reportId}`); console.log(`[reports][api][getReportDetail:SUCCESS] id=${reportId}`); return res; } catch (error) { console.error(`[reports][api][getReportDetail:FAILED] id=${reportId}`, error); throw normalizeApiError(error); } } // [/DEF:getReportDetail:Function] // [/DEF:frontend.src.lib.api.reports:Module]