Add files via upload

This commit is contained in:
jasonMPM
2026-03-05 17:10:30 -06:00
committed by GitHub
parent 1d4d3c66f9
commit c5cb3ebe4a
2 changed files with 89 additions and 16 deletions

View File

@@ -76,8 +76,8 @@ export default function WorkloadHeatmap() {
return labels return labels
}, [weeks]) }, [weeks])
const CELL = 18 const CELL = 16
const GAP = 3 const GAP = 2
return ( return (
<div className="flex flex-col h-full bg-surface overflow-auto"> <div className="flex flex-col h-full bg-surface overflow-auto">
@@ -92,22 +92,95 @@ export default function WorkloadHeatmap() {
</div> </div>
</div> </div>
{/* Stat cards by status */} {/* Stat cards + aligned status heatmaps */}
<div className="grid grid-cols-5 gap-3 px-8 mb-4"> <div className="grid grid-cols-4 gap-4 px-8 pb-8">
{[ {STATUS_KEYS.map((statusKey) => (
{ key: 'total', label: 'Total', color: 'text-text-primary' }, <div key={statusKey} className="flex flex-col gap-3">
{ key: 'upcoming', label: 'Upcoming', color: 'text-blue-400' }, {/* Stat card */}
{ key: 'in_progress', label: 'In Progress', color: 'text-amber-400' }, <div className="bg-surface-elevated border border-surface-border rounded-xl p-4 text-center">
{ key: 'completed', label: 'Completed', color: 'text-green-400' }, <p className={`text-2xl font-bold ${STATUS_COLOR[statusKey]}`}>
{ key: 'overdue', label: 'Overdue', color: 'text-red-400' }, {stats[statusKey]}
].map(({ key, label, color }) => ( </p>
<div key={key} className="bg-surface-elevated border border-surface-border rounded-xl p-4 text-center"> <p className="text-text-muted text-xs mt-1">{STATUS_LABEL[statusKey]}</p>
<p className={`text-2xl font-bold ${color}`}>{stats[key]}</p> </div>
<p className="text-text-muted text-xs mt-1">{label}</p>
{/* Heatmap for this status */}
<div className="bg-surface-elevated/40 border border-surface-border rounded-xl p-3 flex-1 min-h-[160px]">
<div className="flex gap-2 overflow-x-auto pb-1">
{/* Day labels */}
<div className="flex flex-col flex-shrink-0 pt-6" style={{ gap: GAP }}>
{DAY_INIT.map((d, i) => (
<div key={i} style={{ height: CELL }} className="flex items-center text-[9px] text-text-muted/50 font-mono w-3">
{d}
</div>
))}
</div>
{/* Grid */}
<div className="flex flex-col flex-shrink-0">
{/* Month labels */}
<div className="relative h-4 mb-1">
{monthLabels.map(({ wi, label }) => (
<span
key={label+wi}
className="absolute text-[9px] text-text-muted/60 font-medium"
style={{ left: wi * (CELL + GAP) }}
>
{label}
</span>
))}
</div>
<div className="flex" style={{ gap: GAP }}>
{weeks.map((week, wi) => (
<div key={wi} className="flex flex-col flex-shrink-0" style={{ gap: GAP }}>
{week.map(({ date, key, items, statusCounts }) => {
const countForStatus = (statusCounts || {})[statusKey] || 0
const baseDensity = countForStatus
const hoverRing =
statusKey === 'upcoming' ? 'hover:ring-blue-300/80 hover:shadow-[0_0_0_1px_rgba(147,197,253,0.8)]' :
statusKey === 'in_progress' ? 'hover:ring-amber-300/80 hover:shadow-[0_0_0_1px_rgba(252,211,77,0.8)]' :
statusKey === 'completed' ? 'hover:ring-green-300/80 hover:shadow-[0_0_0_1px_rgba(74,222,128,0.8)]' :
statusKey === 'overdue' ? 'hover:ring-red-400/90 hover:shadow-[0_0_0_1px_rgba(248,113,113,0.9)]' :
'hover:ring-white/60';
return (
<div
key={key + statusKey}
style={{ width: CELL, height: CELL }}
className={`rounded-sm border cursor-pointer transition-transform duration-100 hover:scale-125 hover:z-10 relative
${getCellClass(baseDensity, statusCounts || {})}
${isToday(date) ? 'ring-1 ring-white/60' : ''}
${hoverRing}
`}
onClick={() => {
if (!items || !items.length) return
const match = items.find(({ deliverable }) => deliverable.status === statusKey) || items[0]
if (match) openFocus(match.project.id, match.deliverable.id)
}}
onMouseEnter={(e) => {
const filtered = (items || []).filter(({ deliverable }) => deliverable.status === statusKey)
const showItems = filtered.length ? filtered : items || []
setTooltip({
x: e.clientX,
y: e.clientY,
date,
statusKey,
items: showItems,
})
}}
onMouseLeave={() => setTooltip(null)}
/>
)
})}
</div>
))}
</div>
</div>
</div>
</div>
</div> </div>
))} ))}
</div> </div>
{/* Multi-row heatmaps by status */} {/* Multi-row heatmaps by status */}
<div className="flex flex-col gap-6 px-8 pb-8"> <div className="flex flex-col gap-6 px-8 pb-8">
{STATUS_KEYS.map((statusKey) => ( {STATUS_KEYS.map((statusKey) => (

View File

@@ -19,7 +19,7 @@ body {
.animate-slide-up { animation: slide-up 0.2s ease-out forwards; } .animate-slide-up { animation: slide-up 0.2s ease-out forwards; }
/* ── FullCalendar dark theme ── */ /* ── FullCalendar dark theme ── */
.fc-toolbar.fc-header-toolbar { padding-left: 4.5rem !important; } .fc-toolbar.fc-header-toolbar { padding-left: 6rem !important; }
.fc { .fc {
--fc-border-color: #2E2E2E; --fc-border-color: #2E2E2E;