| | 1 | 1 | | import React, { useState } from 'react'; |
| | 1 | 2 | | import { useMaintenance, useMaintenanceDispatch } from '../context/MaintenanceContext.js'; |
| | 1 | 3 | | import { useLiveSearch } from '../context/useHooks.js'; |
| | 1 | 4 | | |
| | 1 | 5 | | export function MachineList() { |
| | 24 | 6 | | const { machines } = useMaintenance(); |
| | 24 | 7 | | const dispatch = useMaintenanceDispatch(); |
| | 24 | 8 | | const [filterStatus, setFilterStatus] = useState('All'); |
| | 24 | 9 | | |
| | 24 | 10 | | // Point 5: Using Custom Hook for search logic |
| | 24 | 11 | | // Point 4: Inside useLiveSearch, useMemo is handling expensive filtering |
| | 24 | 12 | | const { query, setQuery, filteredItems } = useLiveSearch(machines || [], 'name'); |
| | 24 | 13 | | |
| | 24 | 14 | | const finalItems = filterStatus === 'All' |
| | 23 | 15 | | ? filteredItems |
| | 1 | 16 | | : filteredItems.filter(m => m.status === filterStatus); |
| | 24 | 17 | | |
| | 24 | 18 | | return ( |
| | 24 | 19 | | <div className="machine-list"> |
| | 24 | 20 | | <h2 style={{ marginBottom: '15px' }}>Assets Inventory</h2> |
| | 24 | 21 | | <div style={{ marginBottom: '20px' }}> |
| | 24 | 22 | | <input |
| | 24 | 23 | | type="text" |
| | 24 | 24 | | placeholder="Search assets..." |
| | 24 | 25 | | value={query} |
| | 24 | 26 | | onChange={(e) => setQuery(e.target.value)} |
| | 24 | 27 | | style={{ width: '100%', padding: '10px', borderRadius: '4px', border: '1px solid #ddd', marginBottom |
| | 24 | 28 | | /> |
| | 24 | 29 | | <div style={{ display: 'flex', gap: '5px' }}> |
| | 24 | 30 | | {['All', 'Operational', 'Warning', 'Maintenance'].map(s => ( |
| | 96 | 31 | | <button |
| | 96 | 32 | | key={s} |
| | 96 | 33 | | onClick={() => setFilterStatus(s)} |
| | 96 | 34 | | style={{ |
| | 96 | 35 | | padding: '5px 10px', |
| | 96 | 36 | | backgroundColor: filterStatus === s ? '#3498db' : '#eee', |
| | 96 | 37 | | color: filterStatus === s ? 'white' : 'black', |
| | 96 | 38 | | border: 'none', |
| | 96 | 39 | | borderRadius: '4px', |
| | 96 | 40 | | cursor: 'pointer' |
| | 96 | 41 | | }} |
| | 96 | 42 | | > |
| | 96 | 43 | | {s} |
| | 96 | 44 | | </button> |
| | 24 | 45 | | ))} |
| | 24 | 46 | | </div> |
| | 24 | 47 | | </div> |
| | 24 | 48 | | |
| | 24 | 49 | | <div style={{ maxHeight: '600px', overflowY: 'auto' }}> |
| | 24 | 50 | | {finalItems.map((machine) => ( |
| | 10 | 51 | | <MachineCard key={machine.id} machine={machine} /> |
| | 24 | 52 | | ))} |
| | 24 | 53 | | </div> |
| | 24 | 54 | | </div> |
| | 24 | 55 | | ); |
| | 24 | 56 | | } |
| | 1 | 57 | | |
| | 14 | 58 | | function MachineCard({ machine }) { |
| | 14 | 59 | | const dispatch = useMaintenanceDispatch(); |
| | 14 | 60 | | const [showSensors, setShowSensors] = useState(false); |
| | 14 | 61 | | const [sensors, setSensors] = useState([]); |
| | 14 | 62 | | const [loading, setLoading] = useState(false); |
| | 14 | 63 | | |
| | 14 | 64 | | const toggleSensors = async () => { |
| | 2 | 65 | | if (!showSensors) { |
| | 2 | 66 | | setLoading(true); |
| | 2 | 67 | | try { |
| | 2 | 68 | | const res = await fetch(`http://localhost:8080/api/sensors?id=${machine.id}`); |
| | 1 | 69 | | const data = await res.json(); |
| | 1 | 70 | | setSensors(data); |
| | 1 | 71 | | } catch (err) { |
| | 1 | 72 | | console.error("Sensor fetch error:", err); |
| | 2 | 73 | | } finally { |
| | 2 | 74 | | setLoading(false); |
| | 2 | 75 | | } |
| | 2 | 76 | | } |
| | 2 | 77 | | setShowSensors(!showSensors); |
| | 2 | 78 | | }; |
| | 14 | 79 | | |
| | 14 | 80 | | return ( |
| | 14 | 81 | | <div style={{ |
| | 14 | 82 | | padding: '15px', |
| | 14 | 83 | | marginBottom: '15px', |
| | 14 | 84 | | backgroundColor: '#f8f9fa', |
| | 14 | 85 | | borderRadius: '10px', |
| | 14 | 86 | | borderLeft: `6px solid ${machine.status === 'operational' ? '#2ecc71' : machine.status === 'warning' ? '#f1c |
| | 14 | 87 | | boxShadow: '0 2px 4px rgba(0,0,0,0.05)' |
| | 14 | 88 | | }}> |
| | 14 | 89 | | <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}> |
| | 14 | 90 | | <div> |
| | 14 | 91 | | <h4 style={{ margin: 0, fontSize: '1.1rem' }}>{machine.name}</h4> |
| | 14 | 92 | | <div style={{ fontSize: '0.8rem', color: '#7f8c8d' }}>📍 {machine.location} | ID: {machine.id}</div> |
| | 14 | 93 | | </div> |
| | 14 | 94 | | <div style={{ textAlign: 'right' }}> |
| | 14 | 95 | | <span style={{ |
| | 14 | 96 | | padding: '4px 8px', |
| | 14 | 97 | | borderRadius: '4px', |
| | 14 | 98 | | backgroundColor: machine.status === 'operational' ? '#e1f9e1' : '#fff4e5', |
| | 14 | 99 | | color: machine.status === 'operational' ? '#1e7e34' : '#d39e00', |
| | 14 | 100 | | fontSize: '0.75rem', |
| | 14 | 101 | | fontWeight: 'bold', |
| | 14 | 102 | | textTransform: 'uppercase' |
| | 14 | 103 | | }}> |
| | 14 | 104 | | {machine.status} |
| | 14 | 105 | | </span> |
| | 14 | 106 | | <div style={{ marginTop: '10px' }}> |
| | 14 | 107 | | <button |
| | 14 | 108 | | onClick={toggleSensors} |
| | 14 | 109 | | style={{ fontSize: '0.75rem', marginRight: '10px', border: 'none', background: 'none', color |
| | 14 | 110 | | > |
| | 14 | 111 | | {showSensors ? 'CLOSE TELEMETRY' : 'VIEW SENSORS'} |
| | 14 | 112 | | </button> |
| | 14 | 113 | | {localStorage.getItem('user_role') === 'admin' && ( |
| | 1 | 114 | | <button |
| | 1 | 115 | | onClick={() => dispatch({ type: 'REMOVE_MACHINE', payload: machine.id })} |
| | 1 | 116 | | style={{ color: '#e74c3c', border: 'none', background: 'none', cursor: 'pointer', fontSi |
| | 1 | 117 | | > |
| | 1 | 118 | | Deactivate |
| | 1 | 119 | | </button> |
| | 14 | 120 | | )} |
| | 14 | 121 | | </div> |
| | 14 | 122 | | </div> |
| | 14 | 123 | | </div> |
| | 14 | 124 | | |
| | 14 | 125 | | {showSensors && ( |
| | 2 | 126 | | <div style={{ marginTop: '15px', padding: '10px', backgroundColor: '#fff', borderRadius: '6px', border: |
| | 2 | 127 | | <h5 style={{ margin: '0 0 10px 0', fontSize: '0.8rem', color: '#666' }}>LIVE TELEMETRY</h5> |
| | 2 | 128 | | {loading ? ( |
| | 0 | 129 | | <p style={{ fontSize: '0.8rem' }}>Loading sensors...</p> |
| | 2 | 130 | | ) : sensors.length > 0 ? ( |
| | 1 | 131 | | <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '10px' }}> |
| | 1 | 132 | | {sensors.map(s => ( |
| | 1 | 133 | | <div key={s.sensor_id} style={{ padding: '8px', border: '1px solid #f0f0f0', borderRadiu |
| | 1 | 134 | | <div style={{ fontSize: '0.7rem', color: '#95a5a6' }}>{s.type.toUpperCase()}</div> |
| | 1 | 135 | | <div style={{ fontSize: '1rem', fontWeight: 'bold' }}>{s.value} <span style={{ fontS |
| | 1 | 136 | | </div> |
| | 1 | 137 | | ))} |
| | 1 | 138 | | </div> |
| | 1 | 139 | | ) : ( |
| | 1 | 140 | | <p style={{ fontSize: '0.8rem', color: '#999' }}>No active sensors for this unit.</p> |
| | 2 | 141 | | )} |
| | 2 | 142 | | </div> |
| | 14 | 143 | | )} |
| | 14 | 144 | | </div> |
| | 14 | 145 | | ); |
| | 14 | 146 | | } |