@@ -8,6 +8,137 @@ function formatHours(value, t) {
88 return `${ Number ( value || 0 ) . toFixed ( 2 ) } ${ t ( 'common.hoursLabel' ) . toLowerCase ( ) } ` ;
99}
1010
11+ function calculateStopMinutes ( item ) {
12+ return Number ( item . maintenanceMinutes || 0 ) + Number ( item . idleMinutes || 0 ) + Number ( item . otherMinutes || 0 ) ;
13+ }
14+
15+ function calculateAvailabilityPercent ( availableMinutes , maintenanceMinutes ) {
16+ const totalMinutes = Number ( availableMinutes || 0 ) ;
17+
18+ if ( ! totalMinutes ) {
19+ return 0 ;
20+ }
21+
22+ return Number ( ( ( ( totalMinutes - Number ( maintenanceMinutes || 0 ) ) / totalMinutes ) * 100 ) . toFixed ( 1 ) ) ;
23+ }
24+
25+ function calculateClosedCount ( item ) {
26+ return Math . max ( 0 , Number ( item . count || 0 ) - Number ( item . openCount || 0 ) ) ;
27+ }
28+
29+ function EquipmentKpiTable ( { summary, t } ) {
30+ const headers = {
31+ equipment : t ( 'registrations.equipment.code' ) ,
32+ plate : t ( 'registrations.equipment.plate' ) ,
33+ open : t ( 'dashboard.cards.open' ) ,
34+ totalHours : t ( 'dashboard.cards.totalHours' ) ,
35+ stop : t ( 'dashboard.cards.stop' ) ,
36+ maintenance : t ( 'dashboard.cards.maintenance' ) ,
37+ closed : t ( 'dashboard.cards.closed' ) ,
38+ availability : t ( 'dashboard.cards.physicalAvailability' ) ,
39+ utilization : t ( 'dashboard.cards.fleetIU' ) ,
40+ } ;
41+
42+ return (
43+ < section className = "card table-card dashboard-block" >
44+ < div className = "card__head" >
45+ < div >
46+ < p className = "eyebrow" > { t ( 'dashboard.sections.kpisByEquipment' ) } </ p >
47+ < h2 > { t ( 'dashboard.sections.kpisByEquipment' ) } </ h2 >
48+ </ div >
49+ </ div >
50+
51+ < div className = "table-wrap" >
52+ < table className = "records-table dashboard-kpi-table" >
53+ < thead >
54+ < tr className = "dashboard-kpi-table__total" >
55+ < th scope = "col" > { headers . equipment } </ th >
56+ < th scope = "col" > { headers . plate } </ th >
57+ < th scope = "col" > { headers . open } </ th >
58+ < th scope = "col" > { headers . totalHours } </ th >
59+ < th scope = "col" > { headers . stop } </ th >
60+ < th scope = "col" > { headers . maintenance } </ th >
61+ < th scope = "col" > { headers . closed } </ th >
62+ < th scope = "col" > { headers . availability } </ th >
63+ < th scope = "col" > { headers . utilization } </ th >
64+ </ tr >
65+ </ thead >
66+
67+ < tbody >
68+ { summary . equipmentMetrics . map ( ( item ) => {
69+ const stopMinutes = calculateStopMinutes ( item ) ;
70+ const availabilityPercent = calculateAvailabilityPercent ( item . availableMinutes || summary . periodAvailableMinutes , item . maintenanceMinutes ) ;
71+
72+ return (
73+ < tr key = { item . equipmentId } >
74+ < td data-label = { headers . equipment } >
75+ < strong > { item . code } </ strong >
76+ < small > { item . description } </ small >
77+ </ td >
78+ < td data-label = { headers . plate } >
79+ < strong > { item . plate } </ strong >
80+ </ td >
81+ < td data-label = { headers . open } >
82+ < strong > { item . openCount } </ strong >
83+ </ td >
84+ < td data-label = { headers . totalHours } >
85+ < strong > { formatHours ( ( item . totalMinutes || 0 ) / 60 , t ) } </ strong >
86+ </ td >
87+ < td data-label = { headers . stop } >
88+ < strong > { formatHours ( stopMinutes / 60 , t ) } </ strong >
89+ </ td >
90+ < td data-label = { headers . maintenance } >
91+ < strong > { formatHours ( ( item . maintenanceMinutes || 0 ) / 60 , t ) } </ strong >
92+ </ td >
93+ < td data-label = { headers . closed } >
94+ < strong > { calculateClosedCount ( item ) } </ strong >
95+ </ td >
96+ < td data-label = { headers . availability } >
97+ < strong > { availabilityPercent . toFixed ( 1 ) } %</ strong >
98+ </ td >
99+ < td data-label = { headers . utilization } >
100+ < strong > { formatUtilization ( item . utilizationPercent ) } </ strong >
101+ </ td >
102+ </ tr >
103+ ) ;
104+ } ) }
105+
106+ < tr >
107+ < td data-label = { headers . equipment } >
108+ < strong > { t ( 'dashboard.labels.total' ) } </ strong >
109+ </ td >
110+ < td data-label = { headers . plate } >
111+ < strong > -</ strong >
112+ </ td >
113+ < td data-label = { headers . open } >
114+ < strong > { summary . openCount } </ strong >
115+ </ td >
116+ < td data-label = { headers . totalHours } >
117+ < strong > { formatHours ( summary . totalHours , t ) } </ strong >
118+ </ td >
119+ < td data-label = { headers . stop } >
120+ < strong > { formatHours ( summary . stopHours , t ) } </ strong >
121+ </ td >
122+ < td data-label = { headers . maintenance } >
123+ < strong > { formatHours ( summary . maintenanceHours , t ) } </ strong >
124+ </ td >
125+ < td data-label = { headers . closed } >
126+ < strong > { summary . closedCount } </ strong >
127+ </ td >
128+ < td data-label = { headers . availability } >
129+ < strong > { formatUtilization ( summary . physicalAvailabilityPercent ) } </ strong >
130+ </ td >
131+ < td data-label = { headers . utilization } >
132+ < strong > { formatUtilization ( summary . physicalUtilizationPercent ) } </ strong >
133+ </ td >
134+ </ tr >
135+ </ tbody >
136+ </ table >
137+ </ div >
138+ </ section >
139+ ) ;
140+ }
141+
11142function BarList ( { title, items, emptyMessage, locale, t } ) {
12143 const max = Math . max ( ...items . map ( ( item ) => item . minutes || 0 ) , 0 ) ;
13144
@@ -159,6 +290,62 @@ export function DashboardCards({ summary }) {
159290
160291 return (
161292 < div className = "dashboard-layout" >
293+ < section className = "card dashboard-block" >
294+ < div className = "card__head" >
295+ < div >
296+ < p className = "eyebrow" > { t ( 'dashboard.sections.kpisOverview' ) } </ p >
297+ < h2 > { t ( 'dashboard.sections.kpisOverview' ) } </ h2 >
298+ </ div >
299+ < StatusChip tone = "info" > { t ( 'dashboard.labels.total' ) } </ StatusChip >
300+ </ div >
301+
302+ < div className = "stats-grid" >
303+ < article className = "card stat-card stat-card--success" >
304+ < p > { t ( 'dashboard.cards.open' ) } </ p >
305+ < strong > { summary . activeEquipmentCount } </ strong >
306+ < small > { t ( 'dashboard.cards.openSub' ) } </ small >
307+ </ article >
308+
309+ < article className = "card stat-card" >
310+ < p > { t ( 'dashboard.cards.totalHours' ) } </ p >
311+ < strong > { summary . totalHours . toFixed ( 2 ) } h</ strong >
312+ < small > { t ( 'dashboard.cards.totalHoursSub' , { count : summary . totalRecords } ) } </ small >
313+ </ article >
314+
315+ < article className = "card stat-card stat-card--danger" >
316+ < p > { t ( 'dashboard.cards.stop' ) } </ p >
317+ < strong > { summary . stopHours . toFixed ( 2 ) } h</ strong >
318+ < small > { t ( 'dashboard.cards.stopSub' ) } </ small >
319+ </ article >
320+
321+ < article className = "card stat-card stat-card--warning" >
322+ < p > { t ( 'dashboard.cards.maintenance' ) } </ p >
323+ < strong > { summary . maintenanceHours . toFixed ( 2 ) } h</ strong >
324+ < small > { t ( 'dashboard.cards.maintenanceSub' ) } </ small >
325+ </ article >
326+
327+ < article className = "card stat-card" >
328+ < p > { t ( 'dashboard.cards.closed' ) } </ p >
329+ < strong > { summary . closedCount } </ strong >
330+ < small > { t ( 'dashboard.cards.closedSub' ) } </ small >
331+ </ article >
332+
333+ < article className = "card stat-card" >
334+ < p > { t ( 'dashboard.cards.physicalAvailability' ) } </ p >
335+ < strong > { formatUtilization ( summary . physicalAvailabilityPercent ) } </ strong >
336+ < small > { t ( 'dashboard.cards.physicalAvailabilitySub' ) } </ small >
337+ </ article >
338+
339+ < article className = "card stat-card stat-card--info" >
340+ < p > { t ( 'dashboard.cards.fleetIU' ) } </ p >
341+ < strong > { utilizationAverage . toFixed ( 1 ) } %</ strong >
342+ < small > { t ( 'dashboard.cards.fleetIUSub' ) } </ small >
343+ </ article >
344+ </ div >
345+ </ section >
346+
347+ < EquipmentKpiTable summary = { summary } t = { t } />
348+
162349 < section className = "card dashboard-block" >
163350 < div className = "card__head" >
164351 < div >
@@ -187,44 +374,6 @@ export function DashboardCards({ summary }) {
187374 </ div >
188375 </ section >
189376
190- < section className = "stats-grid" >
191- < article className = "card stat-card stat-card--success" >
192- < p > { t ( 'dashboard.cards.open' ) } </ p >
193- < strong > { summary . activeEquipmentCount } </ strong >
194- < small > { t ( 'dashboard.cards.openSub' ) } </ small >
195- </ article >
196- < article className = "card stat-card" >
197- < p > { t ( 'dashboard.cards.totalHours' ) } </ p >
198- < strong > { summary . totalHours . toFixed ( 2 ) } h</ strong >
199- < small > { t ( 'dashboard.cards.totalHoursSub' , { count : summary . totalRecords } ) } </ small >
200- </ article >
201- < article className = "card stat-card stat-card--danger" >
202- < p > { t ( 'dashboard.cards.stop' ) } </ p >
203- < strong > { summary . stopHours . toFixed ( 2 ) } h</ strong >
204- < small > { t ( 'dashboard.cards.stopSub' ) } </ small >
205- </ article >
206- < article className = "card stat-card stat-card--warning" >
207- < p > { t ( 'dashboard.cards.maintenance' ) } </ p >
208- < strong > { summary . maintenanceHours . toFixed ( 2 ) } h</ strong >
209- < small > { t ( 'dashboard.cards.maintenanceSub' ) } </ small >
210- </ article >
211- < article className = "card stat-card" >
212- < p > { t ( 'dashboard.cards.closed' ) } </ p >
213- < strong > { summary . closedCount } </ strong >
214- < small > { t ( 'dashboard.cards.closedSub' ) } </ small >
215- </ article >
216- < article className = "card stat-card" >
217- < p > { t ( 'dashboard.cards.physicalAvailability' ) } </ p >
218- < strong > { formatUtilization ( summary . physicalAvailabilityPercent ) } </ strong >
219- < small > { t ( 'dashboard.cards.physicalAvailabilitySub' ) } </ small >
220- </ article >
221- < article className = "card stat-card stat-card--info" >
222- < p > { t ( 'dashboard.cards.fleetIU' ) } </ p >
223- < strong > { utilizationAverage . toFixed ( 1 ) } %</ strong >
224- < small > { t ( 'dashboard.cards.fleetIUSub' ) } </ small >
225- </ article >
226- </ section >
227-
228377 < section className = "card dashboard-block" >
229378 < div className = "card__head" >
230379 < div >
0 commit comments