Fix grid spacing and fix statcards dialogs

This commit is contained in:
2025-01-01 14:03:11 -05:00
parent a563dbfb39
commit 6c7aed68cc
4 changed files with 244 additions and 78 deletions

View File

@@ -63,34 +63,33 @@ const PinProtectedLayout = ({ children }) => {
// Small Layout
const SmallLayout = () => {
const DATETIME_SCALE = 2;
const STATS_SCALE = 1.5;
const STATS_SCALE = 1.6;
return (
<div className="min-h-screen w-screen overflow-hidden">
<div className="flex flex-col">
<div className="min-h-screen w-screen">
<span className="absolute top-4 left-4 z-50">
<LockButton />
</span>
<div className="p-4 space-y-4 grid grid-cols-12 gap-0">
<div className="p-4 grid grid-cols-12 gap-4">
<div className="col-span-3 relative">
<div
className="col-span-4"
className="origin-top-left"
style={{
transform: `scale(${DATETIME_SCALE})`,
transformOrigin: "top left",
width: `${100/DATETIME_SCALE}%`,
height: `${100/DATETIME_SCALE}%`,
}}
>
<DateTimeWeatherDisplay />
</div>
</div>
<div className="col-span-9 relative">
<div
className="col-span-8"
className="origin-top-left"
style={{
transform: `scale(${STATS_SCALE})`,
transformOrigin: "top left",
width: `${100/STATS_SCALE}%`,
height: `${100/STATS_SCALE}%`,
}}
>
<MiniStatCards
@@ -99,6 +98,8 @@ const SmallLayout = () => {
/>
</div>
</div>
{/* You can easily add more grid items here */}
</div>
</div>
);

View File

@@ -347,7 +347,7 @@ const DateTimeWeatherDisplay = ({ scaleFactor = 1 }) => {
);
return (
<div className={`flex flex-col space-y-2 items-center w-[250px] transition-opacity duration-300 ${mounted ? 'opacity-100' : 'opacity-0'}`}>
<div className="flex flex-col space-y-2 items-center w-full transition-opacity duration-300 ${mounted ? 'opacity-100' : 'opacity-0'}">
{/* Time Display */}
<Card className="bg-gradient-to-br from-slate-900 via-sky-800 to-cyan-800 dark:bg-slate-800 px-1 py-2 w-full hover:scale-[1.02] transition-transform duration-300">
<CardContent className="p-3">

View File

@@ -29,9 +29,18 @@ import {
Package,
AlertCircle,
CircleDollarSign,
Loader2,
} from "lucide-react";
import { Skeleton } from "@/components/ui/skeleton";
import { Tooltip, TooltipContent, TooltipTrigger, TooltipProvider } from "@/components/ui/tooltip";
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from "@/components/ui/table";
// Import the detail view components and utilities from StatCards
import {
@@ -46,6 +55,92 @@ import {
SkeletonCard,
} from "./StatCards";
// Mini skeleton components
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-full relative">
{/* Grid lines */}
{[...Array(5)].map((_, i) => (
<div
key={i}
className="absolute w-full h-px bg-muted"
style={{ top: `${(i + 1) * 20}%` }}
/>
))}
{/* Y-axis labels */}
<div className="absolute left-0 top-0 bottom-0 w-6 flex flex-col justify-between py-2">
{[...Array(5)].map((_, i) => (
<Skeleton key={i} className="h-2 w-4 bg-muted rounded-sm" />
))}
</div>
{/* X-axis labels */}
<div className="absolute left-6 right-2 bottom-0 flex justify-between">
{[...Array(6)].map((_, i) => (
<Skeleton key={i} className="h-2 w-6 bg-muted rounded-sm" />
))}
</div>
{type === "bar" ? (
<div className="absolute inset-x-6 bottom-4 top-2 flex items-end justify-between gap-1">
{[...Array(24)].map((_, i) => (
<div
key={i}
className="w-1 bg-muted rounded-sm"
style={{ height: `${Math.random() * 80 + 10}%` }}
/>
))}
</div>
) : (
<div className="absolute inset-x-6 bottom-4 top-2">
<div className="h-full w-full relative">
<div
className="absolute inset-0 bg-muted rounded-sm"
style={{
opacity: 0.5,
clipPath: "polygon(0 50%, 100% 20%, 100% 100%, 0 100%)",
}}
/>
</div>
</div>
)}
</div>
</div>
);
const MiniSkeletonTable = ({ rows = 8 }) => (
<div className="rounded-lg border bg-white dark:bg-gray-900/60 backdrop-blur-sm">
<Table>
<TableHeader>
<TableRow className="dark:border-gray-800">
<TableHead>
<Skeleton className="h-3 w-24 bg-muted rounded-sm" />
</TableHead>
<TableHead className="text-right">
<Skeleton className="h-3 w-16 ml-auto bg-muted rounded-sm" />
</TableHead>
<TableHead className="text-right">
<Skeleton className="h-3 w-16 ml-auto bg-muted rounded-sm" />
</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{[...Array(rows)].map((_, i) => (
<TableRow key={i} className="dark:border-gray-800">
<TableCell>
<Skeleton className="h-3 w-32 bg-muted rounded-sm" />
</TableCell>
<TableCell className="text-right">
<Skeleton className="h-3 w-12 ml-auto bg-muted rounded-sm" />
</TableCell>
<TableCell className="text-right">
<Skeleton className="h-3 w-12 ml-auto bg-muted rounded-sm" />
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</div>
);
const MiniStatCards = ({
timeRange: initialTimeRange = "today",
startDate,
@@ -195,6 +290,44 @@ const MiniStatCards = ({
return () => clearInterval(interval);
}, [timeRange]);
// Add function to fetch detail data
const fetchDetailData = useCallback(async (metric) => {
if (detailData[metric]) return;
setDetailDataLoading((prev) => ({ ...prev, [metric]: true }));
try {
const response = await axios.get("/api/klaviyo/events/stats/details", {
params: {
timeRange: "last30days",
metric,
daily: true,
},
});
setDetailData((prev) => ({ ...prev, [metric]: response.data.stats }));
} catch (error) {
console.error(`Error fetching detail data for ${metric}:`, error);
} finally {
setDetailDataLoading((prev) => ({ ...prev, [metric]: false }));
}
}, [detailData]);
// Add effect to load detail data when metric is selected
useEffect(() => {
if (selectedMetric) {
fetchDetailData(selectedMetric);
}
}, [selectedMetric, fetchDetailData]);
// Add preload effect
useEffect(() => {
// Preload all detail data when component mounts
const metrics = ["revenue", "orders", "average_order", "shipping"];
metrics.forEach(metric => {
fetchDetailData(metric);
});
}, []); // eslint-disable-line react-hooks/exhaustive-deps
if (loading && !stats) {
return (
<Card className="w-full bg-white dark:bg-gray-900/60 backdrop-blur-sm">
@@ -227,8 +360,7 @@ const MiniStatCards = ({
const aovTrend = calculateAOVTrend();
return (
<Card className="w-full bg-white dark:bg-gray-900/60 backdrop-blur-sm">
<CardHeader className="p-4">
<>
<div className="flex justify-between items-center">
<div>
@@ -255,8 +387,7 @@ const MiniStatCards = ({
</div>
</CardHeader>
<CardContent className="p-4 pt-0">
<div className="grid grid-cols-4 gap-2">
<StatCard
title="Total Revenue"
@@ -322,30 +453,62 @@ const MiniStatCards = ({
/>
</div>
<DetailDialog
open={!!selectedMetric}
onOpenChange={() => setSelectedMetric(null)}
title={
selectedMetric
<Dialog open={!!selectedMetric} onOpenChange={() => setSelectedMetric(null)}>
<DialogContent className="w-[80vw] h-[80vh] max-w-none p-0">
<div className="transform scale-[2] origin-top-left h-[40vh] w-[40vw]">
<div className="h-full w-full p-6">
<DialogHeader>
<DialogTitle>
{selectedMetric
? `${selectedMetric
.split("_")
.map((w) => w.charAt(0).toUpperCase() + w.slice(1))
.join(" ")} Details`
: ""
}
>
{selectedMetric === "revenue" && <RevenueDetails data={detailData.revenue || []} />}
{selectedMetric === "orders" && <OrdersDetails data={detailData.orders || []} />}
: ""}
</DialogTitle>
</DialogHeader>
<div className="mt-4 h-[calc(40vh-4rem)] overflow-auto">
{detailDataLoading[selectedMetric] ? (
<div className="space-y-4 h-full">
{selectedMetric === "shipping" ? (
<MiniSkeletonTable rows={8} />
) : (
<>
<MiniSkeletonChart type={selectedMetric === "orders" ? "bar" : "line"} />
{selectedMetric === "orders" && (
<div className="mt-8">
<h3 className="text-lg font-medium mb-4">Hourly Distribution</h3>
<MiniSkeletonChart type="bar" />
</div>
)}
</>
)}
</div>
) : (
<div className="h-full">
{selectedMetric === "revenue" && (
<RevenueDetails data={detailData.revenue || []} />
)}
{selectedMetric === "orders" && (
<OrdersDetails data={detailData.orders || []} />
)}
{selectedMetric === "average_order" && (
<AverageOrderDetails
data={detailData.average_order || []}
orderCount={stats.orderCount}
/>
)}
{selectedMetric === "shipping" && <ShippingDetails data={[stats]} timeRange={timeRange} />}
</DetailDialog>
</CardContent>
</Card>
{selectedMetric === "shipping" && (
<ShippingDetails data={[stats]} timeRange={timeRange} />
)}
</div>
)}
</div>
</div>
</div>
</DialogContent>
</Dialog>
</>
);
};

View File

@@ -2149,6 +2149,8 @@ export {
formatCurrency,
formatPercentage,
SkeletonCard,
SkeletonChart,
SkeletonTable,
};
export default StatCards;