This commit is contained in:
2026-04-29 01:27:40 +03:30
parent 2ac51fe082
commit 5c548bc6db
4 changed files with 703 additions and 373 deletions
+237
View File
@@ -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()}`,
);
},
};