diff --git a/src/App.tsx b/src/App.tsx index 1b2f45f..5a981c2 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -6,7 +6,7 @@ import { OrdersPage } from './screens/OrdersPage' import { CallsPage } from './screens/CallsPage' import { StoresPage } from './screens/StoresPage' import { DailyLogsPage } from './screens/DailyLogsPage' - +import { AnalyticsPage } from './screens/AnalyticsPage' function App() { return ( @@ -25,6 +25,8 @@ function App() { } /> } /> + + } /> } /> diff --git a/src/api/config.ts b/src/api/config.ts index 68be40f..eaf6229 100644 --- a/src/api/config.ts +++ b/src/api/config.ts @@ -56,6 +56,8 @@ export const ORDER_BOOKING = { recordViews: { ORDERS: '1e424561-9a27-44f5-9b68-64d63296d837', CALLS: '56b21b46-6d2c-4e10-b5a3-c63d058d0afa', + ANALYTICS_ORDERS: '63714ac2-c9fb-40e2-abba-d4081a70b768', + ANALYTICS: '56b21b46-6d2c-4e10-b5a3-c63d058d0afa' }, detailViews: { ORDERS: '0804c6c3-6cf9-4050-94bb-fa48dd5de87d', diff --git a/src/screens/AnalyticsPage.tsx b/src/screens/AnalyticsPage.tsx new file mode 100644 index 0000000..28d24f6 --- /dev/null +++ b/src/screens/AnalyticsPage.tsx @@ -0,0 +1,177 @@ +import { useEffect, useState } from 'react'; +import { orderBookingClient } from '../api/clients'; +import { ORDER_BOOKING } from '../api/config'; +import { Spinner } from '../components/reusable/Spinner'; +import { EmptyState } from '../components/reusable/EmptyState'; +import { Activity, Package, ShoppingBag, BarChart3, TrendingUp, Phone } from 'lucide-react'; +import { formatValue } from '../lib/format'; +import { + BarChart, Bar, XAxis, YAxis, CartesianGrid, Tooltip as RechartsTooltip, ResponsiveContainer, + PieChart, Pie, Cell, Legend +} from 'recharts'; + +interface Tile { + tile_uid: string; + key: string; + value: string | number; +} + +interface ChartRow { + dimension: string | null; + value: number; +} + +interface ChartData { + chart_uid: string; + key: string; + rows: ChartRow[]; +} + +const COLORS = ['#6366f1', '#10b981', '#f59e0b', '#ef4444', '#8b5cf6', '#06b6d4', '#f97316']; + +export function AnalyticsPage() { + const [tiles, setTiles] = useState([]); + const [charts, setCharts] = useState([]); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + + useEffect(() => { + let live = true; + async function run() { + try { + setLoading(true); + const res = await orderBookingClient.recordView(ORDER_BOOKING.recordViews.ANALYTICS, { + limit: 100, + sortBy: 'instance_id', + sortDir: 'desc' + }); + + if (live) { + setTiles((res.tile_values || []) as Tile[]); + setCharts((res.chart_data || []) as ChartData[]); + } + } catch (e: any) { + if (live) setError(e.message || 'Failed to load analytics'); + } finally { + if (live) setLoading(false); + } + } + run(); + return () => { live = false; }; + }, []); + + if (loading) return
; + if (error) return ; + + // Tile Helper + const getTileLabelAndIcon = (key: string) => { + switch (key) { + case 'total_orders': return { label: 'Total Orders', icon: ShoppingBag, color: 'text-indigo-500', bg: 'bg-indigo-50' }; + case 'total_bags': return { label: 'Total Bags', icon: Package, color: 'text-amber-500', bg: 'bg-amber-50' }; + case 'total_kgs': return { label: 'Total KGs', icon: Activity, color: 'text-emerald-500', bg: 'bg-emerald-50' }; + case 'total_calls': return { label: 'Total Calls', icon: Phone, color: 'text-blue-500', bg: 'bg-blue-50' }; + case 'total_productive_calls': return { label: 'Productive Calls', icon: TrendingUp, color: 'text-green-500', bg: 'bg-green-50' }; + case 'no_order_calls': return { label: 'No Order Calls', icon: Activity, color: 'text-red-500', bg: 'bg-red-50' }; + default: return { label: key.replace(/_/g, ' '), icon: BarChart3, color: 'text-slate-500', bg: 'bg-slate-50' }; + } + }; + + const renderChart = (chart: ChartData, index: number) => { + const data = chart.rows.map(r => ({ + name: r.dimension || 'Unknown', + value: r.value + })); + + const title = chart.key.split('_').map(w => w.charAt(0).toUpperCase() + w.slice(1)).join(' '); + + // Decide chart type based on key + if (chart.key === 'status_overview') { + return ( +
+
+

{title}

+
+
+ + + + {data.map((entry, index) => ( + + ))} + + + + + +
+
+ ); + } + + return ( +
+
+

{title}

+
+
+ + + + + + + + + +
+
+ ); + }; + + return ( +
+
+ +

Analytics Overview

+
+ + {/* Tiles Grid */} +
+ {tiles.map((t) => { + const { label, icon: Icon, color, bg } = getTileLabelAndIcon(t.key); + return ( +
+
+ +
+
+ {label} + + {t.key === 'total_kgs' ? Number(t.value).toLocaleString(undefined, { maximumFractionDigits: 1 }) : formatValue(t.value)} + +
+
+ ); + })} +
+ + {/* Charts Grid */} +
+ {charts.map((chart, index) => renderChart(chart, index))} +
+
+ ); +} diff --git a/src/screens/ConsoleLayout.tsx b/src/screens/ConsoleLayout.tsx index 68639dd..bb1ed28 100644 --- a/src/screens/ConsoleLayout.tsx +++ b/src/screens/ConsoleLayout.tsx @@ -52,9 +52,9 @@ export function ConsoleLayout() {