reponsived the website and login fix done
This commit is contained in:
17
src/App.tsx
17
src/App.tsx
@@ -115,7 +115,18 @@ export default function App() {
|
|||||||
|
|
||||||
const [isCoimbatoreView, setIsCoimbatoreView] = useState(false);
|
const [isCoimbatoreView, setIsCoimbatoreView] = useState(false);
|
||||||
const [searchQuery, setSearchQuery] = useState('');
|
const [searchQuery, setSearchQuery] = useState('');
|
||||||
const [sidebarOpen, setSidebarOpen] = useState(true);
|
const [sidebarOpen, setSidebarOpen] = useState(() => typeof window !== 'undefined' ? window.innerWidth >= 768 : true);
|
||||||
|
|
||||||
|
// Auto adapt sidebar state on window resize
|
||||||
|
useEffect(() => {
|
||||||
|
const handleResize = () => {
|
||||||
|
if (window.innerWidth < 768) {
|
||||||
|
setSidebarOpen(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
window.addEventListener('resize', handleResize);
|
||||||
|
return () => window.removeEventListener('resize', handleResize);
|
||||||
|
}, []);
|
||||||
|
|
||||||
// Scope every Fiesta query to the signed-in merchant. The login record carries
|
// 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
|
// the user's tenantid; fall back to the shared constant only when it's absent
|
||||||
@@ -627,8 +638,8 @@ export default function App() {
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
{/* Main core pages payload area */}
|
{/* Main core pages payload area */}
|
||||||
<main className={`flex-1 min-w-0 transition-all duration-300 ${sidebarOpen ? 'md:pl-64' : 'md:pl-16'} ${currentSection === 'inventory' || currentSection === 'dispatch' ? 'h-[calc(100vh-64px)] overflow-hidden' : 'min-h-[calc(100vh-64px)]'}`}>
|
<main className={`flex-1 min-w-0 transition-all duration-300 ${sidebarOpen ? 'md:pl-64' : 'md:pl-16'} ${currentSection === 'inventory' || currentSection === 'dispatch' ? 'md:h-[calc(100vh-64px)] md:overflow-hidden min-h-[calc(100vh-64px)]' : 'min-h-[calc(100vh-64px)]'}`}>
|
||||||
<div className={`w-full transition-all duration-300 ${currentSection === 'dispatch' ? 'h-full overflow-hidden' : currentSection === 'inventory' ? 'h-full px-4 md:px-6 pt-2 pb-4 overflow-hidden' : currentSection === 'settings' ? 'p-4 md:p-6 space-y-lg' : 'p-container-margin md:p-xl space-y-lg'}`}>
|
<div className={`w-full transition-all duration-300 ${currentSection === 'dispatch' ? 'h-full md:overflow-hidden p-2 sm:p-4' : currentSection === 'inventory' ? 'h-full px-2 sm:px-4 md:px-6 pt-2 pb-4 md:overflow-hidden' : currentSection === 'settings' ? 'p-3 sm:p-4 md:p-6 space-y-md sm:space-y-lg' : 'p-3 sm:p-5 md:p-xl space-y-md sm:space-y-lg'}`}>
|
||||||
<Routes>
|
<Routes>
|
||||||
<Route index element={<Navigate to="dashboard" replace />} />
|
<Route index element={<Navigate to="dashboard" replace />} />
|
||||||
<Route path="dashboard" element={
|
<Route path="dashboard" element={
|
||||||
|
|||||||
@@ -155,7 +155,7 @@ export default function DashboardView({ searchQuery, tenantId = FIESTA_TENANT_ID
|
|||||||
Live Core
|
Live Core
|
||||||
</span>
|
</span>
|
||||||
</h1>
|
</h1>
|
||||||
<p className="text-slate-300 font-sans text-sm mt-2 leading-relaxed whitespace-nowrap">
|
<p className="text-slate-300 font-sans text-sm mt-2 leading-relaxed">
|
||||||
Month-to-date order operations for <strong className="text-white font-semibold">{tenantName}</strong>, pulled live from the API.
|
Month-to-date order operations for <strong className="text-white font-semibold">{tenantName}</strong>, pulled live from the API.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
@@ -202,7 +202,7 @@ export default function DashboardView({ searchQuery, tenantId = FIESTA_TENANT_ID
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{/* KPI cards — all live from getordersummary / getinvoiceinsight */}
|
{/* KPI cards — all live from getordersummary / getinvoiceinsight */}
|
||||||
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-4 gap-gutter">
|
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4 sm:gap-gutter">
|
||||||
{kpis.map((kpi) => {
|
{kpis.map((kpi) => {
|
||||||
const Icon = kpi.icon;
|
const Icon = kpi.icon;
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -146,7 +146,7 @@ export default function DeliveriesView({ searchQuery = '', locationid, tenantId
|
|||||||
{/* Status tabs + search */}
|
{/* Status tabs + search */}
|
||||||
<FilterBar className="mb-4">
|
<FilterBar className="mb-4">
|
||||||
<div className="flex flex-col lg:flex-row lg:items-center gap-3">
|
<div className="flex flex-col lg:flex-row lg:items-center gap-3">
|
||||||
<div className="flex items-center gap-2 overflow-x-auto py-0.5 flex-1 min-w-0">
|
<div className="flex items-center gap-2 overflow-x-auto custom-scrollbar no-scrollbar py-0.5 flex-1 min-w-0 touch-scrolling">
|
||||||
{STATUS_TABS.map((t) => (
|
{STATUS_TABS.map((t) => (
|
||||||
<React.Fragment key={t.key}>
|
<React.Fragment key={t.key}>
|
||||||
<Pill active={status === t.key} color={BRAND} onClick={() => setStatus(t.key)} count={statusCounts[t.key] ?? 0}>{t.label}</Pill>
|
<Pill active={status === t.key} color={BRAND} onClick={() => setStatus(t.key)} count={statusCounts[t.key] ?? 0}>{t.label}</Pill>
|
||||||
|
|||||||
@@ -45,24 +45,24 @@ export default function DispatchHubView({ locationid, tenantId = FIESTA_TENANT_I
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col h-[calc(100vh-80px)] w-full">
|
<div className="flex flex-col min-h-[calc(100vh-80px)] md:h-[calc(100vh-80px)] w-full">
|
||||||
<div className="flex flex-col flex-none bg-white">
|
<div className="flex flex-col flex-none bg-white">
|
||||||
{/* ── Unified Hub Header ── */}
|
{/* ── Unified Hub Header ── */}
|
||||||
<div className="flex items-center justify-between px-6 h-14 border-b border-slate-200">
|
<div className="flex flex-col sm:flex-row sm:items-center justify-between px-3 sm:px-6 py-2 sm:py-0 sm:h-14 gap-2 border-b border-slate-200">
|
||||||
<div className="flex items-center gap-5">
|
<div className="flex items-center gap-3 sm:gap-5">
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-2 sm:gap-3">
|
||||||
<div className="w-8 h-8 rounded-lg bg-gradient-to-br from-blue-500 to-blue-600 flex items-center justify-center font-extrabold text-sm text-white shadow-sm">C</div>
|
<div className="w-7 h-7 sm:w-8 sm:h-8 rounded-lg bg-gradient-to-br from-blue-500 to-blue-600 flex items-center justify-center font-extrabold text-xs sm:text-sm text-white shadow-sm">C</div>
|
||||||
<div className="text-lg font-extrabold text-slate-800 tracking-tight">Console</div>
|
<div className="text-base sm:text-lg font-extrabold text-slate-800 tracking-tight">Console</div>
|
||||||
<div className="relative inline-block ml-1">
|
<div className="relative inline-block ml-1">
|
||||||
<span className="flex items-center gap-1 px-2.5 py-1 bg-purple-50 text-purple-700 border border-purple-200/60 rounded-full text-[11px] font-bold cursor-default shadow-sm">
|
<span className="flex items-center gap-1 px-2 sm:px-2.5 py-0.5 sm:py-1 bg-purple-50 text-purple-700 border border-purple-200/60 rounded-full text-[10px] sm:text-[11px] font-bold cursor-default shadow-sm">
|
||||||
<MapPin size={12} className="text-purple-500" />
|
<MapPin size={12} className="text-purple-500" />
|
||||||
<span className="max-w-[180px] truncate">Coimbatore</span>
|
<span className="max-w-[120px] sm:max-w-[180px] truncate">Coimbatore</span>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex items-center gap-4">
|
<div className="flex items-center justify-between sm:justify-end gap-2 sm:gap-4">
|
||||||
{deliveriesQ.isLoading ? (
|
{deliveriesQ.isLoading ? (
|
||||||
<span className="flex items-center gap-2 text-xs font-semibold text-slate-500">
|
<span className="flex items-center gap-2 text-xs font-semibold text-slate-500">
|
||||||
<span className="w-2 h-2 rounded-full bg-slate-300 animate-pulse" /> Syncing
|
<span className="w-2 h-2 rounded-full bg-slate-300 animate-pulse" /> Syncing
|
||||||
@@ -72,26 +72,26 @@ export default function DispatchHubView({ locationid, tenantId = FIESTA_TENANT_I
|
|||||||
<span className="w-2 h-2 rounded-full bg-rose-500" /> Offline
|
<span className="w-2 h-2 rounded-full bg-rose-500" /> Offline
|
||||||
</span>
|
</span>
|
||||||
) : totalOrders === 0 ? (
|
) : totalOrders === 0 ? (
|
||||||
<span className="flex items-center gap-2 text-xs font-semibold text-slate-500 bg-slate-100 px-3 py-1.5 rounded-full" title="No deliveries dispatched for this day">
|
<span className="flex items-center gap-1.5 sm:gap-2 text-[10px] sm:text-xs font-semibold text-slate-500 bg-slate-100 px-2.5 sm:px-3 py-1 sm:py-1.5 rounded-full" title="No deliveries dispatched for this day">
|
||||||
<span className="w-2 h-2 rounded-full bg-slate-400" /> No deliveries today
|
<span className="w-2 h-2 rounded-full bg-slate-400" /> No deliveries today
|
||||||
</span>
|
</span>
|
||||||
) : (
|
) : (
|
||||||
<span className="flex items-center gap-2 text-xs font-semibold text-emerald-600 bg-emerald-50 px-3 py-1.5 rounded-full border border-emerald-100">
|
<span className="flex items-center gap-1.5 sm:gap-2 text-[10px] sm:text-xs font-semibold text-emerald-600 bg-emerald-50 px-2.5 sm:px-3 py-1 sm:py-1.5 rounded-full border border-emerald-100">
|
||||||
<span className="w-2 h-2 rounded-full bg-emerald-500 animate-pulse" /> Live · {totalOrders} orders
|
<span className="w-2 h-2 rounded-full bg-emerald-500 animate-pulse" /> Live · {totalOrders} orders
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div className={`flex items-center bg-slate-50 border border-slate-200 rounded-xl p-1 shadow-sm ${isToday ? 'ring-1 ring-blue-500/20' : ''}`}>
|
<div className={`flex items-center bg-slate-50 border border-slate-200 rounded-xl p-0.5 sm:p-1 shadow-sm ${isToday ? 'ring-1 ring-blue-500/20' : ''}`}>
|
||||||
<button className="p-1.5 rounded-lg text-slate-400 hover:text-slate-700 hover:bg-slate-200/50 transition-colors" onClick={() => shiftDate(-1)} title="Previous day">
|
<button className="p-1 sm:p-1.5 rounded-lg text-slate-400 hover:text-slate-700 hover:bg-slate-200/50 transition-colors" onClick={() => shiftDate(-1)} title="Previous day">
|
||||||
<ChevronLeft size={16} />
|
<ChevronLeft size={16} />
|
||||||
</button>
|
</button>
|
||||||
<div className="relative flex items-center gap-2 px-3 py-1">
|
<div className="relative flex items-center gap-1.5 sm:gap-2 px-2 sm:px-3 py-0.5 sm:py-1">
|
||||||
<Calendar size={14} className="text-slate-400" />
|
<Calendar size={13} className="text-slate-400 hidden sm:block" />
|
||||||
<div className="flex flex-col">
|
<div className="flex flex-col">
|
||||||
<span className="text-[9px] font-bold text-slate-400 uppercase tracking-widest leading-none mb-0.5 flex items-center gap-1">
|
<span className="text-[8px] sm:text-[9px] font-bold text-slate-400 uppercase tracking-widest leading-none mb-0.5 flex items-center gap-1">
|
||||||
Date {isToday && <span className="text-[8px] bg-blue-100 text-blue-600 px-1 rounded-sm">TODAY</span>}
|
Date {isToday && <span className="text-[7px] sm:text-[8px] bg-blue-100 text-blue-600 px-1 rounded-sm">TODAY</span>}
|
||||||
</span>
|
</span>
|
||||||
<span className="text-xs font-bold text-slate-700 leading-none">{prettyDate}</span>
|
<span className="text-[11px] sm:text-xs font-bold text-slate-700 leading-none">{prettyDate}</span>
|
||||||
</div>
|
</div>
|
||||||
<input
|
<input
|
||||||
type="date"
|
type="date"
|
||||||
@@ -102,7 +102,7 @@ export default function DispatchHubView({ locationid, tenantId = FIESTA_TENANT_I
|
|||||||
aria-label="Pick date"
|
aria-label="Pick date"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<button className="p-1.5 rounded-lg text-slate-400 hover:text-slate-700 hover:bg-slate-200/50 transition-colors disabled:opacity-30 disabled:hover:bg-transparent" onClick={() => shiftDate(1)} disabled={isToday} title="Next day">
|
<button className="p-1 sm:p-1.5 rounded-lg text-slate-400 hover:text-slate-700 hover:bg-slate-200/50 transition-colors disabled:opacity-30 disabled:hover:bg-transparent" onClick={() => shiftDate(1)} disabled={isToday} title="Next day">
|
||||||
<ChevronRight size={16} />
|
<ChevronRight size={16} />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -110,11 +110,11 @@ export default function DispatchHubView({ locationid, tenantId = FIESTA_TENANT_I
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* ── Unified Tab Row ── */}
|
{/* ── Unified Tab Row ── */}
|
||||||
<div className="flex items-center px-6 py-3 border-b border-slate-200 shadow-sm z-10 relative">
|
<div className="flex items-center px-3 sm:px-6 py-2.5 border-b border-slate-200 shadow-sm z-10 relative overflow-x-auto custom-scrollbar no-scrollbar touch-scrolling">
|
||||||
<div className="flex items-center p-1 bg-slate-100/80 rounded-lg border border-slate-200/80 shadow-inner">
|
<div className="flex items-center p-1 bg-slate-100/80 rounded-lg border border-slate-200/80 shadow-inner shrink-0">
|
||||||
<button
|
<button
|
||||||
onClick={() => setActiveTab('map')}
|
onClick={() => setActiveTab('map')}
|
||||||
className={`flex items-center gap-1.5 px-4 py-1.5 text-[11px] font-extrabold rounded-md transition-all duration-300 uppercase tracking-wide ${
|
className={`flex items-center gap-1.5 px-3 sm:px-4 py-1 sm:py-1.5 text-[10px] sm:text-[11px] font-extrabold rounded-md transition-all duration-300 uppercase tracking-wide ${
|
||||||
activeTab === 'map' ? 'bg-white text-purple-700 shadow-sm ring-1 ring-slate-900/5' : 'text-slate-500 hover:text-slate-800 hover:bg-slate-200/50'
|
activeTab === 'map' ? 'bg-white text-purple-700 shadow-sm ring-1 ring-slate-900/5' : 'text-slate-500 hover:text-slate-800 hover:bg-slate-200/50'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
@@ -123,7 +123,7 @@ export default function DispatchHubView({ locationid, tenantId = FIESTA_TENANT_I
|
|||||||
|
|
||||||
<button
|
<button
|
||||||
onClick={() => setActiveTab('orders')}
|
onClick={() => setActiveTab('orders')}
|
||||||
className={`flex items-center gap-1.5 px-4 py-1.5 text-[11px] font-extrabold rounded-md transition-all duration-300 uppercase tracking-wide ${
|
className={`flex items-center gap-1.5 px-3 sm:px-4 py-1 sm:py-1.5 text-[10px] sm:text-[11px] font-extrabold rounded-md transition-all duration-300 uppercase tracking-wide ${
|
||||||
activeTab === 'orders' ? 'bg-white text-purple-700 shadow-sm ring-1 ring-slate-900/5' : 'text-slate-500 hover:text-slate-800 hover:bg-slate-200/50'
|
activeTab === 'orders' ? 'bg-white text-purple-700 shadow-sm ring-1 ring-slate-900/5' : 'text-slate-500 hover:text-slate-800 hover:bg-slate-200/50'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
@@ -132,7 +132,7 @@ export default function DispatchHubView({ locationid, tenantId = FIESTA_TENANT_I
|
|||||||
|
|
||||||
<button
|
<button
|
||||||
onClick={() => setActiveTab('deliveries')}
|
onClick={() => setActiveTab('deliveries')}
|
||||||
className={`flex items-center gap-1.5 px-4 py-1.5 text-[11px] font-extrabold rounded-md transition-all duration-300 uppercase tracking-wide ${
|
className={`flex items-center gap-1.5 px-3 sm:px-4 py-1 sm:py-1.5 text-[10px] sm:text-[11px] font-extrabold rounded-md transition-all duration-300 uppercase tracking-wide ${
|
||||||
activeTab === 'deliveries' ? 'bg-white text-purple-700 shadow-sm ring-1 ring-slate-900/5' : 'text-slate-500 hover:text-slate-800 hover:bg-slate-200/50'
|
activeTab === 'deliveries' ? 'bg-white text-purple-700 shadow-sm ring-1 ring-slate-900/5' : 'text-slate-500 hover:text-slate-800 hover:bg-slate-200/50'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
@@ -141,10 +141,10 @@ export default function DispatchHubView({ locationid, tenantId = FIESTA_TENANT_I
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Sub-tabs for Map View */}
|
{/* Sub-tabs for Map View */}
|
||||||
<div className="flex items-center p-1 ml-4 bg-slate-100/80 rounded-lg border border-slate-200/80 shadow-inner">
|
<div className="flex items-center p-1 ml-3 sm:ml-4 bg-slate-100/80 rounded-lg border border-slate-200/80 shadow-inner shrink-0">
|
||||||
<button
|
<button
|
||||||
onClick={() => setMapViewMode('stores')}
|
onClick={() => setMapViewMode('stores')}
|
||||||
className={`flex items-center gap-1.5 px-4 py-1.5 text-[11px] font-extrabold rounded-md transition-all duration-300 tracking-wide ${
|
className={`flex items-center gap-1.5 px-3 sm:px-4 py-1 sm:py-1.5 text-[10px] sm:text-[11px] font-extrabold rounded-md transition-all duration-300 tracking-wide ${
|
||||||
mapViewMode === 'stores' ? 'bg-white text-slate-800 shadow-sm ring-1 ring-slate-900/5' : 'text-slate-500 hover:text-slate-800 hover:bg-slate-200/50'
|
mapViewMode === 'stores' ? 'bg-white text-slate-800 shadow-sm ring-1 ring-slate-900/5' : 'text-slate-500 hover:text-slate-800 hover:bg-slate-200/50'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
@@ -153,7 +153,7 @@ export default function DispatchHubView({ locationid, tenantId = FIESTA_TENANT_I
|
|||||||
|
|
||||||
<button
|
<button
|
||||||
onClick={() => setMapViewMode('customers')}
|
onClick={() => setMapViewMode('customers')}
|
||||||
className={`flex items-center gap-1.5 px-4 py-1.5 text-[11px] font-extrabold rounded-md transition-all duration-300 tracking-wide ${
|
className={`flex items-center gap-1.5 px-3 sm:px-4 py-1 sm:py-1.5 text-[10px] sm:text-[11px] font-extrabold rounded-md transition-all duration-300 tracking-wide ${
|
||||||
mapViewMode === 'customers' ? 'bg-white text-slate-800 shadow-sm ring-1 ring-slate-900/5' : 'text-slate-500 hover:text-slate-800 hover:bg-slate-200/50'
|
mapViewMode === 'customers' ? 'bg-white text-slate-800 shadow-sm ring-1 ring-slate-900/5' : 'text-slate-500 hover:text-slate-800 hover:bg-slate-200/50'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -11063,3 +11063,24 @@
|
|||||||
height: 100%;
|
height: 100%;
|
||||||
transition: width 0.3s, background 0.3s;
|
transition: width 0.3s, background 0.3s;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Mobile & Tablet Responsiveness for Dispatch Cockpit */
|
||||||
|
@media (max-width: 1024px) {
|
||||||
|
.dispatch-container #body {
|
||||||
|
flex-direction: column !important;
|
||||||
|
overflow-y: auto !important;
|
||||||
|
}
|
||||||
|
.dispatch-container #sidebar {
|
||||||
|
width: 100% !important;
|
||||||
|
max-width: 100% !important;
|
||||||
|
min-width: 100% !important;
|
||||||
|
height: auto !important;
|
||||||
|
border-right: none !important;
|
||||||
|
border-bottom: 1px solid var(--border) !important;
|
||||||
|
}
|
||||||
|
.dispatch-container #map-pane {
|
||||||
|
height: 350px !important;
|
||||||
|
min-height: 350px !important;
|
||||||
|
width: 100% !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -72,19 +72,19 @@ export default function Header({
|
|||||||
return (
|
return (
|
||||||
<header className="bg-[#662582] flex justify-between items-center w-full px-container-margin py-md fixed top-0 right-0 left-0 z-50 h-16 text-white">
|
<header className="bg-[#662582] flex justify-between items-center w-full px-container-margin py-md fixed top-0 right-0 left-0 z-50 h-16 text-white">
|
||||||
{/* Brand & Desktop Navigation Tabs */}
|
{/* Brand & Desktop Navigation Tabs */}
|
||||||
<div className="flex items-center gap-md md:pl-0 pl-1">
|
<div className="flex items-center gap-sm sm:gap-md md:pl-0 pl-1 min-w-0">
|
||||||
{/* Brand cell — width mirrors the sidebar rail (64px collapsed / 256px expanded) so the logo/toggle sit directly above it */}
|
{/* Brand cell — width mirrors the sidebar rail (64px collapsed / 256px expanded) so the logo/toggle sit directly above it */}
|
||||||
<div
|
<div
|
||||||
className={`flex items-center shrink-0 transition-all duration-300 ${
|
className={`flex items-center shrink-0 transition-all duration-300 ${
|
||||||
isSidebarOpen ? 'gap-md md:w-64' : 'gap-sm md:w-16 md:justify-center'
|
isSidebarOpen ? 'gap-sm sm:gap-md md:w-64' : 'gap-sm md:w-16 md:justify-center'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
{/* Brand Logo — full wordmark when sidebar open, icon only when collapsed */}
|
{/* Brand Logo — full wordmark when sidebar open, icon only when collapsed */}
|
||||||
<span className="select-none flex items-center shrink-0 -ml-1.5">
|
<span className="select-none flex items-center shrink-0 -ml-1">
|
||||||
<img
|
<img
|
||||||
src={isSidebarOpen ? '/logo.png' : '/favicon.png'}
|
src={isSidebarOpen ? '/logo.png' : '/favicon.png'}
|
||||||
alt="nearledaily logo"
|
alt="nearledaily logo"
|
||||||
className={`w-auto object-contain transition-all duration-300 ${isSidebarOpen ? 'h-7' : 'h-9'}`}
|
className={`w-auto object-contain transition-all duration-300 ${isSidebarOpen ? 'h-6 sm:h-7' : 'h-8 sm:h-9'}`}
|
||||||
/>
|
/>
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
@@ -92,7 +92,7 @@ export default function Header({
|
|||||||
<button
|
<button
|
||||||
onClick={onToggleSidebar}
|
onClick={onToggleSidebar}
|
||||||
title="Toggle sidebar"
|
title="Toggle sidebar"
|
||||||
className={`rounded-full hover:bg-purple-800 transition-colors cursor-pointer text-white ${isSidebarOpen ? 'p-2' : 'p-1.5'}`}
|
className={`rounded-full hover:bg-purple-800 transition-colors cursor-pointer text-white ${isSidebarOpen ? 'p-1.5 sm:p-2' : 'p-1.5'}`}
|
||||||
>
|
>
|
||||||
<Menu size={18} />
|
<Menu size={18} />
|
||||||
</button>
|
</button>
|
||||||
@@ -100,13 +100,13 @@ export default function Header({
|
|||||||
|
|
||||||
{/* Dynamic Store Name Context */}
|
{/* Dynamic Store Name Context */}
|
||||||
{storeContext && (
|
{storeContext && (
|
||||||
<div className="flex flex-col ml-1 border-l border-[#662582] pl-3 py-0.5 select-none">
|
<div className="flex flex-col ml-0.5 sm:ml-1 border-l border-white/20 pl-2 sm:pl-3 py-0.5 select-none min-w-0">
|
||||||
<span className="flex items-center gap-2 text-lg font-bold tracking-tight text-white leading-tight">
|
<span className="flex items-center gap-1.5 sm:gap-2 text-xs sm:text-base md:text-lg font-bold tracking-tight text-white leading-tight truncate max-w-[110px] xs:max-w-[150px] sm:max-w-none">
|
||||||
{storeContext.icon && <storeContext.icon size={20} className="text-purple-300" />}
|
{storeContext.icon && <storeContext.icon size={16} className="text-purple-300 shrink-0 hidden xs:inline" />}
|
||||||
{storeContext.storeName}
|
<span className="truncate">{storeContext.storeName}</span>
|
||||||
</span>
|
</span>
|
||||||
{storeContext.branchName && (
|
{storeContext.branchName && (
|
||||||
<span className="text-[10px] font-medium text-purple-200 tracking-wider uppercase leading-tight">
|
<span className="text-[9px] sm:text-[10px] font-medium text-purple-200 tracking-wider uppercase leading-tight truncate max-w-[100px] sm:max-w-none">
|
||||||
{storeContext.branchName}
|
{storeContext.branchName}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -483,7 +483,7 @@ export default function InventoryView({
|
|||||||
<p className="text-[9px] text-zinc-400 font-medium mt-1 truncate">Pick products & set quantities — selected items appear in every store's catalogue.</p>
|
<p className="text-[9px] text-zinc-400 font-medium mt-1 truncate">Pick products & set quantities — selected items appear in every store's catalogue.</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex flex-nowrap items-center gap-2 shrink-0">
|
<div className="flex flex-wrap items-center gap-2 w-full sm:w-auto shrink-0">
|
||||||
<button
|
<button
|
||||||
onClick={() => setIsLocalSidebarOpen(!isLocalSidebarOpen)}
|
onClick={() => setIsLocalSidebarOpen(!isLocalSidebarOpen)}
|
||||||
className="flex items-center gap-1.5 bg-white border border-slate-200 px-2.5 py-1.5 rounded-lg text-[10px] font-bold hover:bg-slate-50 transition-colors text-slate-700 shadow-sm cursor-pointer h-[32px] whitespace-nowrap"
|
className="flex items-center gap-1.5 bg-white border border-slate-200 px-2.5 py-1.5 rounded-lg text-[10px] font-bold hover:bg-slate-50 transition-colors text-slate-700 shadow-sm cursor-pointer h-[32px] whitespace-nowrap"
|
||||||
@@ -491,7 +491,7 @@ export default function InventoryView({
|
|||||||
<Filter size={12} /> Filter
|
<Filter size={12} /> Filter
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<div className="relative w-32 xl:w-48 h-[32px]">
|
<div className="relative flex-1 sm:flex-none w-full sm:w-32 xl:w-48 h-[32px]">
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="Search..."
|
placeholder="Search..."
|
||||||
|
|||||||
@@ -80,95 +80,38 @@ export default function LoginView({ onLogin }: LoginViewProps) {
|
|||||||
return (
|
return (
|
||||||
<div className="h-screen w-full flex bg-white font-sans text-slate-800 overflow-hidden">
|
<div className="h-screen w-full flex bg-white font-sans text-slate-800 overflow-hidden">
|
||||||
{/* ── Left brand / hero panel (desktop only) ── */}
|
{/* ── Left brand / hero panel (desktop only) ── */}
|
||||||
<div className="hidden lg:flex lg:w-1/2 shrink-0 relative overflow-hidden bg-gradient-to-br from-[#662582] via-[#4c1d66] to-slate-950 text-white flex-col p-12 xl:p-16 justify-center">
|
<div className="hidden lg:flex lg:w-1/2 shrink-0 relative overflow-hidden bg-gradient-to-br from-[#531b6e] via-[#3d1653] to-slate-950 text-white flex-col p-8 xl:p-14 2xl:p-20 justify-center items-center">
|
||||||
{/* Layered ambient glows */}
|
{/* Layered ambient glows */}
|
||||||
<div className="absolute -top-24 -right-24 w-96 h-96 bg-purple-500/25 rounded-full blur-[120px] pointer-events-none" />
|
<div className="absolute -top-24 -right-24 w-96 h-96 bg-purple-500/20 rounded-full blur-[120px] pointer-events-none" />
|
||||||
<div className="absolute -bottom-24 -left-24 w-[26rem] h-[26rem] bg-indigo-500/20 rounded-full blur-[140px] pointer-events-none animate-pulse" style={{ animationDuration: '9s' }} />
|
<div className="absolute -bottom-24 -left-24 w-[24rem] h-[24rem] bg-indigo-500/15 rounded-full blur-[140px] pointer-events-none" />
|
||||||
<div className="absolute top-1/3 left-1/2 w-72 h-72 bg-fuchsia-500/10 rounded-full blur-[120px] pointer-events-none" />
|
|
||||||
{/* Subtle dot-grid texture */}
|
{/* Subtle dot-grid texture */}
|
||||||
<div
|
<div
|
||||||
className="absolute inset-0 opacity-[0.07] pointer-events-none"
|
className="absolute inset-0 opacity-[0.05] pointer-events-none"
|
||||||
style={{ backgroundImage: 'radial-gradient(circle, #ffffff 1px, transparent 1px)', backgroundSize: '22px 22px' }}
|
style={{ backgroundImage: 'radial-gradient(circle, #ffffff 1px, transparent 1px)', backgroundSize: '24px 24px' }}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div className="relative z-10 flex flex-col justify-center w-full max-w-[520px] mx-auto h-full">
|
<div className="relative z-10 flex flex-col justify-center w-full max-w-[480px]">
|
||||||
{/* Logo */}
|
{/* Logo */}
|
||||||
<div className="shrink-0 mb-10">
|
<div className="mb-8">
|
||||||
<img src="/logo.png" alt="nearledaily" className="h-10 w-auto object-contain" />
|
<img src="/logo.png" alt="nearledaily" className="h-10 xl:h-12 w-auto object-contain" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Hero */}
|
{/* Eyebrow badge */}
|
||||||
<div className="min-h-0">
|
<div className="inline-flex items-center gap-1.5 text-xs font-semibold tracking-wider text-purple-200 bg-white/10 border border-white/15 rounded-full px-3.5 py-1.5 backdrop-blur-md self-start">
|
||||||
{/* Eyebrow */}
|
<ShieldCheck size={14} className="text-purple-300 shrink-0" />
|
||||||
<span className="inline-flex items-center gap-1.5 self-start text-[11px] font-bold uppercase tracking-[0.15em] text-purple-100 bg-white/10 border border-white/15 rounded-full px-3 py-1 backdrop-blur-sm">
|
<span>Secure Merchant Console</span>
|
||||||
<ShieldCheck size={13} className="text-purple-200" />
|
</div>
|
||||||
Secure Merchant Console
|
|
||||||
</span>
|
|
||||||
|
|
||||||
<h1
|
<h1 className="text-4xl lg:text-5xl xl:text-6xl font-extrabold tracking-tight leading-[1.1] mt-6">
|
||||||
className="text-5xl xl:text-6xl font-bold tracking-tight leading-[1.1] mt-6 max-w-[460px]"
|
All Operations <br />
|
||||||
style={{ textShadow: '0 2px 16px rgba(0,0,0,0.25)' }}
|
<span className="bg-gradient-to-r from-white via-purple-100 to-purple-300 bg-clip-text text-transparent">
|
||||||
>
|
|
||||||
All Operations
|
|
||||||
<br />
|
|
||||||
In One Console
|
In One Console
|
||||||
</h1>
|
</span>
|
||||||
<p className="text-purple-100/90 text-lg xl:text-xl leading-[1.6] mt-6 max-w-[460px]">
|
</h1>
|
||||||
Monitor regional hubs, track live order operations, and manage your entire store network from a single command center.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
{/* Glassmorphic live-snapshot preview card */}
|
<p className="text-purple-200/90 text-base xl:text-lg leading-relaxed mt-5 max-w-[460px]">
|
||||||
<div className="mt-10 max-w-[460px] rounded-2xl bg-white/10 backdrop-blur-md border border-white/15 p-6 shadow-2xl shadow-purple-950/50">
|
Monitor regional hubs, track live order operations, and manage your entire store network from a single command center.
|
||||||
<div className="flex items-center justify-between">
|
</p>
|
||||||
<span className="text-xs font-bold uppercase tracking-widest text-purple-100">Live Operations</span>
|
|
||||||
<span className="inline-flex items-center gap-1.5 text-xs font-bold text-emerald-300">
|
|
||||||
<span className="w-1.5 h-1.5 rounded-full bg-emerald-400 animate-pulse" />
|
|
||||||
LIVE
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="mt-5 grid grid-cols-2 gap-6">
|
|
||||||
<div>
|
|
||||||
<p className="text-3xl font-extrabold tracking-tight tabular-nums">2,480</p>
|
|
||||||
<p className="text-xs text-purple-200 mt-1">Orders today</p>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<p className="text-3xl font-extrabold tracking-tight text-emerald-300 tabular-nums">98.6%</p>
|
|
||||||
<p className="text-xs text-purple-200 mt-1">Fulfillment</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Mini bar chart */}
|
|
||||||
<div className="mt-5 flex items-end gap-1.5 h-14">
|
|
||||||
{[45, 62, 38, 70, 52, 80, 60, 90, 55, 74].map((h, i) => (
|
|
||||||
<div
|
|
||||||
key={i}
|
|
||||||
className="flex-1 rounded-t bg-gradient-to-t from-purple-500/40 to-purple-200/80"
|
|
||||||
style={{ height: `${h}%` }}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Trust stats + copyright */}
|
|
||||||
<div className="shrink-0 mt-10 pt-6 border-t border-white/10 max-w-[460px]">
|
|
||||||
<div className="grid grid-cols-3 gap-4">
|
|
||||||
{[
|
|
||||||
{ v: '120+', l: 'Regional hubs' },
|
|
||||||
{ v: '99.9%', l: 'Platform uptime' },
|
|
||||||
{ v: '24/7', l: 'Live monitoring' },
|
|
||||||
].map((s) => (
|
|
||||||
<div key={s.l}>
|
|
||||||
<p className="text-2xl font-bold tracking-tight">{s.v}</p>
|
|
||||||
<p className="text-xs text-purple-300 mt-0.5">{s.l}</p>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
<p className="text-xs text-purple-300/70 font-medium mt-4">
|
|
||||||
© 2026 nearledaily · All rights reserved
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -339,3 +339,15 @@
|
|||||||
from { opacity: 0; transform: translateY(20px) scale(0.96); }
|
from { opacity: 0; transform: translateY(20px) scale(0.96); }
|
||||||
to { opacity: 1; transform: translateY(0) scale(1); }
|
to { opacity: 1; transform: translateY(0) scale(1); }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media (max-width: 640px) {
|
||||||
|
.odm-overlay {
|
||||||
|
padding: 12px !important;
|
||||||
|
}
|
||||||
|
.odm-overlay .odm-card {
|
||||||
|
max-width: 100% !important;
|
||||||
|
max-height: 92vh !important;
|
||||||
|
border-radius: 14px !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -322,7 +322,7 @@ export default function OrdersView({ searchQuery = '', locationid, tenantId = FI
|
|||||||
{/* Status tabs + search */}
|
{/* Status tabs + search */}
|
||||||
<FilterBar className="mb-4">
|
<FilterBar className="mb-4">
|
||||||
<div className="flex flex-col lg:flex-row lg:items-center gap-3">
|
<div className="flex flex-col lg:flex-row lg:items-center gap-3">
|
||||||
<div className="flex items-center gap-2 overflow-x-auto py-0.5 flex-1 min-w-0">
|
<div className="flex items-center gap-2 overflow-x-auto custom-scrollbar no-scrollbar py-0.5 flex-1 min-w-0 touch-scrolling">
|
||||||
{STATUS_TABS.map((t) => {
|
{STATUS_TABS.map((t) => {
|
||||||
// Single brand accent for the tab row (calmer than per-status colours);
|
// Single brand accent for the tab row (calmer than per-status colours);
|
||||||
// the per-status hue still appears on the row Status chip where it aids scanning.
|
// the per-status hue still appears on the row Status chip where it aids scanning.
|
||||||
|
|||||||
@@ -471,7 +471,8 @@ export default function PosView({ locationid, tenantId = FIESTA_TENANT_ID }: Pos
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<table className="w-full text-left border-collapse">
|
<div className="w-full overflow-x-auto custom-scrollbar">
|
||||||
|
<table className="w-full text-left border-collapse min-w-[500px]">
|
||||||
<thead className="sticky top-0 z-10 bg-slate-50 border-b border-slate-200">
|
<thead className="sticky top-0 z-10 bg-slate-50 border-b border-slate-200">
|
||||||
<tr>
|
<tr>
|
||||||
<th className="px-5 py-3 text-[11px] font-bold uppercase tracking-wider text-slate-500">Product</th>
|
<th className="px-5 py-3 text-[11px] font-bold uppercase tracking-wider text-slate-500">Product</th>
|
||||||
@@ -604,6 +605,7 @@ export default function PosView({ locationid, tenantId = FIESTA_TENANT_ID }: Pos
|
|||||||
))}
|
))}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -287,7 +287,7 @@ export default function SettingsView({ tenantId = FIESTA_TENANT_ID, user }: Sett
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Navigation tab rail */}
|
{/* Navigation tab rail */}
|
||||||
<nav className="flex lg:flex-col gap-1.5 overflow-x-auto select-none">
|
<nav className="flex lg:flex-col gap-1.5 overflow-x-auto custom-scrollbar no-scrollbar touch-scrolling select-none">
|
||||||
{tabs.map((t) => {
|
{tabs.map((t) => {
|
||||||
const Icon = t.icon;
|
const Icon = t.icon;
|
||||||
const active = activeTab === t.key;
|
const active = activeTab === t.key;
|
||||||
|
|||||||
@@ -67,6 +67,11 @@ export default function Sidebar({
|
|||||||
key={item.id}
|
key={item.id}
|
||||||
to={`/admin/${item.id}`}
|
to={`/admin/${item.id}`}
|
||||||
title={item.label}
|
title={item.label}
|
||||||
|
onClick={() => {
|
||||||
|
if (window.innerWidth < 768) {
|
||||||
|
onClose();
|
||||||
|
}
|
||||||
|
}}
|
||||||
className={({ isActive }) => `w-full flex items-center py-3 rounded-lg text-left transition-all duration-200 cursor-pointer ${
|
className={({ isActive }) => `w-full flex items-center py-3 rounded-lg text-left transition-all duration-200 cursor-pointer ${
|
||||||
isOpen ? 'gap-md px-md border-l-4' : 'justify-center px-0'
|
isOpen ? 'gap-md px-md border-l-4' : 'justify-center px-0'
|
||||||
} ${
|
} ${
|
||||||
|
|||||||
@@ -51,6 +51,11 @@ export default function UserStoreSidebar({ items, isOpen, onClose }: UserStoreSi
|
|||||||
key={item.id}
|
key={item.id}
|
||||||
to={`/store/${item.id}`}
|
to={`/store/${item.id}`}
|
||||||
title={item.label}
|
title={item.label}
|
||||||
|
onClick={() => {
|
||||||
|
if (window.innerWidth < 768) {
|
||||||
|
onClose();
|
||||||
|
}
|
||||||
|
}}
|
||||||
className={({ isActive }) => `w-full flex items-center py-3 rounded-lg text-left transition-all duration-200 cursor-pointer ${
|
className={({ isActive }) => `w-full flex items-center py-3 rounded-lg text-left transition-all duration-200 cursor-pointer ${
|
||||||
isOpen ? 'gap-md px-md border-l-4' : 'justify-center px-0'
|
isOpen ? 'gap-md px-md border-l-4' : 'justify-center px-0'
|
||||||
} ${
|
} ${
|
||||||
|
|||||||
@@ -103,4 +103,15 @@
|
|||||||
scrollbar-width: thin;
|
scrollbar-width: thin;
|
||||||
scrollbar-color: var(--color-slate-350, #cbd5e1) transparent;
|
scrollbar-color: var(--color-slate-350, #cbd5e1) transparent;
|
||||||
}
|
}
|
||||||
|
.no-scrollbar::-webkit-scrollbar {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.no-scrollbar {
|
||||||
|
-ms-overflow-style: none;
|
||||||
|
scrollbar-width: none;
|
||||||
|
}
|
||||||
|
.touch-scrolling {
|
||||||
|
-webkit-overflow-scrolling: touch;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ import { firstRow, num, str, type Row } from './fiestaApi';
|
|||||||
*/
|
*/
|
||||||
const LOGIN_ENDPOINT = import.meta.env.VITE_FIESTA_URL
|
const LOGIN_ENDPOINT = import.meta.env.VITE_FIESTA_URL
|
||||||
? `${import.meta.env.VITE_FIESTA_URL}/users/applogin`
|
? `${import.meta.env.VITE_FIESTA_URL}/users/applogin`
|
||||||
: 'http://localhost:1122/live/api/v1/web/users/applogin';
|
: 'https://fiesta.nearle.app/live/api/v1/web/users/applogin';
|
||||||
|
|
||||||
/** Request body field names the endpoint expects for the credentials. */
|
/** Request body field names the endpoint expects for the credentials. */
|
||||||
const REQUEST_FIELDS = {
|
const REQUEST_FIELDS = {
|
||||||
|
|||||||
Reference in New Issue
Block a user