mirror of
https://github.com/R0m1k3/CollectFlow.git
synced 2026-10-11 17:26:32 +02:00
feat: implement dashboard page and add loading skeletons for all dashboard modules
This commit is contained in:
1 parent
b39e010903
commit
a4debe992b
8 files changed
+158
-2
No files matched your search
@@ -27,7 +27,9 @@
|
|||||||
"Bash(curl -i -s \"https://api.ffnancy.fr/api/performance/ca/nomenclature?dateDebut=2025-03-01&dateFin=2025-03-31&site=&niveau=1\")",
|
"Bash(curl -i -s \"https://api.ffnancy.fr/api/performance/ca/nomenclature?dateDebut=2025-03-01&dateFin=2025-03-31&site=&niveau=1\")",
|
||||||
"Bash(curl -i -s \"https://api.ffnancy.fr/api/performance/ca?dateDebut=2025-03-01&dateFin=2025-03-31&groupBy=mois\")",
|
"Bash(curl -i -s \"https://api.ffnancy.fr/api/performance/ca?dateDebut=2025-03-01&dateFin=2025-03-31&groupBy=mois\")",
|
||||||
"WebFetch(domain:collect.ffnancy.fr)",
|
"WebFetch(domain:collect.ffnancy.fr)",
|
||||||
"Bash(curl -s \"https://collect.ffnancy.fr/api/diag-mvtart\")"
|
"Bash(curl -s \"https://collect.ffnancy.fr/api/diag-mvtart\")",
|
||||||
|
"Read(//c/Users/Michael/VSCODE/CollectFlow/**)",
|
||||||
|
"Bash(xargs wc:*)"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
export default function AnalyticsLoading() {
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col gap-6 animate-pulse">
|
||||||
|
{/* Header */}
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<div className="h-7 w-40 rounded-lg bg-[var(--bg-elevated)]" />
|
||||||
|
<div className="flex gap-2">
|
||||||
|
<div className="h-9 w-32 rounded-lg bg-[var(--bg-elevated)]" />
|
||||||
|
<div className="h-9 w-32 rounded-lg bg-[var(--bg-elevated)]" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Filter bar */}
|
||||||
|
<div className="h-12 rounded-xl bg-[var(--bg-elevated)] border border-[var(--border)]" />
|
||||||
|
|
||||||
|
{/* Table skeleton */}
|
||||||
|
<div className="rounded-2xl border border-[var(--border)] overflow-hidden bg-[var(--bg-elevated)]">
|
||||||
|
<div className="h-10 bg-[var(--bg-base)] border-b border-[var(--border)]" />
|
||||||
|
{Array.from({ length: 8 }).map((_, i) => (
|
||||||
|
<div key={i} className="h-10 border-b border-[var(--border)] flex items-center px-4 gap-4">
|
||||||
|
<div className="h-3 w-32 rounded bg-[var(--border)]" />
|
||||||
|
<div className="h-3 w-20 rounded bg-[var(--border)]" />
|
||||||
|
<div className="h-3 w-20 rounded bg-[var(--border)]" />
|
||||||
|
<div className="h-3 w-16 rounded bg-[var(--border)]" />
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
export default function DashboardLoading() {
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col gap-6 animate-pulse">
|
||||||
|
{/* Header skeleton */}
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<div className="h-7 w-48 rounded-lg bg-[var(--bg-elevated)]" />
|
||||||
|
<div className="h-5 w-32 rounded-lg bg-[var(--bg-elevated)]" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* KPI cards row */}
|
||||||
|
<div className="grid grid-cols-2 md:grid-cols-4 gap-4">
|
||||||
|
{Array.from({ length: 4 }).map((_, i) => (
|
||||||
|
<div key={i} className="h-28 rounded-2xl bg-[var(--bg-elevated)] border border-[var(--border)]" />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Main content area */}
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||||
|
<div className="h-64 rounded-2xl bg-[var(--bg-elevated)] border border-[var(--border)]" />
|
||||||
|
<div className="h-64 rounded-2xl bg-[var(--bg-elevated)] border border-[var(--border)]" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Bottom tables */}
|
||||||
|
<div className="h-80 rounded-2xl bg-[var(--bg-elevated)] border border-[var(--border)]" />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,4 +1,11 @@
|
|||||||
import { pgGetDashboardData, type DashboardTopItem, type DashboardSiteStats, type DashboardSiteTop10 } from "@/lib/pg-ff-client";
|
import { pgGetDashboardData, type DashboardTopItem, type DashboardSiteStats, type DashboardSiteTop10 } from "@/lib/pg-ff-client";
|
||||||
|
import { unstable_cache } from "next/cache";
|
||||||
|
|
||||||
|
const getCachedDashboardData = unstable_cache(
|
||||||
|
pgGetDashboardData,
|
||||||
|
["dashboard-data"],
|
||||||
|
{ revalidate: 600 } // 10 minutes
|
||||||
|
);
|
||||||
import { TrendingUp, TrendingDown, Minus, Store, Euro, Users, ShoppingCart, Award, BarChart3 } from "lucide-react";
|
import { TrendingUp, TrendingDown, Minus, Store, Euro, Users, ShoppingCart, Award, BarChart3 } from "lucide-react";
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
@@ -722,7 +729,7 @@ function SectionHeader({ label, sub }: { label: string; sub?: string }) {
|
|||||||
export default async function DashboardPage() {
|
export default async function DashboardPage() {
|
||||||
let data;
|
let data;
|
||||||
try {
|
try {
|
||||||
data = await pgGetDashboardData();
|
data = await getCachedDashboardData();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
const msg = e instanceof Error ? e.message : String(e);
|
const msg = e instanceof Error ? e.message : String(e);
|
||||||
console.error("[Dashboard] pgGetDashboardData failed:", e);
|
console.error("[Dashboard] pgGetDashboardData failed:", e);
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
export default function ExportsLoading() {
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col gap-6 animate-pulse">
|
||||||
|
{/* Header */}
|
||||||
|
<div className="h-7 w-56 rounded-lg bg-[var(--bg-elevated)]" />
|
||||||
|
|
||||||
|
{/* Table */}
|
||||||
|
<div className="rounded-2xl border border-[var(--border)] overflow-hidden bg-[var(--bg-elevated)]">
|
||||||
|
<div className="h-10 bg-[var(--bg-base)] border-b border-[var(--border)]" />
|
||||||
|
{Array.from({ length: 8 }).map((_, i) => (
|
||||||
|
<div key={i} className="h-10 border-b border-[var(--border)] flex items-center px-4 gap-4">
|
||||||
|
<div className="h-3 w-24 rounded bg-[var(--border)]" />
|
||||||
|
<div className="h-3 w-32 rounded bg-[var(--border)]" />
|
||||||
|
<div className="h-3 w-20 rounded bg-[var(--border)]" />
|
||||||
|
<div className="h-3 w-16 rounded bg-[var(--border)]" />
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
export default function HitParadeLoading() {
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col gap-6 animate-pulse">
|
||||||
|
{/* Header */}
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<div className="h-7 w-44 rounded-lg bg-[var(--bg-elevated)]" />
|
||||||
|
<div className="flex gap-2">
|
||||||
|
<div className="h-9 w-28 rounded-lg bg-[var(--bg-elevated)]" />
|
||||||
|
<div className="h-9 w-28 rounded-lg bg-[var(--bg-elevated)]" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Podium / top cards */}
|
||||||
|
<div className="grid grid-cols-3 gap-4">
|
||||||
|
{Array.from({ length: 3 }).map((_, i) => (
|
||||||
|
<div key={i} className="h-32 rounded-2xl bg-[var(--bg-elevated)] border border-[var(--border)]" />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Rankings table */}
|
||||||
|
<div className="rounded-2xl border border-[var(--border)] overflow-hidden bg-[var(--bg-elevated)]">
|
||||||
|
<div className="h-10 bg-[var(--bg-base)] border-b border-[var(--border)]" />
|
||||||
|
{Array.from({ length: 10 }).map((_, i) => (
|
||||||
|
<div key={i} className="h-10 border-b border-[var(--border)] flex items-center px-4 gap-4">
|
||||||
|
<div className="h-3 w-6 rounded bg-[var(--border)]" />
|
||||||
|
<div className="h-3 w-40 rounded bg-[var(--border)]" />
|
||||||
|
<div className="h-3 w-24 rounded bg-[var(--border)]" />
|
||||||
|
<div className="h-3 w-16 rounded bg-[var(--border)]" />
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
export default function SettingsLoading() {
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col gap-6 animate-pulse max-w-2xl">
|
||||||
|
{/* Header */}
|
||||||
|
<div className="h-7 w-40 rounded-lg bg-[var(--bg-elevated)]" />
|
||||||
|
|
||||||
|
{/* Settings sections */}
|
||||||
|
{Array.from({ length: 3 }).map((_, i) => (
|
||||||
|
<div key={i} className="rounded-2xl border border-[var(--border)] overflow-hidden bg-[var(--bg-elevated)] p-6 flex flex-col gap-4">
|
||||||
|
<div className="h-5 w-36 rounded bg-[var(--border)]" />
|
||||||
|
<div className="h-10 rounded-lg bg-[var(--bg-base)]" />
|
||||||
|
<div className="h-10 rounded-lg bg-[var(--bg-base)]" />
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
export default function SnapshotsLoading() {
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col gap-6 animate-pulse">
|
||||||
|
{/* Header */}
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<div className="h-7 w-36 rounded-lg bg-[var(--bg-elevated)]" />
|
||||||
|
<div className="h-9 w-36 rounded-lg bg-[var(--bg-elevated)]" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Cards grid */}
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||||
|
{Array.from({ length: 6 }).map((_, i) => (
|
||||||
|
<div key={i} className="h-36 rounded-2xl bg-[var(--bg-elevated)] border border-[var(--border)]" />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
Reference in new issue
Block a user