dashboardService.ts 6.38 KB
import { createApiClient } from "../lib/apiClient";
import type {
  DashboardCategorySliceDto,
  DashboardMetricCardDto,
  DashboardOverviewDto,
  DashboardRecentLabelItemDto,
  WeeklyPrintVolumePointDto,
} from "../types/dashboardOverview";

const api = createApiClient({
  getToken: () => {
    try {
      return localStorage.getItem("access_token") ?? localStorage.getItem("token") ?? null;
    } catch {
      return null;
    }
  },
});

const PATH = "/dashboard/overview";

function num(x: unknown, fallback = 0): number {
  const n = typeof x === "number" ? x : Number(x);
  return Number.isFinite(n) ? n : fallback;
}

function normMetric(raw: unknown): DashboardMetricCardDto {
  if (!raw || typeof raw !== "object") {
    return { key: "", title: "", value: 0, previousValue: 0, changeValue: 0, changeRate: 0 };
  }
  const o = raw as Record<string, unknown>;
  return {
    key: String(o.key ?? o.Key ?? ""),
    title: String(o.title ?? o.Title ?? ""),
    value: num(o.value ?? o.Value, 0),
    previousValue: num(o.previousValue ?? o.PreviousValue, 0),
    changeValue: num(o.changeValue ?? o.ChangeValue, 0),
    changeRate: num(o.changeRate ?? o.ChangeRate, 0),
  };
}

function normWeeklyPoint(raw: unknown): WeeklyPrintVolumePointDto | null {
  if (!raw || typeof raw !== "object") return null;
  const o = raw as Record<string, unknown>;
  const date = String(o.date ?? o.Date ?? "");
  if (!date) return null;
  return { date, value: num(o.value ?? o.Value, 0) };
}

function normCategorySlice(raw: unknown): DashboardCategorySliceDto | null {
  if (!raw || typeof raw !== "object") return null;
  const o = raw as Record<string, unknown>;
  const categoryId = String(o.categoryId ?? o.CategoryId ?? "");
  const categoryName = String(o.categoryName ?? o.CategoryName ?? "");
  return {
    categoryId,
    categoryName,
    count: num(o.count ?? o.Count, 0),
    ratio: num(o.ratio ?? o.Ratio, 0),
  };
}

function str(o: Record<string, unknown>, camel: string, pascal: string, fallback = ""): string {
  const v = o[camel] ?? o[pascal];
  if (v === null || v === undefined) return fallback;
  return String(v);
}

function normRecentLabel(raw: unknown): DashboardRecentLabelItemDto | null {
  if (!raw || typeof raw !== "object") return null;
  const o = raw as Record<string, unknown>;
  const taskId = str(o, "taskId", "TaskId", "");
  const labelCode = str(o, "labelCode", "LabelCode", "");
  const displayName = str(o, "displayName", "DisplayName", "");
  const printedAt = str(o, "printedAt", "PrintedAt", "");
  const status = str(o, "status", "Status", "active");
  const labelTypeBadge = str(o, "labelTypeBadge", "LabelTypeBadge", "");
  const printedBy = str(o, "printedByName", "PrintedByName", "");
  const pbUid = o.printedByUserId ?? o.PrintedByUserId;
  const printedByUserId =
    pbUid === null || pbUid === undefined ? null : String(pbUid);
  if (!taskId && !labelCode) return null;
  return {
    taskId: taskId || labelCode,
    labelCode: labelCode || "—",
    displayName: displayName || "—",
    printedByUserId,
    printedByName: printedBy || "—",
    printedAt: printedAt,
    status: status || "active",
    labelTypeBadge: labelTypeBadge || "—",
  };
}

function normalizeOverview(raw: unknown): DashboardOverviewDto {
  if (!raw || typeof raw !== "object") {
    return emptyOverview();
  }
  const o = raw as Record<string, unknown>;

  const weeklyRaw = (o.weeklyPrintVolume ?? o.WeeklyPrintVolume) as unknown;
  const weeklyArr = Array.isArray(weeklyRaw) ? weeklyRaw : [];

  const byCat = (o.byCategory ?? o.ByCategory) as unknown;
  const legacyCat = (o.categoryDistribution ?? o.CategoryDistribution) as unknown;
  const catArr = Array.isArray(byCat) && byCat.length ? byCat : Array.isArray(legacyCat) ? legacyCat : [];

  const totalRaw =
    o.byCategoryTotal ??
    o.ByCategoryTotal ??
    o.categoryDistributionTotal ??
    o.CategoryDistributionTotal;
  const total = num(totalRaw, 0);

  const recentRaw = (o.recentLabels ?? o.RecentLabels) as unknown;
  const recentArr = Array.isArray(recentRaw) ? recentRaw : [];

  return {
    labelsPrintedToday: normMetric(o.labelsPrintedToday ?? o.LabelsPrintedToday),
    activeTemplates: normMetric(o.activeTemplates ?? o.ActiveTemplates),
    activeUsers: normMetric(o.activeUsers ?? o.ActiveUsers),
    locations: normMetric(o.locations ?? o.Locations),
    people: normMetric(o.people ?? o.People),
    products: normMetric(o.products ?? o.Products),
    weeklyPrintVolume: weeklyArr
      .map(normWeeklyPoint)
      .filter((x): x is WeeklyPrintVolumePointDto => x !== null),
    byCategory: catArr
      .map(normCategorySlice)
      .filter((x): x is DashboardCategorySliceDto => x !== null),
    byCategoryTotal: Number.isFinite(total) ? total : 0,
    recentLabels: recentArr
      .map(normRecentLabel)
      .filter((x): x is DashboardRecentLabelItemDto => x !== null),
    generatedAt: (o.generatedAt ?? o.GeneratedAt ?? null) as string | null,
    metricCards: Array.isArray(o.metricCards ?? o.MetricCards)
      ? (o.metricCards ?? o.MetricCards)!.map(normMetric)
      : undefined,
    categoryDistribution: Array.isArray(o.categoryDistribution ?? o.CategoryDistribution)
      ? (o.categoryDistribution ?? o.CategoryDistribution)!.map(normCategorySlice).filter(Boolean) as DashboardCategorySliceDto[]
      : undefined,
    categoryDistributionTotal: num(o.categoryDistributionTotal ?? o.CategoryDistributionTotal, 0),
  };
}

function emptyMetric(key: string, title: string): DashboardMetricCardDto {
  return { key, title, value: 0, previousValue: 0, changeValue: 0, changeRate: 0 };
}

function emptyOverview(): DashboardOverviewDto {
  return {
    labelsPrintedToday: emptyMetric("labelsPrintedToday", "Labels Printed Today"),
    activeTemplates: emptyMetric("activeTemplates", "Active Templates"),
    activeUsers: emptyMetric("activeUsers", "Active Users"),
    locations: emptyMetric("locations", "Locations"),
    people: emptyMetric("people", "People"),
    products: emptyMetric("products", "Products"),
    weeklyPrintVolume: [],
    byCategory: [],
    byCategoryTotal: 0,
    recentLabels: [],
    generatedAt: null,
  };
}

/**
 * Dashboard 首页统计(文档:`Dashboard统计接口对接说明.md`)
 */
export async function getDashboardOverview(signal?: AbortSignal): Promise<DashboardOverviewDto> {
  const raw = await api.requestJson<unknown>({
    path: PATH,
    method: "GET",
    signal,
  });
  return normalizeOverview(raw);
}

export type { DashboardOverviewDto };