Standardize alert messages and clean up headers
This commit is contained in:
@@ -48,6 +48,7 @@ import {
|
|||||||
TooltipProvider,
|
TooltipProvider,
|
||||||
TooltipTrigger,
|
TooltipTrigger,
|
||||||
} from "@/components/ui/tooltip";
|
} from "@/components/ui/tooltip";
|
||||||
|
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
|
||||||
|
|
||||||
const METRIC_IDS = {
|
const METRIC_IDS = {
|
||||||
PLACED_ORDER: "Y8cqcF",
|
PLACED_ORDER: "Y8cqcF",
|
||||||
@@ -1387,34 +1388,35 @@ const EventFeed = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Card className="flex flex-col h-full bg-white dark:bg-gray-900/60 backdrop-blur-sm">
|
<Card className="flex flex-col h-full bg-white dark:bg-gray-900/60 backdrop-blur-sm">
|
||||||
<CardHeader className="p-6 pb-0">
|
<CardHeader className="p-6">
|
||||||
<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>
|
<CardDescription className="text-xs">
|
||||||
Last updated: {format(lastUpdate, "hh:mm a")}
|
Last updated {format(lastUpdate, "h:mm a")}
|
||||||
</CardDescription>
|
</CardDescription>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-wrap gap-1">
|
{!error && (
|
||||||
<TooltipProvider>
|
<div className="flex flex-wrap gap-1">
|
||||||
<Tooltip>
|
<TooltipProvider>
|
||||||
<TooltipTrigger asChild>
|
<Tooltip>
|
||||||
<Button
|
<TooltipTrigger asChild>
|
||||||
variant={activeEventTypes[METRIC_IDS.PLACED_ORDER] ? "default" : "outline"}
|
<Button
|
||||||
size="sm"
|
variant={activeEventTypes[METRIC_IDS.PLACED_ORDER] ? "default" : "outline"}
|
||||||
onClick={() => handleEventTypeClick(METRIC_IDS.PLACED_ORDER)}
|
size="sm"
|
||||||
className="h-8 w-8 p-0"
|
onClick={() => handleEventTypeClick(METRIC_IDS.PLACED_ORDER)}
|
||||||
>
|
className="h-8 w-8 p-0"
|
||||||
<Package className="h-4 w-4" />
|
>
|
||||||
</Button>
|
<Package className="h-4 w-4" />
|
||||||
</TooltipTrigger>
|
</Button>
|
||||||
<TooltipContent>
|
</TooltipTrigger>
|
||||||
<EventTypeTooltipContent />
|
<TooltipContent>
|
||||||
</TooltipContent>
|
<EventTypeTooltipContent />
|
||||||
</Tooltip>
|
</TooltipContent>
|
||||||
</TooltipProvider>
|
</Tooltip>
|
||||||
|
</TooltipProvider>
|
||||||
|
|
||||||
<TooltipProvider>
|
<TooltipProvider>
|
||||||
<Tooltip>
|
<Tooltip>
|
||||||
@@ -1488,155 +1490,150 @@ const EventFeed = ({
|
|||||||
</Tooltip>
|
</Tooltip>
|
||||||
</TooltipProvider>
|
</TooltipProvider>
|
||||||
|
|
||||||
<TooltipProvider>
|
<TooltipProvider>
|
||||||
<Tooltip>
|
<Tooltip>
|
||||||
<TooltipTrigger asChild>
|
<TooltipTrigger asChild>
|
||||||
<Button
|
<Button
|
||||||
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"
|
||||||
>
|
>
|
||||||
<FileText className="h-4 w-4" />
|
<FileText className="h-4 w-4" />
|
||||||
</Button>
|
</Button>
|
||||||
</TooltipTrigger>
|
</TooltipTrigger>
|
||||||
<TooltipContent>
|
<TooltipContent>
|
||||||
<EventTypeTooltipContent />
|
<EventTypeTooltipContent />
|
||||||
</TooltipContent>
|
</TooltipContent>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</TooltipProvider>
|
</TooltipProvider>
|
||||||
</div>
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Order Property Filters */}
|
{/* Order Property Filters - only show if not in error state */}
|
||||||
<div className="flex flex-wrap gap-2 justify-center mt-4">
|
{!error && (
|
||||||
{counts.orderProperties.hasPreorder > 0 && (
|
<div className="flex flex-wrap gap-2 justify-center mt-4 pt-1">
|
||||||
<Badge
|
{counts.orderProperties.hasPreorder > 0 && (
|
||||||
variant="secondary"
|
<Badge
|
||||||
onClick={() => handleOrderPropertyClick('hasPreorder')}
|
variant="secondary"
|
||||||
className={`${
|
onClick={() => handleOrderPropertyClick('hasPreorder')}
|
||||||
orderFilters.hasPreorder
|
className={`${
|
||||||
? 'bg-purple-800 text-purple-200 hover:bg-purple-700'
|
orderFilters.hasPreorder
|
||||||
: '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-800 text-purple-200 hover:bg-purple-700'
|
||||||
} cursor-pointer`}
|
: '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`}
|
||||||
Pre-order ({counts.orderProperties.hasPreorder})
|
>
|
||||||
</Badge>
|
Pre-order ({counts.orderProperties.hasPreorder})
|
||||||
)}
|
</Badge>
|
||||||
{counts.orderProperties.localPickup > 0 && (
|
)}
|
||||||
<Badge
|
{counts.orderProperties.localPickup > 0 && (
|
||||||
variant="secondary"
|
<Badge
|
||||||
onClick={() => handleOrderPropertyClick('localPickup')}
|
variant="secondary"
|
||||||
className={`${
|
onClick={() => handleOrderPropertyClick('localPickup')}
|
||||||
orderFilters.localPickup
|
className={`${
|
||||||
? 'bg-green-800 text-green-200 hover:bg-green-700'
|
orderFilters.localPickup
|
||||||
: '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-800 text-green-200 hover:bg-green-700'
|
||||||
} cursor-pointer`}
|
: '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`}
|
||||||
Local ({counts.orderProperties.localPickup})
|
>
|
||||||
</Badge>
|
Local ({counts.orderProperties.localPickup})
|
||||||
)}
|
</Badge>
|
||||||
{counts.orderProperties.isOnHold > 0 && (
|
)}
|
||||||
<Badge
|
{counts.orderProperties.isOnHold > 0 && (
|
||||||
variant="secondary"
|
<Badge
|
||||||
onClick={() => handleOrderPropertyClick('isOnHold')}
|
variant="secondary"
|
||||||
className={`${
|
onClick={() => handleOrderPropertyClick('isOnHold')}
|
||||||
orderFilters.isOnHold
|
className={`${
|
||||||
? 'bg-blue-800 text-blue-200 hover:bg-blue-700'
|
orderFilters.isOnHold
|
||||||
: '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-800 text-blue-200 hover:bg-blue-700'
|
||||||
} cursor-pointer`}
|
: '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`}
|
||||||
On Hold ({counts.orderProperties.isOnHold})
|
>
|
||||||
</Badge>
|
On Hold ({counts.orderProperties.isOnHold})
|
||||||
)}
|
</Badge>
|
||||||
{counts.orderProperties.onHoldReleased > 0 && (
|
)}
|
||||||
<Badge
|
{counts.orderProperties.onHoldReleased > 0 && (
|
||||||
variant="secondary"
|
<Badge
|
||||||
onClick={() => handleOrderPropertyClick('onHoldReleased')}
|
variant="secondary"
|
||||||
className={`${
|
onClick={() => handleOrderPropertyClick('onHoldReleased')}
|
||||||
orderFilters.onHoldReleased
|
className={`${
|
||||||
? 'bg-green-800 text-green-200 hover:bg-green-700'
|
orderFilters.onHoldReleased
|
||||||
: '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-800 text-green-200 hover:bg-green-700'
|
||||||
} cursor-pointer`}
|
: '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`}
|
||||||
Hold Released ({counts.orderProperties.onHoldReleased})
|
>
|
||||||
</Badge>
|
Hold Released ({counts.orderProperties.onHoldReleased})
|
||||||
)}
|
</Badge>
|
||||||
{counts.orderProperties.hasDigiItem > 0 && (
|
)}
|
||||||
<Badge
|
{counts.orderProperties.hasDigiItem > 0 && (
|
||||||
variant="secondary"
|
<Badge
|
||||||
onClick={() => handleOrderPropertyClick('hasDigiItem')}
|
variant="secondary"
|
||||||
className={`${
|
onClick={() => handleOrderPropertyClick('hasDigiItem')}
|
||||||
orderFilters.hasDigiItem
|
className={`${
|
||||||
? 'bg-indigo-800 text-indigo-200 hover:bg-indigo-700'
|
orderFilters.hasDigiItem
|
||||||
: '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-800 text-indigo-200 hover:bg-indigo-700'
|
||||||
} cursor-pointer`}
|
: '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`}
|
||||||
Digital ({counts.orderProperties.hasDigiItem})
|
>
|
||||||
</Badge>
|
Digital ({counts.orderProperties.hasDigiItem})
|
||||||
)}
|
</Badge>
|
||||||
{counts.orderProperties.hasNotions > 0 && (
|
)}
|
||||||
<Badge
|
{counts.orderProperties.hasNotions > 0 && (
|
||||||
variant="secondary"
|
<Badge
|
||||||
onClick={() => handleOrderPropertyClick('hasNotions')}
|
variant="secondary"
|
||||||
className={`${
|
onClick={() => handleOrderPropertyClick('hasNotions')}
|
||||||
orderFilters.hasNotions
|
className={`${
|
||||||
? 'bg-yellow-800 text-yellow-200 hover:bg-yellow-700'
|
orderFilters.hasNotions
|
||||||
: '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-800 text-yellow-200 hover:bg-yellow-700'
|
||||||
} cursor-pointer`}
|
: '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`}
|
||||||
Notions ({counts.orderProperties.hasNotions})
|
>
|
||||||
</Badge>
|
Notions ({counts.orderProperties.hasNotions})
|
||||||
)}
|
</Badge>
|
||||||
{counts.orderProperties.hasGiftCard > 0 && (
|
)}
|
||||||
<Badge
|
{counts.orderProperties.hasGiftCard > 0 && (
|
||||||
variant="secondary"
|
<Badge
|
||||||
onClick={() => handleOrderPropertyClick('hasGiftCard')}
|
variant="secondary"
|
||||||
className={`${
|
onClick={() => handleOrderPropertyClick('hasGiftCard')}
|
||||||
orderFilters.hasGiftCard
|
className={`${
|
||||||
? 'bg-pink-800 text-pink-200 hover:bg-pink-700'
|
orderFilters.hasGiftCard
|
||||||
: '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-800 text-pink-200 hover:bg-pink-700'
|
||||||
} cursor-pointer`}
|
: '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`}
|
||||||
eGift Card ({counts.orderProperties.hasGiftCard})
|
>
|
||||||
</Badge>
|
eGift Card ({counts.orderProperties.hasGiftCard})
|
||||||
)}
|
</Badge>
|
||||||
{counts.orderProperties.stillOwes > 0 && (
|
)}
|
||||||
<Badge
|
{counts.orderProperties.stillOwes > 0 && (
|
||||||
variant="secondary"
|
<Badge
|
||||||
onClick={() => handleOrderPropertyClick('stillOwes')}
|
variant="secondary"
|
||||||
className={`${
|
onClick={() => handleOrderPropertyClick('stillOwes')}
|
||||||
orderFilters.stillOwes
|
className={`${
|
||||||
? 'bg-red-800 text-red-200 hover:bg-red-700'
|
orderFilters.stillOwes
|
||||||
: '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-800 text-red-200 hover:bg-red-700'
|
||||||
} cursor-pointer`}
|
: '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`}
|
||||||
Owes ({counts.orderProperties.stillOwes})
|
>
|
||||||
</Badge>
|
Owes ({counts.orderProperties.stillOwes})
|
||||||
)}
|
</Badge>
|
||||||
</div>
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
|
|
||||||
<CardContent className="p-6 pt-4 flex-1 overflow-hidden">
|
<CardContent className="p-6 pt-0 flex-1 overflow-hidden -mt-2">
|
||||||
<ScrollArea className="h-full">
|
<ScrollArea className="h-full">
|
||||||
{loading && !events.length ? (
|
{loading && !events.length ? (
|
||||||
<LoadingState />
|
<LoadingState />
|
||||||
) : error ? (
|
) : error ? (
|
||||||
<div className="flex flex-col items-center justify-center p-6 text-center">
|
<Alert variant="destructive" className="mt-1" >
|
||||||
<AlertCircle className="w-12 h-12 text-red-500 mb-4" />
|
<AlertCircle className="h-4 w-4" />
|
||||||
<h3 className="text-lg font-medium text-gray-900 dark:text-gray-100 mb-2">
|
<AlertTitle>Error</AlertTitle>
|
||||||
Error Loading Feed
|
<AlertDescription>
|
||||||
</h3>
|
Failed to load event feed: {error}
|
||||||
<p className="text-sm text-muted-foreground mb-4">
|
</AlertDescription>
|
||||||
{error}
|
</Alert>
|
||||||
</p>
|
|
||||||
<button
|
|
||||||
onClick={() => fetchEvents()}
|
|
||||||
className="px-4 py-2 bg-primary text-primary-foreground rounded-md hover:bg-primary/90 transition-colors"
|
|
||||||
>
|
|
||||||
Try Again
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
) : !filteredEvents || filteredEvents.length === 0 ? (
|
) : !filteredEvents || filteredEvents.length === 0 ? (
|
||||||
<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">
|
||||||
<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">
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ import {
|
|||||||
TooltipProvider,
|
TooltipProvider,
|
||||||
TooltipTrigger,
|
TooltipTrigger,
|
||||||
} from "@/components/ui/tooltip";
|
} from "@/components/ui/tooltip";
|
||||||
|
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
|
||||||
|
|
||||||
const ProductGrid = ({
|
const ProductGrid = ({
|
||||||
timeRange = "today",
|
timeRange = "today",
|
||||||
@@ -161,7 +162,7 @@ const ProductGrid = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Card className="flex flex-col h-full bg-white dark:bg-gray-900/60 backdrop-blur-sm">
|
<Card className="flex flex-col h-full bg-white dark:bg-gray-900/60 backdrop-blur-sm">
|
||||||
<CardHeader className="p-6">
|
<CardHeader className="p-6 pb-4">
|
||||||
<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>
|
||||||
@@ -170,15 +171,17 @@ const ProductGrid = ({
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-4">
|
<div className="flex items-center gap-4">
|
||||||
<div className="relative hidden sm:block">
|
{!error && (
|
||||||
<Search className="absolute left-2 top-2.5 h-4 w-4 text-muted-foreground" />
|
<div className="relative hidden sm:block">
|
||||||
<Input
|
<Search className="absolute left-2 top-2.5 h-4 w-4 text-muted-foreground" />
|
||||||
placeholder="Search products..."
|
<Input
|
||||||
value={searchQuery}
|
placeholder="Search products..."
|
||||||
onChange={(e) => setSearchQuery(e.target.value)}
|
value={searchQuery}
|
||||||
className="pl-8 h-9 w-[200px]"
|
onChange={(e) => setSearchQuery(e.target.value)}
|
||||||
/>
|
className="pl-8 h-9 w-[200px]"
|
||||||
</div>
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
<Select
|
<Select
|
||||||
value={selectedTimeRange}
|
value={selectedTimeRange}
|
||||||
onValueChange={handleTimeRangeChange}
|
onValueChange={handleTimeRangeChange}
|
||||||
@@ -203,11 +206,13 @@ const ProductGrid = ({
|
|||||||
{loading ? (
|
{loading ? (
|
||||||
<LoadingState />
|
<LoadingState />
|
||||||
) : error ? (
|
) : error ? (
|
||||||
<div className="flex flex-col items-center justify-center py-8 text-center">
|
<Alert variant="destructive" >
|
||||||
<AlertCircle className="h-12 w-12 text-destructive mb-4" />
|
<AlertCircle className="h-4 w-4" />
|
||||||
<p className="font-medium mb-2">Error loading products</p>
|
<AlertTitle>Error</AlertTitle>
|
||||||
<p className="text-sm text-muted-foreground">{error}</p>
|
<AlertDescription>
|
||||||
</div>
|
Failed to load products: {error}
|
||||||
|
</AlertDescription>
|
||||||
|
</Alert>
|
||||||
) : !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" />
|
||||||
|
|||||||
@@ -69,6 +69,7 @@ import {
|
|||||||
CollapsibleContent,
|
CollapsibleContent,
|
||||||
CollapsibleTrigger,
|
CollapsibleTrigger,
|
||||||
} from "@/components/ui/collapsible";
|
} from "@/components/ui/collapsible";
|
||||||
|
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
|
||||||
|
|
||||||
const METRIC_IDS = {
|
const METRIC_IDS = {
|
||||||
PLACED_ORDER: "Y8cqcF",
|
PLACED_ORDER: "Y8cqcF",
|
||||||
@@ -675,7 +676,7 @@ const SalesChart = ({
|
|||||||
|
|
||||||
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-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>
|
<div>
|
||||||
@@ -684,171 +685,173 @@ const SalesChart = ({
|
|||||||
</CardTitle>
|
</CardTitle>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-4">
|
<div className="flex items-center gap-4">
|
||||||
<Dialog>
|
{!error && (
|
||||||
<DialogTrigger asChild>
|
<Dialog>
|
||||||
<Button variant="outline" className="h-9">
|
<DialogTrigger asChild>
|
||||||
View Details
|
<Button variant="outline" className="h-9">
|
||||||
</Button>
|
View Details
|
||||||
</DialogTrigger>
|
</Button>
|
||||||
<DialogContent className="min-w-[600px] max-w-[90vw] w-fit max-h-[85vh] overflow-hidden flex flex-col">
|
</DialogTrigger>
|
||||||
<DialogHeader className="flex-none">
|
<DialogContent className="min-w-[600px] max-w-[90vw] w-fit max-h-[85vh] overflow-hidden flex flex-col">
|
||||||
<DialogTitle>Daily Details</DialogTitle>
|
<DialogHeader className="flex-none">
|
||||||
<div className="flex items-center justify-center gap-2 pt-4">
|
<DialogTitle>Daily Details</DialogTitle>
|
||||||
<div className="flex flex-wrap gap-1">
|
<div className="flex items-center justify-center gap-2 pt-4">
|
||||||
|
<div className="flex flex-wrap gap-1">
|
||||||
|
<Button
|
||||||
|
variant={metrics.revenue ? "default" : "outline"}
|
||||||
|
size="sm"
|
||||||
|
onClick={() =>
|
||||||
|
setMetrics((prev) => ({
|
||||||
|
...prev,
|
||||||
|
revenue: !prev.revenue,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
>
|
||||||
|
Revenue
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant={metrics.orders ? "default" : "outline"}
|
||||||
|
size="sm"
|
||||||
|
onClick={() =>
|
||||||
|
setMetrics((prev) => ({
|
||||||
|
...prev,
|
||||||
|
orders: !prev.orders,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
>
|
||||||
|
Orders
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant={metrics.movingAverage ? "default" : "outline"}
|
||||||
|
size="sm"
|
||||||
|
onClick={() =>
|
||||||
|
setMetrics((prev) => ({
|
||||||
|
...prev,
|
||||||
|
movingAverage: !prev.movingAverage,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
>
|
||||||
|
7-Day Avg
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant={metrics.avgOrderValue ? "default" : "outline"}
|
||||||
|
size="sm"
|
||||||
|
onClick={() =>
|
||||||
|
setMetrics((prev) => ({
|
||||||
|
...prev,
|
||||||
|
avgOrderValue: !prev.avgOrderValue,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
>
|
||||||
|
AOV
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Separator orientation="vertical" className="h-6" />
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
variant={metrics.revenue ? "default" : "outline"}
|
variant={metrics.showPrevious ? "default" : "outline"}
|
||||||
size="sm"
|
size="sm"
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
setMetrics((prev) => ({
|
setMetrics((prev) => ({
|
||||||
...prev,
|
...prev,
|
||||||
revenue: !prev.revenue,
|
showPrevious: !prev.showPrevious,
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
Revenue
|
Compare Prev Period
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
variant={metrics.orders ? "default" : "outline"}
|
|
||||||
size="sm"
|
|
||||||
onClick={() =>
|
|
||||||
setMetrics((prev) => ({
|
|
||||||
...prev,
|
|
||||||
orders: !prev.orders,
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
>
|
|
||||||
Orders
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
variant={metrics.movingAverage ? "default" : "outline"}
|
|
||||||
size="sm"
|
|
||||||
onClick={() =>
|
|
||||||
setMetrics((prev) => ({
|
|
||||||
...prev,
|
|
||||||
movingAverage: !prev.movingAverage,
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
>
|
|
||||||
7-Day Avg
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
variant={metrics.avgOrderValue ? "default" : "outline"}
|
|
||||||
size="sm"
|
|
||||||
onClick={() =>
|
|
||||||
setMetrics((prev) => ({
|
|
||||||
...prev,
|
|
||||||
avgOrderValue: !prev.avgOrderValue,
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
>
|
|
||||||
AOV
|
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
</DialogHeader>
|
||||||
<Separator orientation="vertical" className="h-6" />
|
<div className="flex-1 overflow-y-auto mt-6">
|
||||||
|
<div className="rounded-lg border bg-card w-full">
|
||||||
<Button
|
<Table className="w-full">
|
||||||
variant={metrics.showPrevious ? "default" : "outline"}
|
<TableHeader>
|
||||||
size="sm"
|
<TableRow>
|
||||||
onClick={() =>
|
<TableHead className="text-center whitespace-nowrap px-6 w-[120px]">Date</TableHead>
|
||||||
setMetrics((prev) => ({
|
|
||||||
...prev,
|
|
||||||
showPrevious: !prev.showPrevious,
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
>
|
|
||||||
Compare Prev Period
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</DialogHeader>
|
|
||||||
<div className="flex-1 overflow-y-auto mt-6">
|
|
||||||
<div className="rounded-lg border bg-card w-full">
|
|
||||||
<Table className="w-full">
|
|
||||||
<TableHeader>
|
|
||||||
<TableRow>
|
|
||||||
<TableHead className="text-center whitespace-nowrap px-6 w-[120px]">Date</TableHead>
|
|
||||||
{metrics.orders && (
|
|
||||||
<>
|
|
||||||
<TableHead className="text-center whitespace-nowrap px-6 min-w-[100px]">Orders</TableHead>
|
|
||||||
{metrics.showPrevious && (
|
|
||||||
<TableHead className="text-center whitespace-nowrap px-6 min-w-[100px]">Prev Orders</TableHead>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
{metrics.revenue && (
|
|
||||||
<>
|
|
||||||
<TableHead className="text-center whitespace-nowrap px-6 min-w-[140px]">Revenue</TableHead>
|
|
||||||
{metrics.showPrevious && (
|
|
||||||
<TableHead className="text-center whitespace-nowrap px-6 min-w-[140px]">Prev Revenue</TableHead>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
{metrics.avgOrderValue && (
|
|
||||||
<>
|
|
||||||
<TableHead className="text-center whitespace-nowrap px-6 min-w-[120px]">AOV</TableHead>
|
|
||||||
{metrics.showPrevious && (
|
|
||||||
<TableHead className="text-center whitespace-nowrap px-6 min-w-[120px]">Prev AOV</TableHead>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
{metrics.movingAverage && (
|
|
||||||
<TableHead className="text-center whitespace-nowrap px-6 min-w-[140px]">7-Day Avg</TableHead>
|
|
||||||
)}
|
|
||||||
</TableRow>
|
|
||||||
</TableHeader>
|
|
||||||
<TableBody>
|
|
||||||
{data.map((day) => (
|
|
||||||
<TableRow key={day.timestamp}>
|
|
||||||
<TableCell className="text-center whitespace-nowrap px-6">{formatXAxis(day.timestamp)}</TableCell>
|
|
||||||
{metrics.orders && (
|
{metrics.orders && (
|
||||||
<>
|
<>
|
||||||
<TableCell className="text-center whitespace-nowrap px-6">
|
<TableHead className="text-center whitespace-nowrap px-6 min-w-[100px]">Orders</TableHead>
|
||||||
{day.orders.toLocaleString()}
|
|
||||||
</TableCell>
|
|
||||||
{metrics.showPrevious && (
|
{metrics.showPrevious && (
|
||||||
<TableCell className="text-center whitespace-nowrap px-6">
|
<TableHead className="text-center whitespace-nowrap px-6 min-w-[100px]">Prev Orders</TableHead>
|
||||||
{day.prevOrders.toLocaleString()}
|
|
||||||
</TableCell>
|
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
{metrics.revenue && (
|
{metrics.revenue && (
|
||||||
<>
|
<>
|
||||||
<TableCell className="text-center whitespace-nowrap px-6">
|
<TableHead className="text-center whitespace-nowrap px-6 min-w-[140px]">Revenue</TableHead>
|
||||||
{formatCurrency(day.revenue)}
|
|
||||||
</TableCell>
|
|
||||||
{metrics.showPrevious && (
|
{metrics.showPrevious && (
|
||||||
<TableCell className="text-center whitespace-nowrap px-6">
|
<TableHead className="text-center whitespace-nowrap px-6 min-w-[140px]">Prev Revenue</TableHead>
|
||||||
{formatCurrency(day.prevRevenue)}
|
|
||||||
</TableCell>
|
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
{metrics.avgOrderValue && (
|
{metrics.avgOrderValue && (
|
||||||
<>
|
<>
|
||||||
<TableCell className="text-center whitespace-nowrap px-6">
|
<TableHead className="text-center whitespace-nowrap px-6 min-w-[120px]">AOV</TableHead>
|
||||||
{formatCurrency(day.avgOrderValue)}
|
|
||||||
</TableCell>
|
|
||||||
{metrics.showPrevious && (
|
{metrics.showPrevious && (
|
||||||
<TableCell className="text-center whitespace-nowrap px-6">
|
<TableHead className="text-center whitespace-nowrap px-6 min-w-[120px]">Prev AOV</TableHead>
|
||||||
{formatCurrency(day.prevAvgOrderValue)}
|
|
||||||
</TableCell>
|
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
{metrics.movingAverage && (
|
{metrics.movingAverage && (
|
||||||
<TableCell className="text-center whitespace-nowrap px-6">
|
<TableHead className="text-center whitespace-nowrap px-6 min-w-[140px]">7-Day Avg</TableHead>
|
||||||
{formatCurrency(day.movingAverage)}
|
|
||||||
</TableCell>
|
|
||||||
)}
|
)}
|
||||||
</TableRow>
|
</TableRow>
|
||||||
))}
|
</TableHeader>
|
||||||
</TableBody>
|
<TableBody>
|
||||||
</Table>
|
{data.map((day) => (
|
||||||
|
<TableRow key={day.timestamp}>
|
||||||
|
<TableCell className="text-center whitespace-nowrap px-6">{formatXAxis(day.timestamp)}</TableCell>
|
||||||
|
{metrics.orders && (
|
||||||
|
<>
|
||||||
|
<TableCell className="text-center whitespace-nowrap px-6">
|
||||||
|
{day.orders.toLocaleString()}
|
||||||
|
</TableCell>
|
||||||
|
{metrics.showPrevious && (
|
||||||
|
<TableCell className="text-center whitespace-nowrap px-6">
|
||||||
|
{day.prevOrders.toLocaleString()}
|
||||||
|
</TableCell>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
{metrics.revenue && (
|
||||||
|
<>
|
||||||
|
<TableCell className="text-center whitespace-nowrap px-6">
|
||||||
|
{formatCurrency(day.revenue)}
|
||||||
|
</TableCell>
|
||||||
|
{metrics.showPrevious && (
|
||||||
|
<TableCell className="text-center whitespace-nowrap px-6">
|
||||||
|
{formatCurrency(day.prevRevenue)}
|
||||||
|
</TableCell>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
{metrics.avgOrderValue && (
|
||||||
|
<>
|
||||||
|
<TableCell className="text-center whitespace-nowrap px-6">
|
||||||
|
{formatCurrency(day.avgOrderValue)}
|
||||||
|
</TableCell>
|
||||||
|
{metrics.showPrevious && (
|
||||||
|
<TableCell className="text-center whitespace-nowrap px-6">
|
||||||
|
{formatCurrency(day.prevAvgOrderValue)}
|
||||||
|
</TableCell>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
{metrics.movingAverage && (
|
||||||
|
<TableCell className="text-center whitespace-nowrap px-6">
|
||||||
|
{formatCurrency(day.movingAverage)}
|
||||||
|
</TableCell>
|
||||||
|
)}
|
||||||
|
</TableRow>
|
||||||
|
))}
|
||||||
|
</TableBody>
|
||||||
|
</Table>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</DialogContent>
|
||||||
</DialogContent>
|
</Dialog>
|
||||||
</Dialog>
|
)}
|
||||||
<Select
|
<Select
|
||||||
value={selectedTimeRange}
|
value={selectedTimeRange}
|
||||||
onValueChange={handleTimeRangeChange}
|
onValueChange={handleTimeRangeChange}
|
||||||
@@ -867,77 +870,79 @@ const SalesChart = ({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Show either skeletons or actual stats */}
|
{/* Show stats only if not in error state */}
|
||||||
{loading ? <SkeletonStats /> : <SummaryStats stats={summaryStats} />}
|
{!error && (loading ? <SkeletonStats /> : <SummaryStats stats={summaryStats} />)}
|
||||||
|
|
||||||
|
{/* Show metric toggles only if not in error state */}
|
||||||
|
{!error && (
|
||||||
|
<div className="flex items-center gap-2 pt-2">
|
||||||
|
<div className="flex flex-wrap gap-1">
|
||||||
|
<Button
|
||||||
|
variant={metrics.revenue ? "default" : "outline"}
|
||||||
|
size="sm"
|
||||||
|
onClick={() =>
|
||||||
|
setMetrics((prev) => ({
|
||||||
|
...prev,
|
||||||
|
revenue: !prev.revenue,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
>
|
||||||
|
Revenue
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant={metrics.orders ? "default" : "outline"}
|
||||||
|
size="sm"
|
||||||
|
onClick={() =>
|
||||||
|
setMetrics((prev) => ({
|
||||||
|
...prev,
|
||||||
|
orders: !prev.orders,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
>
|
||||||
|
Orders
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant={metrics.movingAverage ? "default" : "outline"}
|
||||||
|
size="sm"
|
||||||
|
onClick={() =>
|
||||||
|
setMetrics((prev) => ({
|
||||||
|
...prev,
|
||||||
|
movingAverage: !prev.movingAverage,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
>
|
||||||
|
7-Day Avg
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant={metrics.avgOrderValue ? "default" : "outline"}
|
||||||
|
size="sm"
|
||||||
|
onClick={() =>
|
||||||
|
setMetrics((prev) => ({
|
||||||
|
...prev,
|
||||||
|
avgOrderValue: !prev.avgOrderValue,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
>
|
||||||
|
AOV
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Separator orientation="vertical" className="h-6" />
|
||||||
|
|
||||||
{/* Metric Toggles */}
|
|
||||||
<div className="flex items-center gap-2 pt-2">
|
|
||||||
<div className="flex flex-wrap gap-1">
|
|
||||||
<Button
|
<Button
|
||||||
variant={metrics.revenue ? "default" : "outline"}
|
variant={metrics.showPrevious ? "default" : "outline"}
|
||||||
size="sm"
|
size="sm"
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
setMetrics((prev) => ({
|
setMetrics((prev) => ({
|
||||||
...prev,
|
...prev,
|
||||||
revenue: !prev.revenue,
|
showPrevious: !prev.showPrevious,
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
Revenue
|
Compare Prev Period
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
variant={metrics.orders ? "default" : "outline"}
|
|
||||||
size="sm"
|
|
||||||
onClick={() =>
|
|
||||||
setMetrics((prev) => ({
|
|
||||||
...prev,
|
|
||||||
orders: !prev.orders,
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
>
|
|
||||||
Orders
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
variant={metrics.movingAverage ? "default" : "outline"}
|
|
||||||
size="sm"
|
|
||||||
onClick={() =>
|
|
||||||
setMetrics((prev) => ({
|
|
||||||
...prev,
|
|
||||||
movingAverage: !prev.movingAverage,
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
>
|
|
||||||
7-Day Avg
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
variant={metrics.avgOrderValue ? "default" : "outline"}
|
|
||||||
size="sm"
|
|
||||||
onClick={() =>
|
|
||||||
setMetrics((prev) => ({
|
|
||||||
...prev,
|
|
||||||
avgOrderValue: !prev.avgOrderValue,
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
>
|
|
||||||
AOV
|
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
)}
|
||||||
<Separator orientation="vertical" className="h-6" />
|
|
||||||
|
|
||||||
<Button
|
|
||||||
variant={metrics.showPrevious ? "default" : "outline"}
|
|
||||||
size="sm"
|
|
||||||
onClick={() =>
|
|
||||||
setMetrics((prev) => ({
|
|
||||||
...prev,
|
|
||||||
showPrevious: !prev.showPrevious,
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
>
|
|
||||||
Compare Prev Period
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
|
|
||||||
@@ -951,13 +956,13 @@ const SalesChart = ({
|
|||||||
{showDailyTable && <SkeletonTable />}
|
{showDailyTable && <SkeletonTable />}
|
||||||
</div>
|
</div>
|
||||||
) : error ? (
|
) : error ? (
|
||||||
<div className="flex items-center justify-center h-[400px] text-destructive">
|
<Alert variant="destructive">
|
||||||
<div className="text-center">
|
<AlertCircle className="h-4 w-4" />
|
||||||
<AlertCircle className="h-12 w-12 mx-auto mb-4" />
|
<AlertTitle>Error</AlertTitle>
|
||||||
<div className="font-medium mb-2">Error loading sales data</div>
|
<AlertDescription>
|
||||||
<div className="text-sm text-muted-foreground">{error}</div>
|
Failed to load sales data: {error}
|
||||||
</div>
|
</AlertDescription>
|
||||||
</div>
|
</Alert>
|
||||||
) : !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">
|
||||||
|
|||||||
@@ -67,6 +67,8 @@ import {
|
|||||||
TableRow,
|
TableRow,
|
||||||
} from "@/components/ui/table";
|
} from "@/components/ui/table";
|
||||||
import { Skeleton } from "@/components/ui/skeleton";
|
import { Skeleton } from "@/components/ui/skeleton";
|
||||||
|
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
|
||||||
const formatCurrency = (value, minimumFractionDigits = 0) => {
|
const formatCurrency = (value, minimumFractionDigits = 0) => {
|
||||||
if (!value || isNaN(value)) return "$0";
|
if (!value || isNaN(value)) return "$0";
|
||||||
@@ -1401,21 +1403,13 @@ const StatCards = ({
|
|||||||
|
|
||||||
if (!cachedData && error) {
|
if (!cachedData && error) {
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col items-center justify-center p-6 text-center">
|
<Alert variant="destructive">
|
||||||
<AlertCircle className="w-12 h-12 text-red-500 mb-4" />
|
<AlertCircle className="h-4 w-4" />
|
||||||
<h3 className="text-lg font-medium text-gray-900 dark:text-gray-100 mb-2">
|
<AlertTitle>Error</AlertTitle>
|
||||||
Error Loading Data
|
<AlertDescription>
|
||||||
</h3>
|
Failed to load stats: {error}
|
||||||
<p className="text-sm text-muted-foreground mb-4">
|
</AlertDescription>
|
||||||
{error}
|
</Alert>
|
||||||
</p>
|
|
||||||
<button
|
|
||||||
onClick={() => fetchDetailData(metric)}
|
|
||||||
className="px-4 py-2 bg-primary text-primary-foreground rounded-md hover:bg-primary/90 transition-colors"
|
|
||||||
>
|
|
||||||
Try Again
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1519,8 +1513,8 @@ const StatCards = ({
|
|||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
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 h-full">
|
||||||
<CardHeader className="p-6">
|
<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>
|
<div>
|
||||||
@@ -1549,14 +1543,16 @@ const StatCards = ({
|
|||||||
</Select>
|
</Select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Rest of the header content */}
|
|
||||||
</div>
|
</div>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className="p-6 pt-0">
|
<CardContent className="p-6 pt-0">
|
||||||
<p className="text-destructive text-center py-8">
|
<Alert variant="destructive">
|
||||||
Error loading stats: {error}
|
<AlertCircle className="h-4 w-4" />
|
||||||
</p>
|
<AlertTitle>Error</AlertTitle>
|
||||||
|
<AlertDescription>
|
||||||
|
Failed to load stats: {error}
|
||||||
|
</AlertDescription>
|
||||||
|
</Alert>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
);
|
);
|
||||||
@@ -1576,16 +1572,12 @@ const StatCards = ({
|
|||||||
<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>
|
||||||
{description && (
|
{lastUpdate && !loading && (
|
||||||
<CardDescription className="mt-1">{description}</CardDescription>
|
<CardDescription className="text-xs"> Last updated {lastUpdate.toFormat("h:mm a")}</CardDescription>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-4">
|
<div className="flex items-center gap-4">
|
||||||
{lastUpdate && !loading && (
|
|
||||||
<span className="text-sm text-muted-foreground">
|
|
||||||
Last updated: {lastUpdate.toFormat("hh:mm a")}
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
<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 time range" />
|
<SelectValue placeholder="Select time range" />
|
||||||
@@ -1600,8 +1592,6 @@ const StatCards = ({
|
|||||||
</Select>
|
</Select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Rest of the header content */}
|
|
||||||
</div>
|
</div>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className="p-6 pt-0">
|
<CardContent className="p-6 pt-0">
|
||||||
|
|||||||
Reference in New Issue
Block a user