diff --git a/distribution_fe/app/(dashboard)/stores/[id]/page.tsx b/distribution_fe/app/(dashboard)/stores/[id]/page.tsx index c9be7e2..270c466 100644 --- a/distribution_fe/app/(dashboard)/stores/[id]/page.tsx +++ b/distribution_fe/app/(dashboard)/stores/[id]/page.tsx @@ -2,7 +2,6 @@ import { useEffect, useState } from 'react' import { useParams, useRouter } from 'next/navigation' -import dynamic from 'next/dynamic' import { HugeiconsIcon } from '@hugeicons/react' import { ArrowLeft01Icon, PencilEdit01Icon } from '@hugeicons/core-free-icons' import { Button } from '@/components/ui/button' @@ -11,36 +10,21 @@ import { toast } from '@/components/ui/toast' import { stores as storesApi, routes as routesApi, getApiError, getPaginatedItems } from '@/lib/api/index' import type { StoreResponse, RouteResponse } from '@/types/api' -const LocationPicker = dynamic( - () => import('../_components/location-picker').then((m) => m.LocationPicker), - { ssr: false, loading: () =>
} -) +import { StoreAnalytics } from '../_components/store-analytics' const fmtDate = (v: string) => new Date(v).toLocaleDateString('en-GB', { day: '2-digit', month: 'short', year: 'numeric' }) -const fmtMoney = (v: number) => - `Rs. ${v.toLocaleString('en-LK', { minimumFractionDigits: 2 })}` function DetailRow({ label, children }: { label: string; children: React.ReactNode }) { return ( -
+
{label} {children}
) } -function StatCard({ label, value, sub }: { label: string; value: string; sub?: string }) { - return ( -
-

{label}

-

{value}

- {sub &&

{sub}

} -
- ) -} - export default function StoreViewPage() { const { id } = useParams<{ id: string }>() const router = useRouter() @@ -66,7 +50,6 @@ export default function StoreViewPage() { const routeName = store ? routesList.find((r) => r.id === store.routeId)?.routeName : undefined - const availableCredit = store ? store.creditLimit - store.outstandingBalance : 0 return (
@@ -103,73 +86,26 @@ export default function StoreViewPage() {
- {/* Body skeleton */} - {loading && ( -
-
- {Array.from({ length: 7 }).map((_, i) => ( -
- ))} -
-
-
- {Array.from({ length: 3 }).map((_, i) => ( -
- ))} -
-
-
-
- )} + {loading &&
} {/* Body */} {!loading && store && ( -
- - {/* Left — store details */} -
-
- {store.ownerName ?? "—"} - {store.contactNumber ?? "—"} - {store.storeAddress} - - {routeName ?? {store.routeId}} - - {store.segment && ( - {store.segment} - )} - {fmtMoney(store.creditLimit)} - {fmtDate(store.createdAt)} -
-
- - {/* Right — credit stats + map */} -
-
- - - -
- - +
+
+ {store.ownerName ?? '—'} + {store.contactNumber ?? '—'} + + {routeName ?? {store.routeId}} + + {store.storeAddress} + {store.segment ?? '—'} + {fmtDate(store.createdAt)}
)} + + {/* Shop analytics */} + {!loading && store && }
) } diff --git a/distribution_fe/app/(dashboard)/stores/_components/store-analytics.tsx b/distribution_fe/app/(dashboard)/stores/_components/store-analytics.tsx new file mode 100644 index 0000000..41b5fb0 --- /dev/null +++ b/distribution_fe/app/(dashboard)/stores/_components/store-analytics.tsx @@ -0,0 +1,403 @@ +'use client' + +import { useEffect, useState } from 'react' +import { + Area, + AreaChart, + Bar, + BarChart, + CartesianGrid, + Cell, + Label, + Line, + LineChart, + PolarAngleAxis, + Pie, + PieChart, + RadialBar, + RadialBarChart, + XAxis, + YAxis, +} from 'recharts' +import { Badge } from '@/components/ui/badge' +import { Skeleton } from '@/components/ui/skeleton' +import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs' +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card' +import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table' +import { + ChartContainer, + ChartLegend, + ChartLegendContent, + ChartTooltip, + ChartTooltipContent, + type ChartConfig, +} from '@/components/ui/chart' +import { toast } from '@/components/ui/toast' +import { stores as storesApi, getApiError } from '@/lib/api/index' +import type { StoreAnalyticsResponse } from '@/types/api' + +const fmtMoney = (v: number) => + `Rs. ${v.toLocaleString('en-LK', { minimumFractionDigits: 2, maximumFractionDigits: 2 })}` + +const fmtCompact = (v: number) => + v.toLocaleString('en-LK', { notation: 'compact', maximumFractionDigits: 1 }) + +const fmtDate = (v: string) => + new Date(v).toLocaleDateString('en-GB', { day: '2-digit', month: 'short', year: 'numeric' }) + +const fmtMonth = (v: string) => + new Date(`${v}-01`).toLocaleDateString('en-GB', { month: 'short', year: '2-digit' }) + +const daysAgo = (v: string) => { + const d = Math.floor((Date.now() - new Date(v).getTime()) / 86_400_000) + return d <= 0 ? 'Today' : d === 1 ? '1 day ago' : `${d} days ago` +} + +const salesConfig = { + invoiced: { label: 'Invoiced', color: 'var(--chart-2)' }, + collected: { label: 'Collected', color: 'var(--chart-4)' }, +} satisfies ChartConfig + +const trendConfig = { + invoiced: { label: "Invoiced", color: "var(--chart-2)" }, + outstanding: { label: "Outstanding", color: "var(--destructive)" }, +} satisfies ChartConfig + +const creditConfig = { + used: { label: 'Credit used', color: 'var(--chart-2)' }, +} satisfies ChartConfig + +const AGEING_FILL: Record = { + 'Current': { color: 'var(--chart-2)', opacity: 1 }, + '1-30': { color: 'var(--destructive)', opacity: 0.45 }, + '31-60': { color: 'var(--destructive)', opacity: 0.65 }, + '61-90': { color: 'var(--destructive)', opacity: 0.85 }, + '90+': { color: 'var(--destructive)', opacity: 1 }, +} + +const PRODUCT_COLORS = ['var(--chart-1)', 'var(--chart-2)', 'var(--chart-3)', 'var(--chart-4)', 'var(--chart-5)', 'var(--muted-foreground)'] + +function DonutCenter({ title, value }: { title: string; value: string }) { + return ( +