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.
-
+
+
setHideNumbers(!hideNumbers)}
- className="flex items-center gap-2"
+ className={`flex items-center gap-2 rounded-xl transition-all font-medium px-4 ${hideNumbers ? 'bg-primary text-primary-foreground shadow-lg' : 'hover:bg-white/5'}`}
>
{hideNumbers ? (
<>
- Show Numbers
+ Numbers Hidden
>
) : (
<>
-
- Hide Numbers
+
+ Hide Numbers
>
)}
+
+
+
- Refresh
+ Refresh Data
- {/* 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.
-
-
-
-
-
-
-
- Last 7 days
- Last 30 days
- Last 90 days
-
-
-
-
- {/* Analytics Tabs */}
-
-
-
-
+
+ {/* Analytics Tabs Setup */}
+
+
+
+
- Growth
+ Overview
-
-
- Revenue
+
+
+ Financials
-
-
- Profit
-
-
-
- Products
-
-
-
- Customers
-
-
+
- Orders
+ Performance
-
-
- Predictions
+
+
+ AI Insights
-
-
- }>
-
-
-
-
+ {/* Contextual Time Range Selector */}
+
+ Range
+
+
+
+
+
+ Last 7 days
+ Last 30 days
+ Last 90 days
+
+
+
+
-
-
- }>
-
-
-
-
+
+
+ {/* 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 && (
-
{
- setSimulationFactor(0);
- setCommittedSimulationFactor(0);
- }}
- title="Reset simulation"
- >
-
-
- )}
+
+
+
+
+
+ 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) && (
+
{
+ setSimulationFactor(0);
+ setCommittedSimulationFactor(0);
+ }}
+ title="Reset Scenario"
+ >
+
+
+ )}
+
+
+
+
+ Export Forecast
+
-
-
- Export
-
-
-
- {isSimulating && (
-
-
+
+ {/* Legend / Key */}
+
+
+ {committedSimulationFactor !== 0 && (
+
)}
-
+
+
+
+ {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] ? (
+
{
+ 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);
};