Add files via upload

This commit is contained in:
jasonMPM
2026-03-06 00:28:33 -06:00
committed by GitHub
parent 748acc41c3
commit 3e21125a6b
3 changed files with 45 additions and 67 deletions

View File

@@ -3,12 +3,12 @@ import { format, startOfWeek, addDays, addWeeks, isToday } from 'date-fns'
import useProjectStore from '../../store/useProjectStore'
import HeatmapDayPanel from './HeatmapDayPanel'
const WEEKS = 20
const WEEKS = 20
const DAY_INIT = ['M','T','W','T','F','S','S']
const CELL = 16
const CELL_LG = 40
const GAP = 2
const GAP_LG = 4
const CELL = 16
const CELL_LG = 40
const GAP = 2
const GAP_LG = 4
const STATUS_KEYS = ['upcoming','in_progress','completed','overdue']
const STATUS_LABEL = { upcoming: 'Upcoming', in_progress: 'In Progress', completed: 'Completed', overdue: 'Overdue' }
@@ -49,7 +49,6 @@ function getDominantStatus(statusCounts) {
return { dominant, total: Object.values(statusCounts).reduce((a, b) => a + b, 0) }
}
// 4-cell density legend strip
function DensityLegend({ statusKey }) {
const c = STATUS_CELL_COLORS[statusKey]
return (
@@ -67,8 +66,8 @@ function DensityLegend({ statusKey }) {
export default function WorkloadHeatmap() {
const projects = useProjectStore(s => s.projects)
const [tooltip, setTooltip] = useState(null)
const [selectedDay, setSelectedDay] = useState(null) // Date | null
const [weekOffset, setWeekOffset] = useState(0) // shifts window in weeks
const [selectedDay, setSelectedDay] = useState(null)
const [weekOffset, setWeekOffset] = useState(0)
const { weeks, stats } = useMemo(() => {
const start = startOfWeek(addWeeks(new Date(), -10 + weekOffset), { weekStartsOn: 1 })
@@ -107,7 +106,7 @@ export default function WorkloadHeatmap() {
const windowStart = weeks[0]?.[0]?.date
const windowEnd = weeks[WEEKS - 1]?.[6]?.date
const windowLabel = windowStart && windowEnd
? `${format(windowStart, 'MMM d')} \u2013 ${format(windowEnd, 'MMM d, yyyy')}`
? `${format(windowStart, 'MMM d')} ${format(windowEnd, 'MMM d, yyyy')}`
: ''
const monthLabels = useMemo(() => {
@@ -131,20 +130,18 @@ export default function WorkloadHeatmap() {
return (
<div className="flex flex-col h-full bg-surface overflow-auto">
{/* Header */}
{/* Header + window navigation */}
<div className="flex items-center justify-between pl-24 pr-8 pt-6 pb-3">
<div>
<h2 className="text-gold font-bold text-lg tracking-wide">Workload Heatmap</h2>
<p className="text-text-muted text-xs mt-0.5">20 weeks of deliverable density by status</p>
</div>
{/* Window navigation */}
<div className="flex items-center gap-2">
<button
onClick={() => setWeekOffset(o => o - 4)}
className="text-xs px-2.5 py-1.5 rounded-lg border border-surface-border bg-surface-elevated text-text-muted hover:text-gold hover:border-gold/40 transition-all"
title="Shift window back 4 weeks"
>\u2190 4 wks</button>
>&larr; 4 wks</button>
<button
onClick={() => setWeekOffset(0)}
@@ -160,7 +157,7 @@ export default function WorkloadHeatmap() {
onClick={() => setWeekOffset(o => o + 4)}
className="text-xs px-2.5 py-1.5 rounded-lg border border-surface-border bg-surface-elevated text-text-muted hover:text-gold hover:border-gold/40 transition-all"
title="Shift window forward 4 weeks"
>4 wks \u2192</button>
>4 wks &rarr;</button>
<span className="text-[10px] text-text-muted/40 font-mono ml-1 hidden xl:block">{windowLabel}</span>
</div>
@@ -170,27 +167,21 @@ export default function WorkloadHeatmap() {
<div className="grid grid-cols-4 gap-4 px-8 pb-4">
{STATUS_KEYS.map((statusKey) => (
<div key={statusKey} className="flex flex-col gap-3">
{/* Stat card */}
<div className="bg-surface-elevated border border-surface-border rounded-xl p-4 text-center">
<p className={`text-2xl font-bold ${STATUS_COLOR[statusKey]}`}>{stats[statusKey]}</p>
<p className="text-text-muted text-xs mt-1">{STATUS_LABEL[statusKey]}</p>
</div>
{/* Mini heatmap */}
<div className="bg-surface-elevated/40 border border-surface-border rounded-xl p-3 flex-1 min-h-[160px] flex flex-col gap-2">
<div className="flex justify-end">
<DensityLegend statusKey={statusKey} />
</div>
<div className="flex items-center justify-center flex-1">
<div className="flex gap-2 overflow-x-auto pb-1 justify-center">
{/* Day initials */}
<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">
<div className="relative h-4 mb-1">
{monthLabels.map(({ wi, label }) => (
@@ -247,10 +238,9 @@ export default function WorkloadHeatmap() {
<span className={STATUS_COLOR[sk]}>{STATUS_LABEL[sk]}</span>
</div>
))}
<span className="ml-1 text-text-muted/40">\u00b7 color = highest priority</span>
<span className="ml-1 text-text-muted/40">&middot; color = highest priority</span>
</div>
</div>
<div className="flex gap-3 overflow-x-auto pb-2 justify-center">
<div className="flex flex-col flex-shrink-0 pt-6" style={{ gap: GAP }}>
{DAY_INIT.map((d, i) => (
@@ -301,7 +291,7 @@ export default function WorkloadHeatmap() {
style={{ left: Math.min(tooltip.x + 14, window.innerWidth - 330), top: Math.max(tooltip.y - 100, 8) }}
>
<p className={`text-xs font-bold mb-1.5 ${isToday(tooltip.date) ? 'text-gold' : 'text-text-primary'}`}>
{isToday(tooltip.date) ? 'Today \u2014 ' : ''}{format(tooltip.date, 'EEE, MMM d, yyyy')}
{isToday(tooltip.date) ? 'Today ' : ''}{format(tooltip.date, 'EEE, MMM d, yyyy')}
</p>
{tooltip.combined && tooltip.statusCounts ? (
<div className="space-y-1 mb-2">
@@ -314,7 +304,7 @@ export default function WorkloadHeatmap() {
</div>
) : (
<p className={`text-[10px] mb-1.5 ${tooltip.statusKey ? STATUS_COLOR[tooltip.statusKey] : 'text-text-muted/60'}`}>
{tooltip.statusKey ? STATUS_LABEL[tooltip.statusKey] : ''} \u00b7 {tooltip.items.length} task{tooltip.items.length !== 1 ? 's' : ''}
{tooltip.statusKey ? STATUS_LABEL[tooltip.statusKey] : ''} &middot; {tooltip.items.length} task{tooltip.items.length !== 1 ? 's' : ''}
</p>
)}
<div className="space-y-1.5">