UPDATE
This commit is contained in:
@@ -2,10 +2,13 @@
|
||||
* API Client for communicating with Backend via Envoy Gateway
|
||||
*/
|
||||
|
||||
const API_BASE_URL =
|
||||
process.env.NEXT_PUBLIC_API_URL ||
|
||||
process.env.ENVOY_GATEWAY_URL ||
|
||||
"http://node.crop-logic.ir";
|
||||
const resolveApiBaseUrl = (): string => {
|
||||
const publicApiUrl = process.env.NEXT_PUBLIC_API_URL;
|
||||
const serverApiUrl =
|
||||
typeof window === "undefined" ? process.env.ENVOY_GATEWAY_URL : undefined;
|
||||
|
||||
return publicApiUrl || serverApiUrl || "http://node.crop-logic.ir";
|
||||
};
|
||||
|
||||
const AUTH_STORAGE_KEYS = {
|
||||
accessToken: "auth_token",
|
||||
@@ -22,7 +25,7 @@ export class ApiClient {
|
||||
private baseURL: string;
|
||||
private defaultHeaders: Record<string, string>;
|
||||
|
||||
constructor(baseURL: string = API_BASE_URL) {
|
||||
constructor(baseURL: string = resolveApiBaseUrl()) {
|
||||
this.baseURL = baseURL.replace(/\/$/, ""); // Remove trailing slash
|
||||
this.defaultHeaders = {
|
||||
"Content-Type": "application/json",
|
||||
|
||||
@@ -16,9 +16,11 @@ export interface AuthUser {
|
||||
|
||||
export interface AuthTokens {
|
||||
access: string;
|
||||
refresh: string;
|
||||
refresh?: string;
|
||||
}
|
||||
|
||||
export type AuthTokenValue = string | AuthTokens;
|
||||
|
||||
export interface LoginRequest {
|
||||
identifier: string;
|
||||
password: string;
|
||||
@@ -37,7 +39,7 @@ export interface AuthResponse {
|
||||
code: number;
|
||||
msg: string;
|
||||
data: AuthUser;
|
||||
token: AuthTokens;
|
||||
token: AuthTokenValue;
|
||||
}
|
||||
|
||||
export interface RefreshTokenResponse {
|
||||
@@ -56,6 +58,24 @@ export interface UpdateProfileResponse {
|
||||
data: AuthUser;
|
||||
}
|
||||
|
||||
const storeAuthToken = (token: AuthTokenValue | null | undefined): void => {
|
||||
if (!token) return;
|
||||
|
||||
if (typeof token === "string") {
|
||||
apiClient.setAuthToken(token);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (token.access) {
|
||||
apiClient.setAuthToken(token.access);
|
||||
}
|
||||
|
||||
if (token.refresh) {
|
||||
apiClient.setRefreshToken(token.refresh);
|
||||
}
|
||||
};
|
||||
|
||||
export const authService = {
|
||||
/**
|
||||
* Login with username, email, or phone number
|
||||
@@ -66,9 +86,7 @@ export const authService = {
|
||||
payload,
|
||||
);
|
||||
|
||||
if (response.token?.access) {
|
||||
apiClient.setAuthTokens(response.token);
|
||||
}
|
||||
storeAuthToken(response.token);
|
||||
|
||||
return response;
|
||||
},
|
||||
@@ -82,9 +100,7 @@ export const authService = {
|
||||
payload,
|
||||
);
|
||||
|
||||
if (response.token?.access) {
|
||||
apiClient.setAuthTokens(response.token);
|
||||
}
|
||||
storeAuthToken(response.token);
|
||||
|
||||
return response;
|
||||
},
|
||||
|
||||
@@ -5,84 +5,166 @@
|
||||
* - Cards: all 15 card payloads from /api/farm-dashboard/
|
||||
*/
|
||||
|
||||
import { apiClient } from '../client'
|
||||
import type { FarmDashboardConfig } from '@/views/dashboards/farm/farmDashboardConfig'
|
||||
import type { CardId } from '@/views/dashboards/farm/farmDashboardConfig'
|
||||
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
|
||||
code: number;
|
||||
msg: string;
|
||||
data: T;
|
||||
}
|
||||
|
||||
export interface FarmDashboardConfigResponse {
|
||||
disabled_card_ids: string[]
|
||||
row_order: string[]
|
||||
enable_drag_reorder?: boolean
|
||||
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>
|
||||
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>;
|
||||
}
|
||||
|
||||
const STORAGE_KEY = 'farm_dashboard_config'
|
||||
interface FarmDashboardCardsTaskResult {
|
||||
sensor_id?: string;
|
||||
all_cards?: FarmDashboardCardsResponse;
|
||||
}
|
||||
|
||||
interface FarmDashboardCardsTaskData {
|
||||
task_id?: string;
|
||||
status?: string;
|
||||
result?: FarmDashboardCardsTaskResult;
|
||||
}
|
||||
|
||||
const STORAGE_KEY = "farm_dashboard_config";
|
||||
|
||||
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 {
|
||||
function fromApiResponse(
|
||||
data: FarmDashboardConfigResponse,
|
||||
): FarmDashboardConfig {
|
||||
return {
|
||||
disabledCardIds: data.disabled_card_ids ?? [],
|
||||
rowOrder: data.row_order ?? [],
|
||||
enableDragReorder: data.enable_drag_reorder ?? true
|
||||
}
|
||||
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
|
||||
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(): FarmDashboardConfig | null {
|
||||
if (typeof window === 'undefined') return null
|
||||
if (typeof window === "undefined") return null;
|
||||
try {
|
||||
const stored = localStorage.getItem(STORAGE_KEY)
|
||||
return stored ? (JSON.parse(stored) as FarmDashboardConfig) : null
|
||||
const stored = localStorage.getItem(STORAGE_KEY);
|
||||
return stored ? (JSON.parse(stored) as FarmDashboardConfig) : null;
|
||||
} catch {
|
||||
return null
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function setLocalConfig(config: FarmDashboardConfig): void {
|
||||
if (typeof window === 'undefined') return
|
||||
if (typeof window === "undefined") return;
|
||||
try {
|
||||
localStorage.setItem(STORAGE_KEY, JSON.stringify(config))
|
||||
localStorage.setItem(STORAGE_KEY, JSON.stringify(config));
|
||||
} catch (e) {
|
||||
console.error('Failed to save farm dashboard config to localStorage', e)
|
||||
console.error("Failed to save farm dashboard config to localStorage", e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -94,46 +176,57 @@ export const farmDashboardService = {
|
||||
try {
|
||||
const response = await apiClient.get<
|
||||
ApiResponse<FarmDashboardConfigResponse> | FarmDashboardConfigResponse
|
||||
>('/api/farm-dashboard-config')
|
||||
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)
|
||||
>("/api/farm-dashboard-config");
|
||||
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')
|
||||
throw new Error("Invalid response");
|
||||
} catch {
|
||||
const local = getLocalConfig()
|
||||
if (local) return local
|
||||
return { disabledCardIds: [], rowOrder: [], enableDragReorder: true }
|
||||
const local = getLocalConfig();
|
||||
if (local) return local;
|
||||
return { disabledCardIds: [], rowOrder: [], enableDragReorder: true };
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Update farm dashboard config
|
||||
*/
|
||||
async updateConfig(data: Partial<FarmDashboardConfig>): Promise<FarmDashboardConfig> {
|
||||
async updateConfig(
|
||||
data: Partial<FarmDashboardConfig>,
|
||||
): Promise<FarmDashboardConfig> {
|
||||
try {
|
||||
const response = await apiClient.patch<
|
||||
ApiResponse<FarmDashboardConfigResponse> | FarmDashboardConfigResponse
|
||||
>('/api/farm-dashboard-config', 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(config)
|
||||
return config
|
||||
>("/api/farm-dashboard-config", 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(config);
|
||||
return config;
|
||||
}
|
||||
throw new Error('Update failed')
|
||||
throw new Error("Update failed");
|
||||
} catch (err) {
|
||||
const local = getLocalConfig()
|
||||
const local = getLocalConfig();
|
||||
if (local) {
|
||||
const merged: FarmDashboardConfig = {
|
||||
disabledCardIds: data.disabledCardIds ?? local.disabledCardIds,
|
||||
rowOrder: data.rowOrder ?? local.rowOrder,
|
||||
enableDragReorder: data.enableDragReorder ?? local.enableDragReorder ?? true
|
||||
}
|
||||
setLocalConfig(merged)
|
||||
return merged
|
||||
enableDragReorder:
|
||||
data.enableDragReorder ?? local.enableDragReorder ?? true,
|
||||
};
|
||||
setLocalConfig(merged);
|
||||
return merged;
|
||||
}
|
||||
throw err
|
||||
throw err;
|
||||
}
|
||||
},
|
||||
|
||||
@@ -141,16 +234,19 @@ export const farmDashboardService = {
|
||||
* Get all dashboard card data from API
|
||||
* Response: { code: 200, msg: "OK", data: { farmOverviewKpis, farmWeatherCard, ... } }
|
||||
*/
|
||||
async getAllCards(): Promise<Partial<Record<CardId, Record<string, unknown>>>> {
|
||||
async getAllCards(): Promise<
|
||||
Partial<Record<CardId, Record<string, unknown>>>
|
||||
> {
|
||||
try {
|
||||
const response = await apiClient.get<ApiResponse<FarmDashboardCardsResponse>>('/api/farm-dashboard/')
|
||||
const raw = response?.data ?? response
|
||||
if (raw && typeof raw === 'object') {
|
||||
return raw as Partial<Record<CardId, Record<string, unknown>>>
|
||||
}
|
||||
return {}
|
||||
const response = await apiClient.get<
|
||||
| ApiResponse<FarmDashboardCardsResponse>
|
||||
| ApiResponse<FarmDashboardCardsTaskData>
|
||||
| FarmDashboardCardsResponse
|
||||
| FarmDashboardCardsTaskData
|
||||
>("/api/farm-dashboard/");
|
||||
return extractCardsPayload(response);
|
||||
} catch {
|
||||
return {}
|
||||
return {};
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user