changes on global catalogue

This commit is contained in:
2026-06-20 18:45:25 +05:30
parent 2e9ffb15bb
commit 8707004405
22 changed files with 3137 additions and 2070 deletions

View File

@@ -50,6 +50,7 @@ import StoreDetailView from './components/StoreDetailView';
import LoginView from './components/LoginView';
import UserStorePage from './components/UserStorePage';
import AwaitingApi from './components/AwaitingApi';
import ComparisonModal from './components/ComparisonModal';
import type { AuthUser } from './services/auth';
import ragulStoreCover from './assets/images/store_front_view_1780299351800.png';
@@ -249,314 +250,314 @@ export default function App() {
const handleLogout = () => setAuthUser(null);
// Define secondary sections (Stores, Logistics, Staffing, Settings) within main body
const renderSecondarySection = () => {
switch (currentSection) {
case 'stores': {
// A single-store merchant has no branches, so skip the one-card registry
// and open that store directly. Add a branch (2+ stores) and the grid
// returns automatically. Clicking a card in multi-store mode also opens
// the console, with a Back-to-registry button.
const isSoleStore = !selectedStore && storesList.length === 1;
const activeStore = selectedStore ?? (isSoleStore ? storesList[0] : null);
const renderStoresSection = () => {
const isSoleStore = !selectedStore && storesList.length === 1;
const activeStore = selectedStore ?? (isSoleStore ? storesList[0] : null);
if (activeStore) {
return (
<div className="space-y-md animate-in fade-in duration-300">
{isSoleStore && (
<div className="flex flex-col md:flex-row md:items-center justify-between gap-4 pb-4 border-b border-zinc-205">
<div>
<h1 className="font-sans font-bold text-2xl tracking-tight text-[#0f172a]">
Store Console
</h1>
<p className="text-zinc-500 font-sans text-xs mt-1">
This merchant operates a single store. Add a branch to manage multiple outlets.
</p>
</div>
</div>
)}
<StoreDetailView
store={activeStore}
onBack={selectedStore ? () => setSelectedStore(null) : undefined}
tenantId={tenantId}
/>
</div>
);
}
return (
<div className="space-y-lg animate-in fade-in duration-300">
{/* Simple and elegant premium header */}
<div className="flex flex-col md:flex-row md:items-center justify-between gap-4 pb-6 border-b border-zinc-205 mb-6">
if (activeStore) {
return (
<div className="space-y-md animate-in fade-in duration-300">
{isSoleStore && (
<div className="flex flex-col md:flex-row md:items-center justify-between gap-4 pb-4 border-b border-zinc-205">
<div>
<h1 className="font-sans font-bold text-2xl tracking-tight text-[#0f172a]">
Stores Registry
Store Console
</h1>
<p className="text-zinc-500 font-sans text-xs mt-1">
Local nodes registry, active manager assignments, and live dispatch and grocery delivery fulfillment statistics.
This merchant operates a single store. Add a branch to manage multiple outlets.
</p>
</div>
</div>
)}
<StoreDetailView
store={activeStore}
onBack={selectedStore ? () => setSelectedStore(null) : undefined}
tenantId={tenantId}
/>
</div>
);
}
{/* Filter control bar */}
<div className="flex flex-col md:flex-row items-center justify-between gap-4 bg-white p-4 rounded-xl border border-[#e2e8f0] shadow-sm mb-6">
{/* Search input with search icon */}
<div className="relative w-full md:max-w-md">
<span className="absolute inset-y-0 left-0 pl-3.5 flex items-center pointer-events-none text-zinc-400">
<Search className="w-4 h-4" />
</span>
<input
type="text"
placeholder="Search stores by name, zone, or manager..."
value={storesSearch}
onChange={(e) => 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-[#662582]/20 focus:border-[#662582] transition-all"
/>
{storesSearch && (
<button
onClick={() => setStoresSearch('')}
className="absolute inset-y-0 right-0 pr-3 flex items-center text-zinc-400 hover:text-zinc-600"
>
<X className="w-3.5 h-3.5" />
</button>
)}
</div>
return (
<div className="space-y-lg animate-in fade-in duration-300">
{/* Filter tabs */}
<div className="flex items-center bg-zinc-100/80 p-1 rounded-lg border border-zinc-200/50 w-full md:w-auto">
<button
onClick={() => setStoresFilter('ALL')}
className={`flex-1 md:flex-none px-4 py-1.5 rounded-md text-xs font-bold transition-all ${
storesFilter === 'ALL'
? 'bg-white text-[#662582] shadow-sm'
: 'text-zinc-500 hover:text-zinc-800 hover:bg-white/40'
}`}
>
All Nodes ({storesList.length})
</button>
<button
onClick={() => setStoresFilter('ACTIVE')}
className={`flex-1 md:flex-none px-4 py-1.5 rounded-md text-xs font-bold transition-all ${
storesFilter === 'ACTIVE'
? 'bg-white text-emerald-600 shadow-sm'
: 'text-zinc-500 hover:text-emerald-600 hover:bg-white/40'
}`}
>
Active ({storesList.filter(s => s.status.toLowerCase() === 'active').length})
</button>
<button
onClick={() => setStoresFilter('INACTIVE')}
className={`flex-1 md:flex-none px-4 py-1.5 rounded-md text-xs font-bold transition-all ${
storesFilter === 'INACTIVE'
? 'bg-white text-rose-600 shadow-sm'
: 'text-zinc-500 hover:text-rose-650 hover:bg-white/40'
}`}
>
Inactive ({storesList.filter(s => s.status.toLowerCase() !== 'active').length})
</button>
</div>
{/* ── Search & Filter Toolbar ── */}
<div className="flex flex-col lg:flex-row items-center gap-3 bg-white p-1.5 rounded-2xl border border-slate-200/70 shadow-sm mb-8 w-full">
{/* Search Input */}
<div className="relative flex-1 w-full group">
<div className="absolute inset-y-0 left-0 pl-4 flex items-center pointer-events-none text-slate-400 group-focus-within:text-purple-600 transition-colors">
<Search className="w-5 h-5" />
</div>
{/* Empty States */}
{filteredStoresList.length === 0 && (
<div className="text-center py-12 text-zinc-400 text-xs border border-dashed border-[#e2e8f0] rounded-xl bg-white p-8">
{locationsQ.isLoading ? (
<div className="flex flex-col items-center justify-center gap-2">
<div className="w-6 h-6 border-2 border-[#662582] border-t-transparent rounded-full animate-spin" />
<span>Loading live store locations</span>
</div>
) : (
<span>No store locations found matching your filter criteria.</span>
)}
</div>
)}
{/* Immersive Background Blur Blobs */}
<div className="relative">
<div className="absolute top-10 left-10 w-72 h-72 bg-purple-400/10 rounded-full blur-[100px] pointer-events-none -z-10 animate-pulse" />
<div className="absolute bottom-10 right-10 w-80 h-80 bg-indigo-400/10 rounded-full blur-[100px] pointer-events-none -z-10 animate-pulse" style={{ animationDuration: '6s' }} />
{/* Store Cards Grid */}
<div className="grid grid-cols-1 md:grid-cols-3 gap-gutter relative z-10">
{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 (
<div
key={st.locationid || i}
onClick={() => 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 */}
<div className="relative h-32 w-full overflow-hidden flex-shrink-0">
<img
src={getStoreCover(st.name)}
alt={st.name}
className="w-full h-full object-cover group-hover:scale-108 transition-transform duration-700 ease-out"
/>
<div className="absolute inset-0 bg-gradient-to-t from-slate-900/90 via-slate-900/20 to-transparent" />
{/* Status Badge */}
<div className="absolute top-3 right-3">
<span className={`px-2.5 py-0.5 rounded-full text-[9px] font-bold uppercase tracking-wider flex items-center gap-1 border backdrop-blur-md ${
st.status.toLowerCase() === 'active'
? st.deliveries > 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'
}`}>
<span className="w-1.5 h-1.5 rounded-full bg-current animate-pulse" />
{st.status.toLowerCase() === 'active' && st.deliveries > 40 ? 'High Load' : st.status}
</span>
</div>
{/* Zone & Title */}
<div className="absolute bottom-3 left-3 right-3 text-white">
<p className="text-[9px] text-purple-200 font-bold uppercase tracking-widest leading-none mb-1">{st.zone}</p>
<h3 className="font-sans font-bold text-sm leading-tight text-white group-hover:text-purple-200 transition-colors">{st.name}</h3>
</div>
</div>
{/* Card Content Area */}
<div className="p-5 flex-1 flex flex-col justify-between">
{/* Metrics Row & Progress Circle */}
<div className="flex items-center justify-between gap-4">
<div className="grid grid-cols-2 gap-4 flex-1">
<div>
<span className="text-[9px] text-zinc-400 uppercase tracking-widest font-bold block">Deliveries</span>
<p className="font-extrabold text-base text-slate-900 mt-0.5 font-mono">{st.deliveries.toLocaleString()}</p>
<span className="text-[8px] text-emerald-600 font-semibold block mt-0.5">Dispatched Today</span>
</div>
<div>
<span className="text-[9px] text-zinc-400 uppercase tracking-widest font-bold block">Total Orders</span>
<p className="font-extrabold text-base text-[#662582] mt-0.5 font-mono">{totalOrders.toLocaleString()}</p>
<span className="text-[8px] text-purple-600 font-semibold block mt-0.5">Incoming Volume</span>
</div>
</div>
{/* Circular Progress Ring */}
<div className="relative flex items-center justify-center flex-shrink-0" title={`Fulfillment Rate: ${fulfillmentRate}%`}>
<svg className="w-12 h-12 transform -rotate-90">
<circle
cx="24"
cy="24"
r="18"
className="stroke-zinc-100"
strokeWidth="3.5"
fill="transparent"
/>
<circle
cx="24"
cy="24"
r="18"
className={`transition-all duration-500 ${
st.status.toLowerCase() === 'active'
? st.deliveries > 40
? 'stroke-rose-500'
: 'stroke-emerald-500'
: 'stroke-amber-500'
}`}
strokeWidth="3.5"
fill="transparent"
strokeDasharray="113"
strokeDashoffset={113 - (113 * fulfillmentRate) / 100}
strokeLinecap="round"
/>
</svg>
<span className="absolute text-[9px] font-extrabold text-zinc-700 font-mono">
{fulfillmentRate}%
</span>
</div>
</div>
{/* Live Sparkline Trend Histogram */}
<div className="mt-4 pt-3.5 border-t border-zinc-100">
<div className="flex justify-between items-center text-[8px] text-zinc-400 font-bold uppercase tracking-widest mb-1.5">
<span>Speed Index (Live Feed)</span>
<span className="text-[#662582] flex items-center gap-0.5 text-[8px] font-bold">
<Activity className="w-2.5 h-2.5 animate-pulse" /> Live
</span>
</div>
<div className="h-6 flex items-end gap-1">
{[30, 48, 25, 62, 54, 75, 42, 80, Math.min(95, Math.max(15, st.deliveries * 1.8))].map((val, idx) => (
<div key={idx} className="flex-1 bg-zinc-100 rounded-t-sm h-full relative overflow-hidden group/bar">
<div
style={{ height: `${val}%` }}
className={`absolute bottom-0 left-0 right-0 rounded-t-sm transition-all duration-500 ${
st.status.toLowerCase() === 'active'
? st.deliveries > 40
? 'bg-rose-500/80 group-hover/bar:bg-rose-500'
: 'bg-[#662582]/70 group-hover/bar:bg-[#662582]'
: 'bg-amber-500/70 group-hover/bar:bg-amber-500'
}`}
/>
</div>
))}
</div>
</div>
{/* Lead Manager Profile block */}
<div className="flex justify-between items-center text-xs text-zinc-650 bg-zinc-50/80 rounded-xl p-2.5 border border-zinc-100/80 mt-4">
<div className="flex items-center gap-2">
<div className="w-7 h-7 rounded-full bg-gradient-to-tr from-purple-600 to-indigo-600 text-white flex items-center justify-center font-bold text-[10px] border border-white shadow-sm flex-shrink-0">
{st.staff.slice(0, 2).toUpperCase()}
</div>
<div className="min-w-0">
<span className="text-[8px] text-zinc-400 block font-semibold leading-none mb-0.5">Node Lead</span>
<span className="font-bold text-[#0f172a] text-xs truncate max-w-[100px] block leading-none">{st.staff}</span>
</div>
</div>
<button
onClick={(e) => {
e.stopPropagation();
alert(`Routing communications channel directly to manager ${st.staff}...`);
}}
className="p-1.5 rounded-lg bg-white border border-zinc-200 hover:border-[#662582] hover:text-[#662582] text-zinc-500 transition-colors shadow-sm"
title="Communicate with Node Lead"
>
<Phone className="w-3.5 h-3.5" />
</button>
</div>
{/* Card footer - enter console */}
<div className="flex items-center justify-between text-[10px] font-bold text-[#662582] mt-4 pt-3 border-t border-zinc-100/80">
<button
onClick={(e) => {
e.stopPropagation();
const newStatus = st.status.toLowerCase() === 'active' ? 'Inactive' : 'Active';
setStoresList(stores => stores.map(s => s.locationid === st.locationid ? { ...s, status: newStatus } : s));
if (st.locationid) {
updateLocation.mutate({ locationid: st.locationid, status: newStatus.toLowerCase() });
}
}}
className={`relative inline-flex h-5 w-9 items-center rounded-full transition-colors focus:outline-none ${st.status.toLowerCase() === 'active' ? 'bg-emerald-500' : 'bg-zinc-300'}`}
title={`Toggle store to ${st.status.toLowerCase() === 'active' ? 'Inactive' : 'Active'}`}
>
<span className={`inline-block h-3 w-3 transform rounded-full bg-white transition-transform ${st.status.toLowerCase() === 'active' ? 'translate-x-5' : 'translate-x-1'}`} />
</button>
<div className="flex items-center gap-1 uppercase tracking-wider">
<span>Enter Terminal Console</span>
<ArrowRight className="w-3.5 h-3.5 transform group-hover:translate-x-1.5 transition-transform duration-350" />
</div>
</div>
</div>
</div>
);
})}
</div>
<input
type="text"
placeholder="Search store by name, zone, or manager..."
value={storesSearch}
onChange={(e) => 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"
/>
<div className="absolute inset-y-0 right-0 pr-3 flex items-center">
{storesSearch && (
<button
onClick={() => setStoresSearch('')}
className="p-1.5 rounded-lg text-slate-400 hover:text-slate-600 hover:bg-slate-100 transition-colors"
>
<X className="w-4 h-4" />
</button>
)}
</div>
</div>
);
}
{/* Vertical Divider (Hidden on Mobile) */}
<div className="hidden lg:block w-px h-8 bg-slate-200" />
{/* Filter Segmented Control */}
<div className="flex items-center p-1 bg-slate-50/80 rounded-xl border border-slate-200/50 w-full lg:w-auto shrink-0">
<button
onClick={() => setStoresFilter('ALL')}
className={`flex-1 lg:flex-none px-5 py-2 rounded-lg text-xs font-bold transition-all duration-300 flex items-center justify-center gap-2 ${
storesFilter === 'ALL'
? 'bg-white text-slate-800 shadow-[0_1px_3px_rgba(0,0,0,0.1)] ring-1 ring-slate-900/5'
: 'text-slate-500 hover:text-slate-700 hover:bg-white/50'
}`}
>
All Nodes
<span className={`text-[10px] px-2 py-0.5 rounded-full font-bold ${storesFilter === 'ALL' ? 'bg-slate-100 text-slate-700' : 'bg-slate-200/50 text-slate-400'}`}>
{storesList.length}
</span>
</button>
<button
onClick={() => setStoresFilter('ACTIVE')}
className={`flex-1 lg:flex-none px-5 py-2 rounded-lg text-xs font-bold transition-all duration-300 flex items-center justify-center gap-2 ${
storesFilter === 'ACTIVE'
? 'bg-white text-emerald-600 shadow-[0_1px_3px_rgba(0,0,0,0.1)] ring-1 ring-emerald-500/20'
: 'text-slate-500 hover:text-emerald-600 hover:bg-emerald-50/50'
}`}
>
Active
<span className={`text-[10px] px-2 py-0.5 rounded-full font-bold ${storesFilter === 'ACTIVE' ? 'bg-emerald-50 text-emerald-600' : 'bg-slate-200/50 text-slate-400'}`}>
{storesList.filter(s => s.status.toLowerCase() === 'active').length}
</span>
</button>
<button
onClick={() => setStoresFilter('INACTIVE')}
className={`flex-1 lg:flex-none px-5 py-2 rounded-lg text-xs font-bold transition-all duration-300 flex items-center justify-center gap-2 ${
storesFilter === 'INACTIVE'
? 'bg-white text-slate-700 shadow-[0_1px_3px_rgba(0,0,0,0.1)] ring-1 ring-slate-900/5'
: 'text-slate-500 hover:text-slate-700 hover:bg-slate-200/50'
}`}
>
Inactive
<span className={`text-[10px] px-2 py-0.5 rounded-full font-bold ${storesFilter === 'INACTIVE' ? 'bg-slate-100 text-slate-700' : 'bg-slate-200/50 text-slate-400'}`}>
{storesList.filter(s => s.status.toLowerCase() !== 'active').length}
</span>
</button>
</div>
</div>
{/* Empty States */}
{filteredStoresList.length === 0 && (
<div className="text-center py-12 text-zinc-400 text-xs border border-dashed border-[#e2e8f0] rounded-xl bg-white p-8">
{locationsQ.isLoading ? (
<div className="flex flex-col items-center justify-center gap-2">
<div className="w-6 h-6 border-2 border-[#662582] border-t-transparent rounded-full animate-spin" />
<span>Loading live store locations</span>
</div>
) : (
<span>No store locations found matching your filter criteria.</span>
)}
</div>
)}
{/* Immersive Background Blur Blobs */}
<div className="relative">
<div className="absolute top-10 left-10 w-72 h-72 bg-purple-400/10 rounded-full blur-[100px] pointer-events-none -z-10 animate-pulse" />
<div className="absolute bottom-10 right-10 w-80 h-80 bg-indigo-400/10 rounded-full blur-[100px] pointer-events-none -z-10 animate-pulse" style={{ animationDuration: '6s' }} />
{/* Store Cards Grid */}
<div className="grid grid-cols-1 md:grid-cols-3 gap-gutter relative z-10">
{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 (
<div
key={st.locationid || i}
onClick={() => 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 */}
<div className="relative h-32 w-full overflow-hidden flex-shrink-0">
<img
src={getStoreCover(st.name)}
alt={st.name}
className="w-full h-full object-cover group-hover:scale-108 transition-transform duration-700 ease-out"
/>
<div className="absolute inset-0 bg-gradient-to-t from-slate-900/90 via-slate-900/20 to-transparent" />
{/* Status Badge */}
<div className="absolute top-3 right-3">
<span className={`px-2.5 py-0.5 rounded-full text-[9px] font-bold uppercase tracking-wider flex items-center gap-1 border backdrop-blur-md ${
st.status.toLowerCase() === 'active'
? st.deliveries > 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'
}`}>
<span className="w-1.5 h-1.5 rounded-full bg-current animate-pulse" />
{st.status.toLowerCase() === 'active' && st.deliveries > 40 ? 'High Load' : st.status}
</span>
</div>
{/* Zone & Title */}
<div className="absolute bottom-3 left-3 right-3 text-white">
<p className="text-[9px] text-purple-200 font-bold uppercase tracking-widest leading-none mb-1">{st.zone}</p>
<h3 className="font-sans font-bold text-sm leading-tight text-white group-hover:text-purple-200 transition-colors">{st.name}</h3>
</div>
</div>
{/* Card Content Area */}
<div className="p-5 flex-1 flex flex-col justify-between">
{/* Metrics Row & Progress Circle */}
<div className="flex items-center justify-between gap-4">
<div className="grid grid-cols-2 gap-4 flex-1">
<div>
<span className="text-[9px] text-zinc-400 uppercase tracking-widest font-bold block">Deliveries</span>
<p className="font-extrabold text-base text-slate-900 mt-0.5 font-mono">{st.deliveries.toLocaleString()}</p>
<span className="text-[8px] text-emerald-600 font-semibold block mt-0.5">Dispatched Today</span>
</div>
<div>
<span className="text-[9px] text-zinc-400 uppercase tracking-widest font-bold block">Total Orders</span>
<p className="font-extrabold text-base text-[#662582] mt-0.5 font-mono">{totalOrders.toLocaleString()}</p>
<span className="text-[8px] text-purple-600 font-semibold block mt-0.5">Incoming Volume</span>
</div>
</div>
{/* Circular Progress Ring */}
<div className="relative flex items-center justify-center flex-shrink-0" title={`Fulfillment Rate: ${fulfillmentRate}%`}>
<svg className="w-12 h-12 transform -rotate-90">
<circle
cx="24"
cy="24"
r="18"
className="stroke-zinc-100"
strokeWidth="3.5"
fill="transparent"
/>
<circle
cx="24"
cy="24"
r="18"
className={`transition-all duration-500 ${
st.status.toLowerCase() === 'active'
? st.deliveries > 40
? 'stroke-rose-500'
: 'stroke-emerald-500'
: 'stroke-amber-500'
}`}
strokeWidth="3.5"
fill="transparent"
strokeDasharray="113"
strokeDashoffset={113 - (113 * fulfillmentRate) / 100}
strokeLinecap="round"
/>
</svg>
<span className="absolute text-[9px] font-extrabold text-zinc-700 font-mono">
{fulfillmentRate}%
</span>
</div>
</div>
{/* Live Sparkline Trend Histogram */}
<div className="mt-4 pt-3.5 border-t border-zinc-100">
<div className="flex justify-between items-center text-[8px] text-zinc-400 font-bold uppercase tracking-widest mb-1.5">
<span>Speed Index (Live Feed)</span>
<span className="text-[#662582] flex items-center gap-0.5 text-[8px] font-bold">
<Activity className="w-2.5 h-2.5 animate-pulse" /> Live
</span>
</div>
<div className="h-6 flex items-end gap-1">
{[30, 48, 25, 62, 54, 75, 42, 80, Math.min(95, Math.max(15, st.deliveries * 1.8))].map((val, idx) => (
<div key={idx} className="flex-1 bg-zinc-100 rounded-t-sm h-full relative overflow-hidden group/bar">
<div
style={{ height: `${val}%` }}
className={`absolute bottom-0 left-0 right-0 rounded-t-sm transition-all duration-500 ${
st.status.toLowerCase() === 'active'
? st.deliveries > 40
? 'bg-rose-500/80 group-hover/bar:bg-rose-500'
: 'bg-[#662582]/70 group-hover/bar:bg-[#662582]'
: 'bg-amber-500/70 group-hover/bar:bg-amber-500'
}`}
/>
</div>
))}
</div>
</div>
{/* Lead Manager Profile block */}
<div className="flex justify-between items-center text-xs text-zinc-650 bg-zinc-50/80 rounded-xl p-2.5 border border-zinc-100/80 mt-4">
<div className="flex items-center gap-2">
<div className="w-7 h-7 rounded-full bg-gradient-to-tr from-purple-600 to-indigo-600 text-white flex items-center justify-center font-bold text-[10px] border border-white shadow-sm flex-shrink-0">
{st.staff.slice(0, 2).toUpperCase()}
</div>
<div className="min-w-0">
<span className="text-[8px] text-zinc-400 block font-semibold leading-none mb-0.5">Node Lead</span>
<span className="font-bold text-[#0f172a] text-xs truncate max-w-[100px] block leading-none">{st.staff}</span>
</div>
</div>
<button
onClick={(e) => {
e.stopPropagation();
alert(`Routing communications channel directly to manager ${st.staff}...`);
}}
className="p-1.5 rounded-lg bg-white border border-zinc-200 hover:border-[#662582] hover:text-[#662582] text-zinc-500 transition-colors shadow-sm"
title="Communicate with Node Lead"
>
<Phone className="w-3.5 h-3.5" />
</button>
</div>
{/* Card footer - enter console */}
<div className="flex items-center justify-between text-[10px] font-bold text-[#662582] mt-4 pt-3 border-t border-zinc-100/80">
<button
onClick={(e) => {
e.stopPropagation();
const newStatus = st.status.toLowerCase() === 'active' ? 'Inactive' : 'Active';
setStoresList(stores => stores.map(s => s.locationid === st.locationid ? { ...s, status: newStatus } : s));
if (st.locationid) {
updateLocation.mutate({ locationid: st.locationid, status: newStatus.toLowerCase() });
}
}}
className={`relative inline-flex h-5 w-9 items-center rounded-full transition-colors focus:outline-none ${st.status.toLowerCase() === 'active' ? 'bg-emerald-500' : 'bg-zinc-300'}`}
title={`Toggle store to ${st.status.toLowerCase() === 'active' ? 'Inactive' : 'Active'}`}
>
<span className={`inline-block h-3 w-3 transform rounded-full bg-white transition-transform ${st.status.toLowerCase() === 'active' ? 'translate-x-5' : 'translate-x-1'}`} />
</button>
<div className="flex items-center gap-1 uppercase tracking-wider">
<span>Enter Terminal Console</span>
<ArrowRight className="w-3.5 h-3.5 transform group-hover:translate-x-1.5 transition-transform duration-350" />
</div>
</div>
</div>
</div>
);
})}
</div>
</div>
</div>
);
};
const renderSecondarySection = () => {
switch (currentSection) {
case 'settings':
return <SettingsView tenantId={tenantId} user={authUser ?? undefined} />;
@@ -626,7 +627,16 @@ export default function App() {
<div className="w-full p-container-margin md:p-xl space-y-lg transition-all duration-300">
{/* Nav content routing */}
{currentSection === 'dashboard' && (
<DashboardView searchQuery={searchQuery} isCoimbatoreView={isCoimbatoreView} tenantId={tenantId} />
selectedStore ? (
renderStoresSection()
) : (
<div className="space-y-6">
<DashboardView searchQuery={searchQuery} isCoimbatoreView={isCoimbatoreView} tenantId={tenantId} />
<div>
{renderStoresSection()}
</div>
</div>
)
)}
{currentSection === 'inventory' && (
@@ -647,8 +657,8 @@ export default function App() {
)}
{/* Handle alternative sections: Stores, Settings */}
{['stores', 'settings'].includes(currentSection) &&
{/* Handle alternative sections: Settings */}
{['settings'].includes(currentSection) &&
renderSecondarySection()
}
</div>
@@ -787,6 +797,7 @@ export default function App() {
</div>
)}
<ComparisonModal />
</div>
);
}