/** * @license * SPDX-License-Identifier: Apache-2.0 */ import React, { useState, useEffect } from 'react'; import { Routes, Route, Navigate, useLocation, useNavigate } from 'react-router-dom'; import { Network, Truck, Sliders, Calendar, FileCheck, Building, CheckCircle2, Clock, ShieldCheck, Send, HelpCircle, Database, ArrowRight, X, Search, Plus, MapPin, Phone, Activity, TrendingUp, Award, Layers, Store, Settings, LayoutDashboard, Users, Box } from 'lucide-react'; import { MainSection } from './types'; import { useFiestaTenantLocations, useFiestaLocationSummary, useFiestaUpdateLocation, useFiestaCreateLocation, useFiestaOrderSummary, } from './services/fiestaQueries'; import { FIESTA_TENANT_ID, FIESTA_PRIMARY_LOCATION_ID, str as fstr, num as fnum } from './services/fiestaApi'; import Sidebar from './components/Sidebar'; import Header from './components/Header'; import DashboardView from './components/DashboardView'; import ReportsView from './components/ReportsView'; import InventoryView from './components/InventoryView'; import SettingsView from './components/SettingsView'; import StoreDetailView from './components/StoreDetailView'; import DispatchHubView from './components/DispatchHubView'; import LoginView from './components/LoginView'; import UserStorePage from './components/UserStorePage'; import SuperAdminPage from './components/SuperAdminPage'; import AwaitingApi from './components/AwaitingApi'; import ComparisonModal from './components/ComparisonModal'; import CatalogueBrowser from './components/CatalogueBrowser'; import type { AuthUser } from './services/auth'; import ragulStoreCover from './assets/images/store_front_view_1780299351800.png'; const AUTH_STORAGE_KEY = 'nearledaily.auth'; /** Rehydrate the signed-in user from localStorage (keeps the session across refreshes). */ function loadStoredUser(): AuthUser | null { try { const raw = localStorage.getItem(AUTH_STORAGE_KEY); if (!raw) return null; const parsed = JSON.parse(raw) as AuthUser; return parsed && typeof parsed === 'object' && (parsed.role === 'admin' || parsed.role === 'user') ? parsed : null; } catch { return null; } } export default function App() { // Auth gate — null = signed out (show login). Admin → full dashboard, user → // their single store console. The verified user is persisted to localStorage so // a refresh keeps the session (rehydrated via the lazy initializer); authRole // is derived from it so the two can't drift out of sync. const [authUser, setAuthUser] = useState(() => loadStoredUser()); const authRole = authUser?.role ?? null; // Persist (or clear) the session whenever the signed-in user changes. useEffect(() => { try { if (authUser) localStorage.setItem(AUTH_STORAGE_KEY, JSON.stringify(authUser)); else localStorage.removeItem(AUTH_STORAGE_KEY); } catch { /* storage unavailable (private mode / quota) — session just won't persist */ } }, [authUser]); const handleLogin = (user: AuthUser) => setAuthUser(user); const currentUser = { name: authUser?.name || 'Account', role: authRole === 'admin' ? 'Operations Admin' : 'Team Member', email: authUser?.email || '—', }; // Navigation indicators states const location = useLocation(); const navigate = useNavigate(); // Derive current section from URL path, default to 'dashboard' const pathParts = location.pathname.split('/'); const currentSection = (pathParts.includes('admin') ? pathParts[pathParts.length - 1] : 'dashboard') as MainSection; const [selectedStore, setSelectedStore] = useState<{ locationid?: number; name: string; zone: string; deliveries: number; sales: string; orders?: number; staff: string; color: string; status: string } | null>(null); const [isCoimbatoreView, setIsCoimbatoreView] = useState(false); const [searchQuery, setSearchQuery] = useState(''); const [sidebarOpen, setSidebarOpen] = useState(true); // Scope every Fiesta query to the signed-in merchant. The login record carries // the user's tenantid; fall back to the shared constant only when it's absent // (e.g. a legacy session before tenantid was captured) so the page still loads. const tenantId = authUser?.tenantid || FIESTA_TENANT_ID; // ── Live data for the secondary sections (Fiesta) ───────────────────────── // Stores ← tenant locations + per-location order summary (seeded into local // state so the "Add Store" handler keeps working). Users management now lives // under Settings → Users & Access (see UsersPanel). const locationsQ = useFiestaTenantLocations(tenantId); const locSummaryQ = useFiestaLocationSummary(tenantId); const updateLocation = useFiestaUpdateLocation(); const today = new Date(); const monthStart = new Date(today); monthStart.setDate(today.getDate() - 30); const ymd = (d: Date) => `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`; const fromdate = ymd(monthStart); const todate = ymd(today); const summaryQ = useFiestaOrderSummary(tenantId, fromdate, todate); const STORE_COVERS = [ 'https://images.unsplash.com/photo-1542838132-92c53300491e?auto=format&fit=crop&w=600&q=80', 'https://images.unsplash.com/photo-1578916171728-46686eac8d58?auto=format&fit=crop&w=600&q=80', 'https://images.unsplash.com/photo-1604719312566-8912e9227c6a?auto=format&fit=crop&w=600&q=80', 'https://images.unsplash.com/photo-1534723452862-4c874018d66d?auto=format&fit=crop&w=600&q=80', 'https://images.unsplash.com/photo-1582408929130-98a2c2640b8a?auto=format&fit=crop&w=600&q=80', 'https://images.unsplash.com/photo-1516594798947-e65505dbb29d?auto=format&fit=crop&w=600&q=80', 'https://images.unsplash.com/photo-1601599561263-60a4e4e083cd?auto=format&fit=crop&w=600&q=80', 'https://images.unsplash.com/photo-1441986300917-64674bd600d8?auto=format&fit=crop&w=600&q=80', 'https://images.unsplash.com/photo-1528698827591-e19ccd7bc23d?auto=format&fit=crop&w=600&q=80', 'https://images.unsplash.com/photo-1536697246787-1f7ae568d89a?auto=format&fit=crop&w=600&q=80', 'https://images.unsplash.com/photo-1506617498306-bd97b3663b65?auto=format&fit=crop&w=600&q=80', 'https://images.unsplash.com/photo-1579621970563-ebec7560ff3e?auto=format&fit=crop&w=600&q=80', ]; const getStoreCover = (name: string) => { if (name.toLowerCase().includes('ragul')) return ragulStoreCover; let hash = 0; for (let j = 0; j < name.length; j++) { hash = name.charCodeAt(j) + ((hash << 5) - hash); } const idx = Math.abs(hash) % STORE_COVERS.length; return STORE_COVERS[idx]; }; // Dynamic Secondary Modules list states (seeded from live data once it loads). const [storesList, setStoresList] = useState>([]); const [storesSearch, setStoresSearch] = useState(''); const [storesFilter, setStoresFilter] = useState<'ALL' | 'ACTIVE' | 'INACTIVE'>('ALL'); const filteredStoresList = storesList.filter((st) => { const q = storesSearch.trim().toLowerCase(); // Match across every field shown on the card — name, zone, manager/contact, // and the outlet id — coercing each to a string so a missing/numeric value // never throws and silently breaks the whole filter. const haystack = [st.name, st.zone, st.staff, st.locationid] .map((v) => String(v ?? '').toLowerCase()) .join(' '); const matchesSearch = !q || haystack.includes(q); if (storesFilter === 'ACTIVE') { return matchesSearch && st.status.toLowerCase() === 'active'; } if (storesFilter === 'INACTIVE') { return ( matchesSearch && (st.status.toLowerCase() !== 'active') ); } return matchesSearch; }); const activeCount = storesList.filter((st) => st.status.toLowerCase() === 'active').length; const totalCount = storesList.length; const totalDeliveries = storesList.reduce((acc, st) => acc + st.deliveries, 0); useEffect(() => { const locations = locationsQ.data ?? []; const summaries = locSummaryQ.data ?? []; if (locations.length) { setStoresList( locations.map((loc) => { const sum = summaries.find((s) => s.locationid === Number(loc.locationid)); return { locationid: Number(loc.locationid), name: fstr(loc.locationname) || `Location ${fstr(loc.locationid)}`, zone: [fstr(loc.suburb), fstr(loc.city)].filter(Boolean).join(', ') || 'Coimbatore', deliveries: sum?.delivered ?? 0, sales: `${(sum?.total ?? 0).toLocaleString('en-IN')} orders`, orders: Math.max(sum?.delivered ?? 0, sum?.total ?? 0), staff: fstr(loc.contactno) || fstr(loc.email) || '—', color: fstr(loc.status).toLowerCase() === 'active' ? 'emerald' : 'amber', status: fstr(loc.status) || 'Active', }; }), ); } }, [locationsQ.data, locSummaryQ.data]); // Secondary sub-sections modals triggers const [showAddStoreModal, setShowAddStoreModal] = useState(false); // New forms states const [newStore, setNewStore] = useState({ name: '', zone: '', lead: '', sales: '₹1,50,000' }); // Form submission handles for secondary sections const handleCreateStore = (e: React.FormEvent) => { e.preventDefault(); if (!newStore.name || !newStore.zone || !newStore.lead) { alert('Kindly fill store metadata completely.'); return; } setStoresList([...storesList, { locationid: 10000 + Math.floor(Math.random() * 9000), name: newStore.name, zone: newStore.zone, deliveries: 0, sales: '0 orders', orders: 0, staff: newStore.lead, color: 'emerald', status: 'Active' }]); setShowAddStoreModal(false); setNewStore({ name: '', zone: '', lead: '', sales: '₹1,50,000' }); alert(`Node outlet "${newStore.name}" commissioned to live operations feed successfully.`); }; // Calendar Event Modal state const [showCalendarModal, setShowCalendarModal] = useState(false); // Callback action triggers const handleHelp = () => { alert('nearledaily User Manual & Documentation Center linked successfully. Contact Coimbatore regional IT hub desk for urgent escalations.'); }; const handleLogout = () => setAuthUser(null); // Define secondary sections (Stores, Logistics, Staffing, Settings) within main body const renderStoresSection = () => { const isSoleStore = !selectedStore && storesList.length === 1; const activeStore = selectedStore ?? (isSoleStore ? storesList[0] : null); if (activeStore) { return (
{isSoleStore && (

Store Console

This merchant operates a single store. Add a branch to manage multiple outlets.

)} setSelectedStore(null) : undefined} tenantId={tenantId} />
); } return (
{/* ── Search & Filter Toolbar ── */}
{/* Search Input */}
setStoresSearch(e.target.value)} className="w-full pl-11 pr-12 py-3 bg-transparent border-none text-sm font-semibold text-slate-800 placeholder-slate-400 focus:outline-none focus:ring-0 transition-all duration-300" />
{storesSearch && ( )}
{/* Vertical Divider (Hidden on Mobile) */}
{/* Filter Segmented Control */}
{/* Empty States */} {filteredStoresList.length === 0 && (
{locationsQ.isLoading ? (
Loading live store locations…
) : ( No store locations found matching your filter criteria. )}
)} {/* Immersive Background Blur Blobs */}
{/* Store Cards Grid */}
{filteredStoresList.map((st, i) => { const totalOrders = st.orders ?? st.deliveries ?? 0; const fulfillmentRate = totalOrders > 0 ? Math.min(100, Math.round((st.deliveries / totalOrders) * 100)) : 100; return (
setSelectedStore(st)} className={`group relative overflow-hidden bg-white/70 backdrop-blur-md border border-zinc-200/80 rounded-2xl shadow-sm hover:shadow-[0_20px_40px_rgba(88,28,135,0.12)] transition-all duration-500 cursor-pointer flex flex-col ${ st.status.toLowerCase() === 'active' ? st.deliveries > 40 ? 'hover:border-rose-300' : 'hover:border-emerald-300' : 'hover:border-amber-300' }`} > {/* Card Cover Image with Zoom effect */}
{st.name}
{/* Status Badge */}
40 ? 'text-rose-200 bg-rose-950/60 border-rose-500/30' : 'text-emerald-200 bg-emerald-950/60 border-emerald-500/30' : 'text-amber-200 bg-amber-950/60 border-amber-500/30' }`}> {st.status.toLowerCase() === 'active' && st.deliveries > 40 ? 'High Load' : st.status}
{/* Zone & Title */}

{st.zone}

{st.name}

{/* Card Content Area */}
{/* Metrics Row & Progress Circle */}
Deliveries

{st.deliveries.toLocaleString()}

Dispatched Today
Total Orders

{totalOrders.toLocaleString()}

Incoming Volume
{/* Circular Progress Ring */}
40 ? 'stroke-rose-500' : 'stroke-emerald-500' : 'stroke-amber-500' }`} strokeWidth="3.5" fill="transparent" strokeDasharray="113" strokeDashoffset={113 - (113 * fulfillmentRate) / 100} strokeLinecap="round" /> {fulfillmentRate}%
{/* Lead Manager Profile block */}
{st.staff.slice(0, 2).toUpperCase()}
Node Lead {st.staff}
{/* Card footer - enter console */}
Enter Terminal Console
); })}
); }; const renderSecondarySection = () => { switch (currentSection) { case 'settings': return ; default: return null; } }; // ── Auth gate ────────────────────────────────────────────────────────────── if (authRole === null) { return ( } /> } /> ); } if (authRole === 'user' && authUser) { return ( } /> } /> ); } // Platform operator, not scoped to any tenant — never sees the merchant // console (dashboard/inventory/catalogue/etc), only tenant onboarding. if (authRole === 'super_admin' && authUser) { return ( } /> } /> ); } const AdminConsole = (
{/* Navbar segment */}
setSidebarOpen((prev) => !prev)} isSidebarOpen={sidebarOpen} onHelpClick={handleHelp} onLogoutClick={handleLogout} profile={currentUser} storeContext={{ storeName: currentSection === 'dashboard' ? (summaryQ.data?.tenantname ? `${summaryQ.data.tenantname} Admin` : 'Admin Console') : currentSection === 'inventory' ? 'Products' : currentSection === 'dispatch' ? 'Console' : currentSection.charAt(0).toUpperCase() + currentSection.slice(1), icon: currentSection === 'dashboard' ? LayoutDashboard : currentSection === 'inventory' ? Layers : currentSection === 'stores' ? Store : currentSection === 'reports' ? TrendingUp : currentSection === 'settings' ? Settings : currentSection === 'dispatch' ? Truck : undefined }} /> {/* Main Container workspace layout splits */}
{/* Interactive Left Rail */} setSidebarOpen(false)} /> {/* Main core pages payload area */}
} />
{renderStoresSection()}
) } /> } /> } /> } /> } /> } />
); return ( <> } /> {/* CALENDAR SCHEDULER DIALOG MODAL */} {showCalendarModal && (
{ if (e.target === e.currentTarget) setShowCalendarModal(false); }} >

Scheduled Reports Calendar

Automated compliance summaries will appear here once scheduled-report exports are available.

)} {/* CREATE NEW STORE MODAL */} {showAddStoreModal && (
{ if (e.target === e.currentTarget) setShowAddStoreModal(false); }} >

Commission New Regional Store Node

setNewStore({ ...newStore, name: e.target.value })} className="w-full border border-[#e2e8f0] rounded-lg p-sm bg-[#f8fafc] focus:bg-white outline-none focus:ring-1 focus:ring-[#662582]" required />
setNewStore({ ...newStore, zone: e.target.value })} className="w-full border border-[#e2e8f0] rounded-lg p-sm bg-[#f8fafc] focus:bg-white outline-none focus:ring-1 focus:ring-[#662582]" required />
setNewStore({ ...newStore, lead: e.target.value })} className="w-full border border-[#e2e8f0] rounded-lg p-sm bg-[#f8fafc] focus:bg-white outline-none focus:ring-1 focus:ring-[#662582]" required />
setNewStore({ ...newStore, sales: e.target.value })} className="w-full border border-[#e2e8f0] rounded-lg p-sm bg-[#f8fafc] focus:bg-white outline-none focus:ring-1 focus:ring-[#662582]" />
)} ); }