import { useState, useEffect, useCallback } from 'react' import { WorkloadHeatmap } from '../components/WorkloadHeatmap' import { WorkloadUserDetail } from '../components/WorkloadUserDetail' import { workloadApi, WorkloadHeatmapResponse } from '../services/workload' // Helper to get Monday of a given week function getMonday(date: Date): Date { const d = new Date(date) const day = d.getDay() const diff = d.getDate() - day + (day === 0 ? -6 : 1) d.setDate(diff) d.setHours(0, 0, 0, 0) return d } // Format date as YYYY-MM-DD function formatDateParam(date: Date): string { return date.toISOString().split('T')[0] } // Format date for display function formatWeekDisplay(date: Date): string { return date.toLocaleDateString('zh-TW', { year: 'numeric', month: 'long', day: 'numeric', }) } export default function WorkloadPage() { const [loading, setLoading] = useState(true) const [error, setError] = useState(null) const [heatmapData, setHeatmapData] = useState(null) const [selectedWeek, setSelectedWeek] = useState(() => getMonday(new Date())) const [selectedUser, setSelectedUser] = useState<{ id: string; name: string } | null>(null) const [showUserDetail, setShowUserDetail] = useState(false) const loadHeatmap = useCallback(async () => { setLoading(true) setError(null) try { const data = await workloadApi.getHeatmap(formatDateParam(selectedWeek)) setHeatmapData(data) } catch (err) { console.error('Failed to load workload heatmap:', err) setError('Failed to load workload data. Please try again.') } finally { setLoading(false) } }, [selectedWeek]) useEffect(() => { loadHeatmap() }, [loadHeatmap]) const handlePrevWeek = () => { setSelectedWeek((prev) => { const newDate = new Date(prev) newDate.setDate(newDate.getDate() - 7) return newDate }) } const handleNextWeek = () => { setSelectedWeek((prev) => { const newDate = new Date(prev) newDate.setDate(newDate.getDate() + 7) return newDate }) } const handleToday = () => { setSelectedWeek(getMonday(new Date())) } const handleUserClick = (userId: string, userName: string) => { setSelectedUser({ id: userId, name: userName }) setShowUserDetail(true) } const handleCloseUserDetail = () => { setShowUserDetail(false) setSelectedUser(null) } const isCurrentWeek = () => { const currentMonday = getMonday(new Date()) return selectedWeek.getTime() === currentMonday.getTime() } return (

Team Workload

Monitor team capacity and task distribution

{/* Week Navigation */}
Week of {formatWeekDisplay(selectedWeek)}
{!isCurrentWeek() && ( )}
{/* Content */} {loading ? (
Loading workload data...
) : error ? (

{error}

) : heatmapData ? ( ) : null} {/* Summary Stats */} {heatmapData && heatmapData.users.length > 0 && (
{heatmapData.users.length} Team Members
{heatmapData.users.filter((u) => u.load_level === 'overloaded').length} Overloaded
{heatmapData.users.filter((u) => u.load_level === 'warning').length} At Risk
{Math.round( heatmapData.users.reduce((sum, u) => sum + u.load_percentage, 0) / heatmapData.users.length )}% Avg. Load
)} {/* User Detail Modal */} {selectedUser && ( )}
) } const styles: { [key: string]: React.CSSProperties } = { container: { padding: '24px', maxWidth: '1200px', margin: '0 auto', }, header: { marginBottom: '24px', }, title: { fontSize: '24px', fontWeight: 600, margin: 0, color: '#333', }, subtitle: { fontSize: '14px', color: '#666', margin: '4px 0 0 0', }, weekNav: { display: 'flex', alignItems: 'center', gap: '16px', marginBottom: '24px', padding: '16px 20px', backgroundColor: 'white', borderRadius: '8px', boxShadow: '0 1px 3px rgba(0, 0, 0, 0.1)', }, navButton: { padding: '8px 16px', backgroundColor: '#f5f5f5', border: '1px solid #ddd', borderRadius: '4px', cursor: 'pointer', fontSize: '14px', color: '#333', transition: 'background-color 0.2s', }, weekDisplay: { flex: 1, display: 'flex', flexDirection: 'column', alignItems: 'center', }, weekLabel: { fontSize: '12px', color: '#666', textTransform: 'uppercase', letterSpacing: '0.5px', }, weekDate: { fontSize: '16px', fontWeight: 600, color: '#333', }, todayButton: { padding: '8px 16px', backgroundColor: '#0066cc', color: 'white', border: 'none', borderRadius: '4px', cursor: 'pointer', fontSize: '14px', fontWeight: 500, }, loadingContainer: { backgroundColor: 'white', borderRadius: '8px', boxShadow: '0 1px 3px rgba(0, 0, 0, 0.1)', padding: '48px', textAlign: 'center', }, loading: { color: '#666', }, errorContainer: { backgroundColor: 'white', borderRadius: '8px', boxShadow: '0 1px 3px rgba(0, 0, 0, 0.1)', padding: '48px', textAlign: 'center', }, error: { color: '#f44336', marginBottom: '16px', }, retryButton: { padding: '10px 20px', backgroundColor: '#0066cc', color: 'white', border: 'none', borderRadius: '4px', cursor: 'pointer', fontSize: '14px', }, statsContainer: { display: 'grid', gridTemplateColumns: 'repeat(4, 1fr)', gap: '16px', marginTop: '24px', }, statCard: { backgroundColor: 'white', borderRadius: '8px', boxShadow: '0 1px 3px rgba(0, 0, 0, 0.1)', padding: '20px', display: 'flex', flexDirection: 'column', alignItems: 'center', gap: '4px', }, statValue: { fontSize: '28px', fontWeight: 600, color: '#333', }, statLabel: { fontSize: '12px', color: '#666', textTransform: 'uppercase', letterSpacing: '0.5px', }, }