EverydayTech Platform - Developer Reference
Complete Source Code Documentation - All Applications
Loading...
Searching...
No Matches
Navigation.tsx
Go to the documentation of this file.
1"use client";
2import { useEffect, useState } from "react";
3import Link from "next/link";
4import { usePathname } from "next/navigation";
5import { defaultConfig } from "@/lib/config";
6import { useStore } from "@/contexts/StoreContext";
7import { Icon } from '@/contexts/IconContext';
8
9interface DashboardItem {
10 title: string;
11 icon: string;
12 path?: string;
13 items?: { name: string; path: string; description?: string }[];
14}
15
16// Helper function to get Material Icon name for section
17function getIconForSection(title: string): string {
18 const iconMap: Record<string, string> = {
19 'Point of Sale': 'point_of_sale',
20 'Account': 'account_circle',
21 'Dashboard': 'dashboard',
22 'Products': 'inventory_2',
23 'Customers': 'group',
24 'Purchasing': 'shopping_bag',
25 'Administration': 'admin_panel_settings',
26 'Reports': 'assessment',
27 'Analytics': 'analytics',
28 'Marketing': 'campaign',
29 'Tools': 'build',
30 'System': 'settings',
31 'Help': 'help'
32 };
33 return iconMap[title] || 'folder';
34}
35
36export default function Navigation() {
37 const { storeName, session, logout } = useStore();
38 const [apiKey, setApiKey] = useState<string | null>(null);
39 const [config, setConfig] = useState(defaultConfig);
40 const pathname = usePathname();
41 const [mobileOpen, setMobileOpen] = useState(false);
42 const [expandedSections, setExpandedSections] = useState<Record<string, boolean>>({});
43
44 // Determine if user is in management portal or retail store
45 const isManagementPortal = session?.store?.type === 'management';
46
47 useEffect(() => {
48 const storedApiKey = sessionStorage.getItem("fieldpine_apikey");
49 setApiKey(storedApiKey);
50
51 const handler = () => setMobileOpen((v) => !v);
52 window.addEventListener('toggle-sidebar', handler as EventListener);
53 return () => window.removeEventListener('toggle-sidebar', handler as EventListener);
54 }, []);
55
56 // Simple POS menu for retail stores
57 const storePosDashboard: DashboardItem[] = [
58 {
59 title: "Dashboard",
60 icon: "D",
61 items: [
62 { name: "Home", path: "/pages/home", description: "Dashboard overview" }
63 ]
64 },
65 {
66 title: "Point of Sale",
67 icon: "💰",
68 items: [
69 { name: "Sales", path: "/sales", description: "Process sales and returns" },
70 { name: "Sales History", path: "/pages/invoices", description: "View customer sales and receipts" },
71 { name: "Sales Report", path: "/pages/reports/pos-sales-report", description: "View today's sales" },
72 { name: "Monthly Report", path: "/pages/reports/franchise-report", description: "Monthly sales summary" },
73 { name: "Products", path: "/pages/products", description: "Edit products" },
74 { name: "Stock Levels", path: "/pages/products/stock-levels", description: "Check inventory" }
75 ]
76 },
77 {
78 title: "Account",
79 icon: "👤",
80 items: [
81 { name: "My Profile", path: "/pages/profile", description: "View profile" },
82 { name: "Settings", path: "/pages/settings", description: "Store settings" }
83 ]
84 },
85 {
86 title: "Help",
87 icon: "❓",
88 items: [
89 { name: "POS Help Center", path: "/pages/pos-help", description: "Store POS guide and FAQ" }
90 ]
91 }
92 ];
93
94 // Full admin menu for IIG management portal
95 const managementDashboard: DashboardItem[] = [
96 {
97 title: "Dashboard",
98 icon: "D",
99 items: [
100 { name: "Home", path: "/pages/home", description: "Overview" },
101 { name: "Start Here", path: "/pages/start-here", description: "Training" },
102 { name: "Advisor", path: "/advisor", description: "Insights" }
103 ]
104 },
105 {
106 title: "Point of Sale",
107 icon: "POS",
108 items: [
109 { name: "Sales", path: "/sales", description: "Process sales and returns" },
110 { name: "Invoices", path: "/pages/invoices", description: "Manage invoices" },
111 { name: "Sales Picking", path: "/pages/sales/picking", description: "Pick & ship orders" },
112 { name: "End of Day", path: "/pages/sales/end-of-day", description: "Closeout" },
113 { name: "Sales Reports", path: "/pages/reports/sales-reports", description: "Analysis" }
114 ]
115 },
116 {
117 title: "Products",
118 icon: "P",
119 items: [
120 { name: "Products", path: "/pages/products", description: "Catalog" },
121 { name: "Purchase Orders", path: "/purchase-orders", description: "Product POs" },
122 { name: "Price Changes", path: "/pages/products/price-changes", description: "Pending Price Changes" },
123 { name: "Signs and Labels", path: "/pages/products/signs-labels", description: "Print" },
124 { name: "Stocktake", path: "/pages/products/stocktake", description: "New, Existing, Archived" }
125 ]
126 },
127 {
128 title: "Customers",
129 icon: "C",
130 items: [
131 { name: "Customers", path: "/pages/customers", description: "CRM" },
132 { name: "Loyalty", path: "/pages/customers/loyalty", description: "Retention" },
133 { name: "Customer Accounts", path: "/pages/customers/customer-accounts", description: "Customer Credit Management" }
134 ]
135 },
136 {
137 title: "Suppliers",
138 icon: "S",
139 items: [
140 { name: "Suppliers", path: "/pages/suppliers", description: "Vendors" }
141 ]
142 },
143 {
144 title: "Reports",
145 icon: "R",
146 items: [
147 { name: "Reports Library", path: "/pages/reports", description: "All business reports" },
148 { name: "Analytics Dashboard", path: "/pages/reports/analytics", description: "Visual Analytics" },
149 { name: "Franchise Reports", path: "/pages/reports/franchise-report", description: "Royalty Reports" },
150 { name: "Sales Reports", path: "/pages/reports/sales-reports", description: "Analysis" }
151 ]
152 },
153 {
154 title: "Marketing",
155 icon: "M",
156 items: [
157 { name: "Marketing Programs", path: "/marketing", description: "Campaigns" },
158 { name: "Prepay", path: "/prepay", description: "Gift cards" }
159 ]
160 },
161 {
162 title: "Operations",
163 icon: "O",
164 items: [
165 { name: "Sale Processing", path: "/pages/settings/operations/sale-processing", description: "Sale behavior" },
166 { name: "Pricing", path: "/pages/settings/operations/pricing", description: "Price rules" },
167 { name: "Stock Control", path: "/pages/settings/operations/stock-control", description: "Inventory" },
168 { name: "Customer Accounts", path: "/pages/settings/operations/accounts", description: "Credit management" },
169 { name: "Loyalty Programs", path: "/pages/settings/operations/loyalty", description: "Rewards" },
170 { name: "Gift Cards", path: "/pages/settings/operations/gift-cards", description: "Prepay cards" },
171 { name: "End of Day", path: "/pages/settings/operations/end-of-day", description: "Till closeout" }
172 ]
173 },
174 {
175 title: "Settings",
176 icon: "S",
177 items: [
178 { name: "Departments", path: "/pages/settings/departments", description: "Department hierarchy" },
179 { name: "Printers & Cartridges", path: "/pages/settings/printers-cartridges", description: "Printer compatibility" },
180 { name: "Staff", path: "/pages/settings/staff", description: "Users" },
181 { name: "Stores", path: "/pages/settings/stores", description: "Locations" },
182 { name: "Lanes", path: "/pages/settings/stores/lanes", description: "POS lanes" },
183 { name: "Payment Types", path: "/pages/settings/payment-types", description: "Accepted payment methods" },
184 { name: "Your Details", path: "/pages/settings/your-details", description: "Company information" },
185 { name: "Photo Management", path: "/pages/settings/media/photos", description: "Product and store photos" },
186 { name: "Cloud Storage", path: "/pages/settings/media/cloud-storage", description: "Configure cloud destinations" },
187 { name: "Receipt Formats", path: "/pages/settings/media/receipt-formats", description: "Customize receipt layouts" }
188 ]
189 },
190 {
191 title: "Devices",
192 icon: "D",
193 items: [
194 { name: "Barcode Scanners", path: "/pages/settings/devices/barcode-scanners", description: "Scanner settings" },
195 { name: "Receipt Printers", path: "/pages/settings/devices/receipt-printers", description: "Printer configuration" },
196 { name: "Cash Drawers", path: "/pages/settings/devices/cash-drawers", description: "Cash drawer setup" },
197 { name: "Label Formats", path: "/pages/settings/devices/label-formats", description: "Price labels and tags" },
198 { name: "EFTPOS Terminals", path: "/pages/settings/devices/eftpos", description: "Payment terminals" },
199 { name: "Cameras", path: "/pages/settings/devices/cameras", description: "Security and product photos" },
200 { name: "Customer Displays", path: "/pages/settings/devices/customer-displays", description: "Pole displays and screens" },
201 { name: "Scales", path: "/pages/settings/devices/scales", description: "Weighing products" }
202 ]
203 },
204 {
205 title: "Technical",
206 icon: "T",
207 items: [
208 { name: "Security Settings", path: "/pages/technical/security", description: "Access controls and authentication" },
209 { name: "Firewall", path: "/pages/technical/firewall", description: "Network security and traffic control" },
210 { name: "GDPR Compliance", path: "/pages/technical/gdpr", description: "Data protection and privacy settings" },
211 { name: "PCI Requirements", path: "/pages/technical/pci", description: "Payment card industry compliance" },
212 { name: "Network Topology", path: "/pages/technical/network-topology", description: "View and manage network infrastructure" },
213 { name: "Database", path: "/pages/technical/database", description: "Database configuration and maintenance" },
214 { name: "API Settings", path: "/pages/technical/api-settings", description: "External API access and configuration" },
215 { name: "Analytics", path: "/pages/technical/analytics", description: "Business intelligence and reporting" },
216 { name: "Internet Services", path: "/pages/technical/internet-services", description: "Cloud services and external connections" },
217 { name: "Code Updates", path: "/pages/technical/code-updates", description: "Software version management" },
218 { name: "Support & Diagnostics", path: "/pages/technical/support-diagnostics", description: "Troubleshooting and system information" },
219 { name: "Documentation", path: "/pages/technical/documentation", description: "Dev Guide" },
220 { name: "API", path: "/api", description: "Dev" },
221 { name: "Audit Logs", path: "/audit", description: "Staff & System Activity" }
222 ]
223 },
224 {
225 title: "Account",
226 icon: "A",
227 items: [
228 { name: "My Profile", path: "/pages/profile", description: "View profile" },
229 { name: "Settings", path: "/pages/settings", description: "Preferences and theme" }
230 ]
231 }
232 ];
233
234 // Determine which dashboard to show based on store type
235 const currentDashboard = isManagementPortal ? managementDashboard : storePosDashboard;
236
237 const toggleSection = (title: string) => {
238 setExpandedSections((prev) => ({ ...prev, [title]: !prev[title] }));
239 };
240
241 const expandAll = () => {
242 const allOpen = currentDashboard.reduce((acc, section) => {
243 acc[section.title] = true;
244 return acc;
245 }, {} as Record<string, boolean>);
246 setExpandedSections(allOpen);
247 };
248
249 const collapseAll = () => {
250 setExpandedSections({});
251 };
252
253 // Hide navigation on landing and login pages
254 if (pathname === "/" || pathname === "/login") {
255 return null;
256 }
257 useEffect(() => {
258 // close mobile nav when route changes
259 setMobileOpen(false);
260 }, [pathname]);
261
262 return (
263 <>
264 {/* overlay for mobile when open */}
265 {mobileOpen && <div className="fixed inset-0 bg-black/30 z-30 md:hidden" onClick={() => setMobileOpen(false)} aria-hidden="true" />}
266
267 <aside className={`fixed md:relative left-0 top-0 h-screen md:h-screen w-[292px] flex-shrink-0 border-r border-border shadow-lg md:shadow-sm transform md:transform-none transition-transform duration-300 ease-in-out z-40 overflow-y-auto ${mobileOpen ? 'translate-x-0' : '-translate-x-full md:translate-x-0'}`} style={{ backgroundColor: 'var(--surface)' }}>
268 <div className="p-4 border-b" style={{ borderColor: 'var(--border)' }}>
269 <div className="flex items-center gap-3">
270 <div className="w-10 h-10 rounded-md bg-brand-2 text-brand flex items-center justify-center font-bold text-sm">
271 <Icon name={isManagementPortal ? 'business' : 'store'} size={24} />
272 </div>
273 <div>
274 <div className="text-sm font-extrabold">{storeName || session?.store?.name || 'EverydayPOS'}</div>
275 <div className="text-xs text-muted capitalize">
276 {isManagementPortal ? 'Management Portal' : 'Store POS'}
277 </div>
278 </div>
279 </div>
280 {apiKey && (
281 <div className="text-xs text-brand mt-2">Connected ✓</div>
282 )}
283 </div>
284
285 <nav className="p-4 space-y-2">
286 {currentDashboard.map((section, index) => (
287 <details key={index} open={expandedSections[section.title] || false} className="rounded-lg overflow-hidden">
288 <summary
289 onClick={(e) => {
290 e.preventDefault();
291 toggleSection(section.title);
292 }}
293 className="cursor-pointer flex items-center p-2 text-text font-semibold hover:bg-surface-2 rounded-lg list-none transition"
294 style={{ color: 'var(--text)' }}
295 >
296 <div className="w-7 h-7 rounded-md bg-brand-2 text-brand mr-3 flex items-center justify-center text-sm font-bold">
297 <Icon name={getIconForSection(section.title)} size={20} />
298 </div>
299 <span>{section.title}</span>
300 <svg className={`ml-auto w-4 h-4 transition-transform ${expandedSections[section.title] ? 'rotate-180' : ''}`} fill="none" stroke="currentColor" viewBox="0 0 24 24">
301 <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 14l-7 7m0 0l-7-7m7 7V3" />
302 </svg>
303 </summary>
304 {section.items && (
305 <div className="ml-4 mt-1 space-y-1 border-l border-border pl-3">
306 {section.items.map((item, itemIndex) => (
307 <Link
308 key={itemIndex}
309 href={item.path}
310 aria-label={item.name}
311 aria-current={pathname === item.path ? 'page' : undefined}
312 className={`block p-2 text-sm rounded transition ${
313 pathname === item.path
314 ? "bg-surface-2 text-brand font-semibold border-l-4 border-brand pl-2"
315 : "text-muted hover:bg-surface-2 hover:text-brand"
316 }`}>
317 <div className="font-medium">{item.name}</div>
318 {item.description && (
319 <div className="text-xs text-muted">{item.description}</div>
320 )}
321 </Link>
322 ))}
323 </div>
324 )}
325 </details>
326 ))}
327 </nav>
328
329 <div className="p-4 border-t space-y-3">
330 {/* User Session Info */}
331 {session && (
332 <div className="bg-surface-2 rounded-lg p-3 mb-3">
333 <div className="mb-2">
334 <div className="text-sm font-semibold text-text">{session.user.name}</div>
335 <div className="text-xs text-muted capitalize">{session.user.role}</div>
336 </div>
337 <button
338 onClick={logout}
339 className="w-full px-3 py-2 text-sm rounded bg-surface-2 text-danger hover:bg-danger/10 transition font-medium border border-danger"
340 title="Logout"
341 >
342 Logout
343 </button>
344 {session.store && (
345 <div className="mt-2 space-y-1">
346 {/* Store Name */}
347 <div className="flex items-center gap-2 text-xs">
348 <span>{session.store.type === 'management' ? '🏢' : '🏪'}</span>
349 <span className="font-medium text-text">{session.store.name}</span>
350 </div>
351 {/* Store Type Badge */}
352 {session.store.type === 'management' ? (
353 <div className="inline-flex items-center px-2 py-1 bg-brand-2 text-brand text-xs font-medium rounded">
354 Management Portal
355 </div>
356 ) : (
357 <div className="inline-flex items-center px-2 py-1 bg-surface-2 text-text text-xs font-medium rounded border border-border">
358 Retail Store
359 </div>
360 )}
361 </div>
362 )}
363 </div>
364 )}
365
366 <div className="flex gap-2">
367 <button
368 onClick={expandAll}
369 className="flex-1 px-3 py-2 text-xs rounded border border-border bg-surface text-text hover:bg-surface-2 transition font-medium">
370 Expand all
371 </button>
372 <button
373 onClick={collapseAll}
374 className="flex-1 px-3 py-2 text-xs rounded border border-border bg-surface text-text hover:bg-surface-2 transition font-medium">
375 Collapse
376 </button>
377 </div>
378 </div>
379 </aside>
380 </>
381 );
382}