/** * Farm Dashboard Service * Handles API calls for dashboard config and card data. * - Config: disabled cards, row order, drag reorder * - Cards: all 15 card payloads from /api/farm-dashboard/ */ import { apiClient } from "../client"; import { CARD_IDS, CARD_TO_ROW, ROW_CARDS, ROW_IDS, type FarmDashboardConfig, type CardId, type RowId, } from "@/views/dashboards/farm/farmDashboardConfig"; export interface ApiResponse { code: number; msg: string; data: T; } export interface FarmDashboardConfigResponse { farm_uuid?: string; disabled_card_ids: string[]; row_order: string[]; enable_drag_reorder?: boolean; } /** API response shape for /api/farm-dashboard/ - each key matches CardId */ export interface FarmDashboardCardsResponse { farmOverviewKpis?: Record; farmWeatherCard?: Record; farmAlertsTracker?: Record; sensorValuesList?: Record; sensorRadarChart?: Record; sensorComparisonChart?: Record; anomalyDetectionCard?: Record; farmAlertsTimeline?: Record; waterNeedPrediction?: Record; harvestPredictionCard?: Record; yieldPredictionChart?: Record; soilMoistureHeatmap?: Record; ndviHealthCard?: Record; recommendationsList?: Record; economicOverview?: Record; } interface FarmDashboardCardsTaskResult { farm_uuid?: string; all_cards?: FarmDashboardCardsResponse; } interface FarmDashboardCardsTaskData { task_id?: string; status?: string; result?: FarmDashboardCardsTaskResult; } const STORAGE_KEY_PREFIX = "farm_dashboard_config"; function getStorageKey(farmUuid: string): string { return `${STORAGE_KEY_PREFIX}:${farmUuid}`; } function isCardId(value: string): value is CardId { return (CARD_IDS as readonly string[]).includes(value); } function isRowId(value: string): value is RowId { return (ROW_IDS as readonly string[]).includes(value); } function normalizeDisabledCardIds(disabledIds: string[] = []): string[] { return Array.from( new Set( disabledIds.flatMap((id) => { if (isCardId(id)) return [id]; if (isRowId(id)) return ROW_CARDS[id]; return []; }), ), ); } function normalizeRowOrder(rowOrder: string[] = []): string[] { const normalized = rowOrder .map((id) => { if (isRowId(id)) return id; if (isCardId(id)) return CARD_TO_ROW[id]; return null; }) .filter((id): id is RowId => !!id); const deduped = Array.from(new Set(normalized)); return deduped.length ? [...deduped, ...ROW_IDS.filter((id) => !deduped.includes(id))] : [...ROW_IDS]; } function extractCardsPayload( response: | ApiResponse | ApiResponse | FarmDashboardCardsResponse | FarmDashboardCardsTaskData, ): Partial>> { const raw = response && "data" in response ? response.data : response; if (!raw || typeof raw !== "object") { return {}; } if ("result" in raw && raw.result && typeof raw.result === "object") { return (raw.result.all_cards ?? {}) as Partial< Record> >; } return raw as Partial>>; } /** * Transform API response to frontend config format */ function fromApiResponse( data: FarmDashboardConfigResponse, ): FarmDashboardConfig { return { disabledCardIds: normalizeDisabledCardIds(data.disabled_card_ids), rowOrder: normalizeRowOrder(data.row_order), enableDragReorder: data.enable_drag_reorder ?? true, }; } /** * Transform frontend config to API request format */ function toApiRequest( config: Partial, ): Partial { const req: Partial = {}; if (config.disabledCardIds !== undefined) req.disabled_card_ids = config.disabledCardIds; if (config.rowOrder !== undefined) req.row_order = config.rowOrder; if (config.enableDragReorder !== undefined) req.enable_drag_reorder = config.enableDragReorder; return req; } /** * localStorage fallback when backend is not ready */ function getLocalConfig(farmUuid: string): FarmDashboardConfig | null { if (typeof window === "undefined") return null; try { const stored = localStorage.getItem(getStorageKey(farmUuid)); return stored ? (JSON.parse(stored) as FarmDashboardConfig) : null; } catch { return null; } } function setLocalConfig(farmUuid: string, config: FarmDashboardConfig): void { if (typeof window === "undefined") return; try { localStorage.setItem(getStorageKey(farmUuid), JSON.stringify(config)); } catch (e) { console.error("Failed to save farm dashboard config to localStorage", e); } } function buildFarmQuery(farmUuid: string): string { return `farm_uuid=${encodeURIComponent(farmUuid)}`; } export const farmDashboardService = { /** * Get farm dashboard config for the selected farm */ async getConfig(farmUuid: string): Promise { try { const response = await apiClient.get< ApiResponse | FarmDashboardConfigResponse >(`/api/farm-dashboard-config/?${buildFarmQuery(farmUuid)}`); const raw = response && "data" in response ? response.data : response; if ( raw && typeof raw === "object" && ("disabled_card_ids" in raw || "row_order" in raw) ) { return fromApiResponse(raw as FarmDashboardConfigResponse); } throw new Error("Invalid response"); } catch { const local = getLocalConfig(farmUuid); if (local) return local; return { disabledCardIds: [], rowOrder: [], enableDragReorder: true }; } }, /** * Update farm dashboard config */ async updateConfig( farmUuid: string, data: Partial, ): Promise { try { const response = await apiClient.patch< ApiResponse | FarmDashboardConfigResponse >("/api/farm-dashboard-config/", { farm_uuid: farmUuid, ...toApiRequest(data), }); const raw = response && "data" in response ? response.data : response; if ( raw && typeof raw === "object" && ("disabled_card_ids" in raw || "row_order" in raw) ) { const config = fromApiResponse(raw as FarmDashboardConfigResponse); setLocalConfig(farmUuid, config); return config; } throw new Error("Update failed"); } catch (err) { const local = getLocalConfig(farmUuid); if (local) { const merged: FarmDashboardConfig = { disabledCardIds: data.disabledCardIds ?? local.disabledCardIds, rowOrder: data.rowOrder ?? local.rowOrder, enableDragReorder: data.enableDragReorder ?? local.enableDragReorder ?? true, }; setLocalConfig(farmUuid, merged); return merged; } throw err; } }, /** * Get all dashboard card data from API * Response: { code: 200, msg: "OK", data: { farmOverviewKpis, farmWeatherCard, ... } } */ async getAllCards( farmUuid: string, ): Promise< Partial>> > { try { const response = await apiClient.get< | ApiResponse | ApiResponse | FarmDashboardCardsResponse | FarmDashboardCardsTaskData >(`/api/farm-dashboard/?${buildFarmQuery(farmUuid)}`); return extractCardsPayload(response); } catch { return {}; } }, };