diff --git a/src/Components/Dashboard/KpiSection.tsx b/src/Components/Dashboard/KpiSection.tsx
index 0de6a26..0b4db0e 100644
--- a/src/Components/Dashboard/KpiSection.tsx
+++ b/src/Components/Dashboard/KpiSection.tsx
@@ -29,26 +29,18 @@ function KpiAnomalyBadge({ anomaly }: { anomaly: EntityKpi["anomaly"] }) {
);
}
-function KpiCardCounts({ total, active, pending }: { total: number; active: number; pending: number }) {
+// Step 8: KpiCardCounts no longer takes/renders `active`/`pending` — it now
+
+// only receives and displays `total`. The active/pending mini-boxes (and
+// their inline styling) have been removed entirely; the total figure keeps
+// its original styling/position exactly as before, so the card's overall
+// layout is unchanged.
+function KpiCardCounts({ total }: { total: number }) {
return (
-
+ {/* Step 9: only `total` is passed now — `active`/`pending` props removed
+ from this call site along with the corresponding fields on EntityKpi. */}
+
);
@@ -101,6 +96,7 @@ interface KpiSectionProps {
export function KpiSection({ entities, loading }: KpiSectionProps) {
// Before the overview resolves, render the static config as zeroed cards
// so the grid never collapses to nothing.
+
const list = entities.length > 0 ? entities : ENTITY_KPI_CONFIG.map((cfg) => ({ ...cfg, ...EMPTY_ENTITY_KPI }));
return (
diff --git a/src/lib/locationHierarchy.ts b/src/lib/locationHierarchy.ts
index c635eb8..01b7588 100644
--- a/src/lib/locationHierarchy.ts
+++ b/src/lib/locationHierarchy.ts
@@ -1,5 +1,3 @@
-// src/lib/locationHierarchy.ts
-//
// Step 1: Pure lookup helpers over SAUDI_LOCATION_HIERARCHY. No side
// effects, no fetch — the dataset is static and imported directly, so
// these functions are safe to call from both client components and the
diff --git a/src/services/audit.service.ts b/src/services/audit.service.ts
new file mode 100644
index 0000000..d12a894
--- /dev/null
+++ b/src/services/audit.service.ts
@@ -0,0 +1,35 @@
+import { get } from "./api";
+import type { AuditLog } from "@/src/types/audit";
+
+// Step 1: New service, following the exact same shape/pattern as the other
+// list services in this file's siblings (userService.getAll, branchService.
+// getAll, roleService.getAll) — GET with page/limit, unwrap { data: { data,
+// meta|pagination } } into a clean { items, total } result. No audit service
+// previously existed even though src/types/audit.ts and AuditTable already
+// consume AuditLog, so this fills that gap using the established contract.
+export interface AuditListResponse {
+ success: boolean;
+ message: string;
+ responseAt: string;
+ data: {
+ data: AuditLog[];
+ meta?: { total: number; page: number; limit: number; totalPages: number };
+ pagination?: { total: number; page: number; pages: number };
+ };
+}
+
+function buildAuditQuery(page: number, limit: number): string {
+ return `?page=${page}&limit=${limit}`;
+}
+
+export const auditService = {
+ // The real backend endpoint is /api/v1/audit and it responds with
+ // { data: { data: [...], meta: { total, page, limit, totalPages } } }.
+ getAll: async (page = 1, limit = 1): Promise<{ items: AuditLog[]; total: number }> => {
+ const res = await get
(`audit${buildAuditQuery(page, limit)}`);
+ return {
+ items: res.data?.data ?? [],
+ total: res.data?.meta?.total ?? res.data?.pagination?.total ?? 0,
+ };
+ },
+};
\ No newline at end of file
diff --git a/src/services/dashboard.service.ts b/src/services/dashboard.service.ts
index d45f05b..c1275ca 100644
--- a/src/services/dashboard.service.ts
+++ b/src/services/dashboard.service.ts
@@ -6,17 +6,35 @@ import type {
DashboardAlert,
} from "@/src/types/dashboard";
import { ENTITY_KPI_CONFIG, EMPTY_ENTITY_KPI } from "@/src/types/dashboard";
+// Step 3: Reuse the exact same services already used by UserTable,
+// BranchTable, and RoleTable (via useUsers/useBranches/useRoles) instead of
+// writing new fetch logic — this keeps the dashboard's data-fetching
+// pattern identical to the rest of the app, per the task instructions.
+import { userService } from "./user.service";
+import { branchService } from "./branch.service";
+import { roleService } from "./role.service";
+import { auditService } from "./audit.service";
-// Maps the stats we DO get from /dashboard/summary onto the entity cards.
-// Entities with no matching stat (users, branches, roles, audit) stay at 0
-// until the real /dashboard/overview endpoint exists.
-function buildEntities(stats: DashboardSummaryResponse["data"]["stats"]): EntityKpi[] {
+// Step 4: buildEntities now takes a second argument with the totals that
+// don't come from /dashboard/summary (users, branches, roles, audit) and
+// merges them into the same totals map the clients/orders/trips/cars/
+// drivers stats already flow through — no change to the entity list shape
+// or ordering (ENTITY_KPI_CONFIG is untouched), so layout stays identical.
+function buildEntities(
+ stats: DashboardSummaryResponse["data"]["stats"],
+ extraTotals: { users: number; branches: number; roles: number; audit: number },
+): EntityKpi[] {
const totals: Partial> = {
clients: stats.clients,
orders: stats.orders,
trips: stats.trips,
cars: stats.cars,
drivers: stats.drivers,
+
+ users: extraTotals.users,
+ branches: extraTotals.branches,
+ roles: extraTotals.roles,
+ audit: extraTotals.audit,
};
return ENTITY_KPI_CONFIG.map((cfg) => ({
@@ -45,6 +63,7 @@ function buildAlerts(alerts: DashboardSummaryResponse["data"]["alerts"]): Dashbo
createdAt: now,
}));
+
return [
...mapGroup(alerts.expiringCars, "cars", "warning", "expiring-car"),
...mapGroup(alerts.expiringDrivers, "drivers", "warning", "expiring-driver"),
@@ -57,15 +76,37 @@ export const dashboardService = {
get("dashboard/summary", token),
// NOTE: /dashboard/overview isn't exposed by the backend yet, so this
- // composes the DashboardOverview shape client-side from /dashboard/summary,
- // same "additive, not destructive" pattern used elsewhere (see
+ // composes the DashboardOverview shape client-side from /dashboard/summary
+ // plus the users/branches/roles/audit totals fetched below, same
+ // "additive, not destructive" pattern used elsewhere (see
// useArchivedCars/useArchivedRoles) while the real route isn't live.
getOverview: async (token: string | null): Promise => {
const res = await get("dashboard/summary", token ?? "");
const { stats, alerts, activeTrips, accountSecurity } = res.data;
+ // Step 5: Fetch the four missing totals in parallel (mirrors the
+ // Promise.all-free-but-concurrent pattern already used in
+ // OrderFormModal, which loads clients + trips together for the same
+ // modal mount). page=1 + empty search is exactly what UserTable /
+ // BranchTable / RoleTable already send on first load — we just read
+ // back `.total` and discard the `.items` page since the KPI card only
+ // needs the count. Each call is defensively caught so one failing
+ // endpoint doesn't blank out the other three cards.
+ const [usersResult, branchesResult, rolesResult, auditResult] = await Promise.all([
+ userService.getAll(1, "", token).catch(() => ({ items: [], total: 0, pages: 1 })),
+ branchService.getAll(1, "", token).catch(() => ({ items: [], total: 0, pages: 1 })),
+ roleService.getAll(1, "", token).catch(() => ({ items: [], total: 0, pages: 1 })),
+
+ auditService.getAll(1, 1).catch(() => ({ items: [], total: 0 })),
+ ]);
+
return {
- entities: buildEntities(stats),
+ entities: buildEntities(stats, {
+ users: usersResult.total,
+ branches: branchesResult.total,
+ roles: rolesResult.total,
+ audit: auditResult.total,
+ }),
alerts: buildAlerts(alerts),
trends: [], // no trend endpoint yet
recentActivity: [], // no audit-log endpoint composed yet
diff --git a/src/types/dashboard.ts b/src/types/dashboard.ts
index 4706e85..181d908 100644
--- a/src/types/dashboard.ts
+++ b/src/types/dashboard.ts
@@ -90,6 +90,10 @@ export type DashboardEntityKey =
| "users" | "drivers" | "cars" | "trips" | "orders"
| "clients" | "branches" | "roles" | "audit";
+// Step 6: Removed `active` and `pending` from EntityKpi — the stat cards
+// (KpiSection.tsx) no longer break totals down into active/pending, they
+// only display the total count, so the type no longer carries fields that
+// are never rendered.
export interface EntityKpi {
key: DashboardEntityKey;
label: string;
@@ -99,18 +103,16 @@ export interface EntityKpi {
accent: string;
href: string;
total: number;
- active: number;
- pending: number;
anomaly: {
severity: "warning" | "critical";
message: string;
} | null;
}
-export const EMPTY_ENTITY_KPI: Pick = {
+// Step 7: EMPTY_ENTITY_KPI narrowed to match the trimmed EntityKpi shape —
+// only `total` and `anomaly` are defaultable now.
+export const EMPTY_ENTITY_KPI: Pick = {
total: 0,
- active: 0,
- pending: 0,
anomaly: null,
};