Standardize various design elements

This commit is contained in:
2024-12-29 09:43:05 -05:00
parent 5504390fc5
commit 4192e64179
10 changed files with 924 additions and 876 deletions

View File

@@ -168,18 +168,18 @@ const AgentPerformanceTable = ({ agents, onSort }) => {
const SkeletonMetricCard = () => ( const SkeletonMetricCard = () => (
<Card className="bg-white dark:bg-gray-900/60 backdrop-blur-sm"> <Card className="bg-white dark:bg-gray-900/60 backdrop-blur-sm">
<CardHeader className="flex flex-col items-start p-4"> <CardHeader className="flex flex-col items-start p-4">
<Skeleton className="h-4 w-24 mb-2" /> <Skeleton className="h-4 w-24 mb-2 bg-muted" />
<Skeleton className="h-8 w-32 mb-2" /> <Skeleton className="h-8 w-32 mb-2 bg-muted" />
<div className="flex gap-4"> <div className="flex gap-4">
<Skeleton className="h-4 w-20" /> <Skeleton className="h-4 w-20 bg-muted" />
<Skeleton className="h-4 w-20" /> <Skeleton className="h-4 w-20 bg-muted" />
</div> </div>
</CardHeader> </CardHeader>
</Card> </Card>
); );
const SkeletonChart = ({ type = "line" }) => ( const SkeletonChart = ({ type = "line" }) => (
<div className="h-[300px] w-full bg-gray-50 dark:bg-gray-800 rounded-lg p-4"> <div className="h-[300px] w-full bg-white dark:bg-gray-900/60 backdrop-blur-sm rounded-lg p-4">
<div className="h-full flex flex-col"> <div className="h-full flex flex-col">
<div className="flex-1 relative"> <div className="flex-1 relative">
{type === "bar" ? ( {type === "bar" ? (
@@ -187,7 +187,7 @@ const SkeletonChart = ({ type = "line" }) => (
{[...Array(24)].map((_, i) => ( {[...Array(24)].map((_, i) => (
<div <div
key={i} key={i}
className="w-full bg-gray-200 dark:bg-gray-700 rounded-t animate-pulse" className="w-full bg-muted rounded-t animate-pulse"
style={{ height: `${15 + Math.random() * 70}%` }} style={{ height: `${15 + Math.random() * 70}%` }}
/> />
))} ))}
@@ -197,12 +197,12 @@ const SkeletonChart = ({ type = "line" }) => (
{[...Array(5)].map((_, i) => ( {[...Array(5)].map((_, i) => (
<div <div
key={i} key={i}
className="absolute w-full h-px bg-gray-200 dark:bg-gray-700" className="absolute w-full h-px bg-muted"
style={{ top: `${20 + i * 20}%` }} style={{ top: `${20 + i * 20}%` }}
/> />
))} ))}
<div <div
className="absolute inset-0 bg-gray-300 dark:bg-gray-600 animate-pulse" className="absolute inset-0 bg-muted animate-pulse"
style={{ style={{
opacity: 0.2, opacity: 0.2,
clipPath: "polygon(0 50%, 100% 20%, 100% 100%, 0 100%)", clipPath: "polygon(0 50%, 100% 20%, 100% 100%, 0 100%)",
@@ -219,21 +219,21 @@ const SkeletonTable = ({ rows = 5 }) => (
<Table> <Table>
<TableHeader> <TableHeader>
<TableRow className="hover:bg-transparent"> <TableRow className="hover:bg-transparent">
<TableHead><Skeleton className="h-4 w-24" /></TableHead> <TableHead><Skeleton className="h-4 w-24 bg-muted" /></TableHead>
<TableHead><Skeleton className="h-4 w-24" /></TableHead> <TableHead><Skeleton className="h-4 w-24 bg-muted" /></TableHead>
<TableHead><Skeleton className="h-4 w-24" /></TableHead> <TableHead><Skeleton className="h-4 w-24 bg-muted" /></TableHead>
<TableHead><Skeleton className="h-4 w-24" /></TableHead> <TableHead><Skeleton className="h-4 w-24 bg-muted" /></TableHead>
<TableHead><Skeleton className="h-4 w-24" /></TableHead> <TableHead><Skeleton className="h-4 w-24 bg-muted" /></TableHead>
</TableRow> </TableRow>
</TableHeader> </TableHeader>
<TableBody> <TableBody>
{[...Array(rows)].map((_, i) => ( {[...Array(rows)].map((_, i) => (
<TableRow key={i} className="hover:bg-gray-50 dark:hover:bg-gray-800/50"> <TableRow key={i} className="hover:bg-muted/50 transition-colors">
<TableCell><Skeleton className="h-4 w-32" /></TableCell> <TableCell><Skeleton className="h-4 w-32 bg-muted" /></TableCell>
<TableCell><Skeleton className="h-4 w-16" /></TableCell> <TableCell><Skeleton className="h-4 w-16 bg-muted" /></TableCell>
<TableCell><Skeleton className="h-4 w-16" /></TableCell> <TableCell><Skeleton className="h-4 w-16 bg-muted" /></TableCell>
<TableCell><Skeleton className="h-4 w-16" /></TableCell> <TableCell><Skeleton className="h-4 w-16 bg-muted" /></TableCell>
<TableCell><Skeleton className="h-4 w-24" /></TableCell> <TableCell><Skeleton className="h-4 w-24 bg-muted" /></TableCell>
</TableRow> </TableRow>
))} ))}
</TableBody> </TableBody>
@@ -356,9 +356,13 @@ const AircallDashboard = () => {
if (error) { if (error) {
return ( return (
<Alert variant="destructive" className="m-4"> <Card className="h-full bg-white dark:bg-gray-900/60 backdrop-blur-sm">
<AlertDescription>Error loading call data: {error}</AlertDescription> <CardContent className="p-4">
</Alert> <div className="p-4 m-6 text-red-600 dark:text-red-400 bg-red-50 dark:bg-red-900/10 rounded-lg border border-red-200 dark:border-red-900/20">
Error loading call data: {error}
</div>
</CardContent>
</Card>
); );
} }
@@ -372,7 +376,7 @@ const AircallDashboard = () => {
</div> </div>
<Select value={timeRange} onValueChange={setTimeRange}> <Select value={timeRange} onValueChange={setTimeRange}>
<SelectTrigger className="w-[130px] h-9"> <SelectTrigger className="w-[130px] h-9 bg-white dark:bg-gray-800">
<SelectValue placeholder="Select range" /> <SelectValue placeholder="Select range" />
</SelectTrigger> </SelectTrigger>
<SelectContent> <SelectContent>
@@ -400,10 +404,10 @@ const AircallDashboard = () => {
<CardTitle className="text-sm font-medium text-gray-600 dark:text-gray-300">Total Calls</CardTitle> <CardTitle className="text-sm font-medium text-gray-600 dark:text-gray-300">Total Calls</CardTitle>
<div className="text-2xl font-bold text-gray-900 dark:text-gray-100 mt-2">{metrics.total}</div> <div className="text-2xl font-bold text-gray-900 dark:text-gray-100 mt-2">{metrics.total}</div>
<div className="flex gap-4 mt-2"> <div className="flex gap-4 mt-2">
<div className="text-sm"> <div className="text-sm text-muted-foreground">
<span className="text-blue-500"> {metrics.by_direction.inbound}</span> inbound <span className="text-blue-500"> {metrics.by_direction.inbound}</span> inbound
</div> </div>
<div className="text-sm"> <div className="text-sm text-muted-foreground">
<span className="text-emerald-500"> {metrics.by_direction.outbound}</span> outbound <span className="text-emerald-500"> {metrics.by_direction.outbound}</span> outbound
</div> </div>
</div> </div>
@@ -416,10 +420,10 @@ const AircallDashboard = () => {
{`${((metrics.by_status.answered / metrics.total) * 100).toFixed(1)}%`} {`${((metrics.by_status.answered / metrics.total) * 100).toFixed(1)}%`}
</div> </div>
<div className="flex gap-6"> <div className="flex gap-6">
<div className="text-sm"> <div className="text-sm text-muted-foreground">
<span className="text-emerald-500">{metrics.by_status.answered}</span> answered <span className="text-emerald-500">{metrics.by_status.answered}</span> answered
</div> </div>
<div className="text-sm"> <div className="text-sm text-muted-foreground">
<span className="text-rose-500">{metrics.by_status.missed}</span> missed <span className="text-rose-500">{metrics.by_status.missed}</span> missed
</div> </div>
</div> </div>
@@ -453,11 +457,11 @@ const AircallDashboard = () => {
</div> </div>
</div> </div>
</TooltipTrigger> </TooltipTrigger>
<TooltipContent side="bottom" className="w-[300px]"> <TooltipContent side="bottom" className="w-[300px] bg-white dark:bg-gray-900/60 backdrop-blur-sm">
<div className="space-y-2"> <div className="space-y-2">
<p className="font-medium">Duration Distribution</p> <p className="font-medium text-gray-900 dark:text-gray-100">Duration Distribution</p>
{metrics?.duration_distribution?.map((d, i) => ( {metrics?.duration_distribution?.map((d, i) => (
<div key={i} className="flex justify-between text-sm"> <div key={i} className="flex justify-between text-sm text-muted-foreground">
<span>{d.range}</span> <span>{d.range}</span>
<span>{d.count} calls</span> <span>{d.count} calls</span>
</div> </div>
@@ -487,15 +491,15 @@ const AircallDashboard = () => {
) : ( ) : (
<ResponsiveContainer width="100%" height="100%"> <ResponsiveContainer width="100%" height="100%">
<BarChart data={chartData.daily} margin={{ top: 0, right: 5, left: -35, bottom: 0 }}> <BarChart data={chartData.daily} margin={{ top: 0, right: 5, left: -35, bottom: 0 }}>
<CartesianGrid strokeDasharray="3 3" className="stroke-gray-200 dark:stroke-gray-700" /> <CartesianGrid strokeDasharray="3 3" className="stroke-muted" />
<XAxis <XAxis
dataKey="date" dataKey="date"
tick={{ fontSize: 12 }} tick={{ fontSize: 12 }}
className="text-gray-600 dark:text-gray-300" className="text-muted-foreground"
/> />
<YAxis <YAxis
tick={{ fontSize: 12 }} tick={{ fontSize: 12 }}
className="text-gray-600 dark:text-gray-300" className="text-muted-foreground"
/> />
<RechartsTooltip content={<CustomTooltip />} /> <RechartsTooltip content={<CustomTooltip />} />
<Legend /> <Legend />
@@ -518,16 +522,16 @@ const AircallDashboard = () => {
) : ( ) : (
<ResponsiveContainer width="100%" height="100%"> <ResponsiveContainer width="100%" height="100%">
<BarChart data={chartData.hourly} margin={{ top: 0, right: 5, left: -35, bottom: 0 }}> <BarChart data={chartData.hourly} margin={{ top: 0, right: 5, left: -35, bottom: 0 }}>
<CartesianGrid strokeDasharray="3 3" className="stroke-gray-200 dark:stroke-gray-700" /> <CartesianGrid strokeDasharray="3 3" className="stroke-muted" />
<XAxis <XAxis
dataKey="hour" dataKey="hour"
tick={{ fontSize: 12 }} tick={{ fontSize: 12 }}
interval={2} interval={2}
className="text-gray-600 dark:text-gray-300" className="text-muted-foreground"
/> />
<YAxis <YAxis
tick={{ fontSize: 12 }} tick={{ fontSize: 12 }}
className="text-gray-600 dark:text-gray-300" className="text-muted-foreground"
/> />
<RechartsTooltip content={<CustomTooltip />} /> <RechartsTooltip content={<CustomTooltip />} />
<Bar dataKey="calls" fill={COLORS.hourly} name="Calls" /> <Bar dataKey="calls" fill={COLORS.hourly} name="Calls" />
@@ -549,10 +553,12 @@ const AircallDashboard = () => {
{isLoading ? ( {isLoading ? (
<SkeletonTable rows={5} /> <SkeletonTable rows={5} />
) : ( ) : (
<div className="overflow-y-auto max-h-[400px] scrollbar-thin scrollbar-thumb-gray-200 dark:scrollbar-thumb-gray-700 scrollbar-track-transparent hover:scrollbar-thumb-gray-300 dark:hover:scrollbar-thumb-gray-600">
<AgentPerformanceTable <AgentPerformanceTable
agents={sortedAgents} agents={sortedAgents}
onSort={(key, direction) => setAgentSort({ key, direction })} onSort={(key, direction) => setAgentSort({ key, direction })}
/> />
</div>
)} )}
</CardContent> </CardContent>
</Card> </Card>
@@ -566,16 +572,17 @@ const AircallDashboard = () => {
{isLoading ? ( {isLoading ? (
<SkeletonTable rows={5} /> <SkeletonTable rows={5} />
) : ( ) : (
<div className="overflow-y-auto max-h-[400px] scrollbar-thin scrollbar-thumb-gray-200 dark:scrollbar-thumb-gray-700 scrollbar-track-transparent hover:scrollbar-thumb-gray-300 dark:hover:scrollbar-thumb-gray-600">
<Table> <Table>
<TableHeader> <TableHeader>
<TableRow className="hover:bg-transparent"> <TableRow className="hover:bg-transparent">
<TableHead>Reason</TableHead> <TableHead className="font-medium text-gray-900 dark:text-gray-100">Reason</TableHead>
<TableHead className="text-right">Count</TableHead> <TableHead className="text-right font-medium text-gray-900 dark:text-gray-100">Count</TableHead>
</TableRow> </TableRow>
</TableHeader> </TableHeader>
<TableBody> <TableBody>
{chartData.missedReasons.map((reason, index) => ( {chartData.missedReasons.map((reason, index) => (
<TableRow key={index} className="hover:bg-gray-50 dark:hover:bg-gray-800/50"> <TableRow key={index} className="hover:bg-muted/50 transition-colors">
<TableCell className="font-medium text-gray-900 dark:text-gray-100"> <TableCell className="font-medium text-gray-900 dark:text-gray-100">
{reason.reason} {reason.reason}
</TableCell> </TableCell>
@@ -586,6 +593,7 @@ const AircallDashboard = () => {
))} ))}
</TableBody> </TableBody>
</Table> </Table>
</div>
)} )}
</CardContent> </CardContent>
</Card> </Card>

View File

@@ -23,6 +23,8 @@ import {
import { Loader2, TrendingUp, AlertCircle } from "lucide-react"; import { Loader2, TrendingUp, AlertCircle } from "lucide-react";
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"; import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
import { Skeleton } from "@/components/ui/skeleton"; import { Skeleton } from "@/components/ui/skeleton";
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger } from "@/components/ui/dialog";
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table";
// Add helper function for currency formatting // Add helper function for currency formatting
const formatCurrency = (value, useFractionDigits = true) => { const formatCurrency = (value, useFractionDigits = true) => {
@@ -38,40 +40,42 @@ const formatCurrency = (value, useFractionDigits = true) => {
// Add skeleton components // Add skeleton components
const SkeletonChart = () => ( const SkeletonChart = () => (
<div className="h-[400px] w-full bg-gray-50 dark:bg-gray-800 rounded-lg p-4"> <div className="h-[400px] w-full bg-white dark:bg-gray-900/60 backdrop-blur-sm rounded-lg p-6">
<div className="h-full flex flex-col"> <div className="h-full flex flex-col">
<div className="flex-1 relative"> <div className="flex-1 relative">
<div className="h-full w-full relative"> {/* Grid lines */}
{[...Array(5)].map((_, i) => ( {[...Array(5)].map((_, i) => (
<div <div
key={i} key={i}
className="absolute w-full h-px bg-gray-200 dark:bg-gray-700" className="absolute w-full h-px bg-muted"
style={{ top: `${20 + i * 20}%` }} style={{ top: `${(i + 1) * 20}%` }}
/> />
))} ))}
{/* Y-axis labels */}
<div className="absolute left-0 top-0 bottom-0 w-8 flex flex-col justify-between py-4">
{[...Array(5)].map((_, i) => (
<Skeleton key={i} className="h-3 w-6 bg-muted rounded-sm" />
))}
</div>
{/* X-axis labels */}
<div className="absolute left-8 right-4 bottom-0 flex justify-between">
{[...Array(6)].map((_, i) => (
<Skeleton key={i} className="h-3 w-8 bg-muted rounded-sm" />
))}
</div>
{/* Chart line */}
<div className="absolute inset-x-8 bottom-6 top-4">
<div className="h-full w-full relative">
<div <div
className="absolute inset-0 bg-gray-300 dark:bg-gray-600 animate-pulse" className="absolute inset-0 bg-muted rounded-sm"
style={{ style={{
opacity: 0.2, opacity: 0.5,
clipPath: "polygon(0 50%, 100% 20%, 100% 100%, 0 100%)", clipPath: "polygon(0 50%, 100% 20%, 100% 100%, 0 100%)",
}} }}
/> />
<div className="absolute inset-0 flex justify-between">
{[...Array(8)].map((_, i) => (
<div
key={i}
className="w-px h-full bg-gray-200 dark:bg-gray-700"
style={{ opacity: 0.5 }}
/>
))}
</div> </div>
</div> </div>
</div> </div>
<div className="flex justify-between pt-4">
{[...Array(6)].map((_, i) => (
<Skeleton key={i} className="h-4 w-12 dark:bg-gray-700" />
))}
</div>
</div> </div>
</div> </div>
); );
@@ -79,13 +83,13 @@ const SkeletonChart = () => (
const SkeletonStats = () => ( const SkeletonStats = () => (
<div className="grid grid-cols-2 lg:grid-cols-4 gap-4 py-4"> <div className="grid grid-cols-2 lg:grid-cols-4 gap-4 py-4">
{[...Array(4)].map((_, i) => ( {[...Array(4)].map((_, i) => (
<Card key={i}> <Card key={i} className="bg-white dark:bg-gray-900/60 backdrop-blur-sm">
<CardHeader className="flex flex-row items-center justify-between space-y-0 p-4 pb-2"> <CardHeader className="flex flex-row items-center justify-between space-y-0 p-4 pb-2">
<Skeleton className="h-4 w-24 dark:bg-gray-700" /> <Skeleton className="h-4 w-24 bg-muted rounded-sm" />
</CardHeader> </CardHeader>
<CardContent className="p-4 pt-0"> <CardContent className="p-4 pt-0">
<Skeleton className="h-8 w-32 mb-2 dark:bg-gray-700" /> <Skeleton className="h-8 w-32 bg-muted rounded-sm mb-2" />
<Skeleton className="h-4 w-24 dark:bg-gray-700" /> <Skeleton className="h-4 w-24 bg-muted rounded-sm" />
</CardContent> </CardContent>
</Card> </Card>
))} ))}
@@ -95,7 +99,7 @@ const SkeletonStats = () => (
const SkeletonButtons = () => ( const SkeletonButtons = () => (
<div className="flex flex-wrap gap-1"> <div className="flex flex-wrap gap-1">
{[...Array(4)].map((_, i) => ( {[...Array(4)].map((_, i) => (
<Skeleton key={i} className="h-8 w-20 dark:bg-gray-700" /> <Skeleton key={i} className="h-8 w-20 bg-muted rounded-sm" />
))} ))}
</div> </div>
); );
@@ -109,12 +113,12 @@ const StatCard = ({
trendValue, trendValue,
colorClass = "text-gray-900 dark:text-gray-100", colorClass = "text-gray-900 dark:text-gray-100",
}) => ( }) => (
<Card> <Card className="bg-white dark:bg-gray-900/60 backdrop-blur-sm">
<CardHeader className="flex flex-row items-center justify-between space-y-0 p-4 pb-2"> <CardHeader className="flex flex-row items-center justify-between space-y-0 p-4 pb-2">
<span className="text-sm text-muted-foreground">{title}</span> <span className="text-sm text-muted-foreground font-medium">{title}</span>
{trend && ( {trend && (
<span <span
className={`text-sm flex items-center gap-1 ${ className={`text-sm flex items-center gap-1 font-medium ${
trend === "up" trend === "up"
? "text-emerald-600 dark:text-emerald-400" ? "text-emerald-600 dark:text-emerald-400"
: "text-rose-600 dark:text-rose-400" : "text-rose-600 dark:text-rose-400"
@@ -125,9 +129,9 @@ const StatCard = ({
)} )}
</CardHeader> </CardHeader>
<CardContent className="p-4 pt-0"> <CardContent className="p-4 pt-0">
<div className={`text-2xl font-bold mb-1 ${colorClass}`}>{value}</div> <div className={`text-2xl font-bold mb-1.5 ${colorClass}`}>{value}</div>
{description && ( {description && (
<div className="text-sm text-muted-foreground">{description}</div> <div className="text-sm font-medium text-muted-foreground">{description}</div>
)} )}
</CardContent> </CardContent>
</Card> </Card>
@@ -252,19 +256,19 @@ export const AnalyticsDashboard = () => {
const CustomTooltip = ({ active, payload, label }) => { const CustomTooltip = ({ active, payload, label }) => {
if (active && payload && payload.length) { if (active && payload && payload.length) {
return ( return (
<Card className="p-3 shadow-lg bg-white dark:bg-gray-800 border-none"> <Card className="p-3 shadow-lg bg-white dark:bg-gray-900/60 backdrop-blur-sm border border-border">
<CardContent className="p-0 space-y-2"> <CardContent className="p-0 space-y-2">
<p className="font-medium text-sm border-b pb-1 mb-2"> <p className="font-medium text-sm border-b border-border pb-1.5 mb-2 text-foreground">
{label instanceof Date ? label.toLocaleDateString() : label} {label instanceof Date ? label.toLocaleDateString() : label}
</p> </p>
<div className="space-y-1"> <div className="space-y-1.5">
{payload.map((entry, index) => ( {payload.map((entry, index) => (
<div <div
key={index} key={index}
className="flex justify-between items-center text-sm" className="flex justify-between items-center text-sm"
> >
<span style={{ color: entry.color }}>{entry.name}:</span> <span className="font-medium" style={{ color: entry.color }}>{entry.name}:</span>
<span className="font-medium ml-4"> <span className="font-medium ml-4 text-foreground">
{entry.value.toLocaleString()} {entry.value.toLocaleString()}
</span> </span>
</div> </div>
@@ -282,12 +286,100 @@ export const AnalyticsDashboard = () => {
<CardHeader className="p-6 pb-4"> <CardHeader className="p-6 pb-4">
<div className="flex flex-col space-y-2"> <div className="flex flex-col space-y-2">
<div className="flex justify-between items-start"> <div className="flex justify-between items-start">
<div>
<CardTitle className="text-xl font-semibold text-gray-900 dark:text-gray-100"> <CardTitle className="text-xl font-semibold text-gray-900 dark:text-gray-100">
Analytics Overview Analytics Overview
</CardTitle> </CardTitle>
</div>
<div className="flex items-center gap-2">
{loading ? ( {loading ? (
<Skeleton className="h-9 w-[130px] dark:bg-gray-700" /> <Skeleton className="h-9 w-[130px] bg-muted rounded-sm" />
) : ( ) : (
<>
<Dialog>
<DialogTrigger asChild>
<Button variant="outline" className="h-9">
Details
</Button>
</DialogTrigger>
<DialogContent className="min-w-[600px] max-w-[90vw] w-fit max-h-[85vh] overflow-hidden flex flex-col bg-white dark:bg-gray-900/60 backdrop-blur-sm">
<DialogHeader className="flex-none">
<DialogTitle className="text-gray-900 dark:text-gray-100">Daily Details</DialogTitle>
<div className="flex items-center justify-center gap-2 pt-4">
<div className="flex flex-wrap gap-1">
{Object.entries(metrics).map(([key, value]) => (
<Button
key={key}
variant={value ? "default" : "outline"}
size="sm"
onClick={() =>
setMetrics((prev) => ({
...prev,
[key]: !prev[key],
}))
}
>
{key === "activeUsers" ? "Active Users" :
key === "newUsers" ? "New Users" :
key === "pageViews" ? "Page Views" :
"Conversions"}
</Button>
))}
</div>
</div>
</DialogHeader>
<div className="flex-1 overflow-y-auto mt-6">
<div className="rounded-lg border bg-white dark:bg-gray-900/60 backdrop-blur-sm w-full">
<Table className="w-full">
<TableHeader>
<TableRow>
<TableHead className="text-center whitespace-nowrap px-6 w-[120px]">Date</TableHead>
{metrics.activeUsers && (
<TableHead className="text-center whitespace-nowrap px-6 min-w-[100px]">Active Users</TableHead>
)}
{metrics.newUsers && (
<TableHead className="text-center whitespace-nowrap px-6 min-w-[100px]">New Users</TableHead>
)}
{metrics.pageViews && (
<TableHead className="text-center whitespace-nowrap px-6 min-w-[140px]">Page Views</TableHead>
)}
{metrics.conversions && (
<TableHead className="text-center whitespace-nowrap px-6 min-w-[120px]">Conversions</TableHead>
)}
</TableRow>
</TableHeader>
<TableBody>
{data.map((day) => (
<TableRow key={day.date}>
<TableCell className="text-center whitespace-nowrap px-6">{formatXAxis(day.date)}</TableCell>
{metrics.activeUsers && (
<TableCell className="text-center whitespace-nowrap px-6">
{day.activeUsers.toLocaleString()}
</TableCell>
)}
{metrics.newUsers && (
<TableCell className="text-center whitespace-nowrap px-6">
{day.newUsers.toLocaleString()}
</TableCell>
)}
{metrics.pageViews && (
<TableCell className="text-center whitespace-nowrap px-6">
{day.pageViews.toLocaleString()}
</TableCell>
)}
{metrics.conversions && (
<TableCell className="text-center whitespace-nowrap px-6">
{day.conversions.toLocaleString()}
</TableCell>
)}
</TableRow>
))}
</TableBody>
</Table>
</div>
</div>
</DialogContent>
</Dialog>
<Select value={timeRange} onValueChange={setTimeRange}> <Select value={timeRange} onValueChange={setTimeRange}>
<SelectTrigger className="w-[130px] h-9"> <SelectTrigger className="w-[130px] h-9">
<SelectValue placeholder="Select range" /> <SelectValue placeholder="Select range" />
@@ -299,8 +391,10 @@ export const AnalyticsDashboard = () => {
<SelectItem value="90">Last 90 days</SelectItem> <SelectItem value="90">Last 90 days</SelectItem>
</SelectContent> </SelectContent>
</Select> </Select>
</>
)} )}
</div> </div>
</div>
{loading ? ( {loading ? (
<SkeletonStats /> <SkeletonStats />
@@ -341,14 +435,12 @@ export const AnalyticsDashboard = () => {
</div> </div>
) : null} ) : null}
<div className="flex flex-col sm:flex-row gap-0 sm:gap-4 pt-2"> <div className="flex items-center flex-col sm:flex-row gap-0 sm:gap-4 pt-2">
{loading ? (
<SkeletonButtons />
) : (
<div className="flex flex-wrap gap-1"> <div className="flex flex-wrap gap-1">
<Button <Button
variant={metrics.activeUsers ? "default" : "outline"} variant={metrics.activeUsers ? "default" : "outline"}
size="sm" size="sm"
className="font-medium"
onClick={() => onClick={() =>
setMetrics((prev) => ({ setMetrics((prev) => ({
...prev, ...prev,
@@ -362,6 +454,7 @@ export const AnalyticsDashboard = () => {
<Button <Button
variant={metrics.newUsers ? "default" : "outline"} variant={metrics.newUsers ? "default" : "outline"}
size="sm" size="sm"
className="font-medium"
onClick={() => onClick={() =>
setMetrics((prev) => ({ setMetrics((prev) => ({
...prev, ...prev,
@@ -375,6 +468,7 @@ export const AnalyticsDashboard = () => {
<Button <Button
variant={metrics.pageViews ? "default" : "outline"} variant={metrics.pageViews ? "default" : "outline"}
size="sm" size="sm"
className="font-medium"
onClick={() => onClick={() =>
setMetrics((prev) => ({ setMetrics((prev) => ({
...prev, ...prev,
@@ -383,11 +477,12 @@ export const AnalyticsDashboard = () => {
} }
> >
<span className="hidden sm:inline">Page Views</span> <span className="hidden sm:inline">Page Views</span>
<span className="sm:hidden">Page Views</span> <span className="sm:hidden">Views</span>
</Button> </Button>
<Button <Button
variant={metrics.conversions ? "default" : "outline"} variant={metrics.conversions ? "default" : "outline"}
size="sm" size="sm"
className="font-medium"
onClick={() => onClick={() =>
setMetrics((prev) => ({ setMetrics((prev) => ({
...prev, ...prev,
@@ -396,10 +491,9 @@ export const AnalyticsDashboard = () => {
} }
> >
<span className="hidden sm:inline">Conversions</span> <span className="hidden sm:inline">Conversions</span>
<span className="sm:hidden">Conversions</span> <span className="sm:hidden">Conv.</span>
</Button> </Button>
</div> </div>
)}
</div> </div>
</div> </div>
</CardHeader> </CardHeader>
@@ -410,17 +504,19 @@ export const AnalyticsDashboard = () => {
) : !data.length ? ( ) : !data.length ? (
<div className="flex items-center justify-center h-[400px] text-muted-foreground"> <div className="flex items-center justify-center h-[400px] text-muted-foreground">
<div className="text-center"> <div className="text-center">
<TrendingUp className="h-12 w-12 mx-auto mb-4" /> <TrendingUp className="h-12 w-12 mx-auto mb-4 opacity-50" />
<div className="font-medium mb-2">No analytics data available</div> <div className="font-medium mb-2 text-gray-900 dark:text-gray-100">No analytics data available</div>
<div className="text-sm">Try selecting a different time range</div> <div className="text-sm text-muted-foreground">
Try selecting a different time range
</div>
</div> </div>
</div> </div>
) : ( ) : (
<div className="h-[400px] mt-4 bg-card rounded-lg p-0"> <div className="h-[400px] mt-4 bg-white dark:bg-gray-900/60 backdrop-blur-sm rounded-lg p-0 relative">
<ResponsiveContainer width="100%" height="100%"> <ResponsiveContainer width="100%" height="100%">
<LineChart <LineChart
data={data} data={data}
margin={{ top: 5, right: 0, left: -5, bottom: 5 }} margin={{ top: 5, right: -30, left: -5, bottom: 5 }}
> >
<CartesianGrid <CartesianGrid
strokeDasharray="3 3" strokeDasharray="3 3"
@@ -429,18 +525,18 @@ export const AnalyticsDashboard = () => {
<XAxis <XAxis
dataKey="date" dataKey="date"
tickFormatter={formatXAxis} tickFormatter={formatXAxis}
className="text-xs" className="text-xs text-muted-foreground"
tick={{ fill: "currentColor" }} tick={{ fill: "currentColor" }}
/> />
<YAxis <YAxis
yAxisId="left" yAxisId="left"
className="text-xs" className="text-xs text-muted-foreground"
tick={{ fill: "currentColor" }} tick={{ fill: "currentColor" }}
/> />
<YAxis <YAxis
yAxisId="right" yAxisId="right"
orientation="right" orientation="right"
className="text-xs" className="text-xs text-muted-foreground"
tick={{ fill: "currentColor" }} tick={{ fill: "currentColor" }}
/> />
<Tooltip content={<CustomTooltip />} /> <Tooltip content={<CustomTooltip />} />

View File

@@ -148,24 +148,22 @@ const LoadingState = () => (
<div className="shrink-0"> <div className="shrink-0">
<Skeleton className="h-10 w-10 rounded-full bg-muted" /> <Skeleton className="h-10 w-10 rounded-full bg-muted" />
</div> </div>
<div className="flex-1 min-w-0"> <div className="flex-1 min-w-0 space-y-2">
<div className="space-y-2">
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<Skeleton className="h-4 w-24 bg-muted" /> <Skeleton className="h-4 w-24 bg-muted rounded-sm" />
</div> </div>
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<Skeleton className="h-4 w-48 bg-muted" /> <Skeleton className="h-4 w-48 bg-muted rounded-sm" />
</div> </div>
<div className="flex gap-1.5 items-center flex-wrap"> <div className="flex gap-1.5 items-center flex-wrap">
<Skeleton className="h-5 w-16 rounded-full bg-muted" /> <Skeleton className="h-5 w-16 bg-muted rounded-md" />
<Skeleton className="h-5 w-20 rounded-full bg-muted" /> <Skeleton className="h-5 w-20 bg-muted rounded-md" />
<Skeleton className="h-5 w-14 rounded-full bg-muted" /> <Skeleton className="h-5 w-14 bg-muted rounded-md" />
</div>
</div> </div>
</div> </div>
<div className="flex items-center gap-2 shrink-0"> <div className="flex items-center gap-2 shrink-0">
<Skeleton className="h-4 w-16 bg-muted" /> <Skeleton className="h-4 w-16 bg-muted rounded-sm" />
<Skeleton className="h-4 w-4 bg-muted" /> <Skeleton className="h-4 w-4 bg-muted rounded-full" />
</div> </div>
</div> </div>
))} ))}
@@ -174,14 +172,14 @@ const LoadingState = () => (
// Empty State Component // Empty State Component
const EmptyState = () => ( const EmptyState = () => (
<div className="h-full flex flex-col items-center justify-center py-16 px-4"> <div className="h-full flex flex-col items-center justify-center py-16 px-4 text-center">
<div className="bg-gray-100 dark:bg-gray-800 rounded-full p-3 mb-4"> <div className="bg-gray-100 dark:bg-gray-800 rounded-full p-3 mb-4">
<Activity className="h-8 w-8 text-gray-400 dark:text-gray-500" /> <Activity className="h-8 w-8 text-muted-foreground" />
</div> </div>
<h3 className="text-lg font-medium text-gray-900 dark:text-gray-100 mb-2"> <h3 className="text-lg font-medium text-gray-900 dark:text-gray-100 mb-2">
No activity yet today No activity yet today
</h3> </h3>
<p className="text-sm text-gray-500 dark:text-gray-400 text-center max-w-sm"> <p className="text-sm text-muted-foreground max-w-sm">
Recent activity will appear here as it happens Recent activity will appear here as it happens
</p> </p>
</div> </div>
@@ -1402,18 +1400,18 @@ const EventFeed = ({
return ( return (
<Card className="flex flex-col h-full bg-white dark:bg-gray-900/60 backdrop-blur-sm w-full"> <Card className="flex flex-col h-full bg-white dark:bg-gray-900/60 backdrop-blur-sm w-full">
<CardHeader className="p-6"> <CardHeader className="p-6 pb-2">
<div className="flex justify-between items-start"> <div className="flex justify-between items-start">
<div> <div>
<CardTitle className="text-xl font-semibold text-gray-900 dark:text-gray-100">{title}</CardTitle> <CardTitle className="text-xl font-semibold text-gray-900 dark:text-gray-100">{title}</CardTitle>
{lastUpdate && ( {lastUpdate && (
<CardDescription className="text-xs"> <CardDescription className="text-sm text-muted-foreground">
Last updated {format(lastUpdate, "h:mm a")} Last updated {format(lastUpdate, "h:mm a")}
</CardDescription> </CardDescription>
)} )}
</div> </div>
{!error && ( {!error && (
<div className="flex flex-wrap gap-1"> <div className="flex flex-wrap gap-2">
<TooltipProvider> <TooltipProvider>
<Tooltip> <Tooltip>
<TooltipTrigger asChild> <TooltipTrigger asChild>
@@ -1421,7 +1419,7 @@ const EventFeed = ({
variant={activeEventTypes[METRIC_IDS.PLACED_ORDER] ? "default" : "outline"} variant={activeEventTypes[METRIC_IDS.PLACED_ORDER] ? "default" : "outline"}
size="sm" size="sm"
onClick={() => handleEventTypeClick(METRIC_IDS.PLACED_ORDER)} onClick={() => handleEventTypeClick(METRIC_IDS.PLACED_ORDER)}
className="h-8 w-8 p-0" className="h-8 w-8 p-0 rounded-md"
> >
<Package className="h-4 w-4" /> <Package className="h-4 w-4" />
</Button> </Button>
@@ -1439,7 +1437,7 @@ const EventFeed = ({
variant={activeEventTypes[METRIC_IDS.SHIPPED_ORDER] ? "default" : "outline"} variant={activeEventTypes[METRIC_IDS.SHIPPED_ORDER] ? "default" : "outline"}
size="sm" size="sm"
onClick={() => handleEventTypeClick(METRIC_IDS.SHIPPED_ORDER)} onClick={() => handleEventTypeClick(METRIC_IDS.SHIPPED_ORDER)}
className="h-8 w-8 p-0" className="h-8 w-8 p-0 rounded-md"
> >
<Truck className="h-4 w-4" /> <Truck className="h-4 w-4" />
</Button> </Button>
@@ -1457,7 +1455,7 @@ const EventFeed = ({
variant={activeEventTypes[METRIC_IDS.ACCOUNT_CREATED] ? "default" : "outline"} variant={activeEventTypes[METRIC_IDS.ACCOUNT_CREATED] ? "default" : "outline"}
size="sm" size="sm"
onClick={() => handleEventTypeClick(METRIC_IDS.ACCOUNT_CREATED)} onClick={() => handleEventTypeClick(METRIC_IDS.ACCOUNT_CREATED)}
className="h-8 w-8 p-0" className="h-8 w-8 p-0 rounded-md"
> >
<UserPlus className="h-4 w-4" /> <UserPlus className="h-4 w-4" />
</Button> </Button>
@@ -1475,7 +1473,7 @@ const EventFeed = ({
variant={activeEventTypes[METRIC_IDS.CANCELED_ORDER] ? "default" : "outline"} variant={activeEventTypes[METRIC_IDS.CANCELED_ORDER] ? "default" : "outline"}
size="sm" size="sm"
onClick={() => handleEventTypeClick(METRIC_IDS.CANCELED_ORDER)} onClick={() => handleEventTypeClick(METRIC_IDS.CANCELED_ORDER)}
className="h-8 w-8 p-0" className="h-8 w-8 p-0 rounded-md"
> >
<XCircle className="h-4 w-4" /> <XCircle className="h-4 w-4" />
</Button> </Button>
@@ -1493,7 +1491,7 @@ const EventFeed = ({
variant={activeEventTypes[METRIC_IDS.PAYMENT_REFUNDED] ? "default" : "outline"} variant={activeEventTypes[METRIC_IDS.PAYMENT_REFUNDED] ? "default" : "outline"}
size="sm" size="sm"
onClick={() => handleEventTypeClick(METRIC_IDS.PAYMENT_REFUNDED)} onClick={() => handleEventTypeClick(METRIC_IDS.PAYMENT_REFUNDED)}
className="h-8 w-8 p-0" className="h-8 w-8 p-0 rounded-md"
> >
<DollarSign className="h-4 w-4" /> <DollarSign className="h-4 w-4" />
</Button> </Button>
@@ -1511,7 +1509,7 @@ const EventFeed = ({
variant={activeEventTypes[METRIC_IDS.NEW_BLOG_POST] ? "default" : "outline"} variant={activeEventTypes[METRIC_IDS.NEW_BLOG_POST] ? "default" : "outline"}
size="sm" size="sm"
onClick={() => handleEventTypeClick(METRIC_IDS.NEW_BLOG_POST)} onClick={() => handleEventTypeClick(METRIC_IDS.NEW_BLOG_POST)}
className="h-8 w-8 p-0" className="h-8 w-8 p-0 rounded-md"
> >
<FileText className="h-4 w-4" /> <FileText className="h-4 w-4" />
</Button> </Button>
@@ -1525,7 +1523,7 @@ const EventFeed = ({
)} )}
</div> </div>
{/* Order Property Filters - only show if not in error state */} {/* Order Property Filters - update styling */}
{!error && ( {!error && (
<div className="flex flex-wrap gap-2 justify-center mt-4 pt-1"> <div className="flex flex-wrap gap-2 justify-center mt-4 pt-1">
{counts.orderProperties.hasPreorder > 0 && ( {counts.orderProperties.hasPreorder > 0 && (
@@ -1536,7 +1534,7 @@ const EventFeed = ({
orderFilters.hasPreorder orderFilters.hasPreorder
? 'bg-purple-800 text-purple-200 hover:bg-purple-700' ? 'bg-purple-800 text-purple-200 hover:bg-purple-700'
: 'bg-purple-100 dark:bg-purple-900/20 text-purple-800 dark:text-purple-300 hover:bg-purple-100 dark:hover:bg-purple-900/20' : 'bg-purple-100 dark:bg-purple-900/20 text-purple-800 dark:text-purple-300 hover:bg-purple-100 dark:hover:bg-purple-900/20'
} cursor-pointer`} } cursor-pointer rounded-md`}
> >
Pre-order ({counts.orderProperties.hasPreorder}) Pre-order ({counts.orderProperties.hasPreorder})
</Badge> </Badge>
@@ -1549,7 +1547,7 @@ const EventFeed = ({
orderFilters.localPickup orderFilters.localPickup
? 'bg-green-800 text-green-200 hover:bg-green-700' ? 'bg-green-800 text-green-200 hover:bg-green-700'
: 'bg-green-100 dark:bg-green-900/20 text-green-800 dark:text-green-300 hover:bg-green-100 dark:hover:bg-green-900/20' : 'bg-green-100 dark:bg-green-900/20 text-green-800 dark:text-green-300 hover:bg-green-100 dark:hover:bg-green-900/20'
} cursor-pointer`} } cursor-pointer rounded-md`}
> >
Local ({counts.orderProperties.localPickup}) Local ({counts.orderProperties.localPickup})
</Badge> </Badge>
@@ -1562,7 +1560,7 @@ const EventFeed = ({
orderFilters.isOnHold orderFilters.isOnHold
? 'bg-blue-800 text-blue-200 hover:bg-blue-700' ? 'bg-blue-800 text-blue-200 hover:bg-blue-700'
: 'bg-blue-100 dark:bg-blue-900/20 text-blue-800 dark:text-blue-300 hover:bg-blue-100 dark:hover:bg-blue-900/20' : 'bg-blue-100 dark:bg-blue-900/20 text-blue-800 dark:text-blue-300 hover:bg-blue-100 dark:hover:bg-blue-900/20'
} cursor-pointer`} } cursor-pointer rounded-md`}
> >
On Hold ({counts.orderProperties.isOnHold}) On Hold ({counts.orderProperties.isOnHold})
</Badge> </Badge>
@@ -1575,7 +1573,7 @@ const EventFeed = ({
orderFilters.onHoldReleased orderFilters.onHoldReleased
? 'bg-green-800 text-green-200 hover:bg-green-700' ? 'bg-green-800 text-green-200 hover:bg-green-700'
: 'bg-green-100 dark:bg-green-900/20 text-green-800 dark:text-green-300 hover:bg-green-100 dark:hover:bg-green-900/20' : 'bg-green-100 dark:bg-green-900/20 text-green-800 dark:text-green-300 hover:bg-green-100 dark:hover:bg-green-900/20'
} cursor-pointer`} } cursor-pointer rounded-md`}
> >
Hold Released ({counts.orderProperties.onHoldReleased}) Hold Released ({counts.orderProperties.onHoldReleased})
</Badge> </Badge>
@@ -1588,7 +1586,7 @@ const EventFeed = ({
orderFilters.hasDigiItem orderFilters.hasDigiItem
? 'bg-indigo-800 text-indigo-200 hover:bg-indigo-700' ? 'bg-indigo-800 text-indigo-200 hover:bg-indigo-700'
: 'bg-indigo-100 dark:bg-indigo-900/20 text-indigo-800 dark:text-indigo-300 hover:bg-indigo-100 dark:hover:bg-indigo-900/20' : 'bg-indigo-100 dark:bg-indigo-900/20 text-indigo-800 dark:text-indigo-300 hover:bg-indigo-100 dark:hover:bg-indigo-900/20'
} cursor-pointer`} } cursor-pointer rounded-md`}
> >
Digital ({counts.orderProperties.hasDigiItem}) Digital ({counts.orderProperties.hasDigiItem})
</Badge> </Badge>
@@ -1601,7 +1599,7 @@ const EventFeed = ({
orderFilters.hasNotions orderFilters.hasNotions
? 'bg-yellow-800 text-yellow-200 hover:bg-yellow-700' ? 'bg-yellow-800 text-yellow-200 hover:bg-yellow-700'
: 'bg-yellow-100 dark:bg-yellow-900/20 text-yellow-800 dark:text-yellow-300 hover:bg-yellow-100 dark:hover:bg-yellow-900/20' : 'bg-yellow-100 dark:bg-yellow-900/20 text-yellow-800 dark:text-yellow-300 hover:bg-yellow-100 dark:hover:bg-yellow-900/20'
} cursor-pointer`} } cursor-pointer rounded-md`}
> >
Notions ({counts.orderProperties.hasNotions}) Notions ({counts.orderProperties.hasNotions})
</Badge> </Badge>
@@ -1614,7 +1612,7 @@ const EventFeed = ({
orderFilters.hasGiftCard orderFilters.hasGiftCard
? 'bg-pink-800 text-pink-200 hover:bg-pink-700' ? 'bg-pink-800 text-pink-200 hover:bg-pink-700'
: 'bg-pink-100 dark:bg-pink-900/20 text-pink-800 dark:text-pink-300 hover:bg-pink-100 dark:hover:bg-pink-900/20' : 'bg-pink-100 dark:bg-pink-900/20 text-pink-800 dark:text-pink-300 hover:bg-pink-100 dark:hover:bg-pink-900/20'
} cursor-pointer`} } cursor-pointer rounded-md`}
> >
eGift Card ({counts.orderProperties.hasGiftCard}) eGift Card ({counts.orderProperties.hasGiftCard})
</Badge> </Badge>
@@ -1627,7 +1625,7 @@ const EventFeed = ({
orderFilters.stillOwes orderFilters.stillOwes
? 'bg-red-800 text-red-200 hover:bg-red-700' ? 'bg-red-800 text-red-200 hover:bg-red-700'
: 'bg-red-100 dark:bg-red-900/20 text-red-800 dark:text-red-300 hover:bg-red-100 dark:hover:bg-red-900/20' : 'bg-red-100 dark:bg-red-900/20 text-red-800 dark:text-red-300 hover:bg-red-100 dark:hover:bg-red-900/20'
} cursor-pointer`} } cursor-pointer rounded-md`}
> >
Owes ({counts.orderProperties.stillOwes}) Owes ({counts.orderProperties.stillOwes})
</Badge> </Badge>
@@ -1641,7 +1639,7 @@ const EventFeed = ({
{loading && !events.length ? ( {loading && !events.length ? (
<LoadingState /> <LoadingState />
) : error ? ( ) : error ? (
<Alert variant="destructive" className="mt-1" > <Alert variant="destructive" className="mt-1 mx-6">
<AlertCircle className="h-4 w-4" /> <AlertCircle className="h-4 w-4" />
<AlertTitle>Error</AlertTitle> <AlertTitle>Error</AlertTitle>
<AlertDescription> <AlertDescription>
@@ -1649,17 +1647,7 @@ const EventFeed = ({
</AlertDescription> </AlertDescription>
</Alert> </Alert>
) : !filteredEvents || filteredEvents.length === 0 ? ( ) : !filteredEvents || filteredEvents.length === 0 ? (
<div className="h-full flex flex-col items-center justify-center py-16 px-4"> <EmptyState />
<div className="bg-gray-100 dark:bg-gray-800 rounded-full p-3 mb-4">
<Activity className="h-8 w-8 text-muted-foreground" />
</div>
<h3 className="text-lg font-medium text-gray-900 dark:text-gray-100 mb-2">
No activity yet today
</h3>
<p className="text-sm text-muted-foreground text-center max-w-sm">
Recent activity will appear here as it happens
</p>
</div>
) : ( ) : (
<div className="divide-y divide-gray-100 dark:divide-gray-800"> <div className="divide-y divide-gray-100 dark:divide-gray-800">
{filteredEvents.map((event) => ( {filteredEvents.map((event) => (

View File

@@ -168,13 +168,13 @@ const SkeletonMetricCard = () => (
<CardContent className="pt-6 h-full"> <CardContent className="pt-6 h-full">
<div className="flex justify-between items-start"> <div className="flex justify-between items-start">
<div className="flex-1 min-w-0"> <div className="flex-1 min-w-0">
<Skeleton className="h-4 w-24 mb-4 dark:bg-gray-700" /> <Skeleton className="h-4 w-24 mb-4 bg-muted" />
<div className="flex items-baseline gap-2"> <div className="flex items-baseline gap-2">
<Skeleton className="h-8 w-20 dark:bg-gray-700" /> <Skeleton className="h-8 w-20 bg-muted" />
<Skeleton className="h-4 w-12 dark:bg-gray-700" /> <Skeleton className="h-4 w-12 bg-muted" />
</div> </div>
</div> </div>
<Skeleton className="h-5 w-5 rounded-full dark:bg-gray-700" /> <Skeleton className="h-5 w-5 rounded-full bg-muted" />
</div> </div>
</CardContent> </CardContent>
</Card> </Card>
@@ -184,19 +184,19 @@ const TableSkeleton = () => (
<Table> <Table>
<TableHeader> <TableHeader>
<TableRow className="dark:border-gray-800"> <TableRow className="dark:border-gray-800">
<TableHead><Skeleton className="h-4 w-24 dark:bg-gray-700" /></TableHead> <TableHead><Skeleton className="h-4 w-24 bg-muted" /></TableHead>
<TableHead className="text-right"><Skeleton className="h-4 w-16 ml-auto dark:bg-gray-700" /></TableHead> <TableHead className="text-right"><Skeleton className="h-4 w-16 ml-auto bg-muted" /></TableHead>
<TableHead className="text-right"><Skeleton className="h-4 w-16 ml-auto dark:bg-gray-700" /></TableHead> <TableHead className="text-right"><Skeleton className="h-4 w-16 ml-auto bg-muted" /></TableHead>
<TableHead className="text-right"><Skeleton className="h-4 w-16 ml-auto dark:bg-gray-700" /></TableHead> <TableHead className="text-right"><Skeleton className="h-4 w-16 ml-auto bg-muted" /></TableHead>
</TableRow> </TableRow>
</TableHeader> </TableHeader>
<TableBody> <TableBody>
{[...Array(5)].map((_, i) => ( {[...Array(5)].map((_, i) => (
<TableRow key={i} className="dark:border-gray-800"> <TableRow key={i} className="dark:border-gray-800">
<TableCell><Skeleton className="h-4 w-32 dark:bg-gray-700" /></TableCell> <TableCell><Skeleton className="h-4 w-32 bg-muted" /></TableCell>
<TableCell className="text-right"><Skeleton className="h-4 w-12 ml-auto dark:bg-gray-700" /></TableCell> <TableCell className="text-right"><Skeleton className="h-4 w-12 ml-auto bg-muted" /></TableCell>
<TableCell className="text-right"><Skeleton className="h-4 w-12 ml-auto dark:bg-gray-700" /></TableCell> <TableCell className="text-right"><Skeleton className="h-4 w-12 ml-auto bg-muted" /></TableCell>
<TableCell className="text-right"><Skeleton className="h-4 w-16 ml-auto dark:bg-gray-700" /></TableCell> <TableCell className="text-right"><Skeleton className="h-4 w-16 ml-auto bg-muted" /></TableCell>
</TableRow> </TableRow>
))} ))}
</TableBody> </TableBody>
@@ -326,13 +326,23 @@ const GorgiasOverview = () => {
console.log('Processed agents:', agents); console.log('Processed agents:', agents);
if (error) return <p className="text-red-500">Error: {error}</p>; if (error) {
return (
<Card className="h-full bg-white dark:bg-gray-900/60 backdrop-blur-sm">
<CardContent className="p-4">
<div className="p-4 m-6 text-red-600 dark:text-red-400 bg-red-50 dark:bg-red-900/10 rounded-lg border border-red-200 dark:border-red-900/20">
{error}
</div>
</CardContent>
</Card>
);
}
return ( return (
<Card className="bg-white dark:bg-gray-900"> <Card className="h-full bg-white dark:bg-gray-900/60 backdrop-blur-sm">
<CardHeader> <CardHeader>
<div className="flex justify-between items-center"> <div className="flex justify-between items-center">
<h2 className="text-xl font-bold text-gray-900 dark:text-gray-100"> <h2 className="text-xl font-semibold text-gray-900 dark:text-gray-100">
Customer Service Customer Service
</h2> </h2>
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
@@ -414,10 +424,6 @@ const GorgiasOverview = () => {
loading={loading} loading={loading}
/> />
</div> </div>
</>
)}
{/* Satisfaction & Efficiency */}
<div className="h-full"> <div className="h-full">
<MetricCard <MetricCard
title="Customer Satisfaction" title="Customer Satisfaction"
@@ -462,40 +468,43 @@ const GorgiasOverview = () => {
loading={loading} loading={loading}
/> />
</div> </div>
</>
)}
</div> </div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4"> <div className="grid grid-cols-1 md:grid-cols-2 gap-4">
{/* Channel Distribution */} {/* Channel Distribution */}
<div className="bg-white dark:bg-gray-900 rounded-lg border dark:border-gray-800 p-4 pt-0"> <Card className="bg-white dark:bg-gray-900/60 backdrop-blur-sm">
<div className="p-4 pl-2"> <CardHeader className="pb-0">
<h3 className="text-lg font-semibold text-gray-900 dark:text-gray-100"> <h3 className="text-lg font-semibold text-gray-900 dark:text-gray-100">
Channel Distribution Channel Distribution
</h3> </h3>
</div> </CardHeader>
<CardContent className="overflow-y-auto max-h-[400px] scrollbar-thin scrollbar-thumb-gray-200 dark:scrollbar-thumb-gray-700 scrollbar-track-transparent hover:scrollbar-thumb-gray-300 dark:hover:scrollbar-thumb-gray-600">
{loading ? ( {loading ? (
<TableSkeleton /> <TableSkeleton />
) : ( ) : (
<Table> <Table>
<TableHeader> <TableHeader>
<TableRow className="dark:border-gray-800"> <TableRow className="dark:border-gray-800">
<TableHead>Channel</TableHead> <TableHead className="text-left font-medium text-gray-900 dark:text-gray-100">Channel</TableHead>
<TableHead className="text-right">Total</TableHead> <TableHead className="text-right font-medium text-gray-900 dark:text-gray-100">Total</TableHead>
<TableHead className="text-right">%</TableHead> <TableHead className="text-right font-medium text-gray-900 dark:text-gray-100">%</TableHead>
<TableHead className="text-right">Change</TableHead> <TableHead className="text-right font-medium text-gray-900 dark:text-gray-100">Change</TableHead>
</TableRow> </TableRow>
</TableHeader> </TableHeader>
<TableBody> <TableBody>
{channels {channels
.sort((a, b) => b.total - a.total) .sort((a, b) => b.total - a.total)
.map((channel, index) => ( .map((channel, index) => (
<TableRow key={index} className="dark:border-gray-800"> <TableRow key={index} className="dark:border-gray-800 hover:bg-muted/50 transition-colors">
<TableCell className="dark:text-gray-300"> <TableCell className="text-gray-900 dark:text-gray-100">
{channel.name} {channel.name}
</TableCell> </TableCell>
<TableCell className="text-right dark:text-gray-300"> <TableCell className="text-right text-muted-foreground">
{channel.total} {channel.total}
</TableCell> </TableCell>
<TableCell className="text-right dark:text-gray-300"> <TableCell className="text-right text-muted-foreground">
{channel.percentage}% {channel.percentage}%
</TableCell> </TableCell>
<TableCell <TableCell
@@ -504,7 +513,7 @@ const GorgiasOverview = () => {
? "text-green-600 dark:text-green-500" ? "text-green-600 dark:text-green-500"
: channel.delta < 0 : channel.delta < 0
? "text-red-600 dark:text-red-500" ? "text-red-600 dark:text-red-500"
: "dark:text-gray-300" : "text-muted-foreground"
}`} }`}
> >
<div className="flex items-center justify-end gap-0.5"> <div className="flex items-center justify-end gap-0.5">
@@ -525,39 +534,41 @@ const GorgiasOverview = () => {
</TableBody> </TableBody>
</Table> </Table>
)} )}
</div> </CardContent>
</Card>
{/* Agent Performance */} {/* Agent Performance */}
<div className="bg-white dark:bg-gray-900 rounded-lg border dark:border-gray-800 p-4 pt-0"> <Card className="bg-white dark:bg-gray-900/60 backdrop-blur-sm">
<div className="p-4 pl-2"> <CardHeader className="pb-0">
<h3 className="text-lg font-semibold text-gray-900 dark:text-gray-100"> <h3 className="text-lg font-semibold text-gray-900 dark:text-gray-100">
Agent Performance Agent Performance
</h3> </h3>
</div> </CardHeader>
<CardContent className="overflow-y-auto max-h-[400px] scrollbar-thin scrollbar-thumb-gray-200 dark:scrollbar-thumb-gray-700 scrollbar-track-transparent hover:scrollbar-thumb-gray-300 dark:hover:scrollbar-thumb-gray-600">
{loading ? ( {loading ? (
<TableSkeleton /> <TableSkeleton />
) : ( ) : (
<Table> <Table>
<TableHeader> <TableHeader>
<TableRow className="dark:border-gray-800"> <TableRow className="dark:border-gray-800">
<TableHead>Agent</TableHead> <TableHead className="text-left font-medium text-gray-900 dark:text-gray-100">Agent</TableHead>
<TableHead className="text-right">Closed</TableHead> <TableHead className="text-right font-medium text-gray-900 dark:text-gray-100">Closed</TableHead>
<TableHead className="text-right">Rating</TableHead> <TableHead className="text-right font-medium text-gray-900 dark:text-gray-100">Rating</TableHead>
<TableHead className="text-right">Change</TableHead> <TableHead className="text-right font-medium text-gray-900 dark:text-gray-100">Change</TableHead>
</TableRow> </TableRow>
</TableHeader> </TableHeader>
<TableBody> <TableBody>
{agents {agents
.filter((agent) => agent.name !== "Unassigned") .filter((agent) => agent.name !== "Unassigned")
.map((agent, index) => ( .map((agent, index) => (
<TableRow key={index} className="dark:border-gray-800"> <TableRow key={index} className="dark:border-gray-800 hover:bg-muted/50 transition-colors">
<TableCell className="dark:text-gray-300"> <TableCell className="text-gray-900 dark:text-gray-100">
{agent.name} {agent.name}
</TableCell> </TableCell>
<TableCell className="text-right dark:text-gray-300"> <TableCell className="text-right text-muted-foreground">
{agent.closed} {agent.closed}
</TableCell> </TableCell>
<TableCell className="text-right dark:text-gray-300"> <TableCell className="text-right text-muted-foreground">
{agent.rating ? `${agent.rating}/5` : "-"} {agent.rating ? `${agent.rating}/5` : "-"}
</TableCell> </TableCell>
<TableCell <TableCell
@@ -566,7 +577,7 @@ const GorgiasOverview = () => {
? "text-green-600 dark:text-green-500" ? "text-green-600 dark:text-green-500"
: agent.delta < 0 : agent.delta < 0
? "text-red-600 dark:text-red-500" ? "text-red-600 dark:text-red-500"
: "dark:text-gray-300" : "text-muted-foreground"
}`} }`}
> >
<div className="flex items-center justify-end gap-0.5"> <div className="flex items-center justify-end gap-0.5">
@@ -587,7 +598,8 @@ const GorgiasOverview = () => {
</TableBody> </TableBody>
</Table> </Table>
)} )}
</div> </CardContent>
</Card>
</div> </div>
</CardContent> </CardContent>
</Card> </Card>

View File

@@ -84,34 +84,26 @@ const summaryCard = (label, value, options = {}) => {
); );
}; };
const MetricCell = ({ const MetricCell = ({ value, label, sublabel, isMonetary = false, isPercentage = false, decimalPlaces = 0 }) => {
value, const formattedValue = isMonetary
label,
sublabel,
isMonetary = false,
isPercentage = false,
decimalPlaces = 0,
}) => (
<td className="p-2 text-center align-top">
<div className="text-blue-600 dark:text-blue-400 text-center font-semibold">
{isMonetary
? formatCurrency(value, decimalPlaces) ? formatCurrency(value, decimalPlaces)
: isPercentage : isPercentage
? formatPercent(value, decimalPlaces) ? formatPercent(value, decimalPlaces)
: formatNumber(value, decimalPlaces)} : formatNumber(value, decimalPlaces);
return (
<td className="p-2 text-center align-top">
<div className="text-blue-600 dark:text-blue-400 text-lg font-semibold">
{formattedValue}
</div> </div>
{label && ( {(label || sublabel) && (
<div className="text-xs text-center text-gray-500 dark:text-gray-400"> <div className="text-muted-foreground text-sm">
{label} {label || sublabel}
</div>
)}
{sublabel && (
<div className="text-xs text-center text-gray-500 dark:text-gray-400">
{sublabel}
</div> </div>
)} )}
</td> </td>
); );
};
const getActionValue = (campaign, actionType) => { const getActionValue = (campaign, actionType) => {
if (actionType === "impressions" || actionType === "reach") { if (actionType === "impressions" || actionType === "reach") {
@@ -256,47 +248,50 @@ const SkeletonMetricCard = () => (
<CardContent className="pt-6 h-full"> <CardContent className="pt-6 h-full">
<div className="flex justify-between items-start"> <div className="flex justify-between items-start">
<div className="flex-1 min-w-0"> <div className="flex-1 min-w-0">
<Skeleton className="h-4 w-24 mb-4 dark:bg-gray-700" /> <Skeleton className="h-4 w-24 mb-4 bg-muted" />
<div className="flex items-baseline gap-2"> <div className="flex items-baseline gap-2">
<Skeleton className="h-8 w-20 dark:bg-gray-700" /> <Skeleton className="h-8 w-20 bg-muted" />
</div> </div>
</div> </div>
<Skeleton className="h-5 w-5 rounded-full dark:bg-gray-700" /> <Skeleton className="h-5 w-5 rounded-full bg-muted" />
</div> </div>
</CardContent> </CardContent>
</Card> </Card>
); );
const SkeletonTable = () => ( const SkeletonTable = () => (
<div className="grid overflow-x-auto"> <div className="h-full max-h-[400px] overflow-y-auto scrollbar-thin scrollbar-thumb-gray-200 dark:scrollbar-thumb-gray-700 scrollbar-track-transparent hover:scrollbar-thumb-gray-300 dark:hover:scrollbar-thumb-gray-600 pr-2">
<div className="overflow-y-auto max-h-[400px]">
<table className="min-w-full"> <table className="min-w-full">
<thead> <thead>
<tr className="border-b border-gray-200 dark:border-gray-800"> <tr className="border-b border-gray-200 dark:border-gray-800">
<th className="p-2 sticky top-0 bg-white dark:bg-gray-900 z-10"> <th className="p-2 sticky top-0 bg-white dark:bg-gray-900/60 backdrop-blur-sm z-10">
<Skeleton className="h-4 w-32 dark:bg-gray-700" /> <Skeleton className="h-4 w-32 bg-muted" />
</th> </th>
{[...Array(8)].map((_, i) => ( {[...Array(8)].map((_, i) => (
<th key={i} className="p-2 text-center sticky top-0 bg-white dark:bg-gray-900 z-10"> <th key={i} className="p-2 text-center sticky top-0 bg-white dark:bg-gray-900/60 backdrop-blur-sm z-10">
<Skeleton className="h-4 w-20 mx-auto dark:bg-gray-700" /> <Skeleton className="h-4 w-20 mx-auto bg-muted" />
</th> </th>
))} ))}
</tr> </tr>
</thead> </thead>
<tbody className="divide-y divide-gray-200 dark:divide-gray-800"> <tbody className="divide-y divide-gray-200 dark:divide-gray-800">
{[...Array(5)].map((_, rowIndex) => ( {[...Array(5)].map((_, rowIndex) => (
<tr key={rowIndex}> <tr key={rowIndex} className="hover:bg-muted/50 transition-colors">
<td className="p-2"> <td className="p-2">
<div className="flex items-center gap-2">
<Skeleton className="h-4 w-4 bg-muted" />
<div className="space-y-2"> <div className="space-y-2">
<Skeleton className="h-4 w-48 dark:bg-gray-700" /> <Skeleton className="h-4 w-48 bg-muted" />
<Skeleton className="h-3 w-24 dark:bg-gray-700" /> <Skeleton className="h-3 w-64 bg-muted" />
<Skeleton className="h-3 w-32 bg-muted" />
</div>
</div> </div>
</td> </td>
{[...Array(8)].map((_, colIndex) => ( {[...Array(8)].map((_, colIndex) => (
<td key={colIndex} className="p-2 text-center"> <td key={colIndex} className="p-2 text-center">
<div className="space-y-1"> <div className="flex flex-col items-center gap-1">
<Skeleton className="h-4 w-16 mx-auto dark:bg-gray-700" /> <Skeleton className="h-4 w-16 bg-muted" />
<Skeleton className="h-3 w-12 mx-auto dark:bg-gray-700" /> <Skeleton className="h-3 w-24 bg-muted" />
</div> </div>
</td> </td>
))} ))}
@@ -305,7 +300,6 @@ const SkeletonTable = () => (
</tbody> </tbody>
</table> </table>
</div> </div>
</div>
); );
const MetaCampaigns = () => { const MetaCampaigns = () => {
@@ -449,7 +443,7 @@ const MetaCampaigns = () => {
if (loading) { if (loading) {
return ( return (
<Card className="bg-white dark:bg-gray-900"> <Card className="h-full bg-white dark:bg-gray-900/60 backdrop-blur-sm">
<CardHeader className="pb-2"> <CardHeader className="pb-2">
<div className="flex justify-between items-start mb-6"> <div className="flex justify-between items-start mb-6">
<CardTitle className="text-xl font-semibold text-gray-900 dark:text-gray-100"> <CardTitle className="text-xl font-semibold text-gray-900 dark:text-gray-100">
@@ -479,16 +473,18 @@ const MetaCampaigns = () => {
if (error) { if (error) {
return ( return (
<Card className="bg-white dark:bg-gray-900"> <Card className="h-full bg-white dark:bg-gray-900/60 backdrop-blur-sm">
<CardContent className="p-4"> <CardContent className="p-4">
<div className="text-red-500 dark:text-red-400">{error}</div> <div className="p-4 m-6 text-red-600 dark:text-red-400 bg-red-50 dark:bg-red-900/10 rounded-lg border border-red-200 dark:border-red-900/20">
{error}
</div>
</CardContent> </CardContent>
</Card> </Card>
); );
} }
return ( return (
<Card className="h-full bg-white dark:bg-gray-900"> <Card className="h-full bg-white dark:bg-gray-900/60 backdrop-blur-sm">
<CardHeader className="pb-2"> <CardHeader className="pb-2">
<div className="flex justify-between items-start mb-6"> <div className="flex justify-between items-start mb-6">
<CardTitle className="text-xl font-semibold text-gray-900 dark:text-gray-100"> <CardTitle className="text-xl font-semibold text-gray-900 dark:text-gray-100">
@@ -577,88 +573,86 @@ const MetaCampaigns = () => {
</div> </div>
</CardHeader> </CardHeader>
<CardContent className="p-4"> <CardContent className="overflow-y-auto pl-4 max-h-[400px] mb-4">
<div className="grid overflow-x-auto"> <table className="w-full">
<div className="overflow-y-auto max-h-[400px]">
<table className="min-w-full">
<thead> <thead>
<tr className="border-b border-gray-200 dark:border-gray-800"> <tr className="border-b border-gray-200 dark:border-gray-800">
<th className="p-2 sticky top-0 bg-white dark:bg-gray-900 z-10 text-gray-900 dark:text-gray-100"> <th className="p-2 text-left font-medium sticky top-0 bg-white dark:bg-gray-900/60 backdrop-blur-sm z-10 text-gray-900 dark:text-gray-100">
<Button <Button
variant="ghost" variant="ghost"
className="pl-0 justify-start w-full" className="pl-0 justify-start w-full h-8"
onClick={() => handleSort("date")} onClick={() => handleSort("date")}
> >
Campaign Campaign
</Button> </Button>
</th> </th>
<th className="p-2 text-center font-medium sticky top-0 bg-white dark:bg-gray-900 z-10 text-gray-900 dark:text-gray-100"> <th className="p-2 font-medium text-center sticky top-0 bg-white dark:bg-gray-900/60 backdrop-blur-sm z-10 text-gray-900 dark:text-gray-100">
<Button <Button
variant={sortConfig.key === "spend" ? "default" : "ghost"} variant={sortConfig.key === "spend" ? "default" : "ghost"}
className="w-full justify-center" className="w-full justify-center h-8"
onClick={() => handleSort("spend")} onClick={() => handleSort("spend")}
> >
Spend Spend
</Button> </Button>
</th> </th>
<th className="p-2 text-center font-medium sticky top-0 bg-white dark:bg-gray-900 z-10 text-gray-900 dark:text-gray-100"> <th className="p-2 font-medium text-center sticky top-0 bg-white dark:bg-gray-900/60 backdrop-blur-sm z-10 text-gray-900 dark:text-gray-100">
<Button <Button
variant={sortConfig.key === "reach" ? "default" : "ghost"} variant={sortConfig.key === "reach" ? "default" : "ghost"}
className="w-full justify-center" className="w-full justify-center h-8"
onClick={() => handleSort("reach")} onClick={() => handleSort("reach")}
> >
Reach Reach
</Button> </Button>
</th> </th>
<th className="p-2 text-center font-medium sticky top-0 bg-white dark:bg-gray-900 z-10 text-gray-900 dark:text-gray-100"> <th className="p-2 font-medium text-center sticky top-0 bg-white dark:bg-gray-900/60 backdrop-blur-sm z-10 text-gray-900 dark:text-gray-100">
<Button <Button
variant={sortConfig.key === "impressions" ? "default" : "ghost"} variant={sortConfig.key === "impressions" ? "default" : "ghost"}
className="w-full justify-center" className="w-full justify-center h-8"
onClick={() => handleSort("impressions")} onClick={() => handleSort("impressions")}
> >
Impressions Impressions
</Button> </Button>
</th> </th>
<th className="p-2 text-center font-medium sticky top-0 bg-white dark:bg-gray-900 z-10 text-gray-900 dark:text-gray-100"> <th className="p-2 font-medium text-center sticky top-0 bg-white dark:bg-gray-900/60 backdrop-blur-sm z-10 text-gray-900 dark:text-gray-100">
<Button <Button
variant={sortConfig.key === "cpm" ? "default" : "ghost"} variant={sortConfig.key === "cpm" ? "default" : "ghost"}
className="w-full justify-center" className="w-full justify-center h-8"
onClick={() => handleSort("cpm")} onClick={() => handleSort("cpm")}
> >
CPM CPM
</Button> </Button>
</th> </th>
<th className="p-2 text-center font-medium sticky top-0 bg-white dark:bg-gray-900 z-10 text-gray-900 dark:text-gray-100"> <th className="p-2 font-medium text-center sticky top-0 bg-white dark:bg-gray-900/60 backdrop-blur-sm z-10 text-gray-900 dark:text-gray-100">
<Button <Button
variant={sortConfig.key === "ctr" ? "default" : "ghost"} variant={sortConfig.key === "ctr" ? "default" : "ghost"}
className="w-full justify-center" className="w-full justify-center h-8"
onClick={() => handleSort("ctr")} onClick={() => handleSort("ctr")}
> >
CTR CTR
</Button> </Button>
</th> </th>
<th className="p-2 text-center font-medium sticky top-0 bg-white dark:bg-gray-900 z-10 text-gray-900 dark:text-gray-100"> <th className="p-2 font-medium text-center sticky top-0 bg-white dark:bg-gray-900/60 backdrop-blur-sm z-10 text-gray-900 dark:text-gray-100">
<Button <Button
variant={sortConfig.key === "results" ? "default" : "ghost"} variant={sortConfig.key === "results" ? "default" : "ghost"}
className="w-full justify-center" className="w-full justify-center h-8"
onClick={() => handleSort("results")} onClick={() => handleSort("results")}
> >
Results Results
</Button> </Button>
</th> </th>
<th className="p-2 text-center font-medium sticky top-0 bg-white dark:bg-gray-900 z-10 text-gray-900 dark:text-gray-100"> <th className="p-2 font-medium text-center sticky top-0 bg-white dark:bg-gray-900/60 backdrop-blur-sm z-10 text-gray-900 dark:text-gray-100">
<Button <Button
variant={sortConfig.key === "value" ? "default" : "ghost"} variant={sortConfig.key === "value" ? "default" : "ghost"}
className="w-full justify-center" className="w-full justify-center h-8"
onClick={() => handleSort("value")} onClick={() => handleSort("value")}
> >
Value Value
</Button> </Button>
</th> </th>
<th className="p-2 text-center font-medium sticky top-0 bg-white dark:bg-gray-900 z-10 text-gray-900 dark:text-gray-100"> <th className="p-2 font-medium text-center sticky top-0 bg-white dark:bg-gray-900/60 backdrop-blur-sm z-10 text-gray-900 dark:text-gray-100">
<Button <Button
variant={sortConfig.key === "engagements" ? "default" : "ghost"} variant={sortConfig.key === "engagements" ? "default" : "ghost"}
className="w-full justify-center" className="w-full justify-center h-8"
onClick={() => handleSort("engagements")} onClick={() => handleSort("engagements")}
> >
Engagements Engagements
@@ -670,15 +664,17 @@ const MetaCampaigns = () => {
{sortedCampaigns.map((campaign) => ( {sortedCampaigns.map((campaign) => (
<tr <tr
key={campaign.id} key={campaign.id}
className="hover:bg-gray-50 dark:hover:bg-gray-800 transition-colors" className="hover:bg-muted/50 transition-colors"
> >
<td className="p-2 align-top"> <td className="p-2 align-top">
<div>
<div className="font-medium text-gray-900 dark:text-gray-100 break-words min-w-[200px] max-w-[300px]"> <div className="font-medium text-gray-900 dark:text-gray-100 break-words min-w-[200px] max-w-[300px]">
<CampaignName name={campaign.name} /> <CampaignName name={campaign.name} />
</div> </div>
<div className="text-xs text-gray-500 dark:text-gray-400"> <div className="text-sm text-muted-foreground">
{campaign.objective} {campaign.objective}
</div> </div>
</div>
</td> </td>
<MetricCell <MetricCell
@@ -733,8 +729,6 @@ const MetaCampaigns = () => {
))} ))}
</tbody> </tbody>
</table> </table>
</div>
</div>
</CardContent> </CardContent>
</Card> </Card>
); );

View File

@@ -106,18 +106,18 @@ const ProductGrid = ({
</td> </td>
<td className="p-1 align-middle min-w-[200px]"> <td className="p-1 align-middle min-w-[200px]">
<div className="flex flex-col gap-1.5"> <div className="flex flex-col gap-1.5">
<Skeleton className="h-4 w-[180px] bg-muted" /> <Skeleton className="h-4 w-[180px] bg-muted rounded-sm" />
<Skeleton className="h-3 w-[140px] bg-muted" /> <Skeleton className="h-3 w-[140px] bg-muted rounded-sm" />
</div> </div>
</td> </td>
<td className="p-1 align-middle text-center"> <td className="p-1 align-middle text-center">
<Skeleton className="h-4 w-8 mx-auto bg-muted" /> <Skeleton className="h-4 w-8 mx-auto bg-muted rounded-sm" />
</td> </td>
<td className="p-1 align-middle text-center"> <td className="p-1 align-middle text-center">
<Skeleton className="h-4 w-16 mx-auto bg-muted" /> <Skeleton className="h-4 w-16 mx-auto bg-muted rounded-sm" />
</td> </td>
<td className="p-1 align-middle text-center"> <td className="p-1 align-middle text-center">
<Skeleton className="h-4 w-8 mx-auto bg-muted" /> <Skeleton className="h-4 w-8 mx-auto bg-muted rounded-sm" />
</td> </td>
</tr> </tr>
); );
@@ -128,41 +128,41 @@ const ProductGrid = ({
<table className="w-full"> <table className="w-full">
<thead> <thead>
<tr className="hover:bg-transparent"> <tr className="hover:bg-transparent">
<th className="p-1.5 text-left font-medium sticky top-0 bg-white dark:bg-background z-10 w-[50px] min-w-[50px] border-b dark:border-gray-800" /> <th className="p-1.5 text-left font-medium sticky top-0 bg-white dark:bg-gray-900/60 backdrop-blur-sm z-10 w-[50px] min-w-[50px] border-b dark:border-gray-800" />
<th className="p-1.5 text-left font-medium sticky top-0 bg-white dark:bg-background z-10 min-w-[200px] border-b dark:border-gray-800"> <th className="p-1.5 text-left font-medium sticky top-0 bg-white dark:bg-gray-900/60 backdrop-blur-sm z-10 min-w-[200px] border-b dark:border-gray-800">
<Button <Button
variant="ghost" variant="ghost"
className="w-full p-2 justify-start h-8 pointer-events-none" className="w-full p-2 justify-start h-8 pointer-events-none"
disabled disabled
> >
<Skeleton className="h-4 w-16 bg-muted" /> <Skeleton className="h-4 w-16 bg-muted rounded-sm" />
</Button> </Button>
</th> </th>
<th className="p-1.5 font-medium text-center sticky top-0 bg-white dark:bg-background z-10 border-b dark:border-gray-800"> <th className="p-1.5 font-medium text-center sticky top-0 bg-white dark:bg-gray-900/60 backdrop-blur-sm z-10 border-b dark:border-gray-800">
<Button <Button
variant="ghost" variant="ghost"
className="w-full p-2 justify-center h-8 pointer-events-none" className="w-full p-2 justify-center h-8 pointer-events-none"
disabled disabled
> >
<Skeleton className="h-4 w-12 bg-muted" /> <Skeleton className="h-4 w-12 bg-muted rounded-sm" />
</Button> </Button>
</th> </th>
<th className="p-1.5 font-medium text-center sticky top-0 bg-white dark:bg-background z-10 border-b dark:border-gray-800"> <th className="p-1.5 font-medium text-center sticky top-0 bg-white dark:bg-gray-900/60 backdrop-blur-sm z-10 border-b dark:border-gray-800">
<Button <Button
variant="ghost" variant="ghost"
className="w-full p-2 justify-center h-8 pointer-events-none" className="w-full p-2 justify-center h-8 pointer-events-none"
disabled disabled
> >
<Skeleton className="h-4 w-12 bg-muted" /> <Skeleton className="h-4 w-12 bg-muted rounded-sm" />
</Button> </Button>
</th> </th>
<th className="p-1.5 font-medium text-center sticky top-0 bg-white dark:bg-background z-10 border-b dark:border-gray-800"> <th className="p-1.5 font-medium text-center sticky top-0 bg-white dark:bg-gray-900/60 backdrop-blur-sm z-10 border-b dark:border-gray-800">
<Button <Button
variant="ghost" variant="ghost"
className="w-full p-2 justify-center h-8 pointer-events-none" className="w-full p-2 justify-center h-8 pointer-events-none"
disabled disabled
> >
<Skeleton className="h-4 w-16 bg-muted" /> <Skeleton className="h-4 w-16 bg-muted rounded-sm" />
</Button> </Button>
</th> </th>
</tr> </tr>
@@ -185,17 +185,17 @@ const ProductGrid = ({
<div className="flex justify-between items-start"> <div className="flex justify-between items-start">
<div> <div>
<CardTitle className="text-xl font-semibold text-gray-900 dark:text-gray-100"> <CardTitle className="text-xl font-semibold text-gray-900 dark:text-gray-100">
<Skeleton className="h-6 w-32 bg-muted" /> <Skeleton className="h-6 w-32 bg-muted rounded-sm" />
</CardTitle> </CardTitle>
{description && ( {description && (
<CardDescription className="mt-1"> <CardDescription className="mt-1">
<Skeleton className="h-4 w-48 bg-muted" /> <Skeleton className="h-4 w-48 bg-muted rounded-sm" />
</CardDescription> </CardDescription>
)} )}
</div> </div>
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<Skeleton className="h-9 w-9 bg-muted" /> <Skeleton className="h-9 w-9 bg-muted rounded-sm" />
<Skeleton className="h-9 w-[130px] bg-muted" /> <Skeleton className="h-9 w-[130px] bg-muted rounded-sm" />
</div> </div>
</div> </div>
</div> </div>
@@ -218,7 +218,7 @@ const ProductGrid = ({
<div> <div>
<CardTitle className="text-xl font-semibold text-gray-900 dark:text-gray-100">{title}</CardTitle> <CardTitle className="text-xl font-semibold text-gray-900 dark:text-gray-100">{title}</CardTitle>
{description && ( {description && (
<CardDescription className="mt-1">{description}</CardDescription> <CardDescription className="mt-1 text-muted-foreground">{description}</CardDescription>
)} )}
</div> </div>
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
@@ -280,7 +280,7 @@ const ProductGrid = ({
<CardContent className="p-6 pt-0 flex-1 overflow-hidden -mt-1"> <CardContent className="p-6 pt-0 flex-1 overflow-hidden -mt-1">
<div className="h-full"> <div className="h-full">
{error ? ( {error ? (
<Alert variant="destructive" > <Alert variant="destructive" className="bg-white dark:bg-gray-900/60 backdrop-blur-sm">
<AlertCircle className="h-4 w-4" /> <AlertCircle className="h-4 w-4" />
<AlertTitle>Error</AlertTitle> <AlertTitle>Error</AlertTitle>
<AlertDescription> <AlertDescription>
@@ -290,7 +290,7 @@ const ProductGrid = ({
) : !products?.length ? ( ) : !products?.length ? (
<div className="flex flex-col items-center justify-center py-8 text-center"> <div className="flex flex-col items-center justify-center py-8 text-center">
<Package className="h-12 w-12 text-muted-foreground mb-4" /> <Package className="h-12 w-12 text-muted-foreground mb-4" />
<p className="font-medium mb-2">No product data available</p> <p className="font-medium mb-2 text-gray-900 dark:text-gray-100">No product data available</p>
<p className="text-sm text-muted-foreground">Try selecting a different time range</p> <p className="text-sm text-muted-foreground">Try selecting a different time range</p>
</div> </div>
) : ( ) : (
@@ -299,8 +299,8 @@ const ProductGrid = ({
<table className="w-full"> <table className="w-full">
<thead> <thead>
<tr className="hover:bg-transparent"> <tr className="hover:bg-transparent">
<th className="p-1 text-left font-medium sticky top-0 bg-white dark:bg-background z-10 h-[50px] min-h-[50px] w-[50px] min-w-[35px] border-b dark:border-gray-800" /> <th className="p-1 text-left font-medium sticky top-0 bg-white dark:bg-gray-900/60 backdrop-blur-sm z-10 h-[50px] min-h-[50px] w-[50px] min-w-[35px] border-b dark:border-gray-800" />
<th className="p-1 text-left font-medium sticky top-0 bg-white dark:bg-background z-10 border-b dark:border-gray-800"> <th className="p-1 text-left font-medium sticky top-0 bg-white dark:bg-gray-900/60 backdrop-blur-sm z-10 border-b dark:border-gray-800">
<Button <Button
variant={sorting.column === "name" ? "default" : "ghost"} variant={sorting.column === "name" ? "default" : "ghost"}
onClick={() => handleSort("name")} onClick={() => handleSort("name")}
@@ -309,7 +309,7 @@ const ProductGrid = ({
Product Product
</Button> </Button>
</th> </th>
<th className="p-1 font-medium text-center sticky top-0 bg-white dark:bg-background z-10 border-b dark:border-gray-800"> <th className="p-1 font-medium text-center sticky top-0 bg-white dark:bg-gray-900/60 backdrop-blur-sm z-10 border-b dark:border-gray-800">
<Button <Button
variant={sorting.column === "totalQuantity" ? "default" : "ghost"} variant={sorting.column === "totalQuantity" ? "default" : "ghost"}
onClick={() => handleSort("totalQuantity")} onClick={() => handleSort("totalQuantity")}
@@ -318,7 +318,7 @@ const ProductGrid = ({
Sold Sold
</Button> </Button>
</th> </th>
<th className="p-1 font-medium text-center sticky top-0 bg-white dark:bg-background z-10 border-b dark:border-gray-800"> <th className="p-1 font-medium text-center sticky top-0 bg-white dark:bg-gray-900/60 backdrop-blur-sm z-10 border-b dark:border-gray-800">
<Button <Button
variant={sorting.column === "totalRevenue" ? "default" : "ghost"} variant={sorting.column === "totalRevenue" ? "default" : "ghost"}
onClick={() => handleSort("totalRevenue")} onClick={() => handleSort("totalRevenue")}
@@ -327,7 +327,7 @@ const ProductGrid = ({
Rev Rev
</Button> </Button>
</th> </th>
<th className="p-1 font-medium text-center sticky top-0 bg-white dark:bg-background z-10 border-b dark:border-gray-800"> <th className="p-1 font-medium text-center sticky top-0 bg-white dark:bg-gray-900/60 backdrop-blur-sm z-10 border-b dark:border-gray-800">
<Button <Button
variant={sorting.column === "orderCount" ? "default" : "ghost"} variant={sorting.column === "orderCount" ? "default" : "ghost"}
onClick={() => handleSort("orderCount")} onClick={() => handleSort("orderCount")}
@@ -377,7 +377,7 @@ const ProductGrid = ({
</TooltipProvider> </TooltipProvider>
</div> </div>
</td> </td>
<td className="p-1 align-middle text-center text-sm font-medium"> <td className="p-1 align-middle text-center text-sm font-medium text-gray-900 dark:text-gray-100">
{product.totalQuantity} {product.totalQuantity}
</td> </td>
<td className="p-1 align-middle text-center text-emerald-600 dark:text-emerald-400 text-sm font-medium"> <td className="p-1 align-middle text-center text-emerald-600 dark:text-emerald-400 text-sm font-medium">

View File

@@ -181,7 +181,7 @@ const QuotaInfo = ({ tokenQuota }) => {
}; };
const SkeletonSummaryCard = () => ( const SkeletonSummaryCard = () => (
<Card> <Card className="bg-white dark:bg-gray-900/60 backdrop-blur-sm">
<CardHeader className="flex flex-row items-center justify-between space-y-0 px-4 py-2"> <CardHeader className="flex flex-row items-center justify-between space-y-0 px-4 py-2">
<Skeleton className="h-4 w-24 bg-muted" /> <Skeleton className="h-4 w-24 bg-muted" />
</CardHeader> </CardHeader>
@@ -247,10 +247,10 @@ const SkeletonTable = () => (
<TableBody> <TableBody>
{[...Array(8)].map((_, i) => ( {[...Array(8)].map((_, i) => (
<TableRow key={i} className="dark:border-gray-800"> <TableRow key={i} className="dark:border-gray-800">
<TableCell> <TableCell className="py-2.5">
<Skeleton className="h-4 w-48 bg-muted" /> <Skeleton className="h-4 w-48 bg-muted" />
</TableCell> </TableCell>
<TableCell className="text-right"> <TableCell className="text-right py-2.5">
<Skeleton className="h-4 w-12 ml-auto bg-muted" /> <Skeleton className="h-4 w-12 ml-auto bg-muted" />
</TableCell> </TableCell>
</TableRow> </TableRow>
@@ -438,14 +438,14 @@ export const RealtimeAnalytics = () => {
<CardHeader className="p-6 pb-2"> <CardHeader className="p-6 pb-2">
<div className="flex justify-between items-center"> <div className="flex justify-between items-center">
<CardTitle className="text-xl font-semibold text-gray-900 dark:text-gray-100"> <CardTitle className="text-xl font-semibold text-gray-900 dark:text-gray-100">
<Skeleton className="h-6 w-48 bg-muted" /> Real-Time Analytics
</CardTitle> </CardTitle>
<Skeleton className="h-4 w-32 bg-muted" /> <Skeleton className="h-4 w-32 bg-muted" />
</div> </div>
</CardHeader> </CardHeader>
<CardContent className="p-6 pt-0"> <CardContent className="p-6 pt-0">
<div className="grid grid-cols-2 gap-4 mt-1 mb-3"> <div className="grid grid-cols-2 gap-2 md:gap-3 mt-1 mb-3">
<SkeletonSummaryCard /> <SkeletonSummaryCard />
<SkeletonSummaryCard /> <SkeletonSummaryCard />
</div> </div>
@@ -453,7 +453,7 @@ export const RealtimeAnalytics = () => {
<div className="space-y-4"> <div className="space-y-4">
<div className="flex gap-2"> <div className="flex gap-2">
{[...Array(3)].map((_, i) => ( {[...Array(3)].map((_, i) => (
<Skeleton key={i} className="h-8 w-20 bg-muted" /> <Skeleton key={i} className="h-8 w-20 bg-muted rounded-md" />
))} ))}
</div> </div>
<SkeletonBarChart /> <SkeletonBarChart />

View File

@@ -502,7 +502,7 @@ SummaryStats.displayName = "SummaryStats";
// Add these skeleton components near the top of the file // Add these skeleton components near the top of the file
const SkeletonChart = () => ( const SkeletonChart = () => (
<div className="h-[400px] w-full bg-card rounded-lg p-4"> <div className="h-[400px] w-full bg-white dark:bg-gray-900/60 backdrop-blur-sm rounded-lg p-6">
<div className="h-full flex flex-col"> <div className="h-full flex flex-col">
<div className="flex-1 relative"> <div className="flex-1 relative">
{/* Grid lines */} {/* Grid lines */}
@@ -516,13 +516,13 @@ const SkeletonChart = () => (
{/* Y-axis labels */} {/* Y-axis labels */}
<div className="absolute left-0 top-0 bottom-0 w-16 flex flex-col justify-between py-4"> <div className="absolute left-0 top-0 bottom-0 w-16 flex flex-col justify-between py-4">
{[...Array(6)].map((_, i) => ( {[...Array(6)].map((_, i) => (
<Skeleton key={i} className="h-4 w-12 bg-muted" /> <Skeleton key={i} className="h-4 w-12 bg-muted rounded-sm" />
))} ))}
</div> </div>
{/* X-axis labels */} {/* X-axis labels */}
<div className="absolute left-16 right-0 bottom-0 flex justify-between px-4"> <div className="absolute left-16 right-0 bottom-0 flex justify-between px-4">
{[...Array(7)].map((_, i) => ( {[...Array(7)].map((_, i) => (
<Skeleton key={i} className="h-4 w-12 bg-muted" /> <Skeleton key={i} className="h-4 w-12 bg-muted rounded-sm" />
))} ))}
</div> </div>
{/* Chart line */} {/* Chart line */}
@@ -540,16 +540,16 @@ const SkeletonChart = () => (
); );
const SkeletonStats = () => ( const SkeletonStats = () => (
<div className="grid grid-cols-2 lg:grid-cols-4 gap-4 py-4"> <div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-2 md:gap-3">
{[...Array(4)].map((_, i) => ( {[...Array(4)].map((_, i) => (
<Card key={i}> <Card key={i} className="bg-white dark:bg-gray-900/60 backdrop-blur-sm">
<CardHeader className="flex flex-row items-center justify-between space-y-0 p-4 pb-2"> <CardHeader className="flex flex-row items-center justify-between space-y-0 p-4 pb-2">
<Skeleton className="h-4 w-24 bg-muted" /> <Skeleton className="h-4 w-24 bg-muted rounded-sm" />
<Skeleton className="h-4 w-16 bg-muted" /> <Skeleton className="h-4 w-8 bg-muted rounded-sm" />
</CardHeader> </CardHeader>
<CardContent className="p-4 pt-0"> <CardContent className="p-4 pt-0">
<Skeleton className="h-7 w-28 mb-1 bg-muted" /> <Skeleton className="h-7 w-32 bg-muted rounded-sm mb-1" />
<Skeleton className="h-4 w-20 bg-muted" /> <Skeleton className="h-4 w-24 bg-muted rounded-sm" />
</CardContent> </CardContent>
</Card> </Card>
))} ))}
@@ -557,49 +557,15 @@ const SkeletonStats = () => (
); );
const SkeletonTable = () => ( const SkeletonTable = () => (
<div className="rounded-lg border bg-card"> <div className="rounded-lg border bg-white dark:bg-gray-900/60 backdrop-blur-sm">
<Table> <div className="p-4 space-y-4">
<TableHeader> {[...Array(7)].map((_, i) => (
<TableRow className="hover:bg-transparent"> <div key={i} className="flex justify-between items-center">
<TableHead className="w-[120px]"> <Skeleton className="h-4 w-24 bg-muted rounded-sm" />
<Skeleton className="h-4 w-16 bg-muted" /> <Skeleton className="h-4 w-16 bg-muted rounded-sm" />
</TableHead> </div>
<TableHead className="text-center">
<Skeleton className="h-4 w-20 mx-auto bg-muted" />
</TableHead>
<TableHead className="text-center">
<Skeleton className="h-4 w-24 mx-auto bg-muted" />
</TableHead>
<TableHead className="text-center">
<Skeleton className="h-4 w-16 mx-auto bg-muted" />
</TableHead>
<TableHead className="text-center">
<Skeleton className="h-4 w-24 mx-auto bg-muted" />
</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{[...Array(10)].map((_, i) => (
<TableRow key={i} className="hover:bg-muted/50">
<TableCell>
<Skeleton className="h-4 w-20 bg-muted" />
</TableCell>
<TableCell className="text-center">
<Skeleton className="h-4 w-12 mx-auto bg-muted" />
</TableCell>
<TableCell className="text-center">
<Skeleton className="h-4 w-20 mx-auto bg-muted" />
</TableCell>
<TableCell className="text-center">
<Skeleton className="h-4 w-16 mx-auto bg-muted" />
</TableCell>
<TableCell className="text-center">
<Skeleton className="h-4 w-20 mx-auto bg-muted" />
</TableCell>
</TableRow>
))} ))}
</TableBody> </div>
</Table>
</div> </div>
); );
@@ -724,9 +690,9 @@ const SalesChart = ({
Details Details
</Button> </Button>
</DialogTrigger> </DialogTrigger>
<DialogContent className="min-w-[600px] max-w-[90vw] w-fit max-h-[85vh] overflow-hidden flex flex-col"> <DialogContent className="min-w-[600px] max-w-[90vw] w-fit max-h-[85vh] overflow-hidden flex flex-col bg-white dark:bg-gray-900/60 backdrop-blur-sm">
<DialogHeader className="flex-none"> <DialogHeader className="flex-none">
<DialogTitle>Daily Details</DialogTitle> <DialogTitle className="text-gray-900 dark:text-gray-100">Daily Details</DialogTitle>
<div className="flex items-center justify-center gap-2 pt-4"> <div className="flex items-center justify-center gap-2 pt-4">
<div className="flex flex-wrap gap-1"> <div className="flex flex-wrap gap-1">
<Button <Button
@@ -796,7 +762,7 @@ const SalesChart = ({
</div> </div>
</DialogHeader> </DialogHeader>
<div className="flex-1 overflow-y-auto mt-6"> <div className="flex-1 overflow-y-auto mt-6">
<div className="rounded-lg border bg-card w-full"> <div className="rounded-lg border bg-white dark:bg-gray-900/60 backdrop-blur-sm w-full">
<Table className="w-full"> <Table className="w-full">
<TableHeader> <TableHeader>
<TableRow> <TableRow>
@@ -984,12 +950,12 @@ const SalesChart = ({
<div className="space-y-6"> <div className="space-y-6">
<SkeletonChart /> <SkeletonChart />
<div className="mt-4 flex justify-end"> <div className="mt-4 flex justify-end">
<Skeleton className="h-9 w-24" /> <Skeleton className="h-9 w-24 bg-muted rounded-sm" />
</div> </div>
{showDailyTable && <SkeletonTable />} {showDailyTable && <SkeletonTable />}
</div> </div>
) : error ? ( ) : error ? (
<Alert variant="destructive"> <Alert variant="destructive" className="bg-white dark:bg-gray-900/60 backdrop-blur-sm">
<AlertCircle className="h-4 w-4" /> <AlertCircle className="h-4 w-4" />
<AlertTitle>Error</AlertTitle> <AlertTitle>Error</AlertTitle>
<AlertDescription> <AlertDescription>
@@ -1000,15 +966,15 @@ const SalesChart = ({
<div className="flex items-center justify-center h-[400px] text-muted-foreground"> <div className="flex items-center justify-center h-[400px] text-muted-foreground">
<div className="text-center"> <div className="text-center">
<TrendingUp className="h-12 w-12 mx-auto mb-4" /> <TrendingUp className="h-12 w-12 mx-auto mb-4" />
<div className="font-medium mb-2">No sales data available</div> <div className="font-medium mb-2 text-gray-900 dark:text-gray-100">No sales data available</div>
<div className="text-sm"> <div className="text-sm text-muted-foreground">
Try selecting a different time range Try selecting a different time range
</div> </div>
</div> </div>
</div> </div>
) : ( ) : (
<> <>
<div className="h-[400px] mt-4 bg-card rounded-lg p-0 relative"> <div className="h-[400px] mt-4 bg-white dark:bg-gray-900/60 backdrop-blur-sm rounded-lg p-0 relative">
<ResponsiveContainer width="100%" height="100%"> <ResponsiveContainer width="100%" height="100%">
<LineChart <LineChart
data={data} data={data}
@@ -1021,20 +987,20 @@ const SalesChart = ({
<XAxis <XAxis
dataKey="timestamp" dataKey="timestamp"
tickFormatter={formatXAxis} tickFormatter={formatXAxis}
className="text-xs" className="text-xs text-muted-foreground"
tick={{ fill: "currentColor" }} tick={{ fill: "currentColor" }}
/> />
<YAxis <YAxis
yAxisId="revenue" yAxisId="revenue"
tickFormatter={(value) => formatCurrency(value, false)} tickFormatter={(value) => formatCurrency(value, false)}
className="text-xs" className="text-xs text-muted-foreground"
tick={{ fill: "currentColor" }} tick={{ fill: "currentColor" }}
/> />
<YAxis <YAxis
yAxisId="orders" yAxisId="orders"
orientation="right" orientation="right"
tickFormatter={(value) => value.toLocaleString()} tickFormatter={(value) => value.toLocaleString()}
className="text-xs" className="text-xs text-muted-foreground"
tick={{ fill: "currentColor" }} tick={{ fill: "currentColor" }}
/> />
<Tooltip content={<CustomTooltip />} /> <Tooltip content={<CustomTooltip />} />

View File

@@ -981,7 +981,7 @@ const StatCard = ({
progress, progress,
}) => ( }) => (
<Card <Card
className={`${className} ${ className={`${className} bg-white dark:bg-gray-900/60 backdrop-blur-sm ${
onClick || onDetailsClick onClick || onDetailsClick
? "cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800/50 transition-colors" ? "cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800/50 transition-colors"
: "" : ""
@@ -1005,8 +1005,8 @@ const StatCard = ({
<CardContent className="p-4 pt-0"> <CardContent className="p-4 pt-0">
{isLoading ? ( {isLoading ? (
<> <>
<Skeleton className="h-8 w-32 mb-2" /> <Skeleton className="h-8 w-32 mb-2 bg-muted rounded-sm" />
<Skeleton className="h-4 w-24" /> <Skeleton className="h-4 w-24 bg-muted rounded-sm" />
</> </>
) : ( ) : (
<div className="space-y-2"> <div className="space-y-2">
@@ -1094,7 +1094,7 @@ const useDebouncedEffect = (effect, deps, delay) => {
// Add these skeleton components near the top of the file // Add these skeleton components near the top of the file
const SkeletonCard = () => ( const SkeletonCard = () => (
<Card className="relative"> <Card className="relative bg-white dark:bg-gray-900/60 backdrop-blur-sm">
<CardHeader className="flex flex-row items-center justify-between space-y-0 p-4 pb-2"> <CardHeader className="flex flex-row items-center justify-between space-y-0 p-4 pb-2">
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<Skeleton className="h-4 w-24 bg-muted" /> <Skeleton className="h-4 w-24 bg-muted" />
@@ -1107,7 +1107,7 @@ const SkeletonCard = () => (
<Skeleton className="h-8 w-24 bg-muted" /> <Skeleton className="h-8 w-24 bg-muted" />
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<Skeleton className="h-4 w-32 bg-muted" /> <Skeleton className="h-4 w-32 bg-muted" />
<Skeleton className="h-4 w-16 bg-muted" /> <Skeleton className="h-4 w-16 bg-muted rounded-md" />
</div> </div>
</div> </div>
</CardContent> </CardContent>
@@ -1115,7 +1115,7 @@ const SkeletonCard = () => (
); );
const SkeletonChart = ({ type = "line" }) => ( const SkeletonChart = ({ type = "line" }) => (
<div className="h-[400px] w-full bg-card rounded-lg p-4"> <div className="h-[400px] w-full bg-white dark:bg-gray-900/60 backdrop-blur-sm rounded-lg p-6">
<div className="h-full relative"> <div className="h-full relative">
{/* Grid lines */} {/* Grid lines */}
{[...Array(5)].map((_, i) => ( {[...Array(5)].map((_, i) => (
@@ -1128,21 +1128,21 @@ const SkeletonChart = ({ type = "line" }) => (
{/* Y-axis labels */} {/* Y-axis labels */}
<div className="absolute left-0 top-0 bottom-0 w-8 flex flex-col justify-between py-4"> <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-3 w-6 bg-muted" /> <Skeleton key={i} className="h-3 w-6 bg-muted rounded-sm" />
))} ))}
</div> </div>
{/* X-axis labels */} {/* X-axis labels */}
<div className="absolute left-8 right-4 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-3 w-8 bg-muted" /> <Skeleton key={i} className="h-3 w-8 bg-muted rounded-sm" />
))} ))}
</div> </div>
{type === "bar" ? ( {type === "bar" ? (
<div className="absolute inset-x-8 bottom-6 top-4 flex items-end justify-between"> <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-2 bg-muted" className="w-2 bg-muted rounded-sm"
style={{ height: `${Math.random() * 80 + 10}%` }} style={{ height: `${Math.random() * 80 + 10}%` }}
/> />
))} ))}
@@ -1151,7 +1151,7 @@ const SkeletonChart = ({ type = "line" }) => (
<div className="absolute inset-x-8 bottom-6 top-4"> <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" className="absolute inset-0 bg-muted 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%)",
@@ -1165,18 +1165,18 @@ const SkeletonChart = ({ type = "line" }) => (
); );
const SkeletonTable = ({ rows = 8 }) => ( const SkeletonTable = ({ rows = 8 }) => (
<div className="rounded-lg border bg-card"> <div className="rounded-lg border bg-white dark:bg-gray-900/60 backdrop-blur-sm">
<Table> <Table>
<TableHeader> <TableHeader>
<TableRow className="dark:border-gray-800"> <TableRow className="dark:border-gray-800">
<TableHead> <TableHead>
<Skeleton className="h-4 w-32 bg-muted" /> <Skeleton className="h-4 w-32 bg-muted rounded-sm" />
</TableHead> </TableHead>
<TableHead className="text-right"> <TableHead className="text-right">
<Skeleton className="h-4 w-24 ml-auto bg-muted" /> <Skeleton className="h-4 w-24 ml-auto bg-muted rounded-sm" />
</TableHead> </TableHead>
<TableHead className="text-right"> <TableHead className="text-right">
<Skeleton className="h-4 w-24 ml-auto bg-muted" /> <Skeleton className="h-4 w-24 ml-auto bg-muted rounded-sm" />
</TableHead> </TableHead>
</TableRow> </TableRow>
</TableHeader> </TableHeader>
@@ -1184,13 +1184,13 @@ const SkeletonTable = ({ rows = 8 }) => (
{[...Array(rows)].map((_, i) => ( {[...Array(rows)].map((_, i) => (
<TableRow key={i} className="dark:border-gray-800"> <TableRow key={i} className="dark:border-gray-800">
<TableCell> <TableCell>
<Skeleton className="h-4 w-48 bg-muted" /> <Skeleton className="h-4 w-48 bg-muted rounded-sm" />
</TableCell> </TableCell>
<TableCell className="text-right"> <TableCell className="text-right">
<Skeleton className="h-4 w-16 ml-auto bg-muted" /> <Skeleton className="h-4 w-16 ml-auto bg-muted rounded-sm" />
</TableCell> </TableCell>
<TableCell className="text-right"> <TableCell className="text-right">
<Skeleton className="h-4 w-16 ml-auto bg-muted" /> <Skeleton className="h-4 w-16 ml-auto bg-muted rounded-sm" />
</TableCell> </TableCell>
</TableRow> </TableRow>
))} ))}
@@ -1731,7 +1731,7 @@ const StatCards = ({
if (loading && !stats) { if (loading && !stats) {
return ( return (
<Card className="w-full bg-white dark:bg-gray-900/60 backdrop-blur-sm"> <Card className="w-full bg-white dark:bg-gray-900/60 backdrop-blur-sm">
<CardHeader className="p-6"> <CardHeader className="p-6 pb-2">
<div className="flex flex-col space-y-2"> <div className="flex flex-col space-y-2">
<div className="flex justify-between items-start"> <div className="flex justify-between items-start">
<div> <div>
@@ -1739,19 +1739,19 @@ const StatCards = ({
{title} {title}
</CardTitle> </CardTitle>
{description && ( {description && (
<CardDescription className="mt-1"> <CardDescription className="mt-1 text-muted-foreground">
{description} {description}
</CardDescription> </CardDescription>
)} )}
</div> </div>
<div className="flex items-center gap-4"> <div className="flex items-center gap-4">
<Skeleton className="h-4 w-32 bg-muted" /> <Skeleton className="h-4 w-32 bg-muted rounded-sm" />
<Skeleton className="h-9 w-[130px] bg-muted rounded-md" /> <Skeleton className="h-9 w-[130px] bg-muted rounded-md" />
</div> </div>
</div> </div>
</div> </div>
</CardHeader> </CardHeader>
<CardContent className="p-6 pt-0"> <CardContent className="p-6 pt-0 space-y-4">
<div className="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-3 lg:grid-cols-4 xl:grid-cols-3 2xl:grid-cols-4 gap-2 md:gap-3"> <div className="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-3 lg:grid-cols-4 xl:grid-cols-3 2xl:grid-cols-4 gap-2 md:gap-3">
{[...Array(12)].map((_, i) => ( {[...Array(12)].map((_, i) => (
<SkeletonCard key={i} /> <SkeletonCard key={i} />
@@ -1773,7 +1773,7 @@ const StatCards = ({
{title} {title}
</CardTitle> </CardTitle>
{description && ( {description && (
<CardDescription className="mt-1"> <CardDescription className="mt-1 text-muted-foreground">
{description} {description}
</CardDescription> </CardDescription>
)} )}

View File

@@ -33,19 +33,19 @@ const SkeletonTable = ({ rows = 12 }) => (
<Table> <Table>
<TableHeader> <TableHeader>
<TableRow className="dark:border-gray-800"> <TableRow className="dark:border-gray-800">
<TableHead><Skeleton className="h-4 w-48 dark:bg-gray-700" /></TableHead> <TableHead><Skeleton className="h-4 w-48 bg-muted rounded-sm" /></TableHead>
<TableHead className="text-right"><Skeleton className="h-4 w-16 ml-auto dark:bg-gray-700" /></TableHead> <TableHead className="text-right"><Skeleton className="h-4 w-16 ml-auto bg-muted rounded-sm" /></TableHead>
<TableHead className="text-right"><Skeleton className="h-4 w-16 ml-auto dark:bg-gray-700" /></TableHead> <TableHead className="text-right"><Skeleton className="h-4 w-16 ml-auto bg-muted rounded-sm" /></TableHead>
<TableHead className="text-right"><Skeleton className="h-4 w-16 ml-auto dark:bg-gray-700" /></TableHead> <TableHead className="text-right"><Skeleton className="h-4 w-16 ml-auto bg-muted rounded-sm" /></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} className="dark:border-gray-800">
<TableCell className="py-3"><Skeleton className="h-4 w-64 dark:bg-gray-700" /></TableCell> <TableCell className="py-3"><Skeleton className="h-4 w-64 bg-muted rounded-sm" /></TableCell>
<TableCell className="text-right py-3"><Skeleton className="h-4 w-12 ml-auto dark:bg-gray-700" /></TableCell> <TableCell className="text-right py-3"><Skeleton className="h-4 w-12 ml-auto bg-muted rounded-sm" /></TableCell>
<TableCell className="text-right py-3"><Skeleton className="h-4 w-12 ml-auto dark:bg-gray-700" /></TableCell> <TableCell className="text-right py-3"><Skeleton className="h-4 w-12 ml-auto bg-muted rounded-sm" /></TableCell>
<TableCell className="text-right py-3"><Skeleton className="h-4 w-16 ml-auto dark:bg-gray-700" /></TableCell> <TableCell className="text-right py-3"><Skeleton className="h-4 w-16 ml-auto bg-muted rounded-sm" /></TableCell>
</TableRow> </TableRow>
))} ))}
</TableBody> </TableBody>
@@ -56,13 +56,13 @@ const SkeletonTable = ({ rows = 12 }) => (
const SkeletonPieChart = () => ( const SkeletonPieChart = () => (
<div className="h-60 relative"> <div className="h-60 relative">
<div className="absolute inset-0 flex items-center justify-center"> <div className="absolute inset-0 flex items-center justify-center">
<div className="w-40 h-40 rounded-full bg-gray-100 dark:bg-gray-800 animate-pulse" /> <div className="w-40 h-40 rounded-full bg-muted animate-pulse" />
</div> </div>
<div className="absolute bottom-0 left-1/2 transform -translate-x-1/2 flex gap-4"> <div className="absolute bottom-0 left-1/2 transform -translate-x-1/2 flex gap-4">
{[...Array(3)].map((_, i) => ( {[...Array(3)].map((_, i) => (
<div key={i} className="flex items-center gap-2"> <div key={i} className="flex items-center gap-2">
<Skeleton className="h-3 w-3 rounded-full dark:bg-gray-700" /> <Skeleton className="h-3 w-3 rounded-full bg-muted" />
<Skeleton className="h-4 w-16 dark:bg-gray-700" /> <Skeleton className="h-4 w-16 bg-muted rounded-sm" />
</div> </div>
))} ))}
</div> </div>
@@ -73,7 +73,7 @@ const SkeletonTabs = () => (
<div className="space-y-2"> <div className="space-y-2">
<div className="flex gap-2 mb-4"> <div className="flex gap-2 mb-4">
{[...Array(3)].map((_, i) => ( {[...Array(3)].map((_, i) => (
<Skeleton key={i} className="h-8 w-24 dark:bg-gray-700" /> <Skeleton key={i} className="h-8 w-24 bg-muted rounded-sm" />
))} ))}
</div> </div>
</div> </div>
@@ -194,16 +194,16 @@ export const UserBehaviorDashboard = () => {
if (loading) { if (loading) {
return ( return (
<Card className="bg-white dark:bg-gray-900 h-full"> <Card className="bg-white dark:bg-gray-900/60 backdrop-blur-sm h-full">
<CardHeader> <CardHeader className="p-6 pb-4">
<div className="flex justify-between items-start"> <div className="flex justify-between items-start">
<CardTitle className="text-xl font-semibold text-gray-900 dark:text-gray-100"> <CardTitle className="text-xl font-semibold text-gray-900 dark:text-gray-100">
User Behavior Analysis User Behavior Analysis
</CardTitle> </CardTitle>
<Skeleton className="h-9 w-36 dark:bg-gray-700" /> <Skeleton className="h-9 w-36 bg-muted rounded-sm" />
</div> </div>
</CardHeader> </CardHeader>
<CardContent> <CardContent className="p-6 pt-0">
<Tabs defaultValue="pages" className="w-full"> <Tabs defaultValue="pages" className="w-full">
<TabsList className="mb-4"> <TabsList className="mb-4">
<TabsTrigger value="pages" disabled>Top Pages</TabsTrigger> <TabsTrigger value="pages" disabled>Top Pages</TabsTrigger>
@@ -245,21 +245,21 @@ export const UserBehaviorDashboard = () => {
if (active && payload && payload.length) { if (active && payload && payload.length) {
const data = payload[0].payload; const data = payload[0].payload;
const percentage = ((data.pageViews / totalViews) * 100).toFixed(1); const percentage = ((data.pageViews / totalViews) * 100).toFixed(1);
const sessionPercentage = ((data.sessions / totalSessions) * 100).toFixed( const sessionPercentage = ((data.sessions / totalSessions) * 100).toFixed(1);
1
);
return ( return (
<div className="bg-white dark:bg-gray-800 border dark:border-gray-700 rounded-lg shadow-lg p-3"> <Card className="p-3 shadow-lg bg-white dark:bg-gray-900/60 backdrop-blur-sm border border-border">
<CardContent className="p-0 space-y-2">
<p className="text-sm font-medium text-gray-900 dark:text-gray-100"> <p className="text-sm font-medium text-gray-900 dark:text-gray-100">
{data.device} {data.device}
</p> </p>
<p className="text-sm text-gray-600 dark:text-gray-300"> <p className="text-sm text-muted-foreground">
{data.pageViews.toLocaleString()} views ({percentage}%) {data.pageViews.toLocaleString()} views ({percentage}%)
</p> </p>
<p className="text-sm text-gray-600 dark:text-gray-300"> <p className="text-sm text-muted-foreground">
{data.sessions.toLocaleString()} sessions ({sessionPercentage}%) {data.sessions.toLocaleString()} sessions ({sessionPercentage}%)
</p> </p>
</div> </CardContent>
</Card>
); );
} }
return null; return null;
@@ -272,14 +272,14 @@ export const UserBehaviorDashboard = () => {
}; };
return ( return (
<Card className="bg-white dark:bg-gray-900 h-full"> <Card className="bg-white dark:bg-gray-900/60 backdrop-blur-sm h-full">
<CardHeader> <CardHeader className="p-6 pb-4">
<div className="flex justify-between items-start"> <div className="flex justify-between items-start">
<CardTitle className="text-xl font-semibold text-gray-900 dark:text-gray-100"> <CardTitle className="text-xl font-semibold text-gray-900 dark:text-gray-100">
User Behavior Analysis User Behavior Analysis
</CardTitle> </CardTitle>
<Select value={timeRange} onValueChange={setTimeRange}> <Select value={timeRange} onValueChange={setTimeRange}>
<SelectTrigger className="w-36 bg-white dark:bg-gray-800"> <SelectTrigger className="w-36 h-9">
<SelectValue> <SelectValue>
{timeRange === "7" && "Last 7 days"} {timeRange === "7" && "Last 7 days"}
{timeRange === "14" && "Last 14 days"} {timeRange === "14" && "Last 14 days"}
@@ -296,7 +296,7 @@ export const UserBehaviorDashboard = () => {
</Select> </Select>
</div> </div>
</CardHeader> </CardHeader>
<CardContent> <CardContent className="p-6 pt-0">
<Tabs defaultValue="pages" className="w-full"> <Tabs defaultValue="pages" className="w-full">
<TabsList className="mb-4"> <TabsList className="mb-4">
<TabsTrigger value="pages">Top Pages</TabsTrigger> <TabsTrigger value="pages">Top Pages</TabsTrigger>
@@ -311,33 +311,25 @@ export const UserBehaviorDashboard = () => {
<Table> <Table>
<TableHeader> <TableHeader>
<TableRow className="dark:border-gray-800"> <TableRow className="dark:border-gray-800">
<TableHead className="text-gray-900 dark:text-gray-100"> <TableHead className="text-foreground">Page Path</TableHead>
Page Path <TableHead className="text-right text-foreground">Views</TableHead>
</TableHead> <TableHead className="text-right text-foreground">Bounce Rate</TableHead>
<TableHead className="text-right text-gray-900 dark:text-gray-100"> <TableHead className="text-right text-foreground">Avg. Duration</TableHead>
Views
</TableHead>
<TableHead className="text-right text-gray-900 dark:text-gray-100">
Bounce Rate
</TableHead>
<TableHead className="text-right text-gray-900 dark:text-gray-100">
Avg. Duration
</TableHead>
</TableRow> </TableRow>
</TableHeader> </TableHeader>
<TableBody> <TableBody>
{data?.data?.pageData?.pageData.map((page, index) => ( {data?.data?.pageData?.pageData.map((page, index) => (
<TableRow key={index} className="dark:border-gray-800"> <TableRow key={index} className="dark:border-gray-800">
<TableCell className="font-medium text-gray-900 dark:text-gray-100"> <TableCell className="font-medium text-foreground">
{page.path} {page.path}
</TableCell> </TableCell>
<TableCell className="text-right text-gray-600 dark:text-gray-300"> <TableCell className="text-right text-muted-foreground">
{page.pageViews.toLocaleString()} {page.pageViews.toLocaleString()}
</TableCell> </TableCell>
<TableCell className="text-right text-gray-600 dark:text-gray-300"> <TableCell className="text-right text-muted-foreground">
{page.bounceRate.toFixed(1)}% {page.bounceRate.toFixed(1)}%
</TableCell> </TableCell>
<TableCell className="text-right text-gray-600 dark:text-gray-300"> <TableCell className="text-right text-muted-foreground">
{formatDuration(page.avgSessionDuration)} {formatDuration(page.avgSessionDuration)}
</TableCell> </TableCell>
</TableRow> </TableRow>
@@ -353,33 +345,25 @@ export const UserBehaviorDashboard = () => {
<Table> <Table>
<TableHeader> <TableHeader>
<TableRow className="dark:border-gray-800"> <TableRow className="dark:border-gray-800">
<TableHead className="text-gray-900 dark:text-gray-100"> <TableHead className="text-foreground w-[35%] min-w-[120px]">Source</TableHead>
Source <TableHead className="text-right text-foreground w-[20%] min-w-[80px]">Sessions</TableHead>
</TableHead> <TableHead className="text-right text-foreground w-[20%] min-w-[80px]">Conv.</TableHead>
<TableHead className="text-right text-gray-900 dark:text-gray-100"> <TableHead className="text-right text-foreground w-[25%] min-w-[80px]">Conv. Rate</TableHead>
Sessions
</TableHead>
<TableHead className="text-right text-gray-900 dark:text-gray-100">
Conversions
</TableHead>
<TableHead className="text-right text-gray-900 dark:text-gray-100">
Conv. Rate
</TableHead>
</TableRow> </TableRow>
</TableHeader> </TableHeader>
<TableBody> <TableBody>
{data?.data?.sourceData?.map((source, index) => ( {data?.data?.sourceData?.map((source, index) => (
<TableRow key={index} className="dark:border-gray-800"> <TableRow key={index} className="dark:border-gray-800">
<TableCell className="font-medium text-gray-900 dark:text-gray-100"> <TableCell className="font-medium text-foreground break-words max-w-[160px]">
{source.source} {source.source}
</TableCell> </TableCell>
<TableCell className="text-right text-gray-600 dark:text-gray-300"> <TableCell className="text-right text-muted-foreground whitespace-nowrap">
{source.sessions.toLocaleString()} {source.sessions.toLocaleString()}
</TableCell> </TableCell>
<TableCell className="text-right text-gray-600 dark:text-gray-300"> <TableCell className="text-right text-muted-foreground whitespace-nowrap">
{source.conversions.toLocaleString()} {source.conversions.toLocaleString()}
</TableCell> </TableCell>
<TableCell className="text-right text-gray-600 dark:text-gray-300"> <TableCell className="text-right text-muted-foreground whitespace-nowrap">
{((source.conversions / source.sessions) * 100).toFixed(1)}% {((source.conversions / source.sessions) * 100).toFixed(1)}%
</TableCell> </TableCell>
</TableRow> </TableRow>
@@ -392,7 +376,7 @@ export const UserBehaviorDashboard = () => {
value="devices" value="devices"
className="mt-4 space-y-2 h-full max-h-[540px] overflow-y-auto scrollbar-thin scrollbar-thumb-gray-200 dark:scrollbar-thumb-gray-700 scrollbar-track-transparent hover:scrollbar-thumb-gray-300 dark:hover:scrollbar-thumb-gray-600 pr-2" className="mt-4 space-y-2 h-full max-h-[540px] overflow-y-auto scrollbar-thin scrollbar-thumb-gray-200 dark:scrollbar-thumb-gray-700 scrollbar-track-transparent hover:scrollbar-thumb-gray-300 dark:hover:scrollbar-thumb-gray-600 pr-2"
> >
<div className="h-60"> <div className="h-60 bg-white dark:bg-gray-900/60 backdrop-blur-sm rounded-lg p-4">
<ResponsiveContainer width="100%" height="100%"> <ResponsiveContainer width="100%" height="100%">
<PieChart> <PieChart>
<Pie <Pie