313 lines
11 KiB
JavaScript
313 lines
11 KiB
JavaScript
import { FaClock, FaUsers, FaChartLine } from 'react-icons/fa'
|
|
|
|
export default function WorksheetStats({ worksheets }) {
|
|
if (!worksheets || worksheets.length === 0) {
|
|
return null
|
|
}
|
|
|
|
// Gesamtarbeitszeit
|
|
const totalMinutes = worksheets
|
|
.filter(ws => !ws.isComment)
|
|
.reduce((sum, ws) => sum + (ws.totalTime || 0), 0)
|
|
|
|
// Nach Mitarbeiter gruppieren
|
|
const byEmployee = worksheets.reduce((acc, ws) => {
|
|
const empId = ws.employeeId
|
|
if (!acc[empId]) {
|
|
acc[empId] = {
|
|
name: ws.employeeName,
|
|
short: ws.employeeShort,
|
|
time: 0,
|
|
count: 0
|
|
}
|
|
}
|
|
if (!ws.isComment) {
|
|
acc[empId].time += ws.totalTime || 0
|
|
}
|
|
acc[empId].count += 1
|
|
return acc
|
|
}, {})
|
|
|
|
|
|
// Service Type Verteilung
|
|
const byServiceType = worksheets.reduce((acc, ws) => {
|
|
const type = ws.serviceType || 'Unknown'
|
|
acc[type] = (acc[type] || 0) + 1
|
|
return acc
|
|
}, {})
|
|
|
|
const formatTime = (minutes) => {
|
|
if (!minutes || minutes === 0) return '0min'
|
|
const hours = Math.floor(minutes / 60)
|
|
const mins = minutes % 60
|
|
return hours > 0 ? `${hours}h ${mins}min` : `${mins}min`
|
|
}
|
|
|
|
return (
|
|
<div className="worksheet-stats" style={{
|
|
display: 'flex',
|
|
flexDirection: 'column',
|
|
gap: '16px',
|
|
height: '100%'
|
|
}}>
|
|
{/* Gesamtübersicht */}
|
|
<div style={{
|
|
background: 'rgba(45, 55, 72, 0.5)',
|
|
borderRadius: '12px',
|
|
padding: '16px',
|
|
border: '1px solid rgba(16, 185, 129, 0.2)',
|
|
flex: '0 0 auto'
|
|
}}>
|
|
<h6 style={{
|
|
color: 'var(--dark-text)',
|
|
marginBottom: '12px',
|
|
fontSize: '14px',
|
|
fontWeight: 'bold',
|
|
display: 'flex',
|
|
alignItems: 'center',
|
|
gap: '8px'
|
|
}}>
|
|
<FaChartLine size={16} style={{ color: '#10b981' }} />
|
|
Gesamtübersicht
|
|
</h6>
|
|
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '12px' }}>
|
|
<div style={{
|
|
display: 'flex',
|
|
justifyContent: 'space-between',
|
|
alignItems: 'center',
|
|
padding: '8px',
|
|
background: 'rgba(26, 32, 44, 0.4)',
|
|
borderRadius: '6px'
|
|
}}>
|
|
<span style={{ color: 'rgba(226, 232, 240, 0.8)', fontSize: '12px' }}>Worksheets:</span>
|
|
<strong style={{ color: 'var(--dark-text)', fontSize: '16px' }}>{worksheets.length}</strong>
|
|
</div>
|
|
<div style={{
|
|
display: 'flex',
|
|
justifyContent: 'space-between',
|
|
alignItems: 'center',
|
|
padding: '8px',
|
|
background: 'rgba(26, 32, 44, 0.4)',
|
|
borderRadius: '6px'
|
|
}}>
|
|
<span style={{ color: 'rgba(226, 232, 240, 0.8)', fontSize: '12px' }}>Arbeitszeit:</span>
|
|
<strong style={{ color: '#10b981', fontSize: '16px' }}>{formatTime(totalMinutes)}</strong>
|
|
</div>
|
|
<div style={{
|
|
display: 'flex',
|
|
justifyContent: 'space-between',
|
|
alignItems: 'center',
|
|
padding: '8px',
|
|
background: 'rgba(26, 32, 44, 0.4)',
|
|
borderRadius: '6px'
|
|
}}>
|
|
<span style={{ color: 'rgba(226, 232, 240, 0.8)', fontSize: '12px' }}>Kommentare:</span>
|
|
<strong style={{ color: 'var(--dark-text)', fontSize: '16px' }}>{worksheets.filter(ws => ws.isComment).length}</strong>
|
|
</div>
|
|
<div style={{
|
|
display: 'flex',
|
|
justifyContent: 'space-between',
|
|
alignItems: 'center',
|
|
padding: '8px',
|
|
background: 'rgba(26, 32, 44, 0.4)',
|
|
borderRadius: '6px'
|
|
}}>
|
|
<span style={{ color: 'rgba(226, 232, 240, 0.8)', fontSize: '12px' }}>Ø pro WS:</span>
|
|
<strong style={{ color: 'var(--dark-text)', fontSize: '16px' }}>
|
|
{formatTime(Math.round(totalMinutes / (worksheets.filter(ws => !ws.isComment).length || 1)))}
|
|
</strong>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Mitarbeiter Kombiniertes Diagramm */}
|
|
<div style={{
|
|
background: 'rgba(45, 55, 72, 0.5)',
|
|
borderRadius: '12px',
|
|
padding: '16px',
|
|
border: '1px solid rgba(16, 185, 129, 0.2)',
|
|
flex: '1 1 auto',
|
|
minHeight: '200px'
|
|
}}>
|
|
<h6 style={{
|
|
color: 'var(--dark-text)',
|
|
marginBottom: '16px',
|
|
fontSize: '14px',
|
|
fontWeight: 'bold',
|
|
display: 'flex',
|
|
alignItems: 'center',
|
|
gap: '8px'
|
|
}}>
|
|
<FaUsers size={16} style={{ color: '#10b981' }} />
|
|
Mitarbeiter-Statistiken
|
|
</h6>
|
|
|
|
{Object.keys(byEmployee).length === 0 ? (
|
|
<div style={{ color: '#a0aec0', textAlign: 'center', padding: '20px' }}>
|
|
Keine Mitarbeiter-Daten verfügbar
|
|
</div>
|
|
) : (
|
|
<div style={{ display: 'flex', flexDirection: 'column', gap: '12px' }}>
|
|
{(() => {
|
|
const employeeArray = Object.values(byEmployee).sort((a, b) => b.time - a.time)
|
|
const maxTime = Math.max(...employeeArray.map(e => e.time), 1)
|
|
|
|
return employeeArray.map((emp, idx) => {
|
|
const percentage = maxTime > 0 ? (emp.time / maxTime) * 100 : 0
|
|
return (
|
|
<div key={idx}>
|
|
<div style={{
|
|
display: 'flex',
|
|
alignItems: 'center',
|
|
marginBottom: '6px',
|
|
fontSize: '11px'
|
|
}}>
|
|
<span style={{
|
|
color: 'var(--dark-text)',
|
|
fontWeight: '500',
|
|
display: 'flex',
|
|
alignItems: 'center',
|
|
gap: '6px',
|
|
minWidth: '120px'
|
|
}}>
|
|
{emp.short && (
|
|
<span style={{
|
|
background: 'rgba(16, 185, 129, 0.2)',
|
|
color: '#10b981',
|
|
padding: '2px 6px',
|
|
borderRadius: '3px',
|
|
fontSize: '9px',
|
|
fontWeight: 'bold'
|
|
}}>{emp.short}</span>
|
|
)}
|
|
<span style={{ maxWidth: '100px', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>
|
|
{emp.name}
|
|
</span>
|
|
</span>
|
|
</div>
|
|
<div style={{
|
|
width: '100%',
|
|
height: '28px',
|
|
background: 'rgba(26, 32, 44, 0.6)',
|
|
borderRadius: '6px',
|
|
overflow: 'hidden',
|
|
position: 'relative',
|
|
display: 'flex',
|
|
alignItems: 'center'
|
|
}}>
|
|
{/* WS Anzahl am Anfang */}
|
|
<div style={{
|
|
position: 'absolute',
|
|
left: '8px',
|
|
zIndex: 2,
|
|
color: 'white',
|
|
fontSize: '11px',
|
|
fontWeight: 'bold',
|
|
display: 'flex',
|
|
alignItems: 'center',
|
|
gap: '4px'
|
|
}}>
|
|
<span>WS</span>
|
|
<span style={{
|
|
background: 'rgba(255, 255, 255, 0.3)',
|
|
padding: '2px 6px',
|
|
borderRadius: '4px'
|
|
}}>{emp.count}</span>
|
|
</div>
|
|
|
|
{/* Balken mit Zeit */}
|
|
<div style={{
|
|
width: `${percentage}%`,
|
|
height: '100%',
|
|
background: 'linear-gradient(90deg, #10b981 0%, #059669 100%)',
|
|
borderRadius: '6px',
|
|
transition: 'width 0.5s ease',
|
|
display: 'flex',
|
|
alignItems: 'center',
|
|
justifyContent: 'flex-end',
|
|
paddingRight: '8px',
|
|
paddingLeft: '60px',
|
|
boxShadow: '0 2px 8px rgba(16, 185, 129, 0.3)',
|
|
position: 'relative'
|
|
}}>
|
|
{/* Zeit am Ende des Balkens */}
|
|
<span style={{
|
|
color: 'white',
|
|
fontSize: '11px',
|
|
fontWeight: 'bold',
|
|
whiteSpace: 'nowrap'
|
|
}}>
|
|
{formatTime(emp.time)}
|
|
</span>
|
|
</div>
|
|
|
|
{/* Zeit außerhalb des Balkens (falls Balken zu kurz) */}
|
|
{percentage < 30 && (
|
|
<div style={{
|
|
position: 'absolute',
|
|
right: '8px',
|
|
zIndex: 2,
|
|
color: '#10b981',
|
|
fontSize: '11px',
|
|
fontWeight: 'bold'
|
|
}}>
|
|
{formatTime(emp.time)}
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
)
|
|
})
|
|
})()}
|
|
</div>
|
|
)}
|
|
</div>
|
|
|
|
{/* Service Type Verteilung */}
|
|
<div style={{
|
|
background: 'rgba(45, 55, 72, 0.5)',
|
|
borderRadius: '12px',
|
|
padding: '16px',
|
|
border: '1px solid rgba(16, 185, 129, 0.2)',
|
|
flex: '0 0 auto'
|
|
}}>
|
|
<h6 style={{
|
|
color: 'var(--dark-text)',
|
|
marginBottom: '12px',
|
|
fontSize: '14px',
|
|
fontWeight: 'bold',
|
|
display: 'flex',
|
|
alignItems: 'center',
|
|
gap: '8px'
|
|
}}>
|
|
<FaClock size={16} style={{ color: '#10b981' }} />
|
|
Service Types
|
|
</h6>
|
|
<div style={{ display: 'flex', flexDirection: 'column', gap: '8px' }}>
|
|
{Object.entries(byServiceType).map(([type, count]) => (
|
|
<div key={type} style={{
|
|
display: 'flex',
|
|
justifyContent: 'space-between',
|
|
alignItems: 'center',
|
|
padding: '8px',
|
|
background: 'rgba(26, 32, 44, 0.4)',
|
|
borderRadius: '6px'
|
|
}}>
|
|
<span style={{
|
|
color: 'var(--dark-text)',
|
|
fontSize: '12px',
|
|
fontWeight: '500'
|
|
}}>{type}</span>
|
|
<strong style={{
|
|
color: '#10b981',
|
|
fontSize: '14px'
|
|
}}>{count}</strong>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|
|
|