'use client' import { useState, useEffect, useCallback } from 'react'; import { useRouter } from 'next/navigation'; import { analyticsService } from '@/src/services/api'; import type { AnalyticsOverview, TopSeller, RevenueDataPoint, InventoryAlert, TypeBreakdown } from '@/src/types/sales'; import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer, BarChart, Bar, PieChart, Pie, Cell, Legend } from 'recharts'; type Period = '7d' | '30d' | '90d' | '6m' | '1y' | 'all'; const PERIOD_LABELS: Record = { '7d': '7d', '30d': '30d', '90d': '90d', '6m': '6m', '1y': '1y', 'all': 'Sve', }; const PERIOD_GROUP: Record = { '7d': 'day', '30d': 'day', '90d': 'week', '6m': 'month', '1y': 'month', 'all': 'month', }; function formatRSD(value: number): string { return new Intl.NumberFormat('sr-RS', { style: 'currency', currency: 'RSD', minimumFractionDigits: 0, maximumFractionDigits: 0, }).format(value); } export default function AnalyticsDashboard() { const router = useRouter(); const [mounted, setMounted] = useState(false); const [darkMode, setDarkMode] = useState(false); const [period, setPeriod] = useState('30d'); const [loading, setLoading] = useState(true); const [error, setError] = useState(''); const [overview, setOverview] = useState(null); const [topSellers, setTopSellers] = useState([]); const [revenueData, setRevenueData] = useState([]); const [inventoryAlerts, setInventoryAlerts] = useState([]); const [typeBreakdown, setTypeBreakdown] = useState([]); // Initialize dark mode useEffect(() => { setMounted(true); const saved = localStorage.getItem('darkMode'); setDarkMode(saved !== null ? JSON.parse(saved) : true); }, []); useEffect(() => { if (!mounted) return; localStorage.setItem('darkMode', JSON.stringify(darkMode)); if (darkMode) document.documentElement.classList.add('dark'); else document.documentElement.classList.remove('dark'); }, [darkMode, mounted]); // Check authentication useEffect(() => { if (!mounted) return; const token = localStorage.getItem('authToken'); const expiry = localStorage.getItem('tokenExpiry'); if (!token || !expiry || Date.now() > parseInt(expiry)) { window.location.href = '/upadaj'; } }, [mounted]); const fetchData = useCallback(async () => { try { setLoading(true); setError(''); const [overviewData, topSellersData, revenueChartData, alertsData, breakdownData] = await Promise.all([ analyticsService.getOverview(period), analyticsService.getTopSellers(period), analyticsService.getRevenueChart(period, PERIOD_GROUP[period]), analyticsService.getInventoryAlerts(), analyticsService.getTypeBreakdown(period), ]); setOverview(overviewData); setTopSellers(topSellersData); setRevenueData(revenueChartData); setInventoryAlerts(alertsData); setTypeBreakdown(breakdownData); } catch (err) { setError('Greska pri ucitavanju analitike'); console.error('Analytics fetch error:', err); } finally { setLoading(false); } }, [period]); useEffect(() => { fetchData(); }, [fetchData]); const handleLogout = () => { localStorage.removeItem('authToken'); localStorage.removeItem('tokenExpiry'); router.push('/upadaj'); }; const PIE_COLORS = ['#22c55e', '#3b82f6']; const getStockRowClass = (alert: InventoryAlert): string => { if (alert.days_until_stockout === null) return ''; if (alert.days_until_stockout < 7) return 'bg-red-50 dark:bg-red-900/20'; if (alert.days_until_stockout < 14) return 'bg-yellow-50 dark:bg-yellow-900/20'; return 'bg-green-50 dark:bg-green-900/20'; }; if (!mounted) { return (
Ucitavanje...
); } return (
{/* Sidebar */} {/* Main Content */}
Filamenteka

Analitika

{mounted && ( )}
{error && (
{error}
)} {/* Period Selector */}
{(Object.keys(PERIOD_LABELS) as Period[]).map((p) => ( ))}
{loading ? (
Ucitavanje analitike...
) : ( <> {/* Overview Cards */}

Prihod

{overview ? formatRSD(overview.revenue) : '-'}

Broj prodaja

{overview ? overview.sales_count : '-'}

Prosecna vrednost

{overview ? formatRSD(overview.avg_order_value) : '-'}

Jedinstveni kupci

{overview ? overview.unique_customers : '-'}

{/* Revenue Chart */}

Prihod po periodu

{mounted && revenueData.length > 0 ? ( formatRSD(value)} tick={{ fontSize: 12 }} /> [formatRSD(value), 'Prihod']) as any} contentStyle={{ backgroundColor: darkMode ? '#1f2937' : '#ffffff', border: `1px solid ${darkMode ? '#374151' : '#e5e7eb'}`, borderRadius: '0.5rem', color: darkMode ? '#f3f4f6' : '#111827', }} /> ) : (
Nema podataka za prikaz
)}
{/* Charts Row */}
{/* Top Sellers */}

Najprodavanije boje

{mounted && topSellers.length > 0 ? ( { const seller = topSellers[index]; return seller ? `${value} (${seller.tip})` : value; }} /> [value, 'Kolicina']) as any} contentStyle={{ backgroundColor: darkMode ? '#1f2937' : '#ffffff', border: `1px solid ${darkMode ? '#374151' : '#e5e7eb'}`, borderRadius: '0.5rem', color: darkMode ? '#f3f4f6' : '#111827', }} /> ) : (
Nema podataka za prikaz
)}
{/* Type Breakdown */}

Refill vs Spulna

{mounted && typeBreakdown.length > 0 ? ( `${name} (${((percent ?? 0) * 100).toFixed(0)}%)`} > {typeBreakdown.map((_, index) => ( ))} [ `${value} kom | ${formatRSD(typeBreakdown.find(t => t.item_type === name)?.total_revenue ?? 0)}`, name, ]) as any} contentStyle={{ backgroundColor: darkMode ? '#1f2937' : '#ffffff', border: `1px solid ${darkMode ? '#374151' : '#e5e7eb'}`, borderRadius: '0.5rem', color: darkMode ? '#f3f4f6' : '#111827', }} /> ) : (
Nema podataka za prikaz
)}
{/* Inventory Alerts */}

Upozorenja za zalihe

{inventoryAlerts.length > 0 ? (
{inventoryAlerts.map((alert) => ( ))}
Boja Tip Finish Refill Spulna Kolicina Pros. dnevna prodaja Dana do nestanka
{alert.boja} {alert.tip} {alert.finish} {alert.refill} {alert.spulna} {alert.kolicina} {alert.avg_daily_sales > 0 ? alert.avg_daily_sales.toFixed(1) : 'N/A'} {alert.days_until_stockout !== null ? ( {Math.round(alert.days_until_stockout)} ) : ( N/A )}
) : (

Sve zalihe su na zadovoljavajucem nivou.

)}
)}
); }