From 236a676ac59c6a57c017e7d10040a62348876052 Mon Sep 17 00:00:00 2001 From: NotII <46204250+NotII@users.noreply.github.com> Date: Sun, 29 Jun 2025 04:13:50 +0100 Subject: [PATCH] holy fkn airball --- app/dashboard/analytics/page.tsx | 177 ++++++++++++---- components/analytics/AnalyticsDashboard.tsx | 57 +----- .../analytics/CustomerInsightsChart.tsx | 28 +-- components/analytics/OrderAnalyticsChart.tsx | 28 +-- .../analytics/ProductPerformanceChart.tsx | 24 +-- components/analytics/RevenueChart.tsx | 23 +-- components/analytics/StoreSelector.tsx | 114 +++++++++++ lib/api.ts | 34 +++- lib/server-api.ts | 116 ++++++++++- lib/services/analytics-service.ts | 190 ++++++++++++++++++ utils/format.ts | 11 +- 11 files changed, 638 insertions(+), 164 deletions(-) create mode 100644 components/analytics/StoreSelector.tsx create mode 100644 lib/services/analytics-service.ts diff --git a/app/dashboard/analytics/page.tsx b/app/dashboard/analytics/page.tsx index bb94346..8849f96 100644 --- a/app/dashboard/analytics/page.tsx +++ b/app/dashboard/analytics/page.tsx @@ -1,40 +1,149 @@ -import AnalyticsDashboard from "@/components/analytics/AnalyticsDashboard"; +import { Suspense } from 'react'; +import { cookies } from 'next/headers'; +import { redirect } from 'next/navigation'; +import Dashboard from "@/components/dashboard/dashboard"; +import AnalyticsDashboard from '@/components/analytics/AnalyticsDashboard'; +import StoreSelector from '@/components/analytics/StoreSelector'; +import { getAnalyticsOverviewServer } from '@/lib/server-api'; import { fetchServer } from '@/lib/api'; +import { performance } from 'perf_hooks'; +import { Info, GitCommit, User, Zap, BarChart3 } from 'lucide-react'; +import packageJson from '../../../package.json'; +import { getGitInfo } from '@/lib/utils/git'; -interface AnalyticsOverview { - orders: { - total: number; - completed: number; - pending: number; - completionRate: string; - }; - revenue: { - total: number; - monthly: number; - weekly: number; - averageOrderValue: number; - }; - products: { - total: number; - }; - customers: { - unique: number; - }; +// Vendor type for consistency +interface Vendor { + _id: string; + username: string; + storeId: string; + pgpKey: string; + __v: number; } -export default async function AnalyticsPage() { - const analyticsData = await fetchServer("/analytics/overview"); +interface User { + vendor: Vendor; +} - return ( -
-
-

Analytics Dashboard

-

- Comprehensive insights into your store performance, sales trends, and customer behavior. -

-
- - -
- ); +export default async function AnalyticsPage({ + searchParams, +}: { + searchParams: { storeId?: string }; +}) { + const startTime = performance.now(); + + // Check for storeId in query parameters (for staff users) + const storeId = searchParams.storeId; + + // Check for storeId in cookies (alternative method for staff users) + const cookieStore = await cookies(); + const cookieStoreId = cookieStore.get('storeId')?.value; + + // Use query parameter first, then cookie, then undefined (for vendors) + const finalStoreId = storeId || cookieStoreId; + + try { + // Fetch user data and analytics data in parallel + const [userResponse, initialData] = await Promise.all([ + fetchServer("/auth/me"), + getAnalyticsOverviewServer(finalStoreId) + ]); + + // Get git info using the utility + const gitInfo = getGitInfo(); + + const endTime = performance.now(); + const generationTime = (endTime - startTime).toFixed(2); + const panelVersion = packageJson.version; + const commitHash = gitInfo.hash; + + const vendor = userResponse.vendor; + + return ( + +
+ {/* Analytics Content */} + +
+
+

Loading analytics...

+
+
+ }> + + + + + {/* Footer with version info */} +
+
+ + v{panelVersion} +
+ +
+ + {vendor.username} +
+ +
+ + {commitHash} +
+ +
+ + Generated in {generationTime}ms +
+ + + {process.env.NODE_ENV || 'development'} + +
+
+ ); + } catch (error) { + console.error('Error fetching analytics data:', error); + + // If it's a 401/403 error, redirect to login + if (error instanceof Error && error.message.includes('401')) { + redirect('/login'); + } + + // If it's a 400 error (missing storeId for staff), show store selector + if (error instanceof Error && error.message.includes('400')) { + return ( + +
+
+

Analytics Dashboard

+

+ Please select a store to view analytics data. +

+
+ +
+
+ ); + } + + // For other errors, show a fallback + return ( + +
+
+

