UPDATE
This commit is contained in:
@@ -0,0 +1,237 @@
|
||||
import { apiClient } from "../client";
|
||||
|
||||
const SUMMARY_PREFIX = "/api/sensor-7-in-1";
|
||||
const SENSORS_PREFIX = "/api/sensors";
|
||||
|
||||
export interface ApiResponse<T> {
|
||||
code: number;
|
||||
msg: string;
|
||||
data: T;
|
||||
}
|
||||
|
||||
export interface SensorSummaryMeta {
|
||||
name: string;
|
||||
physicalDeviceUuid: string | null;
|
||||
sensorCatalogCode: string;
|
||||
updatedAt: string | null;
|
||||
}
|
||||
|
||||
export interface SensorValuesListItem {
|
||||
id?: string;
|
||||
title: string;
|
||||
subtitle: string;
|
||||
trendNumber: number;
|
||||
trend?: "positive" | "negative";
|
||||
unit: string;
|
||||
}
|
||||
|
||||
export interface SensorValuesListResponse {
|
||||
sensor?: SensorSummaryMeta | null;
|
||||
sensors: SensorValuesListItem[];
|
||||
}
|
||||
|
||||
export interface SensorChartSeries {
|
||||
name: string;
|
||||
data: number[];
|
||||
}
|
||||
|
||||
export interface SensorComparisonChartResponse {
|
||||
currentValue: number;
|
||||
vsLastWeek: string;
|
||||
vsLastWeekValue?: number;
|
||||
categories: string[];
|
||||
series: SensorChartSeries[];
|
||||
}
|
||||
|
||||
export interface SensorRadarChartResponse {
|
||||
labels: string[];
|
||||
series: SensorChartSeries[];
|
||||
}
|
||||
|
||||
export interface Sensor7SummaryData {
|
||||
sensor?: SensorSummaryMeta | null;
|
||||
sensorValuesList?: SensorValuesListResponse | null;
|
||||
avgSoilMoisture?: Record<string, unknown> | null;
|
||||
sensorRadarChart?: SensorRadarChartResponse | null;
|
||||
sensorComparisonChart?: SensorComparisonChartResponse | null;
|
||||
anomalyDetectionCard?: Record<string, unknown> | null;
|
||||
soilMoistureHeatmap?: Record<string, unknown> | null;
|
||||
}
|
||||
|
||||
const EMPTY_COMPARISON_CHART: SensorComparisonChartResponse = {
|
||||
currentValue: 0,
|
||||
vsLastWeek: "+0.0%",
|
||||
categories: [],
|
||||
series: [],
|
||||
};
|
||||
|
||||
const EMPTY_RADAR_CHART: SensorRadarChartResponse = {
|
||||
labels: [],
|
||||
series: [],
|
||||
};
|
||||
|
||||
const EMPTY_VALUES_LIST: SensorValuesListResponse = {
|
||||
sensors: [],
|
||||
};
|
||||
|
||||
function extract<T>(response: ApiResponse<T> | T): T {
|
||||
return response && typeof response === "object" && "data" in response
|
||||
? (response as ApiResponse<T>).data
|
||||
: (response as T);
|
||||
}
|
||||
|
||||
function normalizeSeries(series: unknown): SensorChartSeries[] {
|
||||
if (!Array.isArray(series)) return [];
|
||||
|
||||
return series
|
||||
.filter((item): item is { name?: unknown; data?: unknown } => !!item && typeof item === "object")
|
||||
.map((item) => ({
|
||||
name: typeof item.name === "string" ? item.name : "",
|
||||
data: Array.isArray(item.data)
|
||||
? item.data.filter((value): value is number => typeof value === "number" && Number.isFinite(value))
|
||||
: [],
|
||||
}))
|
||||
.filter((item) => item.name || item.data.length > 0);
|
||||
}
|
||||
|
||||
export function normalizeComparisonChartResponse(
|
||||
data?: Partial<SensorComparisonChartResponse> | null,
|
||||
): SensorComparisonChartResponse {
|
||||
if (!data || typeof data !== "object") {
|
||||
return { ...EMPTY_COMPARISON_CHART };
|
||||
}
|
||||
|
||||
return {
|
||||
currentValue:
|
||||
typeof data.currentValue === "number" && Number.isFinite(data.currentValue)
|
||||
? data.currentValue
|
||||
: 0,
|
||||
vsLastWeek: typeof data.vsLastWeek === "string" ? data.vsLastWeek : "+0.0%",
|
||||
vsLastWeekValue:
|
||||
typeof data.vsLastWeekValue === "number" && Number.isFinite(data.vsLastWeekValue)
|
||||
? data.vsLastWeekValue
|
||||
: undefined,
|
||||
categories: Array.isArray(data.categories)
|
||||
? data.categories.filter((value): value is string => typeof value === "string")
|
||||
: [],
|
||||
series: normalizeSeries(data.series),
|
||||
};
|
||||
}
|
||||
|
||||
export function normalizeRadarChartResponse(
|
||||
data?: Partial<SensorRadarChartResponse> | null,
|
||||
): SensorRadarChartResponse {
|
||||
if (!data || typeof data !== "object") {
|
||||
return { ...EMPTY_RADAR_CHART };
|
||||
}
|
||||
|
||||
return {
|
||||
labels: Array.isArray(data.labels)
|
||||
? data.labels.filter((value): value is string => typeof value === "string")
|
||||
: [],
|
||||
series: normalizeSeries(data.series),
|
||||
};
|
||||
}
|
||||
|
||||
export function normalizeValuesListResponse(
|
||||
data?: Partial<SensorValuesListResponse> | null,
|
||||
): SensorValuesListResponse {
|
||||
if (!data || typeof data !== "object") {
|
||||
return { ...EMPTY_VALUES_LIST };
|
||||
}
|
||||
|
||||
const sensors = Array.isArray(data.sensors)
|
||||
? data.sensors
|
||||
.filter((item): item is SensorValuesListItem => !!item && typeof item === "object")
|
||||
.map((item) => {
|
||||
const trend: SensorValuesListItem["trend"] =
|
||||
item.trend === "negative"
|
||||
? "negative"
|
||||
: item.trend === "positive"
|
||||
? "positive"
|
||||
: undefined;
|
||||
|
||||
return {
|
||||
id: typeof item.id === "string" ? item.id : undefined,
|
||||
title: typeof item.title === "string" ? item.title : "-",
|
||||
subtitle: typeof item.subtitle === "string" ? item.subtitle : "-",
|
||||
trendNumber:
|
||||
typeof item.trendNumber === "number" && Number.isFinite(item.trendNumber)
|
||||
? item.trendNumber
|
||||
: 0,
|
||||
trend,
|
||||
unit: typeof item.unit === "string" ? item.unit : "",
|
||||
};
|
||||
})
|
||||
: [];
|
||||
|
||||
return {
|
||||
sensor: data.sensor ?? undefined,
|
||||
sensors,
|
||||
};
|
||||
}
|
||||
|
||||
export const sensor7Service = {
|
||||
async getSummary(farmUuid: string): Promise<Sensor7SummaryData> {
|
||||
const response = await apiClient.get<ApiResponse<Sensor7SummaryData> | Sensor7SummaryData>(
|
||||
`${SUMMARY_PREFIX}/summary/?farm_uuid=${encodeURIComponent(farmUuid)}`,
|
||||
);
|
||||
|
||||
const data = extract(response) ?? {};
|
||||
|
||||
return {
|
||||
...data,
|
||||
sensorValuesList: normalizeValuesListResponse(data.sensorValuesList),
|
||||
sensorComparisonChart: normalizeComparisonChartResponse(data.sensorComparisonChart),
|
||||
sensorRadarChart: normalizeRadarChartResponse(data.sensorRadarChart),
|
||||
};
|
||||
},
|
||||
|
||||
async getComparisonChart(params: {
|
||||
farmUuid: string;
|
||||
range?: "7d" | "30d";
|
||||
}): Promise<SensorComparisonChartResponse> {
|
||||
const searchParams = new URLSearchParams({
|
||||
farm_uuid: params.farmUuid,
|
||||
range: params.range ?? "7d",
|
||||
});
|
||||
|
||||
const response = await apiClient.get<Partial<SensorComparisonChartResponse>>(
|
||||
`${SENSORS_PREFIX}/comparison-chart/?${searchParams.toString()}`,
|
||||
);
|
||||
|
||||
return normalizeComparisonChartResponse(response);
|
||||
},
|
||||
|
||||
async getRadarChart(params: {
|
||||
farmUuid: string;
|
||||
range?: "today" | "7d" | "30d";
|
||||
}): Promise<SensorRadarChartResponse> {
|
||||
const searchParams = new URLSearchParams({
|
||||
farm_uuid: params.farmUuid,
|
||||
range: params.range ?? "7d",
|
||||
});
|
||||
|
||||
const response = await apiClient.get<Partial<SensorRadarChartResponse>>(
|
||||
`${SENSORS_PREFIX}/radar-chart/?${searchParams.toString()}`,
|
||||
);
|
||||
|
||||
return normalizeRadarChartResponse(response);
|
||||
},
|
||||
|
||||
async getValuesList(params: {
|
||||
farmUuid: string;
|
||||
range?: "1h" | "24h" | "7d";
|
||||
}): Promise<SensorValuesListResponse> {
|
||||
const searchParams = new URLSearchParams({
|
||||
farm_uuid: params.farmUuid,
|
||||
range: params.range ?? "7d",
|
||||
});
|
||||
|
||||
const response = await apiClient.get<Partial<SensorValuesListResponse>>(
|
||||
`${SENSORS_PREFIX}/values-list/?${searchParams.toString()}`,
|
||||
);
|
||||
|
||||
return normalizeValuesListResponse(response);
|
||||
},
|
||||
};
|
||||
@@ -1,7 +1,3 @@
|
||||
import { apiClient } from "../client";
|
||||
|
||||
const PREFIX = "/api/sensor-external-api";
|
||||
|
||||
export interface SensorExternalFarmSensor {
|
||||
uuid: string;
|
||||
sensor_catalog_uuid: string | null;
|
||||
@@ -49,13 +45,66 @@ export interface SensorExternalRequestLogsResponse {
|
||||
data: SensorExternalRequestLog[];
|
||||
}
|
||||
|
||||
interface SensorExternalLogsParams {
|
||||
farmUuid: string;
|
||||
page?: number;
|
||||
pageSize?: number;
|
||||
physicalDeviceUuid?: string;
|
||||
sensorType?: string;
|
||||
dateFrom?: string;
|
||||
dateTo?: string;
|
||||
}
|
||||
|
||||
const getAuthHeaders = (): Record<string, string> => {
|
||||
if (typeof window === "undefined") {
|
||||
return {};
|
||||
}
|
||||
|
||||
const token = localStorage.getItem("auth_token");
|
||||
|
||||
return token ? { Authorization: `Bearer ${token}` } : {};
|
||||
};
|
||||
|
||||
async function fetchLocalJson<T>(url: string): Promise<T> {
|
||||
const response = await fetch(url, {
|
||||
method: "GET",
|
||||
headers: {
|
||||
Accept: "application/json",
|
||||
...getAuthHeaders(),
|
||||
},
|
||||
cache: "no-store",
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
let errorData: Record<string, unknown> = {};
|
||||
|
||||
try {
|
||||
errorData = (await response.json()) as Record<string, unknown>;
|
||||
} catch {
|
||||
errorData = { message: response.statusText };
|
||||
}
|
||||
|
||||
const message =
|
||||
(typeof errorData.msg === "string" && errorData.msg) ||
|
||||
(typeof errorData.detail === "string" && errorData.detail) ||
|
||||
(typeof errorData.message === "string" && errorData.message) ||
|
||||
"An error occurred";
|
||||
|
||||
throw {
|
||||
message,
|
||||
code: response.status,
|
||||
details: errorData,
|
||||
};
|
||||
}
|
||||
|
||||
return response.json() as Promise<T>;
|
||||
}
|
||||
|
||||
export const sensorExternalApiService = {
|
||||
listRequestLogs(params: {
|
||||
farmUuid: string;
|
||||
page?: number;
|
||||
pageSize?: number;
|
||||
}): Promise<SensorExternalRequestLogsResponse> {
|
||||
const searchParams = new URLSearchParams({ farm_uuid: params.farmUuid });
|
||||
listRequestLogs(params: SensorExternalLogsParams): Promise<SensorExternalRequestLogsResponse> {
|
||||
const searchParams = new URLSearchParams({
|
||||
farm_uuid: params.farmUuid,
|
||||
});
|
||||
|
||||
if (typeof params.page === "number") {
|
||||
searchParams.set("page", String(params.page));
|
||||
@@ -65,8 +114,24 @@ export const sensorExternalApiService = {
|
||||
searchParams.set("page_size", String(params.pageSize));
|
||||
}
|
||||
|
||||
return apiClient.get<SensorExternalRequestLogsResponse>(
|
||||
`${PREFIX}/logs/?${searchParams.toString()}`,
|
||||
if (params.physicalDeviceUuid) {
|
||||
searchParams.set("physical_device_uuid", params.physicalDeviceUuid);
|
||||
}
|
||||
|
||||
if (params.sensorType) {
|
||||
searchParams.set("sensor_type", params.sensorType);
|
||||
}
|
||||
|
||||
if (params.dateFrom) {
|
||||
searchParams.set("date_from", params.dateFrom);
|
||||
}
|
||||
|
||||
if (params.dateTo) {
|
||||
searchParams.set("date_to", params.dateTo);
|
||||
}
|
||||
|
||||
return fetchLocalJson<SensorExternalRequestLogsResponse>(
|
||||
`/api/sensor-external-api/logs/?${searchParams.toString()}`,
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user