| @@ -0,0 +1,90 @@ | |||||
| import { NEXT_PUBLIC_API_URL } from "@/config/api"; | |||||
| import { clientAuthFetch } from "@/app/utils/clientAuthFetch"; | |||||
| export const FEATURE_USAGE = { | |||||
| REPORT_MANAGEMENT: "REPORT_MANAGEMENT", | |||||
| TRUCK_ROUTING_SUMMARY: "TRUCK_ROUTING_SUMMARY", | |||||
| } as const; | |||||
| export const FEATURE_USAGE_ACTION = { | |||||
| PAGE_VIEW: "PAGE_VIEW", | |||||
| DOWNLOAD: "DOWNLOAD", | |||||
| PRINT: "PRINT", | |||||
| } as const; | |||||
| export type FeatureUsageSummaryRow = { | |||||
| userId: number; | |||||
| username: string; | |||||
| pageViews: number; | |||||
| downloads: number; | |||||
| prints: number; | |||||
| }; | |||||
| export type FeatureUsageSummaryPayload = { | |||||
| reportManagement: FeatureUsageSummaryRow[]; | |||||
| truckRoutingSummary: FeatureUsageSummaryRow[]; | |||||
| }; | |||||
| function toInt(v: unknown): number { | |||||
| if (v == null) return 0; | |||||
| if (typeof v === "number") return Number.isFinite(v) ? v : 0; | |||||
| const n = Number(v); | |||||
| return Number.isFinite(n) ? n : 0; | |||||
| } | |||||
| function normalizeRow(raw: Record<string, unknown>): FeatureUsageSummaryRow { | |||||
| return { | |||||
| userId: toInt(raw.userId ?? raw.USER_ID), | |||||
| username: String(raw.username ?? raw.USERNAME ?? ""), | |||||
| pageViews: toInt(raw.pageViews ?? raw.PAGEVIEWS), | |||||
| downloads: toInt(raw.downloads ?? raw.DOWNLOADS), | |||||
| prints: toInt(raw.prints ?? raw.PRINTS), | |||||
| }; | |||||
| } | |||||
| /** Fire-and-forget client log; failures are ignored (console only). */ | |||||
| export function logFeatureUsage( | |||||
| featureCode: string, | |||||
| actionType: string, | |||||
| detail?: string | null, | |||||
| ): void { | |||||
| void (async () => { | |||||
| try { | |||||
| const res = await clientAuthFetch(`${NEXT_PUBLIC_API_URL}/feature-usage/log`, { | |||||
| method: "POST", | |||||
| headers: { "Content-Type": "application/json" }, | |||||
| body: JSON.stringify({ | |||||
| featureCode, | |||||
| actionType, | |||||
| detail: detail == null || detail === "" ? null : detail, | |||||
| }), | |||||
| }); | |||||
| if (!res.ok) { | |||||
| console.warn("feature-usage log failed", res.status); | |||||
| } | |||||
| } catch (e) { | |||||
| console.warn("feature-usage log error", e); | |||||
| } | |||||
| })(); | |||||
| } | |||||
| export async function fetchFeatureUsageSummary(): Promise<FeatureUsageSummaryPayload> { | |||||
| const res = await clientAuthFetch(`${NEXT_PUBLIC_API_URL}/feature-usage/summary`, { | |||||
| method: "GET", | |||||
| headers: { Accept: "application/json" }, | |||||
| }); | |||||
| if (!res.ok) { | |||||
| throw new Error(`feature-usage summary ${res.status}`); | |||||
| } | |||||
| const body = (await res.json()) as { | |||||
| data?: { | |||||
| reportManagement?: Record<string, unknown>[]; | |||||
| truckRoutingSummary?: Record<string, unknown>[]; | |||||
| }; | |||||
| }; | |||||
| const d = body.data; | |||||
| return { | |||||
| reportManagement: (d?.reportManagement ?? []).map((r) => normalizeRow(r)), | |||||
| truckRoutingSummary: (d?.truckRoutingSummary ?? []).map((r) => normalizeRow(r)), | |||||
| }; | |||||
| } | |||||