EverydayTech Platform - Developer Reference
Complete Source Code Documentation - All Applications
Loading...
Searching...
No Matches
page.tsx
Go to the documentation of this file.
1'use client';
2
3import { useState, useEffect } from 'react';
4import Link from 'next/link';
5import { Icon } from '@/contexts/IconContext';
6
7interface Lane {
8 f100: string; // ID
9 f101: string; // Name
10 f102: string; // Location/Store
11 f104: string; // Store Name
12 f107: string; // Last Seen
13 f110: number; // Authorized
14 f114: string; // IP Address
15 f124: string; // Comments
16 f125: string; // Asset Number
17 f126: number; // Life Phase
18 f128: number; // Profile
19 f129: string; // Active UI
20 f130: string; // Set UI
21 f131: number; // Canary Level
22 f132: string; // Auto Update
23 uistr?: string; // UI display string
24 Version?: string;
25 Time?: string;
26}
27
28interface Profile {
29 id: number;
30 name: string;
31 used: number;
32}
33
34export default function LanesPage() {
35 const [lanes, setLanes] = useState<Lane[]>([]);
36 const [filteredLanes, setFilteredLanes] = useState<Lane[]>([]);
37 const [profiles, setProfiles] = useState<Profile[]>([]);
38 const [loading, setLoading] = useState(true);
39 const [searchTerm, setSearchTerm] = useState('');
40 const [authFilter, setAuthFilter] = useState('0'); // 0=All, 1=Only Auth, 2=Only Non-Auth
41 const [phaseFilter, setPhaseFilter] = useState('-1'); // -3=All, -1=All Active, -2=All Non-Active, 0-3=specific, 64-66=retired/test/deployed
42
43 const [showEditModal, setShowEditModal] = useState(false);
44 const [showProfileModal, setShowProfileModal] = useState(false);
45 const [showScanModal, setShowScanModal] = useState(false);
46 const [editingLane, setEditingLane] = useState<Lane | null>(null);
47
48 const [laneForm, setLaneForm] = useState({
49 f100: '',
50 f101: '',
51 f102: '',
52 f104: '',
53 f110: true,
54 f114: '',
55 f124: '',
56 f125: '',
57 f126: 0,
58 f128: 0,
59 f130: '',
60 f131: 0,
61 f132: '0'
62 });
63
64 useEffect(() => {
65 loadData();
66 }, []);
67
68 useEffect(() => {
69 applyFilters();
70 }, [lanes, searchTerm, authFilter, phaseFilter]);
71
72 const loadData = async () => {
73 try {
74 setLoading(true);
75 // In production: await apiClient.getLanes()
76 // For now, using placeholder data
77 const mockLanes: Lane[] = [
78 {
79 f100: '1001',
80 f101: 'POS 1',
81 f102: '1',
82 f104: 'Main Store',
83 f107: '2025-12-24T10:30:00',
84 f110: 1,
85 f114: '192.168.1.100',
86 f124: 'Main counter',
87 f125: 'ASSET-001',
88 f126: 0,
89 f128: 1,
90 f129: 'Touch Screen UI',
91 f130: 'Touch Screen UI',
92 f131: 50,
93 f132: 'stable',
94 uistr: 'Touch Screen UI',
95 Version: 'P2200',
96 Time: '24-Dec-2025 10:30'
97 },
98 {
99 f100: '1002',
100 f101: 'POS 2',
101 f102: '1',
102 f104: 'Main Store',
103 f107: '2025-12-24T09:15:00',
104 f110: 1,
105 f114: '192.168.1.101',
106 f124: 'Back counter',
107 f125: 'ASSET-002',
108 f126: 0,
109 f128: 1,
110 f129: 'Touch Screen UI',
111 f130: 'Touch Screen UI',
112 f131: 25,
113 f132: 'stable',
114 uistr: 'Touch Screen UI',
115 Version: 'P2200',
116 Time: '24-Dec-2025 09:15'
117 },
118 {
119 f100: '2001',
120 f101: 'BackOffice',
121 f102: '1',
122 f104: 'Main Store',
123 f107: '2025-12-24T08:00:00',
124 f110: 1,
125 f114: '192.168.1.150',
126 f124: 'Office system',
127 f125: 'ASSET-050',
128 f126: 1,
129 f128: 2,
130 f129: 'Default POS Interface',
131 f130: 'Default POS Interface',
132 f131: 75,
133 f132: 'latest',
134 uistr: 'Default POS Interface',
135 Version: 'P2210',
136 Time: '24-Dec-2025 08:00'
137 }
138 ];
139
140 setLanes(mockLanes);
141
142 // Load profiles
143 const mockProfiles: Profile[] = [
144 { id: 1, name: 'Selling Counters', used: 2 },
145 { id: 2, name: 'Back Office', used: 1 },
146 { id: 3, name: 'Profile 3', used: 0 },
147 { id: 4, name: 'Profile 4', used: 0 },
148 { id: 5, name: 'Profile 5', used: 0 },
149 { id: 6, name: 'Profile 6', used: 0 },
150 { id: 7, name: 'Profile 7', used: 0 },
151 { id: 8, name: 'Profile 8', used: 0 }
152 ];
153 setProfiles(mockProfiles);
154 } catch (error) {
155 console.error('Error loading lanes:', error);
156 alert('Failed to load lanes data');
157 } finally {
158 setLoading(false);
159 }
160 };
161
162 const applyFilters = () => {
163 let filtered = [...lanes];
164
165 // Auth filter
166 if (authFilter === '1') {
167 filtered = filtered.filter(l => l.f110 === 1);
168 } else if (authFilter === '2') {
169 filtered = filtered.filter(l => l.f110 === 0);
170 }
171
172 // Phase filter
173 const phase = Number(phaseFilter);
174 if (phase !== -3) {
175 if (phase === -1) {
176 // All active (0-3)
177 filtered = filtered.filter(l => l.f126 >= 0 && l.f126 < 64);
178 } else if (phase === -2) {
179 // All non-active (64+)
180 filtered = filtered.filter(l => l.f126 >= 64);
181 } else {
182 // Specific phase
183 filtered = filtered.filter(l => l.f126 === phase);
184 }
185 }
186
187 // Search filter
188 if (searchTerm) {
189 const term = searchTerm.toLowerCase();
190 filtered = filtered.filter(l =>
191 l.f100.toLowerCase().includes(term) ||
192 l.f101.toLowerCase().includes(term) ||
193 l.f102.toLowerCase().includes(term) ||
194 l.f104.toLowerCase().includes(term) ||
195 l.f114.toLowerCase().includes(term) ||
196 l.f125.toLowerCase().includes(term)
197 );
198 }
199
200 setFilteredLanes(filtered);
201 };
202
203 const openNewLaneModal = () => {
204 setEditingLane(null);
205 setLaneForm({
206 f100: '',
207 f101: '',
208 f102: '',
209 f104: '',
210 f110: true,
211 f114: '',
212 f124: '',
213 f125: '',
214 f126: 0,
215 f128: 0,
216 f130: '',
217 f131: 0,
218 f132: '0'
219 });
220 setShowEditModal(true);
221 };
222
223 const openEditLaneModal = (lane: Lane) => {
224 setEditingLane(lane);
225 setLaneForm({
226 f100: lane.f100,
227 f101: lane.f101,
228 f102: lane.f102,
229 f104: lane.f104,
230 f110: lane.f110 === 1,
231 f114: lane.f114,
232 f124: lane.f124,
233 f125: lane.f125,
234 f126: lane.f126,
235 f128: lane.f128,
236 f130: lane.f130,
237 f131: lane.f131,
238 f132: lane.f132
239 });
240 setShowEditModal(true);
241 };
242
243 const saveLane = async () => {
244 try {
245 if (!editingLane && !laneForm.f100) {
246 alert('A valid unique ID is required');
247 return;
248 }
249
250 // In production: await apiClient.saveLane(laneForm, editingLane ? 'edit' : 'new')
251 console.log('Saving lane:', laneForm);
252 alert('Lane saved successfully');
253 setShowEditModal(false);
254 loadData();
255 } catch (error) {
256 console.error('Error saving lane:', error);
257 alert('Failed to save lane');
258 }
259 };
260
261 const getLifePhaseName = (phase: number) => {
262 const phases: { [key: number]: string } = {
263 0: 'Trading Lane',
264 1: 'Back Office Lane',
265 2: 'Head Office Lane',
266 3: 'IT/Support Lane',
267 64: 'Retired/Historic',
268 65: 'Test Lane',
269 66: 'Deployed Lane'
270 };
271 return phases[phase] || 'Unknown';
272 };
273
274 const formatDateTime = (dateStr: string) => {
275 if (!dateStr) return '';
276 const date = new Date(dateStr);
277 return date.toLocaleString();
278 };
279
280 if (loading) {
281 return (
282 <div className="p-8">
283 <div className="text-center">Loading lanes...</div>
284 </div>
285 );
286 }
287
288 return (
289 <div className="min-h-screen bg-bg">
290 {/* Page Title */}
291 <div className="bg-surface border-b-2 border-[var(--brand)] p-6">
292 <h1 className="text-3xl font-bold text-text flex items-center gap-2">
293 <Icon name="storefront" size={32} className="text-brand" />
294 Lanes
295 </h1>
296 </div>
297
298 <div className="p-6">
299 {/* Action Cards */}
300 <div className="grid grid-cols-2 md:grid-cols-3 gap-4 mb-6">
301 <button
302 onClick={() => setShowProfileModal(true)}
303 className="bg-surface rounded-lg shadow p-4 hover:shadow-lg transition-shadow flex flex-col items-center justify-center gap-2 border-2 border-transparent hover:border-brand"
304 >
305 <Icon name="dashboard" size={40} className="text-brand" />
306 <span className="font-semibold text-text text-sm text-center">Manage Profiles</span>
307 </button>
308
309 <button
310 onClick={openNewLaneModal}
311 className="bg-surface rounded-lg shadow p-4 hover:shadow-lg transition-shadow flex flex-col items-center justify-center gap-2 border-2 border-transparent hover:border-brand"
312 >
313 <Icon name="add" size={40} className="text-brand" />
314 <span className="font-semibold text-text text-sm text-center">Add New Lane</span>
315 </button>
316
317 <button
318 onClick={() => setShowScanModal(true)}
319 className="bg-surface rounded-lg shadow p-4 hover:shadow-lg transition-shadow flex flex-col items-center justify-center gap-2 border-2 border-transparent hover:border-brand"
320 >
321 <Icon name="search" size={40} className="text-brand" />
322 <span className="font-semibold text-text text-sm text-center">Scan All Lanes</span>
323 </button>
324 </div>
325
326 {/* Search and Filters */}
327 <div className="bg-surface rounded-lg shadow p-4 mb-6">
328 <div className="flex flex-wrap items-center gap-4">
329 <div className="flex-1 min-w-[200px]">
330 <input
331 type="text"
332 placeholder="Search lanes..."
333 value={searchTerm}
334 onChange={(e) => setSearchTerm(e.target.value)}
335 className="w-full px-4 py-2 border border-border rounded-lg focus:ring-2 focus:ring-brand focus:border-transparent"
336 />
337 </div>
338
339 <div className="flex items-center gap-2">
340 <label className="text-sm font-medium text-text">Auth:</label>
341 <select
342 value={authFilter}
343 onChange={(e) => setAuthFilter(e.target.value)}
344 className="px-3 py-2 border border-border rounded-lg focus:ring-2 focus:ring-[var(--brand)] focus:border-transparent"
345 >
346 <option value="0">All Lanes</option>
347 <option value="1">Only Authorised</option>
348 <option value="2">Only Non-Authorised</option>
349 </select>
350 </div>
351
352 <div className="flex items-center gap-2">
353 <label className="text-sm font-medium text-text">Phase:</label>
354 <select
355 value={phaseFilter}
356 onChange={(e) => setPhaseFilter(e.target.value)}
357 className="px-3 py-2 border border-border rounded-lg focus:ring-2 focus:ring-[var(--brand)] focus:border-transparent"
358 >
359 <option value="-3">All Lanes Regardless</option>
360 <optgroup label="Active">
361 <option value="-1">All Active Lanes</option>
362 <option value="0">Trading Lanes</option>
363 <option value="1">Store Back Office Lanes</option>
364 <option value="2">Head Office Lanes</option>
365 <option value="3">IT Support Lanes</option>
366 </optgroup>
367 <optgroup label="Not Active">
368 <option value="-2">All Non Active Lanes</option>
369 <option value="64">Retired Lanes</option>
370 <option value="66">Deployed Lanes</option>
371 <option value="65">Test Lanes</option>
372 </optgroup>
373 </select>
374 </div>
375
376 <div className="text-sm text-muted">
377 Displaying {filteredLanes.length} of {lanes.length}
378 </div>
379 </div>
380 </div>
381
382 {/* Lanes Table */}
383 <div className="bg-surface rounded-lg shadow overflow-x-auto">
384 <table className="w-full">
385 <thead className="bg-[var(--brand)] text-surface">
386 <tr>
387 <th className="p-3 text-left">ID</th>
388 <th className="p-3 text-left">Last IP</th>
389 <th className="p-3 text-left">Store</th>
390 <th className="p-3 text-left">Name</th>
391 <th className="p-3 text-left">Profile</th>
392 <th className="p-3 text-left">User Interface</th>
393 <th className="p-3 text-left">Asset #</th>
394 <th className="p-3 text-left">Last Seen</th>
395 <th className="p-3 text-left">Options</th>
396 </tr>
397 </thead>
398 <tbody>
399 {filteredLanes.map((lane, index) => (
400 <tr key={lane.f100} className={index % 2 === 0 ? 'bg-surface-2' : 'bg-surface'}>
401 <td className="p-3 border-t border-border">
402 <div className="font-semibold text-text">{lane.f100}</div>
403 <div className="text-xs text-muted flex items-center gap-1">
404 {lane.f110 ? (
405 <><Icon name="check_circle" size={12} className="text-success" /> Auth</>
406 ) : (
407 <><Icon name="cancel" size={12} className="text-danger" /> Not Auth</>
408 )}
409 </div>
410 </td>
411 <td className="p-3 border-t border-border text-sm">{lane.f114}</td>
412 <td className="p-3 border-t border-border text-sm">{lane.f102}</td>
413 <td className="p-3 border-t border-border font-medium">{lane.f101}</td>
414 <td className="p-3 border-t border-border text-sm">
415 {lane.f128 > 0 ? `Profile ${lane.f128}` : 'None'}
416 </td>
417 <td className="p-3 border-t border-border text-sm">
418 {lane.uistr || lane.f129 || 'Default'}
419 </td>
420 <td className="p-3 border-t border-border text-sm">{lane.f125}</td>
421 <td className="p-3 border-t border-border text-sm">{formatDateTime(lane.f107)}</td>
422 <td className="p-3 border-t border-border">
423 <div className="flex gap-2">
424 <button
425 onClick={() => openEditLaneModal(lane)}
426 className="px-3 py-1 bg-info text-surface text-sm rounded hover:bg-info/80 flex items-center gap-1"
427 >
428 <Icon name="edit" size={14} />
429 Edit
430 </button>
431 <Link
432 href={`/pages/settings/staff?lane=${lane.f100}`}
433 className="px-3 py-1 bg-brand text-surface text-sm rounded hover:bg-brand/90 flex items-center gap-1"
434 >
435 <Icon name="assignment" size={14} />
436 Login Report
437 </Link>
438 </div>
439 </td>
440 </tr>
441 ))}
442 </tbody>
443 </table>
444
445 {filteredLanes.length === 0 && (
446 <div className="p-8 text-center text-muted">
447 No lanes found matching your criteria
448 </div>
449 )}
450 </div>
451 </div>
452
453 {/* Edit/New Lane Modal */}
454 {showEditModal && (
455 <div className="fixed inset-0 bg-black/30 backdrop-blur-sm flex items-center justify-center z-50 p-4">
456 <div className="bg-surface/95 backdrop-blur-md rounded-lg shadow-2xl max-w-2xl w-full max-h-[90vh] overflow-y-auto">
457 <div className="sticky top-0 bg-[var(--brand)] text-surface p-4 flex justify-between items-center">
458 <h2 className="text-xl font-bold">{editingLane ? 'Edit Lane' : 'Add New Lane'}</h2>
459 <button
460 onClick={() => setShowEditModal(false)}
461 className="text-surface hover:text-muted/70 text-2xl font-bold"
462 >
463 <Icon name="close" size={24} />
464 </button>
465 </div>
466
467 <div className="p-6">
468 <div className="space-y-4">
469 <div className="grid grid-cols-2 gap-4">
470 <div>
471 <label className="block text-sm font-medium text-text mb-1">
472 Unique ID {!editingLane && <span className="text-danger">*</span>}
473 </label>
474 <input
475 type="text"
476 value={laneForm.f100}
477 onChange={(e) => setLaneForm({...laneForm, f100: e.target.value})}
478 disabled={!!editingLane}
479 className="w-full px-3 py-2 border border-border rounded-lg focus:ring-2 focus:ring-[var(--brand)] focus:border-transparent disabled:bg-surface-2"
480 />
481 </div>
482
483 <div>
484 <label className="flex items-center gap-2 cursor-pointer">
485 <input
486 type="checkbox"
487 checked={laneForm.f110}
488 onChange={(e) => setLaneForm({...laneForm, f110: e.target.checked})}
489 className="w-5 h-5 text-[var(--brand)] focus:ring-[var(--brand)]"
490 />
491 <span className="text-sm font-medium text-text">Authorised</span>
492 </label>
493 </div>
494 </div>
495
496 <div>
497 <label className="block text-sm font-medium text-text mb-1">Name</label>
498 <input
499 type="text"
500 value={laneForm.f101}
501 onChange={(e) => setLaneForm({...laneForm, f101: e.target.value})}
502 placeholder="e.g., POS1"
503 className="w-full px-3 py-2 border border-border rounded-lg focus:ring-2 focus:ring-[var(--brand)] focus:border-transparent"
504 />
505 <p className="text-xs text-muted mt-1">A brief name for this lane</p>
506 </div>
507
508 <div>
509 <label className="block text-sm font-medium text-text mb-1">Store Name</label>
510 <input
511 type="text"
512 value={laneForm.f104}
513 onChange={(e) => setLaneForm({...laneForm, f104: e.target.value})}
514 className="w-full px-3 py-2 border border-border rounded-lg focus:ring-2 focus:ring-[var(--brand)] focus:border-transparent"
515 />
516 <p className="text-xs text-muted mt-1">Optional, only used for some reports</p>
517 </div>
518
519 <div className="grid grid-cols-2 gap-4">
520 <div>
521 <label className="block text-sm font-medium text-text mb-1">Location</label>
522 <input
523 type="text"
524 value={laneForm.f102}
525 onChange={(e) => setLaneForm({...laneForm, f102: e.target.value})}
526 className="w-full px-3 py-2 border border-border rounded-lg focus:ring-2 focus:ring-[var(--brand)] focus:border-transparent"
527 />
528 </div>
529
530 <div>
531 <label className="block text-sm font-medium text-text mb-1">Profile</label>
532 <select
533 value={laneForm.f128}
534 onChange={(e) => setLaneForm({...laneForm, f128: Number(e.target.value)})}
535 className="w-full px-3 py-2 border border-border rounded-lg focus:ring-2 focus:ring-[var(--brand)] focus:border-transparent"
536 >
537 <option value={0}>None</option>
538 {profiles.map(p => (
539 <option key={p.id} value={p.id}>
540 {p.name || `Profile ${p.id}`}
541 </option>
542 ))}
543 </select>
544 </div>
545 </div>
546
547 <div>
548 <label className="block text-sm font-medium text-text mb-1">Automatic Code Update</label>
549 <select
550 value={laneForm.f132}
551 onChange={(e) => setLaneForm({...laneForm, f132: e.target.value})}
552 className="w-full px-3 py-2 border border-border rounded-lg focus:ring-2 focus:ring-[var(--brand)] focus:border-transparent"
553 >
554 <option value="0">Inherit from profile</option>
555 <option value="manual">Manual</option>
556 <option value="latest">Latest</option>
557 <option value="stable">Stable</option>
558 <option value="dev">Development</option>
559 <option value="whitehot">Whitehot</option>
560 <option value="longterm">Previous Generation</option>
561 </select>
562 </div>
563
564 <div>
565 <label className="block text-sm font-medium text-text mb-1">
566 Canary Level: {laneForm.f131}
567 </label>
568 <input
569 type="range"
570 min="0"
571 max="100"
572 value={laneForm.f131}
573 onChange={(e) => setLaneForm({...laneForm, f131: Number(e.target.value)})}
574 className="w-full"
575 />
576 <p className="text-xs text-muted mt-1">
577 Controls how aggressive updates are applied. Higher = earlier updates.
578 </p>
579 </div>
580
581 <div>
582 <label className="block text-sm font-medium text-text mb-1">User Interface #</label>
583 <input
584 type="text"
585 value={laneForm.f130}
586 onChange={(e) => setLaneForm({...laneForm, f130: e.target.value})}
587 className="w-full px-3 py-2 border border-border rounded-lg focus:ring-2 focus:ring-[var(--brand)] focus:border-transparent"
588 />
589 </div>
590
591 <div>
592 <label className="block text-sm font-medium text-text mb-1">Asset Number</label>
593 <input
594 type="text"
595 value={laneForm.f125}
596 onChange={(e) => setLaneForm({...laneForm, f125: e.target.value})}
597 className="w-full px-3 py-2 border border-border rounded-lg focus:ring-2 focus:ring-[var(--brand)] focus:border-transparent"
598 />
599 </div>
600
601 <div>
602 <label className="block text-sm font-medium text-text mb-1">Comments</label>
603 <textarea
604 rows={2}
605 value={laneForm.f124}
606 onChange={(e) => setLaneForm({...laneForm, f124: e.target.value})}
607 className="w-full px-3 py-2 border border-border rounded-lg focus:ring-2 focus:ring-[var(--brand)] focus:border-transparent"
608 />
609 </div>
610
611 <div>
612 <label className="block text-sm font-medium text-text mb-1">IP Address</label>
613 <input
614 type="text"
615 value={laneForm.f114}
616 onChange={(e) => setLaneForm({...laneForm, f114: e.target.value})}
617 placeholder="e.g., 192.168.1.100"
618 className="w-full px-3 py-2 border border-border rounded-lg focus:ring-2 focus:ring-[var(--brand)] focus:border-transparent"
619 />
620 <p className="text-xs text-muted mt-1">Optional, will be updated when it connects</p>
621 </div>
622
623 <div>
624 <label className="block text-sm font-medium text-text mb-1">Life Phase</label>
625 <select
626 value={laneForm.f126}
627 onChange={(e) => setLaneForm({...laneForm, f126: Number(e.target.value)})}
628 className="w-full px-3 py-2 border border-border rounded-lg focus:ring-2 focus:ring-[var(--brand)] focus:border-transparent"
629 >
630 <optgroup label="Active">
631 <option value={0}>Trading Lane</option>
632 <option value={1}>Back Office Lane</option>
633 <option value={2}>Head Office Lane</option>
634 <option value={3}>IT/Support Lane</option>
635 </optgroup>
636 <optgroup label="Non Active">
637 <option value={64}>Retired/Historic</option>
638 <option value={65}>Test Lane</option>
639 <option value={66}>Deployed Lane</option>
640 </optgroup>
641 </select>
642 </div>
643 </div>
644
645 <div className="mt-6 flex gap-3">
646 <button
647 onClick={saveLane}
648 className="flex-1 px-6 py-3 bg-[var(--brand)] text-surface rounded-lg hover:bg-[var(--brand2)] transition-colors font-semibold flex items-center justify-center gap-2"
649 >
650 <Icon name="save" size={20} />
651 Save
652 </button>
653 <button
654 onClick={() => setShowEditModal(false)}
655 className="px-6 py-3 bg-surface-2 text-text rounded-lg hover:bg-surface-2/80 transition-colors"
656 >
657 Cancel
658 </button>
659 </div>
660 </div>
661 </div>
662 </div>
663 )}
664
665 {/* Profile Management Modal */}
666 {showProfileModal && (
667 <div className="fixed inset-0 bg-black/30 backdrop-blur-sm flex items-center justify-center z-50 p-4">
668 <div className="bg-surface/95 backdrop-blur-md rounded-lg shadow-2xl max-w-3xl w-full">
669 <div className="bg-[var(--brand)] text-surface p-4 flex justify-between items-center rounded-t-lg">
670 <h2 className="text-xl font-bold">Manage Profiles</h2>
671 <button
672 onClick={() => setShowProfileModal(false)}
673 className="text-surface hover:text-muted/70 text-2xl font-bold"
674 >
675 <Icon name="close" size={24} />
676 </button>
677 </div>
678
679 <div className="p-6">
680 <p className="text-text mb-6" style={{maxWidth: '26em'}}>
681 A profile is a grouping of lanes that share similar attributes and should be configured the same way.
682 Typically all selling counters might belong to one profile and all backoffice systems to another profile.
683 Several of the configuration options allow you to configure based on profile.
684 </p>
685
686 <table className="w-full border-collapse">
687 <thead>
688 <tr className="bg-surface-2">
689 <th className="p-3 text-center border border-border">Profile Number</th>
690 <th className="p-3 text-center border border-border">Profile Name</th>
691 <th className="p-3 text-center border border-border">Count Using this profile</th>
692 </tr>
693 </thead>
694 <tbody>
695 {profiles.map((profile) => (
696 <tr key={profile.id}>
697 <td className="p-3 text-center border border-border font-semibold">{profile.id}</td>
698 <td className="p-3 border border-border">
699 <input
700 type="text"
701 size={14}
702 value={profile.name}
703 onChange={(e) => {
704 const updated = profiles.map(p =>
705 p.id === profile.id ? {...p, name: e.target.value} : p
706 );
707 setProfiles(updated);
708 }}
709 onBlur={async (e) => {
710 // Save profile name on blur
711 const name = e.target.value || `Profile ${profile.id}`;
712 try {
713 // In production: await apiClient.saveProfileName(profile.id, name)
714 console.log(`Saving profile ${profile.id} name:`, name);
715 } catch (error) {
716 console.error('Error saving profile name:', error);
717 }
718 }}
719 className="w-full px-2 py-1 border border-border rounded focus:ring-1 focus:ring-[var(--brand)] focus:border-transparent"
720 />
721 </td>
722 <td className="p-3 text-center border border-border">{profile.used}</td>
723 </tr>
724 ))}
725 <tr>
726 <td colSpan={2} className="p-3 text-center border border-border font-semibold">
727 Lanes not assigned to any profile
728 </td>
729 <td className="p-3 text-center border border-border">0</td>
730 </tr>
731 </tbody>
732 </table>
733 </div>
734 </div>
735 </div>
736 )}
737
738 {/* Scan Lanes Modal */}
739 {showScanModal && (
740 <div className="fixed inset-0 bg-black/30 backdrop-blur-sm flex items-center justify-center z-50 p-4">
741 <div className="bg-surface/95 backdrop-blur-md rounded-lg shadow-2xl max-w-2xl w-full">
742 <div className="bg-[var(--brand)] text-surface p-4 flex justify-between items-center rounded-t-lg">
743 <h2 className="text-xl font-bold">Scan All Lanes</h2>
744 <button
745 onClick={() => setShowScanModal(false)}
746 className="text-surface hover:text-muted/70 text-2xl font-bold"
747 >
748 <Icon name="close" size={24} />
749 </button>
750 </div>
751
752 <div className="p-6">
753 <h3 className="text-lg font-semibold mb-4">PosGreen Lanes</h3>
754
755 <div className="space-y-4">
756 <div>
757 <label className="block text-sm font-medium text-text mb-1">
758 Retrieve Current Setting
759 </label>
760 <input
761 type="text"
762 placeholder="Setting name"
763 className="w-full px-3 py-2 border border-border rounded-lg focus:ring-2 focus:ring-[var(--brand)] focus:border-transparent"
764 />
765 </div>
766
767 <div>
768 <label className="block text-sm font-medium text-text mb-1">
769 Database Table Row Estimate
770 </label>
771 <input
772 type="text"
773 placeholder="Table name"
774 className="w-full px-3 py-2 border border-border rounded-lg focus:ring-2 focus:ring-[var(--brand)] focus:border-transparent"
775 />
776 </div>
777
778 <div>
779 <label className="block text-sm font-medium text-text mb-1">
780 TCP Port
781 </label>
782 <input
783 type="number"
784 defaultValue={8095}
785 className="w-full px-3 py-2 border border-border rounded-lg focus:ring-2 focus:ring-[var(--brand)] focus:border-transparent"
786 />
787 </div>
788
789 <button
790 onClick={() => alert('Scan functionality will be implemented with API integration')}
791 className="w-full px-6 py-3 bg-[var(--brand)] text-surface rounded-lg hover:bg-[var(--brand2)] transition-colors font-semibold flex items-center justify-center gap-2"
792 >
793 <Icon name="add_circle" size={20} />
794 Add Column
795 </button>
796 </div>
797 </div>
798 </div>
799 </div>
800 )}
801 </div>
802 );
803}