/** * @license * SPDX-License-Identifier: Apache-2.0 */ import React, { useState, useEffect } from 'react'; import { Network, Truck, Users, Sliders, Calendar, AlertTriangle, FileCheck, Building, CheckCircle2, Clock, ShieldCheck, Send, HelpCircle, Database, ArrowRight, X, Search, Plus, MapPin, Phone, Activity, TrendingUp, Award } from 'lucide-react'; import { MainSection } from './types'; import { useFiestaTenantLocations, useFiestaLocationSummary, useFiestaUsers, useFiestaCreateUser, } from './services/fiestaQueries'; import { FIESTA_TENANT_ID, str as fstr, roleName } from './services/fiestaApi'; import Sidebar from './components/Sidebar'; import Header from './components/Header'; import DashboardView from './components/DashboardView'; import OperationsView from './components/OperationsView'; import ReportsView from './components/ReportsView'; import InventoryView from './components/InventoryView'; import SettingsView from './components/SettingsView'; import StoreDetailView from './components/StoreDetailView'; import ragulStoreCover from './assets/images/store_front_view_1780299351800.png'; export default function App() { // Navigation indicators states const [currentSection, setCurrentSection] = useState('dashboard'); 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 handleSetSection = (sec: MainSection) => { setCurrentSection(sec); setSelectedStore(null); }; const [isCoimbatoreView, setIsCoimbatoreView] = useState(false); const [searchQuery, setSearchQuery] = useState(''); const [sidebarOpen, setSidebarOpen] = useState(true); // ── 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 come straight from // the live Users API and render directly from the query, with the "Add User" // form posting back through the create-user mutation. const locationsQ = useFiestaTenantLocations(FIESTA_TENANT_ID); const locSummaryQ = useFiestaLocationSummary(FIESTA_TENANT_ID); const usersQ = useFiestaUsers({ tenantid: FIESTA_TENANT_ID, pagesize: 100 }); const createUserMut = useFiestaCreateUser(); const USER_AVATARS = [ 'https://images.unsplash.com/photo-1534528741775-53994a69daeb?auto=format&fit=crop&w=150&q=80', 'https://images.unsplash.com/photo-1494790108377-be9c29b29330?auto=format&fit=crop&w=150&q=80', 'https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?auto=format&fit=crop&w=150&q=80', ]; 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]; }; // Live users mapped to display rows (rendered directly from the query). const users = (usersQ.data ?? []).map((u, i) => { const shift = fstr(u.shiftname).trim(); return { userid: Number(u.userid), name: fstr(u.fullname).trim() || `${fstr(u.firstname)} ${fstr(u.lastname)}`.trim() || fstr(u.authname) || 'User', email: fstr(u.email) || fstr(u.authname) || '—', contact: fstr(u.contactno) || '—', roleid: Number(u.roleid), role: roleName(Number(u.roleid)), shift: shift && shift !== '-' ? shift : '—', location: fstr(u.applocation) || fstr(u.city) || 'Coimbatore', status: fstr(u.status) || 'Active', avatar: USER_AVATARS[i % USER_AVATARS.length], }; }); // Role filter for the Users section ('ALL' or a numeric roleid). const [userRoleFilter, setUserRoleFilter] = useState('ALL'); const filteredUsers = users.filter((u) => { const q = searchQuery.toLowerCase(); const matchesSearch = !q || u.name.toLowerCase().includes(q) || u.email.toLowerCase().includes(q) || u.contact.toLowerCase().includes(q); const matchesRole = userRoleFilter === 'ALL' || u.roleid === userRoleFilter; return matchesSearch && matchesRole; }); const roleOptions = Array.from(new Set(users.map((u) => u.roleid))); // 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' | 'CRITICAL'>('ALL'); const filteredStoresList = storesList.filter((st) => { const q = storesSearch.toLowerCase(); const matchesSearch = !q || st.name.toLowerCase().includes(q) || st.zone.toLowerCase().includes(q) || st.staff.toLowerCase().includes(q); if (storesFilter === 'ACTIVE') { return matchesSearch && st.status.toLowerCase() === 'active'; } if (storesFilter === 'CRITICAL') { return ( matchesSearch && (st.status.toLowerCase() !== 'active' || st.deliveries > 40) ); } 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); const [showAddUserModal, setShowAddUserModal] = useState(false); // New forms states const [newStore, setNewStore] = useState({ name: '', zone: '', lead: '', sales: '₹1,50,000' }); const [newUser, setNewUser] = useState({ firstname: '', lastname: '', email: '', contactno: '', password: '', roleid: 4, }); // 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.`); }; const handleCreateUser = async (e: React.FormEvent) => { e.preventDefault(); if (!newUser.firstname || !newUser.email || !newUser.contactno || !newUser.password) { alert('Please provide first name, email, contact number, and a password.'); return; } try { await createUserMut.mutateAsync({ firstname: newUser.firstname, lastname: newUser.lastname, email: newUser.email, contactno: newUser.contactno, password: newUser.password, roleid: Number(newUser.roleid), tenantid: FIESTA_TENANT_ID, }); setShowAddUserModal(false); setNewUser({ firstname: '', lastname: '', email: '', contactno: '', password: '', roleid: 4 }); alert(`User "${newUser.firstname}" created successfully and synced to the live Users directory.`); } catch (err) { alert(`Could not create user: ${err instanceof Error ? err.message : 'Unknown error'}`); } }; // Calendar Event Modal state const [showCalendarModal, setShowCalendarModal] = useState(false); // Callback action triggers const handleNewReport = () => { setCurrentSection('reports'); alert('System routed back to reports dashboard interface. Select product item metadata matrices.'); }; const handleHelp = () => { alert('nearledaily User Manual & Documentation Center linked successfully. Contact Coimbatore regional IT hub desk for urgent escalations.'); }; const handleLogout = () => { const ok = window.confirm('Are you sure you want to terminate this active secure session?'); if (ok) { alert('Secure session suspended. Page reloading and restarting database state simulation.'); window.location.reload(); } }; // Define secondary sections (Stores, Logistics, Staffing, Settings) within main body const renderSecondarySection = () => { switch (currentSection) { case 'stores': if (selectedStore) { return ( setSelectedStore(null)} /> ); } return (
{/* Simple and elegant premium header */}

Stores Registry

Local nodes registry, active manager assignments, and live dispatch and grocery delivery fulfillment statistics.

{/* Filter control bar */}
{/* Search input with search icon */}
setStoresSearch(e.target.value)} className="w-full pl-10 pr-4 py-2 bg-zinc-50 border border-zinc-200 rounded-lg text-xs font-medium text-zinc-800 placeholder-zinc-400 focus:outline-none focus:ring-2 focus:ring-[#581c87]/20 focus:border-[#581c87] transition-all" /> {storesSearch && ( )}
{/* Filter tabs */}
{/* 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}%
{/* Live Sparkline Trend Histogram */}
Speed Index (Live Feed) Live
{[30, 48, 25, 62, 54, 75, 42, 80, Math.min(95, Math.max(15, st.deliveries * 1.8))].map((val, idx) => (
40 ? 'bg-rose-500/80 group-hover/bar:bg-rose-500' : 'bg-[#581c87]/70 group-hover/bar:bg-[#581c87]' : 'bg-amber-500/70 group-hover/bar:bg-amber-500' }`} />
))}
{/* Lead Manager Profile block */}
{st.staff.slice(0, 2).toUpperCase()}
Node Lead {st.staff}
{/* Card footer - enter console */}
Enter Terminal Console
); })}
); case 'users': return (

Users & Access

Tenant staff accounts, roles, assigned shifts, and account status — live from the Users API.

{usersQ.isLoading ? ( Loading live users… ) : usersQ.isError ? ( Live data unavailable ) : ( Live · {users.length} users )}
{/* Role filter pills */}
{roleOptions.map((rid) => ( ))}
{/* Users table */}

Directory ({filteredUsers.length})

Tenant {FIESTA_TENANT_ID}
{filteredUsers.length === 0 ? ( ) : ( filteredUsers.map((u) => ( )) )}
User Role Contact Shift Location Status
{usersQ.isLoading ? 'Loading live users…' : 'No users match this filter.'}
{u.name}

{u.name}

{u.email}

{u.role} {u.contact} {u.shift} {u.location} {u.status}
); case 'settings': return ; default: return null; } }; return (
{/* Navbar segment */}
setSidebarOpen((prev) => !prev)} isSidebarOpen={sidebarOpen} onNewReportClick={handleNewReport} onHelpClick={handleHelp} onLogoutClick={handleLogout} /> {/* Main Container workspace layout splits */}
{/* Interactive Left Rail */} {/* Main core pages payload area */}
{/* Nav content routing */} {currentSection === 'dashboard' && ( )} {currentSection === 'inventory' && ( )} {currentSection === 'operations' && ( )} {currentSection === 'reports' && ( )} {/* Handle alternative sections: Stores, Logistics, Staffing, Settings */} {['stores', 'users', 'settings'].includes(currentSection) && renderSecondarySection() }
{/* CALENDAR SCHEDULER DIALOG MODAL */} {showCalendarModal && (
{ if (e.target === e.currentTarget) setShowCalendarModal(false); }} >

Scheduled Reports Calendar

Automated compliance summaries are scheduled to generate and export on the following dates:

Monthly Assortment Audit Ledger Oct 31, 2023
Daily Regional Turnover Sheet Everyday 23:59 (GMT)
Q4 Outlook Forecast Draft Nov 15, 2023
Next automated sync will occur at standard local closing hour thresholds.
)} {/* 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-[#581c87]" 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-[#581c87]" 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-[#581c87]" 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-[#581c87]" />
)} {/* CREATE NEW USER MODAL */} {showAddUserModal && (
{ if (e.target === e.currentTarget) setShowAddUserModal(false); }} >

Create User Account

Creates a real user against the live Users API for tenant {FIESTA_TENANT_ID}.

setNewUser({ ...newUser, firstname: 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-[#581c87]" required />
setNewUser({ ...newUser, lastname: 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-[#581c87]" />
setNewUser({ ...newUser, email: 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-[#581c87]" required />
setNewUser({ ...newUser, contactno: 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-[#581c87]" required />
setNewUser({ ...newUser, password: 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-[#581c87] font-mono" required />
)}
); }