diff --git a/app/globals.css b/app/globals.css index 3ee6b34..dd54312 100644 --- a/app/globals.css +++ b/app/globals.css @@ -10,17 +10,18 @@ body { .text-balance { text-wrap: balance; } - + /* Shimmer animation for loading indicators */ @keyframes shimmer { 0% { background-position: -200% 0; } + 100% { background-position: 200% 0; } } - + /* Accessibility improvements */ .sr-only { position: absolute; @@ -33,164 +34,182 @@ body { white-space: nowrap; border: 0; } - + /* Better focus states for keyboard navigation */ .focus-visible:focus-visible { outline: 2px solid hsl(var(--ring)); outline-offset: 2px; } - + /* Improved touch targets for mobile/Chromebook */ .touch-target { min-height: 44px; min-width: 44px; } - + /* Better contrast for Chromebook displays */ @media (prefers-contrast: high) { .border-input { border-color: hsl(var(--foreground)); } } - + /* Chromebook and touch device optimizations */ @media (pointer: coarse) { .touch-target { min-height: 48px; min-width: 48px; } - + /* Larger touch targets for interactive elements */ - button, input, textarea, [role="button"] { + button, + input, + textarea, + [role="button"] { min-height: 44px; } } - + /* Better focus indicators for keyboard navigation */ .focus-visible:focus-visible { outline: 2px solid hsl(var(--ring)); outline-offset: 2px; box-shadow: 0 0 0 2px hsl(var(--ring) / 0.2); } - + /* Improved scrolling for touch devices */ .overflow-y-auto { -webkit-overflow-scrolling: touch; scroll-behavior: smooth; } - + /* Enhanced contrast for better visibility */ .text-muted-foreground { color: hsl(var(--muted-foreground) / 0.8); } - + /* Better button contrast */ button:not(:disabled):hover { filter: brightness(1.05); } - + /* Improved focus visibility */ - input:focus, textarea:focus, button:focus { + input:focus, + textarea:focus, + button:focus { outline: 2px solid hsl(var(--ring)); outline-offset: 2px; } - + /* Better message bubble contrast */ .bg-primary { background-color: hsl(var(--primary) / 0.9); } - + /* Chromebook-specific optimizations */ @media screen and (max-width: 1366px) and (min-resolution: 1.5dppx) { + /* Chromebook display optimizations */ .text-sm { font-size: 0.875rem; line-height: 1.25rem; } - + .text-base { font-size: 1rem; line-height: 1.5rem; } - + /* Better touch targets for Chromebooks */ - button, input, textarea, [role="button"], [role="tab"] { + button, + input, + textarea, + [role="button"], + [role="tab"] { min-height: 48px; min-width: 48px; } - + /* Improved spacing for Chromebook screens */ - .space-y-2 > * + * { + .space-y-2>*+* { margin-top: 0.75rem; } - - .space-y-4 > * + * { + + .space-y-4>*+* { margin-top: 1.25rem; } } - + /* Chromebook touch screen optimizations */ @media (pointer: coarse) and (hover: none) { + /* Larger touch targets */ .touch-target { min-height: 52px; min-width: 52px; } - + /* Better spacing for touch interactions */ - .space-y-2 > * + * { + .space-y-2>*+* { margin-top: 1rem; } - + /* Improved button padding */ button { padding: 0.75rem 1rem; } - + /* Better input field sizing */ - input, textarea { + input, + textarea { padding: 0.875rem; font-size: 1rem; } - + /* Enhanced focus states for touch */ - button:focus-visible, input:focus-visible, textarea:focus-visible { + button:focus-visible, + input:focus-visible, + textarea:focus-visible { outline: 3px solid hsl(var(--ring)); outline-offset: 2px; } } - + /* Chromebook keyboard navigation improvements */ @media (hover: hover) and (pointer: fine) { + /* Better hover states for mouse/trackpad */ button:hover:not(:disabled) { transform: translateY(-1px); box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); } - + /* Improved focus indicators */ - button:focus-visible, input:focus-visible, textarea:focus-visible { + button:focus-visible, + input:focus-visible, + textarea:focus-visible { outline: 2px solid hsl(var(--ring)); outline-offset: 2px; box-shadow: 0 0 0 4px hsl(var(--ring) / 0.2); } } - + /* Chromebook display scaling fixes */ @media screen and (min-resolution: 1.5dppx) { + /* Prevent text from being too small on high-DPI displays */ html { -webkit-text-size-adjust: 100%; text-size-adjust: 100%; } - + /* Better font rendering */ body { -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; } } - + /* Chromebook scrolling improvements */ .overflow-y-auto { -webkit-overflow-scrolling: touch; @@ -198,14 +217,14 @@ body { /* Better momentum scrolling for Chromebooks */ overscroll-behavior: contain; } - + /* Chromebook chat interface optimizations */ .chat-message { /* Better message bubble sizing for touch */ min-height: 44px; padding: 0.75rem; } - + /* Chromebook form optimizations */ .form-input { /* Better input field sizing for Chromebooks */ @@ -213,7 +232,7 @@ body { font-size: 1rem; padding: 0.75rem 1rem; } - + /* Chromebook button optimizations */ .btn-chromebook { min-height: 48px; @@ -222,17 +241,17 @@ body { font-size: 1rem; border-radius: 0.5rem; } - + /* Enhanced keyboard focus indicators for Chromebooks */ .keyboard-focus { outline: 3px solid hsl(var(--ring)); outline-offset: 2px; box-shadow: 0 0 0 4px hsl(var(--ring) / 0.3); } - + /* Better focus management for Chromebook keyboard navigation */ - button:focus-visible, - input:focus-visible, + button:focus-visible, + input:focus-visible, textarea:focus-visible, [role="button"]:focus-visible, [role="tab"]:focus-visible { @@ -240,22 +259,31 @@ body { outline-offset: 2px; box-shadow: 0 0 0 4px hsl(var(--ring) / 0.3); } - + /* Chromebook-specific focus ring */ @media (prefers-reduced-motion: no-preference) { .keyboard-focus { transition: outline 0.2s ease, box-shadow 0.2s ease; } } - + .bg-muted { background-color: hsl(var(--muted) / 0.8); } /* Christmas-themed animations */ @keyframes twinkle { - 0%, 100% { opacity: 1; transform: scale(1); } - 50% { opacity: 0.3; transform: scale(0.8); } + + 0%, + 100% { + opacity: 1; + transform: scale(1); + } + + 50% { + opacity: 0.3; + transform: scale(0.8); + } } @keyframes snowflake { @@ -263,6 +291,7 @@ body { transform: translateY(-100vh) rotate(0deg); opacity: 1; } + 100% { transform: translateY(100vh) rotate(360deg); opacity: 0; @@ -270,20 +299,26 @@ body { } @keyframes sparkle { - 0%, 100% { + + 0%, + 100% { opacity: 0; transform: scale(0) rotate(0deg); } - 50% { + + 50% { opacity: 1; transform: scale(1) rotate(180deg); } } @keyframes glow { - 0%, 100% { + + 0%, + 100% { box-shadow: 0 0 5px hsl(var(--christmas-red)), 0 0 10px hsl(var(--christmas-red)), 0 0 15px hsl(var(--christmas-red)); } + 50% { box-shadow: 0 0 10px hsl(var(--christmas-green)), 0 0 20px hsl(var(--christmas-green)), 0 0 30px hsl(var(--christmas-green)); } @@ -312,19 +347,19 @@ body { /* Subtle Christmas gradient backgrounds */ .christmas-gradient { - background: linear-gradient(135deg, - hsl(var(--christmas-red) / 0.1) 0%, - hsl(var(--christmas-green) / 0.1) 50%, - hsl(var(--christmas-gold) / 0.1) 100%); + background: linear-gradient(135deg, + hsl(var(--christmas-red) / 0.1) 0%, + hsl(var(--christmas-green) / 0.1) 50%, + hsl(var(--christmas-gold) / 0.1) 100%); } /* Christmas-themed borders */ .christmas-border { border: 2px solid; - border-image: linear-gradient(45deg, - hsl(var(--christmas-red)), - hsl(var(--christmas-green)), - hsl(var(--christmas-gold))) 1; + border-image: linear-gradient(45deg, + hsl(var(--christmas-red)), + hsl(var(--christmas-green)), + hsl(var(--christmas-gold))) 1; } /* Christmas-themed styles - only active in December */ @@ -342,7 +377,7 @@ body { left: 0; width: 100%; height: 100%; - background-image: + background-image: radial-gradient(circle at 20% 50%, hsl(var(--christmas-red) / 0.03) 0%, transparent 50%), radial-gradient(circle at 80% 80%, hsl(var(--christmas-green) / 0.03) 0%, transparent 50%), radial-gradient(circle at 40% 20%, hsl(var(--christmas-gold) / 0.03) 0%, transparent 50%); @@ -360,9 +395,9 @@ body { /* Using more specific selector to avoid Turbopack CSS parsing issues */ .christmas-theme button[class*="bg-primary"]:hover, .christmas-theme [class*="bg-primary"]:hover { - background: linear-gradient(135deg, - hsl(var(--christmas-red)), - hsl(var(--christmas-green))); + background: linear-gradient(135deg, + hsl(var(--christmas-red)), + hsl(var(--christmas-green))); transition: background 0.3s ease; } @@ -376,6 +411,42 @@ body { .christmas-theme *:focus-visible { outline-color: hsl(var(--christmas-red)); } + + /* Premium UI Utilities */ + .glass-morphism { + @apply bg-background/60 backdrop-blur-md border border-border/50; + } + + .dark .glass-morphism { + @apply bg-black/40 backdrop-blur-xl border-white/5; + } + + .premium-card { + @apply transition-all duration-300; + } + + .premium-card:hover { + box-shadow: 0 8px 30px rgba(0, 0, 0, 0.4); + border-color: hsl(var(--primary) / 0.2); + } + + .dark .premium-card { + @apply bg-card; + } + + .dark .premium-card:hover { + box-shadow: 0 8px 40px rgba(0, 0, 0, 0.6); + border-color: hsl(var(--primary) / 0.2); + } + + .text-gradient { + @apply bg-clip-text text-transparent bg-gradient-to-r from-primary to-primary/60; + } + + .bg-gradient-premium { + background: radial-gradient(circle at top left, hsl(var(--primary) / 0.05), transparent), + radial-gradient(circle at bottom right, hsl(var(--primary) / 0.02), transparent); + } } @layer base { @@ -410,26 +481,27 @@ body { --christmas-green: 142 76% 36%; --christmas-gold: 43 96% 56%; } + .dark { - --background: 0 0% 3.9%; + --background: 240 10% 2%; --foreground: 0 0% 98%; - --card: 0 0% 3.9%; + --card: 240 10% 3%; --card-foreground: 0 0% 98%; - --popover: 0 0% 3.9%; + --popover: 240 10% 2%; --popover-foreground: 0 0% 98%; --primary: 0 0% 98%; --primary-foreground: 0 0% 9%; - --secondary: 0 0% 14.9%; + --secondary: 240 4% 10%; --secondary-foreground: 0 0% 98%; - --muted: 0 0% 14.9%; - --muted-foreground: 0 0% 63.9%; - --accent: 0 0% 14.9%; + --muted: 240 4% 10%; + --muted-foreground: 240 5% 64.9%; + --accent: 240 4% 10%; --accent-foreground: 0 0% 98%; --destructive: 0 62.8% 30.6%; --destructive-foreground: 0 0% 98%; - --border: 0 0% 14.9%; - --input: 0 0% 14.9%; - --ring: 0 0% 83.1%; + --border: 240 4% 12%; + --input: 240 4% 12%; + --ring: 240 5% 83.1%; --chart-1: 220 70% 50%; --chart-2: 160 60% 45%; --chart-3: 30 80% 55%; @@ -464,7 +536,8 @@ body { * { @apply border-border; } + body { @apply bg-background text-foreground; } -} +} \ No newline at end of file diff --git a/components/admin/AdminAnalytics.tsx b/components/admin/AdminAnalytics.tsx index 2e2e0a0..a44991f 100644 --- a/components/admin/AdminAnalytics.tsx +++ b/components/admin/AdminAnalytics.tsx @@ -263,7 +263,7 @@ export default function AdminAnalytics() { // Helper to transform data for recharts const transformChartData = ( - data: Array<{ date: string; [key: string]: any }>, + data: Array<{ date: string;[key: string]: any }>, valueKey: string = "count", ) => { if (!data || data.length === 0) return []; @@ -275,10 +275,10 @@ export default function AdminAnalytics() { const date = parts.length === 3 ? new Date( - parseInt(parts[0]), - parseInt(parts[1]) - 1, - parseInt(parts[2]), - ) + parseInt(parts[0]), + parseInt(parts[1]) - 1, + parseInt(parts[2]), + ) : new Date(dateStr); // Format with day of week: "Mon, Nov 21" @@ -329,10 +329,10 @@ export default function AdminAnalytics() { const date = parts.length === 3 ? new Date( - parseInt(parts[0]), - parseInt(parts[1]) - 1, - parseInt(parts[2]), - ) + parseInt(parts[0]), + parseInt(parts[1]) - 1, + parseInt(parts[2]), + ) : new Date(dateStr); // Format with day of week: "Mon, Nov 21" @@ -1365,8 +1365,8 @@ export default function AdminAnalytics() {
) : growthData?.customers ? ( -
- +
+ diff --git a/components/analytics/AnalyticsDashboard.tsx b/components/analytics/AnalyticsDashboard.tsx index 4e37f52..df8f163 100644 --- a/components/analytics/AnalyticsDashboard.tsx +++ b/components/analytics/AnalyticsDashboard.tsx @@ -195,211 +195,204 @@ export default function AnalyticsDashboard({ ]; return ( -
- {/* Header with Privacy Toggle */} -
+
+ {/* Header with Integrated Toolbar */} +

Analytics Dashboard

-

- Overview of your store's performance and metrics. +

+ Real-time performance metrics and AI-driven insights.

-
+ +
+ +
+
- {/* Key Metrics Cards */} - -
- {isLoading - ? [...Array(4)].map((_, i) => ) - : metrics.map((metric) => ( - - ))} -
- - {/* Completion Rate Card */} - - - - - - Order Completion Rate - - - Percentage of orders that have been successfully completed - - - - {isLoading ? ( -
-
-
-
-
-
-
- ) : ( -
-
- {hideNumbers ? "**%" : `${data.orders.completionRate}%`} -
-
-
-
-
-
- - {hideNumbers - ? "** / **" - : `${data.orders.completed} / ${data.orders.total}`} - -
- )} - - - - - {/* Time Period Selector */} -
-
-

Time Period

-

- Revenue, Profit, and Orders tabs use time filtering. Products and - Customers show all-time data. -

-
- -
- - {/* Analytics Tabs */} -
- - - + + {/* Analytics Tabs Setup */} + +
+ + - Growth + Overview - - - Revenue + + + Financials - - - Profit - - - - Products - - - - Customers - - + - Orders + Performance - - - Predictions + + + AI Insights - - - }> - - - - + {/* Contextual Time Range Selector */} +
+ Range + +
+
- - - }> - - - - + + + {/* Key Metrics Cards */} +
+ {isLoading + ? [...Array(4)].map((_, i) => ) + : metrics.map((metric) => ( + + ))} +
- - - {/* Date Range Selector for Profit Calculator */} - +
+ {/* Completion Rate Card */} + - Date Range + + + Order Completion + - Select a custom date range for profit calculations + Successfully processed orders -
- - {profitDateRange?.from && profitDateRange?.to && ( -
- - {profitDateRange.from.toLocaleDateString()} -{" "} - {profitDateRange.to.toLocaleDateString()} - + {isLoading ? ( + + ) : ( +
+
+
+ {hideNumbers ? "**%" : `${data.orders.completionRate}%`} +
+ + {hideNumbers + ? "** / **" + : `${data.orders.completed} / ${data.orders.total}`} +
- )} -
+
+ +
+
+ )} + + {/* Growth Chart Snippet (Simplified) */} +
+ }> + + +
+
+ + + + + + }> +
+ +
+
+ +
+ + + Profit Range + + Custom date selection for analysis + + + + + + + }> - - +
+
+
- - - }> + + + }> +
- - - - - - - }> - - - - - - - +
+
+
}> - - - - - }> - + - - - -
+
+
+
+ + + + }> + + + + +
); diff --git a/components/analytics/GrowthAnalyticsChart.tsx b/components/analytics/GrowthAnalyticsChart.tsx index cde1ba3..52289de 100644 --- a/components/analytics/GrowthAnalyticsChart.tsx +++ b/components/analytics/GrowthAnalyticsChart.tsx @@ -183,7 +183,7 @@ export default function GrowthAnalyticsChart({
) : growthData?.monthly && growthData.monthly.length > 0 ? (
- + ({ ...m, diff --git a/components/analytics/MetricsCard.tsx b/components/analytics/MetricsCard.tsx index 933883a..450870c 100644 --- a/components/analytics/MetricsCard.tsx +++ b/components/analytics/MetricsCard.tsx @@ -25,42 +25,88 @@ export default function MetricsCard({ const getTrendIcon = () => { switch (trend) { case "up": - return ; + return ; case "down": - return ; + return ; default: - return ; + return ; } }; const getTrendColor = () => { switch (trend) { case "up": - return "text-green-600"; + return "text-emerald-400 bg-emerald-500/10 border-emerald-500/10"; case "down": - return "text-red-600"; + return "text-rose-400 bg-rose-500/10 border-rose-500/10"; default: - return "text-gray-600"; + return "text-blue-400 bg-blue-500/10 border-blue-500/10"; } }; + const getCategoryColor = () => { + const t = title.toLowerCase(); + if (t.includes("revenue") || t.includes("profit")) return "amber"; + if (t.includes("order")) return "blue"; + if (t.includes("customer")) return "indigo"; + if (t.includes("product") || t.includes("inventory")) return "purple"; + return "primary"; + } + + const categoryColor = getCategoryColor(); + + const getIconContainerColor = () => { + switch (categoryColor) { + case "amber": return "bg-amber-500/15 text-amber-500 border-amber-500/20"; + case "blue": return "bg-blue-500/15 text-blue-500 border-blue-500/20"; + case "indigo": return "bg-indigo-500/15 text-indigo-500 border-indigo-500/20"; + case "purple": return "bg-purple-500/15 text-purple-500 border-purple-500/20"; + default: return "bg-primary/15 text-primary border-primary/20"; + } + } + + const getBadgeColor = () => { + switch (categoryColor) { + case "amber": return "bg-amber-500/10 text-amber-400/80 border-amber-500/20"; + case "blue": return "bg-blue-500/10 text-blue-400/80 border-blue-500/20"; + case "indigo": return "bg-indigo-500/10 text-indigo-400/80 border-indigo-500/20"; + case "purple": return "bg-purple-500/10 text-purple-400/80 border-purple-500/20"; + default: return "bg-primary/10 text-primary/60 border-primary/20"; + } + } + return ( - - - - + + + + {title} - +
+ +
- -
{value}
-

{description}

-
- {getTrendIcon()} - - {trendValue} - + + +
+
+ {value} +
+

+ {description} +

+
+ +
+
+ {getTrendIcon()} + + {trend === "up" ? "+" : ""}{trendValue} + +
diff --git a/components/analytics/PredictionsChart.tsx b/components/analytics/PredictionsChart.tsx index af37d4a..ad12647 100644 --- a/components/analytics/PredictionsChart.tsx +++ b/components/analytics/PredictionsChart.tsx @@ -164,6 +164,13 @@ export default function PredictionsChart({ setSimulationFactor(0); }, [timeRange]); + // Auto-adjust daysAhead if it exceeds historical timeRange + useEffect(() => { + if (daysAhead > timeRange) { + setDaysAhead(timeRange); + } + }, [timeRange, daysAhead]); + // Switch predictions when daysAhead changes (instant, from batch) useEffect(() => { if (batchData) { @@ -322,10 +329,18 @@ export default function PredictionsChart({ 7 days - 14 days - 30 days - 60 days - 90 days + + 14 days {timeRange < 14 && "(Needs 14d history)"} + + + 30 days {timeRange < 30 && "(Needs 30d history)"} + + + 60 days {timeRange < 60 && "(Needs 60d history)"} + + + 90 days {timeRange < 90 && "(Needs 90d history)"} +
- +

Predicted daily average revenue for the next {daysAhead} days

@@ -409,7 +424,7 @@ export default function PredictionsChart({ - +

Based on data consistency, historical accuracy, and model agreement

@@ -428,7 +443,7 @@ export default function PredictionsChart({ - +

Predictions generated using a Deep Learning Ensemble Model

@@ -461,7 +476,7 @@ export default function PredictionsChart({ - +

Direction of the recent sales trend (slope analysis)

@@ -504,7 +519,7 @@ export default function PredictionsChart({ - +

Technical details about the active prediction model

@@ -521,7 +536,7 @@ export default function PredictionsChart({ Hybrid Ensemble (Deep Learning) - +

Combines LSTM Neural Networks with Statistical Methods (Holt-Winters, ARIMA)

@@ -561,58 +576,104 @@ export default function PredictionsChart({ {/* Daily Predictions Chart */} {predictions?.sales?.dailyPredictions && predictions?.sales?.dailyPredictions.length > 0 && ( - - - - Daily Revenue Forecast - -
-
- - Simulate Traffic:{" "} - 0 ? "text-green-600" : simulationFactor < 0 ? "text-red-600" : ""}> - {simulationFactor > 0 ? "+" : ""} - {simulationFactor}% - - - setSimulationFactor(val[0])} - onValueCommit={(val) => setCommittedSimulationFactor(val[0])} - className="w-[150px] mt-1.5" - /> + + +
+
+ + + Scenario Lab + + + Adjust variables to see how traffic shifts impact your bottom line. +
- {simulationFactor !== 0 && ( - - )} + +
+
+
+ + Traffic Simulation + + + + + + + +

+ Simulate traffic growth or decline to see how it might impact your future revenue and order volume. +

+
+
+
+
+
+ setSimulationFactor(val[0])} + onValueCommit={(val) => setCommittedSimulationFactor(val[0])} + className="w-full flex-1" + /> + 0 ? "text-emerald-400 border-emerald-500/30 bg-emerald-500/10" : simulationFactor < 0 ? "text-rose-400 border-rose-500/30 bg-rose-500/10" : "text-primary/60"}`}> + {simulationFactor > 0 ? "+" : ""}{simulationFactor}% + +
+
+ + {(simulationFactor !== 0 || committedSimulationFactor !== 0) && ( + + )} +
+ +
-
- -
- {isSimulating && ( -
- + + {/* Legend / Key */} +
+
+
+ Baseline Forecast +
+ {committedSimulationFactor !== 0 && ( +
+
+ Simulated Scenario
)} - +
+ +
+ {isSimulating && ( +
+
+
+ + +
+ Running Neural Simulation... +
+
+ )} + - + `£${value}`} /> { if (active && payload?.length) { const data = payload[0].payload; return ( -
-

{data.formattedDate}

-

- Baseline: {formatGBP(data.baseline)} -

- {committedSimulationFactor !== 0 && ( -

- Simulated: {formatGBP(data.simulated)} -

- )} +
+

{data.formattedDate}

+
+
+ Baseline: + {formatGBP(data.baseline)} +
+ {committedSimulationFactor !== 0 && ( +
+ Simulated: +
+ {formatGBP(data.simulated)} + data.baseline ? 'text-emerald-500' : 'text-rose-500'}`}> + {data.simulated > data.baseline ? '▴' : '▾'} {Math.abs(((data.simulated / data.baseline - 1) * 100)).toFixed(1)}% + +
+
+ )} +
+ Est. Orders: + + {Math.round(data.orders)} +
+
); } return null; }} /> - {/* Always show baseline as solid line */} + {/* Always show baseline */} {/* Show simulated line when simulation is active */} {committedSimulationFactor !== 0 && ( @@ -694,11 +771,12 @@ export default function PredictionsChart({ type="monotone" dataKey="simulated" stroke="#10b981" - fillOpacity={0.6} + fillOpacity={1} fill="url(#colorSimulated)" + strokeWidth={3} strokeDasharray="5 5" - strokeWidth={2} - activeDot={{ r: 5, strokeWidth: 0 }} + dot={false} + activeDot={{ r: 6, strokeWidth: 3, stroke: "#fff", fill: "#10b981" }} /> )} diff --git a/components/analytics/ProfitAnalyticsChart.tsx b/components/analytics/ProfitAnalyticsChart.tsx index 9331d57..e2f3978 100644 --- a/components/analytics/ProfitAnalyticsChart.tsx +++ b/components/analytics/ProfitAnalyticsChart.tsx @@ -4,14 +4,15 @@ import { useState, useEffect } from 'react'; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; import { Badge } from "@/components/ui/badge"; import { Alert, AlertDescription } from "@/components/ui/alert"; -import { - TrendingUp, - TrendingDown, - DollarSign, +import { + TrendingUp, + TrendingDown, + DollarSign, PieChart, Calculator, Info, - AlertTriangle + AlertTriangle, + Package } from "lucide-react"; import { useToast } from "@/hooks/use-toast"; import { formatGBP } from "@/utils/format"; @@ -28,6 +29,7 @@ export default function ProfitAnalyticsChart({ timeRange, dateRange, hideNumbers const [data, setData] = useState(null); const [isLoading, setIsLoading] = useState(true); const [error, setError] = useState(null); + const [imageErrors, setImageErrors] = useState>({}); const { toast } = useToast(); const maskValue = (value: string): string => { @@ -93,7 +95,7 @@ export default function ProfitAnalyticsChart({ timeRange, dateRange, hideNumbers ))}
- + {/* Coverage Card Skeleton */} @@ -110,7 +112,7 @@ export default function ProfitAnalyticsChart({ timeRange, dateRange, hideNumbers
- + {/* Products List Skeleton */} @@ -188,7 +190,7 @@ export default function ProfitAnalyticsChart({ timeRange, dateRange, hideNumbers } const profitDirection = data.summary.totalProfit >= 0; - + // Fallback for backwards compatibility const revenueFromTracked = data.summary.revenueFromTrackedProducts || data.summary.totalRevenue || 0; const totalRevenue = data.summary.totalRevenue || 0; @@ -237,9 +239,8 @@ export default function ProfitAnalyticsChart({ timeRange, dateRange, hideNumbers Total Profit -
+
{profitDirection ? ( ) : ( @@ -286,7 +287,7 @@ export default function ProfitAnalyticsChart({ timeRange, dateRange, hideNumbers
-
@@ -307,7 +308,7 @@ export default function ProfitAnalyticsChart({ timeRange, dateRange, hideNumbers Most Profitable Products - {dateRange + {dateRange ? `Products generating the highest total profit (${new Date(dateRange.from).toLocaleDateString()} - ${new Date(dateRange.to).toLocaleDateString()})` : `Products generating the highest total profit (last ${timeRange || '30'} days)` } @@ -323,24 +324,43 @@ export default function ProfitAnalyticsChart({ timeRange, dateRange, hideNumbers
{data.topProfitableProducts.map((product, index) => { const profitPositive = product.totalProfit >= 0; - + return (
-
-
- {index + 1} +
+
+
+ {product.image && !imageErrors[product.productId] ? ( + {product.productName} { + setImageErrors(prev => ({ ...prev, [product.productId]: true })); + }} + /> + ) : ( +
+ {product.productName.charAt(0)} +
+ )} +
+
+ {index + 1} +
-

{product.productName}

-

+

{product.productName}

+

+ {product.totalQuantitySold} units sold

- +
{maskValue(formatGBP(product.totalProfit))} diff --git a/components/analytics/RevenueChart.tsx b/components/analytics/RevenueChart.tsx index c941b82..7c27504 100644 --- a/components/analytics/RevenueChart.tsx +++ b/components/analytics/RevenueChart.tsx @@ -175,7 +175,7 @@ export default function RevenueChart({ timeRange, hideNumbers = false }: Revenue
{/* Chart */}
- + diff --git a/lib/services/profit-analytics-service.ts b/lib/services/profit-analytics-service.ts index b9c16d3..1028055 100644 --- a/lib/services/profit-analytics-service.ts +++ b/lib/services/profit-analytics-service.ts @@ -18,6 +18,7 @@ export interface ProfitOverview { topProfitableProducts: Array<{ productId: string; productName: string; + image?: string; totalQuantitySold: number; totalRevenue: number; totalCost: number; @@ -51,7 +52,7 @@ export const getProfitOverview = async ( periodOrRange?: string | DateRange ): Promise => { let url = '/analytics/profit-overview'; - + if (periodOrRange && typeof periodOrRange !== 'string') { // Date range provided const startDate = periodOrRange.from.toISOString().split('T')[0]; @@ -62,7 +63,7 @@ export const getProfitOverview = async ( const period = periodOrRange || '30'; url += `?period=${period}`; } - + return apiRequest(url); }; @@ -70,7 +71,7 @@ export const getProfitTrends = async ( periodOrRange?: string | DateRange ): Promise => { let url = '/analytics/profit-trends'; - + if (periodOrRange && typeof periodOrRange !== 'string') { // Date range provided const startDate = periodOrRange.from.toISOString().split('T')[0]; @@ -81,6 +82,6 @@ export const getProfitTrends = async ( const period = periodOrRange || '30'; url += `?period=${period}`; } - + return apiRequest(url); };