268 lines
7.7 KiB
TypeScript
268 lines
7.7 KiB
TypeScript
/**
|
|
* 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<T> {
|
|
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<string, unknown>;
|
|
farmWeatherCard?: Record<string, unknown>;
|
|
farmAlertsTracker?: Record<string, unknown>;
|
|
sensorValuesList?: Record<string, unknown>;
|
|
sensorRadarChart?: Record<string, unknown>;
|
|
sensorComparisonChart?: Record<string, unknown>;
|
|
anomalyDetectionCard?: Record<string, unknown>;
|
|
farmAlertsTimeline?: Record<string, unknown>;
|
|
waterNeedPrediction?: Record<string, unknown>;
|
|
harvestPredictionCard?: Record<string, unknown>;
|
|
yieldPredictionChart?: Record<string, unknown>;
|
|
soilMoistureHeatmap?: Record<string, unknown>;
|
|
ndviHealthCard?: Record<string, unknown>;
|
|
recommendationsList?: Record<string, unknown>;
|
|
economicOverview?: Record<string, unknown>;
|
|
}
|
|
|
|
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<FarmDashboardCardsResponse>
|
|
| ApiResponse<FarmDashboardCardsTaskData>
|
|
| FarmDashboardCardsResponse
|
|
| FarmDashboardCardsTaskData,
|
|
): Partial<Record<CardId, Record<string, unknown>>> {
|
|
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<CardId, Record<string, unknown>>
|
|
>;
|
|
}
|
|
|
|
return raw as Partial<Record<CardId, Record<string, unknown>>>;
|
|
}
|
|
|
|
/**
|
|
* 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<FarmDashboardConfig>,
|
|
): Partial<FarmDashboardConfigResponse> {
|
|
const req: Partial<FarmDashboardConfigResponse> = {};
|
|
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<FarmDashboardConfig> {
|
|
try {
|
|
const response = await apiClient.get<
|
|
ApiResponse<FarmDashboardConfigResponse> | 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<FarmDashboardConfig>,
|
|
): Promise<FarmDashboardConfig> {
|
|
try {
|
|
const response = await apiClient.patch<
|
|
ApiResponse<FarmDashboardConfigResponse> | 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<Record<CardId, Record<string, unknown>>>
|
|
> {
|
|
try {
|
|
const response = await apiClient.get<
|
|
| ApiResponse<FarmDashboardCardsResponse>
|
|
| ApiResponse<FarmDashboardCardsTaskData>
|
|
| FarmDashboardCardsResponse
|
|
| FarmDashboardCardsTaskData
|
|
>(`/api/farm-dashboard/?${buildFarmQuery(farmUuid)}`);
|
|
return extractCardsPayload(response);
|
|
} catch {
|
|
return {};
|
|
}
|
|
},
|
|
};
|