Update loading states
This commit is contained in:
@@ -62,43 +62,73 @@ import {
|
|||||||
|
|
||||||
// Mini skeleton components
|
// Mini skeleton components
|
||||||
const MiniSkeletonChart = ({ type = "line" }) => (
|
const MiniSkeletonChart = ({ type = "line" }) => (
|
||||||
<div className="h-[200px] w-full bg-white dark:bg-gray-900/60 backdrop-blur-sm rounded-lg p-4">
|
<div className={`h-[230px] w-full ${
|
||||||
|
type === 'revenue' ? 'bg-emerald-50/10' :
|
||||||
|
type === 'orders' ? 'bg-blue-50/10' :
|
||||||
|
type === 'average_order' ? 'bg-violet-50/10' :
|
||||||
|
'bg-orange-50/10'
|
||||||
|
} rounded-lg p-4`}>
|
||||||
<div className="h-full relative">
|
<div className="h-full relative">
|
||||||
{/* Grid lines */}
|
{/* Grid lines */}
|
||||||
{[...Array(5)].map((_, i) => (
|
{[...Array(5)].map((_, i) => (
|
||||||
<div
|
<div
|
||||||
key={i}
|
key={i}
|
||||||
className="absolute w-full h-px bg-muted"
|
className={`absolute w-full h-px ${
|
||||||
|
type === 'revenue' ? 'bg-emerald-200/20' :
|
||||||
|
type === 'orders' ? 'bg-blue-200/20' :
|
||||||
|
type === 'average_order' ? 'bg-violet-200/20' :
|
||||||
|
'bg-orange-200/20'
|
||||||
|
}`}
|
||||||
style={{ top: `${(i + 1) * 20}%` }}
|
style={{ top: `${(i + 1) * 20}%` }}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
{/* Y-axis labels */}
|
{/* Y-axis labels */}
|
||||||
<div className="absolute left-0 top-0 bottom-0 w-6 flex flex-col justify-between py-2">
|
<div className="absolute left-0 top-0 bottom-0 w-8 flex flex-col justify-between py-4">
|
||||||
{[...Array(5)].map((_, i) => (
|
{[...Array(5)].map((_, i) => (
|
||||||
<Skeleton key={i} className="h-2 w-4 bg-muted rounded-sm" />
|
<Skeleton key={i} className={`h-3 w-6 ${
|
||||||
|
type === 'revenue' ? 'bg-emerald-200/20' :
|
||||||
|
type === 'orders' ? 'bg-blue-200/20' :
|
||||||
|
type === 'average_order' ? 'bg-violet-200/20' :
|
||||||
|
'bg-orange-200/20'
|
||||||
|
} rounded-sm`} />
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
{/* X-axis labels */}
|
{/* X-axis labels */}
|
||||||
<div className="absolute left-6 right-2 bottom-0 flex justify-between">
|
<div className="absolute left-8 right-4 bottom-0 flex justify-between">
|
||||||
{[...Array(6)].map((_, i) => (
|
{[...Array(6)].map((_, i) => (
|
||||||
<Skeleton key={i} className="h-2 w-6 bg-muted rounded-sm" />
|
<Skeleton key={i} className={`h-3 w-8 ${
|
||||||
|
type === 'revenue' ? 'bg-emerald-200/20' :
|
||||||
|
type === 'orders' ? 'bg-blue-200/20' :
|
||||||
|
type === 'average_order' ? 'bg-violet-200/20' :
|
||||||
|
'bg-orange-200/20'
|
||||||
|
} rounded-sm`} />
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
{type === "bar" ? (
|
{type === "bar" ? (
|
||||||
<div className="absolute inset-x-6 bottom-4 top-2 flex items-end justify-between gap-1">
|
<div className="absolute inset-x-8 bottom-6 top-4 flex items-end justify-between gap-1">
|
||||||
{[...Array(24)].map((_, i) => (
|
{[...Array(24)].map((_, i) => (
|
||||||
<div
|
<div
|
||||||
key={i}
|
key={i}
|
||||||
className="w-1 bg-muted rounded-sm"
|
className={`w-2 ${
|
||||||
|
type === 'revenue' ? 'bg-emerald-200/20' :
|
||||||
|
type === 'orders' ? 'bg-blue-200/20' :
|
||||||
|
type === 'average_order' ? 'bg-violet-200/20' :
|
||||||
|
'bg-orange-200/20'
|
||||||
|
} rounded-sm`}
|
||||||
style={{ height: `${Math.random() * 80 + 10}%` }}
|
style={{ height: `${Math.random() * 80 + 10}%` }}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="absolute inset-x-6 bottom-4 top-2">
|
<div className="absolute inset-x-8 bottom-6 top-4">
|
||||||
<div className="h-full w-full relative">
|
<div className="h-full w-full relative">
|
||||||
<div
|
<div
|
||||||
className="absolute inset-0 bg-muted rounded-sm"
|
className={`absolute inset-0 ${
|
||||||
|
type === 'revenue' ? 'bg-emerald-200/20' :
|
||||||
|
type === 'orders' ? 'bg-blue-200/20' :
|
||||||
|
type === 'average_order' ? 'bg-violet-200/20' :
|
||||||
|
'bg-orange-200/20'
|
||||||
|
} rounded-sm`}
|
||||||
style={{
|
style={{
|
||||||
opacity: 0.5,
|
opacity: 0.5,
|
||||||
clipPath: "polygon(0 50%, 100% 20%, 100% 100%, 0 100%)",
|
clipPath: "polygon(0 50%, 100% 20%, 100% 100%, 0 100%)",
|
||||||
@@ -111,33 +141,68 @@ const MiniSkeletonChart = ({ type = "line" }) => (
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
const MiniSkeletonTable = ({ rows = 8 }) => (
|
const MiniSkeletonTable = ({ rows = 8, colorScheme = "orange" }) => (
|
||||||
<div className="rounded-lg border bg-white dark:bg-gray-900/60 backdrop-blur-sm">
|
<div className={`rounded-lg border ${
|
||||||
|
colorScheme === 'orange' ? 'bg-orange-50/10 border-orange-200/20' :
|
||||||
|
colorScheme === 'emerald' ? 'bg-emerald-50/10 border-emerald-200/20' :
|
||||||
|
colorScheme === 'blue' ? 'bg-blue-50/10 border-blue-200/20' :
|
||||||
|
'bg-violet-50/10 border-violet-200/20'
|
||||||
|
}`}>
|
||||||
<Table>
|
<Table>
|
||||||
<TableHeader>
|
<TableHeader>
|
||||||
<TableRow className="dark:border-gray-800">
|
<TableRow>
|
||||||
<TableHead>
|
<TableHead>
|
||||||
<Skeleton className="h-3 w-24 bg-muted rounded-sm" />
|
<Skeleton className={`h-4 w-32 ${
|
||||||
|
colorScheme === 'orange' ? 'bg-orange-200/20' :
|
||||||
|
colorScheme === 'emerald' ? 'bg-emerald-200/20' :
|
||||||
|
colorScheme === 'blue' ? 'bg-blue-200/20' :
|
||||||
|
'bg-violet-200/20'
|
||||||
|
} rounded-sm`} />
|
||||||
</TableHead>
|
</TableHead>
|
||||||
<TableHead className="text-right">
|
<TableHead className="text-right">
|
||||||
<Skeleton className="h-3 w-16 ml-auto bg-muted rounded-sm" />
|
<Skeleton className={`h-4 w-24 ml-auto ${
|
||||||
|
colorScheme === 'orange' ? 'bg-orange-200/20' :
|
||||||
|
colorScheme === 'emerald' ? 'bg-emerald-200/20' :
|
||||||
|
colorScheme === 'blue' ? 'bg-blue-200/20' :
|
||||||
|
'bg-violet-200/20'
|
||||||
|
} rounded-sm`} />
|
||||||
</TableHead>
|
</TableHead>
|
||||||
<TableHead className="text-right">
|
<TableHead className="text-right">
|
||||||
<Skeleton className="h-3 w-16 ml-auto bg-muted rounded-sm" />
|
<Skeleton className={`h-4 w-24 ml-auto ${
|
||||||
|
colorScheme === 'orange' ? 'bg-orange-200/20' :
|
||||||
|
colorScheme === 'emerald' ? 'bg-emerald-200/20' :
|
||||||
|
colorScheme === 'blue' ? 'bg-blue-200/20' :
|
||||||
|
'bg-violet-200/20'
|
||||||
|
} rounded-sm`} />
|
||||||
</TableHead>
|
</TableHead>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
</TableHeader>
|
</TableHeader>
|
||||||
<TableBody>
|
<TableBody>
|
||||||
{[...Array(rows)].map((_, i) => (
|
{[...Array(rows)].map((_, i) => (
|
||||||
<TableRow key={i} className="dark:border-gray-800">
|
<TableRow key={i}>
|
||||||
<TableCell>
|
<TableCell>
|
||||||
<Skeleton className="h-3 w-32 bg-muted rounded-sm" />
|
<Skeleton className={`h-4 w-48 ${
|
||||||
|
colorScheme === 'orange' ? 'bg-orange-200/20' :
|
||||||
|
colorScheme === 'emerald' ? 'bg-emerald-200/20' :
|
||||||
|
colorScheme === 'blue' ? 'bg-blue-200/20' :
|
||||||
|
'bg-violet-200/20'
|
||||||
|
} rounded-sm`} />
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell className="text-right">
|
<TableCell className="text-right">
|
||||||
<Skeleton className="h-3 w-12 ml-auto bg-muted rounded-sm" />
|
<Skeleton className={`h-4 w-16 ml-auto ${
|
||||||
|
colorScheme === 'orange' ? 'bg-orange-200/20' :
|
||||||
|
colorScheme === 'emerald' ? 'bg-emerald-200/20' :
|
||||||
|
colorScheme === 'blue' ? 'bg-blue-200/20' :
|
||||||
|
'bg-violet-200/20'
|
||||||
|
} rounded-sm`} />
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell className="text-right">
|
<TableCell className="text-right">
|
||||||
<Skeleton className="h-3 w-12 ml-auto bg-muted rounded-sm" />
|
<Skeleton className={`h-4 w-16 ml-auto ${
|
||||||
|
colorScheme === 'orange' ? 'bg-orange-200/20' :
|
||||||
|
colorScheme === 'emerald' ? 'bg-emerald-200/20' :
|
||||||
|
colorScheme === 'blue' ? 'bg-blue-200/20' :
|
||||||
|
'bg-violet-200/20'
|
||||||
|
} rounded-sm`} />
|
||||||
</TableCell>
|
</TableCell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
))}
|
))}
|
||||||
@@ -465,7 +530,9 @@ const MiniStatCards = ({
|
|||||||
<div className="flex items-center gap-1">
|
<div className="flex items-center gap-1">
|
||||||
<span>Proj: </span>
|
<span>Proj: </span>
|
||||||
{projectionLoading ? (
|
{projectionLoading ? (
|
||||||
<Skeleton className="h-4 w-15" />
|
<div className="w-20">
|
||||||
|
<Skeleton className="h-4 w-15 bg-emerald-700" />
|
||||||
|
</div>
|
||||||
) : (
|
) : (
|
||||||
formatCurrency(
|
formatCurrency(
|
||||||
projection?.projectedRevenue || stats.projectedRevenue
|
projection?.projectedRevenue || stats.projectedRevenue
|
||||||
@@ -476,7 +543,16 @@ const MiniStatCards = ({
|
|||||||
}
|
}
|
||||||
progress={stats?.periodProgress < 100 ? stats.periodProgress : undefined}
|
progress={stats?.periodProgress < 100 ? stats.periodProgress : undefined}
|
||||||
trend={projectionLoading && stats?.periodProgress < 100 ? undefined : revenueTrend?.trend}
|
trend={projectionLoading && stats?.periodProgress < 100 ? undefined : revenueTrend?.trend}
|
||||||
trendValue={revenueTrend?.value ? formatPercentage(revenueTrend.value) : null}
|
trendValue={
|
||||||
|
projectionLoading && stats?.periodProgress < 100 ? (
|
||||||
|
<div className="flex items-center gap-1">
|
||||||
|
<Skeleton className="h-4 w-4 bg-emerald-700 rounded-full" />
|
||||||
|
<Skeleton className="h-4 w-8 bg-emerald-700" />
|
||||||
|
</div>
|
||||||
|
) : revenueTrend?.value ? (
|
||||||
|
formatPercentage(revenueTrend.value)
|
||||||
|
) : null
|
||||||
|
}
|
||||||
colorClass="text-emerald-200"
|
colorClass="text-emerald-200"
|
||||||
titleClass="text-emerald-100 font-bold text-md"
|
titleClass="text-emerald-100 font-bold text-md"
|
||||||
descriptionClass="text-emerald-200 text-md font-semibold"
|
descriptionClass="text-emerald-200 text-md font-semibold"
|
||||||
@@ -576,11 +652,20 @@ const MiniStatCards = ({
|
|||||||
{detailDataLoading[selectedMetric] ? (
|
{detailDataLoading[selectedMetric] ? (
|
||||||
<div className="space-y-4 h-full">
|
<div className="space-y-4 h-full">
|
||||||
{selectedMetric === "shipping" ? (
|
{selectedMetric === "shipping" ? (
|
||||||
<MiniSkeletonTable rows={8} />
|
<MiniSkeletonTable
|
||||||
|
rows={8}
|
||||||
|
colorScheme={
|
||||||
|
selectedMetric === 'revenue' ? 'emerald' :
|
||||||
|
selectedMetric === 'orders' ? 'blue' :
|
||||||
|
selectedMetric === 'average_order' ? 'violet' :
|
||||||
|
'orange'
|
||||||
|
}
|
||||||
|
/>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<MiniSkeletonChart
|
<MiniSkeletonChart
|
||||||
type={selectedMetric === "orders" ? "bar" : "line"}
|
type={selectedMetric === "orders" ? "bar" : "line"}
|
||||||
|
metric={selectedMetric}
|
||||||
/>
|
/>
|
||||||
{selectedMetric === "orders" && (
|
{selectedMetric === "orders" && (
|
||||||
<div className="mt-8">
|
<div className="mt-8">
|
||||||
@@ -593,7 +678,7 @@ const MiniStatCards = ({
|
|||||||
}`}>
|
}`}>
|
||||||
Hourly Distribution
|
Hourly Distribution
|
||||||
</h3>
|
</h3>
|
||||||
<MiniSkeletonChart type="bar" />
|
<MiniSkeletonChart type="bar" metric={selectedMetric} />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
|
|||||||
Reference in New Issue
Block a user