Analytics Dashboard

+

+ Unable to load analytics data. Please try refreshing the page. +

+ {finalStoreId && ( +

+ Accessing store: {finalStoreId} +

+ )} +
+
+
+ ); + } } \ No newline at end of file diff --git a/components/analytics/AnalyticsDashboard.tsx b/components/analytics/AnalyticsDashboard.tsx index a4968ec..823761d 100644 --- a/components/analytics/AnalyticsDashboard.tsx +++ b/components/analytics/AnalyticsDashboard.tsx @@ -17,34 +17,14 @@ import { Activity, RefreshCw } from "lucide-react"; -import { clientFetch } from "@/lib/api"; import { useToast } from "@/hooks/use-toast"; import RevenueChart from "./RevenueChart"; import ProductPerformanceChart from "./ProductPerformanceChart"; import CustomerInsightsChart from "./CustomerInsightsChart"; import OrderAnalyticsChart from "./OrderAnalyticsChart"; import MetricsCard from "./MetricsCard"; - -interface AnalyticsOverview { - orders: { - total: number; - completed: number; - pending: number; - completionRate: string; - }; - revenue: { - total: number; - monthly: number; - weekly: number; - averageOrderValue: number; - }; - products: { - total: number; - }; - customers: { - unique: number; - }; -} +import { getAnalyticsOverviewWithStore, type AnalyticsOverview } from "@/lib/services/analytics-service"; +import { formatGBP } from "@/utils/format"; interface AnalyticsDashboardProps { initialData: AnalyticsOverview; @@ -59,7 +39,7 @@ export default function AnalyticsDashboard({ initialData }: AnalyticsDashboardPr const refreshData = async () => { try { setIsLoading(true); - const newData = await clientFetch("/analytics/overview"); + const newData = await getAnalyticsOverviewWithStore(); setData(newData); toast({ title: "Data refreshed", @@ -79,18 +59,18 @@ export default function AnalyticsDashboard({ initialData }: AnalyticsDashboardPr const metrics = [ { title: "Total Revenue", - value: `$${data.revenue.total.toLocaleString()}`, + value: formatGBP(data.revenue.total), description: "All-time revenue", icon: DollarSign, - trend: data.revenue.monthly > 0 ? "up" : "neutral", - trendValue: `$${data.revenue.monthly.toLocaleString()} this month` + trend: data.revenue.monthly > 0 ? "up" as const : "neutral" as const, + trendValue: `${formatGBP(data.revenue.monthly)} this month` }, { title: "Total Orders", value: data.orders.total.toLocaleString(), description: "All-time orders", icon: ShoppingCart, - trend: data.orders.completed > 0 ? "up" : "neutral", + trend: data.orders.completed > 0 ? "up" as const : "neutral" as const, trendValue: `${data.orders.completed} completed` }, { @@ -98,7 +78,7 @@ export default function AnalyticsDashboard({ initialData }: AnalyticsDashboardPr value: data.customers.unique.toLocaleString(), description: "Total customers", icon: Users, - trend: "neutral", + trend: "neutral" as const, trendValue: "Lifetime customers" }, { @@ -106,32 +86,13 @@ export default function AnalyticsDashboard({ initialData }: AnalyticsDashboardPr value: data.products.total.toLocaleString(), description: "Active products", icon: Package, - trend: "neutral", + trend: "neutral" as const, trendValue: "In your store" } ]; return (
- {/* Header with refresh button */} -
-
-

Performance Overview

-

- Key metrics and insights about your store performance -

-
- -
- {/* Key Metrics Cards */}
{metrics.map((metric) => ( diff --git a/components/analytics/CustomerInsightsChart.tsx b/components/analytics/CustomerInsightsChart.tsx index a20b254..19dea43 100644 --- a/components/analytics/CustomerInsightsChart.tsx +++ b/components/analytics/CustomerInsightsChart.tsx @@ -3,29 +3,11 @@ import { useState, useEffect } from 'react'; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; import { Badge } from "@/components/ui/badge"; -import { clientFetch } from "@/lib/api"; import { useToast } from "@/hooks/use-toast"; import { Skeleton } from "@/components/ui/skeleton"; import { Users, Crown, UserPlus, UserCheck, Star } from "lucide-react"; - -interface CustomerInsights { - totalCustomers: number; - segments: { - new: number; - returning: number; - loyal: number; - vip: number; - }; - topCustomers: Array<{ - _id: string; - orderCount: number; - totalSpent: number; - averageOrderValue: number; - firstOrder: string; - lastOrder: string; - }>; - averageOrdersPerCustomer: string; -} +import { getCustomerInsightsWithStore, type CustomerInsights } from "@/lib/services/analytics-service"; +import { formatGBP } from "@/utils/format"; export default function CustomerInsightsChart() { const [data, setData] = useState(null); @@ -38,7 +20,7 @@ export default function CustomerInsightsChart() { try { setIsLoading(true); setError(null); - const response = await clientFetch('/analytics/customer-insights'); + const response = await getCustomerInsightsWithStore(); setData(response); } catch (err) { console.error('Error fetching customer insights:', err); @@ -251,10 +233,10 @@ export default function CustomerInsightsChart() {
- ${customer.totalSpent.toFixed(2)} + {formatGBP(customer.totalSpent)}
- ${customer.averageOrderValue.toFixed(2)} avg + {formatGBP(customer.averageOrderValue)} avg
diff --git a/components/analytics/OrderAnalyticsChart.tsx b/components/analytics/OrderAnalyticsChart.tsx index 11bdcbb..6d50313 100644 --- a/components/analytics/OrderAnalyticsChart.tsx +++ b/components/analytics/OrderAnalyticsChart.tsx @@ -3,27 +3,11 @@ import { useState, useEffect } from 'react'; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; import { Badge } from "@/components/ui/badge"; -import { clientFetch } from "@/lib/api"; import { useToast } from "@/hooks/use-toast"; import { Skeleton } from "@/components/ui/skeleton"; import { BarChart3, Clock, CheckCircle, XCircle, AlertCircle } from "lucide-react"; - -interface OrderAnalytics { - statusDistribution: Array<{ - _id: string; - count: number; - }>; - dailyOrders: Array<{ - _id: { - year: number; - month: number; - day: number; - }; - orders: number; - revenue: number; - }>; - averageProcessingDays: number; -} +import { getOrderAnalyticsWithStore, type OrderAnalytics } from "@/lib/services/analytics-service"; +import { formatGBP } from "@/utils/format"; interface OrderAnalyticsChartProps { timeRange: string; @@ -40,7 +24,7 @@ export default function OrderAnalyticsChart({ timeRange }: OrderAnalyticsChartPr try { setIsLoading(true); setError(null); - const response = await clientFetch(`/analytics/order-analytics?period=${timeRange}`); + const response = await getOrderAnalyticsWithStore(timeRange); setData(response); } catch (err) { console.error('Error fetching order analytics:', err); @@ -235,7 +219,7 @@ export default function OrderAnalyticsChart({ timeRange }: OrderAnalyticsChartPr
- ${totalRevenue.toFixed(2)} + {formatGBP(totalRevenue)}
Total Revenue
@@ -247,7 +231,7 @@ export default function OrderAnalyticsChart({ timeRange }: OrderAnalyticsChartPr const maxOrders = Math.max(...data.dailyOrders.map(d => d.orders)); const height = maxOrders > 0 ? (item.orders / maxOrders) * 100 : 0; const date = new Date(item._id.year, item._id.month - 1, item._id.day); - const dateLabel = date.toLocaleDateString('en-US', { + const dateLabel = date.toLocaleDateString('en-GB', { month: 'short', day: 'numeric' }); @@ -257,7 +241,7 @@ export default function OrderAnalyticsChart({ timeRange }: OrderAnalyticsChartPr
{dateLabel} diff --git a/components/analytics/ProductPerformanceChart.tsx b/components/analytics/ProductPerformanceChart.tsx index a5034fa..5ee0077 100644 --- a/components/analytics/ProductPerformanceChart.tsx +++ b/components/analytics/ProductPerformanceChart.tsx @@ -4,23 +4,11 @@ import { useState, useEffect } from 'react'; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"; import { Badge } from "@/components/ui/badge"; -import { clientFetch } from "@/lib/api"; import { useToast } from "@/hooks/use-toast"; import { Skeleton } from "@/components/ui/skeleton"; -import { Package, TrendingUp } from "lucide-react"; - -interface ProductPerformance { - productId: string; - name: string; - image: string; - unitType: string; - currentStock: number; - stockStatus: string; - totalSold: number; - totalRevenue: number; - orderCount: number; - averagePrice: number; -} +import { Package } from "lucide-react"; +import { getProductPerformanceWithStore, type ProductPerformance } from "@/lib/services/analytics-service"; +import { formatGBP } from "@/utils/format"; export default function ProductPerformanceChart() { const [data, setData] = useState([]); @@ -33,7 +21,7 @@ export default function ProductPerformanceChart() { try { setIsLoading(true); setError(null); - const response = await clientFetch('/analytics/product-performance'); + const response = await getProductPerformanceWithStore(); setData(response); } catch (err) { console.error('Error fetching product performance:', err); @@ -214,13 +202,13 @@ export default function ProductPerformanceChart() { {product.totalSold.toFixed(2)} - ${product.totalRevenue.toFixed(2)} + {formatGBP(product.totalRevenue)} {product.orderCount} - ${product.averagePrice.toFixed(2)} + {formatGBP(product.averagePrice)} ))} diff --git a/components/analytics/RevenueChart.tsx b/components/analytics/RevenueChart.tsx index bdb04d2..cf4d60f 100644 --- a/components/analytics/RevenueChart.tsx +++ b/components/analytics/RevenueChart.tsx @@ -2,20 +2,11 @@ import { useState, useEffect } from 'react'; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; -import { clientFetch } from "@/lib/api"; import { useToast } from "@/hooks/use-toast"; import { Skeleton } from "@/components/ui/skeleton"; import { TrendingUp, DollarSign } from "lucide-react"; - -interface RevenueData { - _id: { - year: number; - month: number; - day: number; - }; - revenue: number; - orders: number; -} +import { getRevenueTrendsWithStore, type RevenueData } from "@/lib/services/analytics-service"; +import { formatGBP } from "@/utils/format"; interface RevenueChartProps { timeRange: string; @@ -32,7 +23,7 @@ export default function RevenueChart({ timeRange }: RevenueChartProps) { try { setIsLoading(true); setError(null); - const response = await clientFetch(`/analytics/revenue-trends?period=${timeRange}`); + const response = await getRevenueTrendsWithStore(timeRange); setData(response); } catch (err) { console.error('Error fetching revenue data:', err); @@ -56,7 +47,7 @@ export default function RevenueChart({ timeRange }: RevenueChartProps) { const formatDate = (item: RevenueData) => { const date = new Date(item._id.year, item._id.month - 1, item._id.day); - return date.toLocaleDateString('en-US', { + return date.toLocaleDateString('en-GB', { month: 'short', day: 'numeric' }); @@ -153,7 +144,7 @@ export default function RevenueChart({ timeRange }: RevenueChartProps) {
{formatDate(item)} @@ -167,7 +158,7 @@ export default function RevenueChart({ timeRange }: RevenueChartProps) {
- ${totalRevenue.toFixed(2)} + {formatGBP(totalRevenue)}
Total Revenue
@@ -179,7 +170,7 @@ export default function RevenueChart({ timeRange }: RevenueChartProps) {
- ${averageRevenue.toFixed(2)} + {formatGBP(averageRevenue)}
Avg Daily Revenue
diff --git a/components/analytics/StoreSelector.tsx b/components/analytics/StoreSelector.tsx new file mode 100644 index 0000000..ebc9b5c --- /dev/null +++ b/components/analytics/StoreSelector.tsx @@ -0,0 +1,114 @@ +"use client" + +import { useState, useEffect } from 'react'; +import { useRouter, useSearchParams } from 'next/navigation'; +import { Button } from "@/components/ui/button"; +import { Input } from "@/components/ui/input"; +import { Label } from "@/components/ui/label"; +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; +import { Store, Search } from "lucide-react"; +import { useToast } from "@/hooks/use-toast"; + +export default function StoreSelector() { + const [storeId, setStoreId] = useState(''); + const [isLoading, setIsLoading] = useState(false); + const router = useRouter(); + const searchParams = useSearchParams(); + const { toast } = useToast(); + + // Initialize with current storeId from URL + useEffect(() => { + const currentStoreId = searchParams.get('storeId'); + if (currentStoreId) { + setStoreId(currentStoreId); + } + }, [searchParams]); + + const handleStoreSelect = async () => { + if (!storeId.trim()) { + toast({ + title: "Error", + description: "Please enter a store ID", + variant: "destructive", + }); + return; + } + + setIsLoading(true); + + try { + // Navigate to analytics with the selected storeId + router.push(`/dashboard/analytics?storeId=${encodeURIComponent(storeId.trim())}`); + + toast({ + title: "Store Selected", + description: `Analytics will now show data for store: ${storeId}`, + }); + } catch (error) { + toast({ + title: "Error", + description: "Failed to switch store", + variant: "destructive", + }); + } finally { + setIsLoading(false); + } + }; + + const handleKeyPress = (e: React.KeyboardEvent) => { + if (e.key === 'Enter') { + handleStoreSelect(); + } + }; + + return ( + + + + + Select Store + + + Enter the store ID to view analytics for that store + + + +
+ +
+ setStoreId(e.target.value)} + onKeyPress={handleKeyPress} + className="flex-1" + /> + +
+
+ + + + {searchParams.get('storeId') && ( +
+ Currently viewing: {searchParams.get('storeId')} +
+ )} +
+
+ ); +} \ No newline at end of file diff --git a/lib/api.ts b/lib/api.ts index 6f4cdfd..d0c8743 100644 --- a/lib/api.ts +++ b/lib/api.ts @@ -45,6 +45,28 @@ export { type ShippingOptionsResponse, } from './services/shipping-service'; +// Re-export analytics services +export { + getAnalyticsOverview, + getRevenueTrends, + getProductPerformance, + getCustomerInsights, + getOrderAnalytics, + getAnalyticsOverviewWithStore, + getRevenueTrendsWithStore, + getProductPerformanceWithStore, + getCustomerInsightsWithStore, + getOrderAnalyticsWithStore, + getStoreIdForUser, + + // Types + type AnalyticsOverview, + type RevenueData, + type ProductPerformance, + type CustomerInsights, + type OrderAnalytics, +} from './services/analytics-service'; + // Define the PlatformStats interface to match the expected format export interface PlatformStats { orders: { @@ -70,7 +92,17 @@ export { fetchServer, getCustomersServer, getCustomerDetailsServer, - getPlatformStatsServer + getPlatformStatsServer, + getAnalyticsOverviewServer, + getRevenueTrendsServer, + getProductPerformanceServer, + getCustomerInsightsServer, + getOrderAnalyticsServer, + type AnalyticsOverview as ServerAnalyticsOverview, + type RevenueData as ServerRevenueData, + type ProductPerformance as ServerProductPerformance, + type CustomerInsights as ServerCustomerInsights, + type OrderAnalytics as ServerOrderAnalytics, } from './server-api'; // Get clientFetch first so we can use it in the compatibility functions diff --git a/lib/server-api.ts b/lib/server-api.ts index df8778c..6732e49 100644 --- a/lib/server-api.ts +++ b/lib/server-api.ts @@ -161,4 +161,118 @@ export async function getPlatformStatsServer() { } }; } -} \ No newline at end of file +} + +// Analytics Types for server-side +export interface AnalyticsOverview { + orders: { + total: number; + completed: number; + pending: number; + completionRate: string; + }; + revenue: { + total: number; + monthly: number; + weekly: number; + averageOrderValue: number; + }; + products: { + total: number; + }; + customers: { + unique: number; + }; + userType?: 'vendor' | 'staff'; +} + +export interface RevenueData { + _id: { + year: number; + month: number; + day: number; + }; + revenue: number; + orders: number; +} + +export interface ProductPerformance { + productId: string; + name: string; + image: string; + unitType: string; + currentStock: number; + stockStatus: string; + totalSold: number; + totalRevenue: number; + orderCount: number; + averagePrice: number; +} + +export interface CustomerInsights { + totalCustomers: number; + segments: { + new: number; + returning: number; + loyal: number; + vip: number; + }; + topCustomers: Array<{ + _id: string; + orderCount: number; + totalSpent: number; + averageOrderValue: number; + firstOrder: string; + lastOrder: string; + }>; + averageOrdersPerCustomer: string; +} + +export interface OrderAnalytics { + statusDistribution: Array<{ + _id: string; + count: number; + }>; + dailyOrders: Array<{ + _id: { + year: number; + month: number; + day: number; + }; + orders: number; + revenue: number; + }>; + averageProcessingDays: number; +} + +// Server-side analytics functions +export const getAnalyticsOverviewServer = async (storeId?: string): Promise => { + const url = storeId ? `/analytics/overview?storeId=${storeId}` : '/analytics/overview'; + return fetchServer(url); +}; + +export const getRevenueTrendsServer = async (period: string = '30', storeId?: string): Promise => { + const params = new URLSearchParams({ period }); + if (storeId) params.append('storeId', storeId); + + const url = `/analytics/revenue-trends?${params.toString()}`; + return fetchServer(url); +}; + +export const getProductPerformanceServer = async (storeId?: string): Promise => { + const url = storeId ? `/analytics/product-performance?storeId=${storeId}` : '/analytics/product-performance'; + return fetchServer(url); +}; + +export const getCustomerInsightsServer = async (storeId?: string): Promise => { + const url = storeId ? `/analytics/customer-insights?storeId=${storeId}` : '/analytics/customer-insights'; + return fetchServer(url); +}; + +export const getOrderAnalyticsServer = async (period: string = '30', storeId?: string): Promise => { + const params = new URLSearchParams({ period }); + if (storeId) params.append('storeId', storeId); + + const url = `/analytics/order-analytics?${params.toString()}`; + return fetchServer(url); +}; \ No newline at end of file diff --git a/lib/services/analytics-service.ts b/lib/services/analytics-service.ts new file mode 100644 index 0000000..7e37177 --- /dev/null +++ b/lib/services/analytics-service.ts @@ -0,0 +1,190 @@ +'use client'; + +import { clientFetch } from '../api-client'; + +// Analytics Types +export interface AnalyticsOverview { + orders: { + total: number; + completed: number; + pending: number; + completionRate: string; + }; + revenue: { + total: number; + monthly: number; + weekly: number; + averageOrderValue: number; + }; + products: { + total: number; + }; + customers: { + unique: number; + }; + userType?: 'vendor' | 'staff'; +} + +export interface RevenueData { + _id: { + year: number; + month: number; + day: number; + }; + revenue: number; + orders: number; +} + +export interface ProductPerformance { + productId: string; + name: string; + image: string; + unitType: string; + currentStock: number; + stockStatus: string; + totalSold: number; + totalRevenue: number; + orderCount: number; + averagePrice: number; +} + +export interface CustomerInsights { + totalCustomers: number; + segments: { + new: number; + returning: number; + loyal: number; + vip: number; + }; + topCustomers: Array<{ + _id: string; + orderCount: number; + totalSpent: number; + averageOrderValue: number; + firstOrder: string; + lastOrder: string; + }>; + averageOrdersPerCustomer: string; +} + +export interface OrderAnalytics { + statusDistribution: Array<{ + _id: string; + count: number; + }>; + dailyOrders: Array<{ + _id: { + year: number; + month: number; + day: number; + }; + orders: number; + revenue: number; + }>; + averageProcessingDays: number; +} + +// Analytics Service Functions + +/** + * Get analytics overview data + * @param storeId Optional storeId for staff users + */ +export const getAnalyticsOverview = async (storeId?: string): Promise => { + const url = storeId ? `/analytics/overview?storeId=${storeId}` : '/analytics/overview'; + return clientFetch(url); +}; + +/** + * Get revenue trends data + * @param period Time period in days (7, 30, 90) + * @param storeId Optional storeId for staff users + */ +export const getRevenueTrends = async (period: string = '30', storeId?: string): Promise => { + const params = new URLSearchParams({ period }); + if (storeId) params.append('storeId', storeId); + + const url = `/analytics/revenue-trends?${params.toString()}`; + return clientFetch(url); +}; + +/** + * Get product performance data + * @param storeId Optional storeId for staff users + */ +export const getProductPerformance = async (storeId?: string): Promise => { + const url = storeId ? `/analytics/product-performance?storeId=${storeId}` : '/analytics/product-performance'; + return clientFetch(url); +}; + +/** + * Get customer insights data + * @param storeId Optional storeId for staff users + */ +export const getCustomerInsights = async (storeId?: string): Promise => { + const url = storeId ? `/analytics/customer-insights?storeId=${storeId}` : '/analytics/customer-insights'; + return clientFetch(url); +}; + +/** + * Get order analytics data + * @param period Time period in days (7, 30, 90) + * @param storeId Optional storeId for staff users + */ +export const getOrderAnalytics = async (period: string = '30', storeId?: string): Promise => { + const params = new URLSearchParams({ period }); + if (storeId) params.append('storeId', storeId); + + const url = `/analytics/order-analytics?${params.toString()}`; + return clientFetch(url); +}; + +// Helper function to determine if user is staff and get storeId +export const getStoreIdForUser = (): string | undefined => { + if (typeof window === 'undefined') return undefined; + + // Check if user is staff (you might need to adjust this based on your auth system) + const userType = localStorage.getItem('userType'); + const storeId = localStorage.getItem('storeId'); + + if (userType === 'staff' && storeId) { + return storeId; + } + + return undefined; +}; + +// Enhanced analytics functions that automatically handle storeId for staff users +export const getAnalyticsOverviewWithStore = async (): Promise => { + const storeId = getStoreIdForUser(); + return getAnalyticsOverview(storeId); +}; + +export const getRevenueTrendsWithStore = async (period: string = '30'): Promise => { + const storeId = getStoreIdForUser(); + return getRevenueTrends(period, storeId); +}; + +export const getProductPerformanceWithStore = async (): Promise => { + const storeId = getStoreIdForUser(); + return getProductPerformance(storeId); +}; + +export const getCustomerInsightsWithStore = async (): Promise => { + const storeId = getStoreIdForUser(); + return getCustomerInsights(storeId); +}; + +export const getOrderAnalyticsWithStore = async (period: string = '30'): Promise => { + const storeId = getStoreIdForUser(); + return getOrderAnalytics(period, storeId); +}; + +export function formatGBP(value: number) { + return value.toLocaleString('en-GB', { + style: 'currency', + currency: 'GBP', + minimumFractionDigits: 2, + maximumFractionDigits: 2, + }); +} \ No newline at end of file diff --git a/utils/format.ts b/utils/format.ts index 8dcde0a..62d055f 100644 --- a/utils/format.ts +++ b/utils/format.ts @@ -3,4 +3,13 @@ export const formatCurrency = (amount: number): string => { style: 'currency', currency: 'GBP' }).format(amount); -}; \ No newline at end of file +}; + +export function formatGBP(value: number) { + return value.toLocaleString('en-GB', { + style: 'currency', + currency: 'GBP', + minimumFractionDigits: 2, + maximumFractionDigits: 2, + }); +} \ No newline at end of file