180 lines
7.8 KiB
JavaScript
180 lines
7.8 KiB
JavaScript
// Clean CalendarView Component - Optimized for Morning Meeting Overview
|
|
const CalendarView = ({ calendarData, employees, activeOrders, onCreateAllocation, onUpdateAllocation, onDeleteAllocation }) => {
|
|
const [viewMode, setViewMode] = useState('morning');
|
|
|
|
// Generate employee colors for visual distinction
|
|
const employeeColors = employees.reduce((colors, employee, index) => {
|
|
const hue = (index * 137.5) % 360;
|
|
colors[employee.id] = `hsl(${hue}, 70%, 55%)`;
|
|
return colors;
|
|
}, {});
|
|
|
|
// Helper function to get employee name
|
|
const getEmployeeName = (employeeId) => {
|
|
const employee = employees.find(emp => emp.id === employeeId);
|
|
return employee ? `${employee.first_name} ${employee.last_name}` : `Medarbejder ${employeeId}`;
|
|
};
|
|
|
|
return (
|
|
<div className="calendar-view morning-meeting-view">
|
|
{/* View Mode Selector */}
|
|
<div className="view-selector">
|
|
<button
|
|
className={`view-btn ${viewMode === 'morning' ? 'active' : ''}`}
|
|
onClick={() => setViewMode('morning')}
|
|
>
|
|
🌅 Morgenmøde Oversigt
|
|
</button>
|
|
</div>
|
|
|
|
{/* Morning Meeting View */}
|
|
{viewMode === 'morning' && (
|
|
<div className="morning-meeting-dashboard">
|
|
{/* Header with Date and Stats */}
|
|
<div className="meeting-header">
|
|
<div className="date-section">
|
|
<h2 className="meeting-date">📅 {new Date().toLocaleDateString('da-DK')}</h2>
|
|
<p className="meeting-subtitle">Morgenmøde Oversigt - Hvem arbejder på hvad og hvor længe</p>
|
|
</div>
|
|
<div className="stats-section">
|
|
<div className="stat-card">
|
|
<div className="stat-number">{calendarData.entries?.length || 0}</div>
|
|
<div className="stat-label">Opgaver i dag</div>
|
|
</div>
|
|
<div className="stat-card">
|
|
<div className="stat-number">{employees.length}</div>
|
|
<div className="stat-label">Medarbejdere</div>
|
|
</div>
|
|
<div className="stat-card">
|
|
<div className="stat-number">{calendarData.totalHours || 0}</div>
|
|
<div className="stat-label">Timer planlagt</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Today's Schedule */}
|
|
<div className="schedule-section">
|
|
<h3 className="section-title">📋 Dagens Arbejdsplan</h3>
|
|
<div className="schedule-grid">
|
|
{calendarData.entries && calendarData.entries.length > 0 ? (
|
|
calendarData.entries.slice(0, 12).map((entry, index) => (
|
|
<div key={index} className="schedule-item today">
|
|
<div
|
|
className="employee-badge"
|
|
style={{ backgroundColor: employeeColors[entry.employee_id] || '#3b82f6' }}
|
|
>
|
|
{getEmployeeName(entry.employee_id)?.split(' ').map(n => n[0]).join('') || 'M'}
|
|
</div>
|
|
<div className="schedule-details">
|
|
<div className="schedule-time">
|
|
{new Date(entry.start_time).toLocaleTimeString('da-DK', {
|
|
hour: '2-digit',
|
|
minute: '2-digit'
|
|
})} - {new Date(entry.end_time).toLocaleTimeString('da-DK', {
|
|
hour: '2-digit',
|
|
minute: '2-digit'
|
|
})}
|
|
</div>
|
|
<div className="schedule-employee">{getEmployeeName(entry.employee_id)}</div>
|
|
<div className="schedule-description">
|
|
{entry.description || entry.title || 'Arbejdsopgave'}
|
|
</div>
|
|
<div className="schedule-duration">
|
|
{Math.round((new Date(entry.end_time) - new Date(entry.start_time)) / (1000 * 60 * 60 * 10)) / 10}h
|
|
</div>
|
|
</div>
|
|
</div>
|
|
))
|
|
) : (
|
|
<div className="no-tasks">
|
|
<span className="no-tasks-icon">📋</span>
|
|
<div>
|
|
<strong>Ingen kalenderdata tilgængelig</strong>
|
|
<br />
|
|
Kontroller at kalender sync kører korrekt
|
|
</div>
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
|
|
{/* Employee Workload Overview */}
|
|
<div className="workload-section">
|
|
<h3 className="section-title">👷 Medarbejder Arbejdsbelastning</h3>
|
|
<div className="employee-workload-grid">
|
|
{employees.map((employee) => {
|
|
const employeeTasks = calendarData.entries?.filter(entry =>
|
|
entry.employee_id === employee.id
|
|
) || [];
|
|
|
|
const totalHours = employeeTasks.reduce((sum, task) => {
|
|
const duration = (new Date(task.end_time) - new Date(task.start_time)) / (1000 * 60 * 60);
|
|
return sum + duration;
|
|
}, 0);
|
|
|
|
return (
|
|
<div key={employee.id} className={`employee-card ${employeeTasks.length > 0 ? 'active' : 'available'}`}>
|
|
<div
|
|
className="employee-avatar"
|
|
style={{ backgroundColor: employeeColors[employee.id] || '#64748b' }}
|
|
>
|
|
{employee.first_name?.charAt(0)}{employee.last_name?.charAt(0)}
|
|
</div>
|
|
<div className="employee-info">
|
|
<div className="employee-name">
|
|
{employee.first_name} {employee.last_name}
|
|
</div>
|
|
<div className="employee-tasks">
|
|
<span className="task-count today">
|
|
{employeeTasks.length} opgaver
|
|
</span>
|
|
<span className="task-hours">
|
|
{totalHours.toFixed(1)}h i dag
|
|
</span>
|
|
</div>
|
|
<div className={`employee-status ${employeeTasks.length > 0 ? 'active' : 'available'}`}>
|
|
{employeeTasks.length > 0 ? '🔥 Aktiv' : '✅ Ledig'}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
})}
|
|
</div>
|
|
</div>
|
|
|
|
{/* Calendar Statistics */}
|
|
<div className="calendar-stats-section">
|
|
<h3 className="section-title">📊 Dagens Statistik</h3>
|
|
<div className="stats-grid">
|
|
<div className="stat-item">
|
|
<label>Synkroniserede indtastninger:</label>
|
|
<span>{calendarData.entries?.length || 0}</span>
|
|
</div>
|
|
<div className="stat-item">
|
|
<label>Sidste sync:</label>
|
|
<span>{calendarData.lastSync ? new Date(calendarData.lastSync).toLocaleString('da-DK') : 'Aldrig'}</span>
|
|
</div>
|
|
<div className="stat-item">
|
|
<label>Aktive medarbejdere:</label>
|
|
<span>
|
|
{employees.filter(emp =>
|
|
calendarData.entries?.some(entry => entry.employee_id === emp.id)
|
|
).length} / {employees.length}
|
|
</span>
|
|
</div>
|
|
<div className="stat-item">
|
|
<label>Total arbejdstimer:</label>
|
|
<span>
|
|
{calendarData.entries?.reduce((sum, entry) => {
|
|
const duration = (new Date(entry.end_time) - new Date(entry.start_time)) / (1000 * 60 * 60);
|
|
return sum + duration;
|
|
}, 0).toFixed(1) || 0}h
|
|
</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)}
|
|
</div>
|
|
);
|
|
}; |