Merge pull request #28 from jasonMPM/fix-heatmap-again
Add files via upload
This commit is contained in:
@@ -20,12 +20,43 @@ const STATUS_COLOR = {
|
|||||||
overdue: 'text-red-400',
|
overdue: 'text-red-400',
|
||||||
}
|
}
|
||||||
|
|
||||||
function getCellClass(baseDensity, statusCounts) {
|
// Cell colors per status, three density levels: low / mid / high
|
||||||
const total = Object.values(statusCounts).reduce((a, b) => a + b, 0)
|
const STATUS_CELL_COLORS = {
|
||||||
if (total === 0) return 'bg-surface border-surface-border'
|
upcoming: [
|
||||||
if (baseDensity === 1) return 'bg-gold/25 border-gold/40'
|
'bg-blue-400/20 border-blue-400/30',
|
||||||
if (baseDensity === 2) return 'bg-gold/55 border-gold/70'
|
'bg-blue-400/55 border-blue-400/70',
|
||||||
return 'bg-gold border-gold shadow-gold'
|
'bg-blue-400 border-blue-400 shadow-[0_0_4px_rgba(96,165,250,0.6)]',
|
||||||
|
],
|
||||||
|
in_progress: [
|
||||||
|
'bg-amber-400/20 border-amber-400/30',
|
||||||
|
'bg-amber-400/55 border-amber-400/70',
|
||||||
|
'bg-amber-400 border-amber-400 shadow-[0_0_4px_rgba(251,191,36,0.6)]',
|
||||||
|
],
|
||||||
|
completed: [
|
||||||
|
'bg-green-400/20 border-green-400/30',
|
||||||
|
'bg-green-400/55 border-green-400/70',
|
||||||
|
'bg-green-400 border-green-400 shadow-[0_0_4px_rgba(74,222,128,0.6)]',
|
||||||
|
],
|
||||||
|
overdue: [
|
||||||
|
'bg-red-400/20 border-red-400/30',
|
||||||
|
'bg-red-400/55 border-red-400/70',
|
||||||
|
'bg-red-400 border-red-400 shadow-[0_0_4px_rgba(248,113,113,0.6)]',
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
const STATUS_HOVER_RING = {
|
||||||
|
upcoming: 'hover:ring-1 hover:ring-blue-300/80',
|
||||||
|
in_progress: 'hover:ring-1 hover:ring-amber-300/80',
|
||||||
|
completed: 'hover:ring-1 hover:ring-green-300/80',
|
||||||
|
overdue: 'hover:ring-1 hover:ring-red-400/90',
|
||||||
|
}
|
||||||
|
|
||||||
|
function getCellClass(count, statusKey) {
|
||||||
|
if (count === 0) return 'bg-surface border-surface-border'
|
||||||
|
const colors = STATUS_CELL_COLORS[statusKey] || STATUS_CELL_COLORS.upcoming
|
||||||
|
if (count === 1) return colors[0]
|
||||||
|
if (count === 2) return colors[1]
|
||||||
|
return colors[2]
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function WorkloadHeatmap() {
|
export default function WorkloadHeatmap() {
|
||||||
@@ -104,7 +135,7 @@ export default function WorkloadHeatmap() {
|
|||||||
<p className="text-text-muted text-xs mt-1">{STATUS_LABEL[statusKey]}</p>
|
<p className="text-text-muted text-xs mt-1">{STATUS_LABEL[statusKey]}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Heatmap for this status */}
|
{/* Heatmap filtered to this status */}
|
||||||
<div className="bg-surface-elevated/40 border border-surface-border rounded-xl p-3 flex-1 min-h-[160px] flex items-center justify-center">
|
<div className="bg-surface-elevated/40 border border-surface-border rounded-xl p-3 flex-1 min-h-[160px] flex items-center justify-center">
|
||||||
<div className="flex gap-2 overflow-x-auto pb-1 justify-center">
|
<div className="flex gap-2 overflow-x-auto pb-1 justify-center">
|
||||||
{/* Day labels */}
|
{/* Day labels */}
|
||||||
@@ -135,22 +166,15 @@ export default function WorkloadHeatmap() {
|
|||||||
{weeks.map((week, wi) => (
|
{weeks.map((week, wi) => (
|
||||||
<div key={wi} className="flex flex-col flex-shrink-0" style={{ gap: GAP }}>
|
<div key={wi} className="flex flex-col flex-shrink-0" style={{ gap: GAP }}>
|
||||||
{week.map(({ date, key, items, statusCounts }) => {
|
{week.map(({ date, key, items, statusCounts }) => {
|
||||||
const countForStatus = (statusCounts || {})[statusKey] || 0
|
const count = (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 (
|
return (
|
||||||
<div
|
<div
|
||||||
key={key + statusKey}
|
key={key + statusKey}
|
||||||
style={{ width: CELL, height: CELL }}
|
style={{ width: CELL, height: CELL }}
|
||||||
className={`rounded-sm border cursor-pointer transition-transform duration-100 hover:scale-125 hover:z-10 relative
|
className={`rounded-sm border cursor-pointer transition-transform duration-100 hover:scale-125 hover:z-10 relative
|
||||||
${getCellClass(baseDensity, statusCounts || {})}
|
${getCellClass(count, statusKey)}
|
||||||
${isToday(date) ? 'ring-1 ring-white/60' : ''}
|
${isToday(date) ? 'ring-1 ring-white/60' : ''}
|
||||||
${hoverRing}
|
${count > 0 ? STATUS_HOVER_RING[statusKey] : ''}
|
||||||
`}
|
`}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (!items || !items.length) return
|
if (!items || !items.length) return
|
||||||
@@ -159,13 +183,13 @@ export default function WorkloadHeatmap() {
|
|||||||
}}
|
}}
|
||||||
onMouseEnter={(e) => {
|
onMouseEnter={(e) => {
|
||||||
const filtered = (items || []).filter(({ deliverable }) => deliverable.status === statusKey)
|
const filtered = (items || []).filter(({ deliverable }) => deliverable.status === statusKey)
|
||||||
const showItems = filtered.length ? filtered : items || []
|
if (!filtered.length) return
|
||||||
setTooltip({
|
setTooltip({
|
||||||
x: e.clientX,
|
x: e.clientX,
|
||||||
y: e.clientY,
|
y: e.clientY,
|
||||||
date,
|
date,
|
||||||
statusKey,
|
statusKey,
|
||||||
items: showItems,
|
items: filtered,
|
||||||
})
|
})
|
||||||
}}
|
}}
|
||||||
onMouseLeave={() => setTooltip(null)}
|
onMouseLeave={() => setTooltip(null)}
|
||||||
@@ -191,7 +215,7 @@ export default function WorkloadHeatmap() {
|
|||||||
<p className={`text-xs font-bold mb-1.5 ${isToday(tooltip.date) ? 'text-gold' : 'text-text-primary'}`}>
|
<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 \u2014 ' : ''}{format(tooltip.date, 'EEE, MMM d, yyyy')}
|
||||||
</p>
|
</p>
|
||||||
<p className="text-[10px] text-text-muted/60 mb-1.5">
|
<p className={`text-[10px] mb-1.5 ${STATUS_COLOR[tooltip.statusKey]}`}>
|
||||||
{STATUS_LABEL[tooltip.statusKey]} \u00b7 {tooltip.items.length} task{tooltip.items.length !== 1 ? 's' : ''}
|
{STATUS_LABEL[tooltip.statusKey]} \u00b7 {tooltip.items.length} task{tooltip.items.length !== 1 ? 's' : ''}
|
||||||
</p>
|
</p>
|
||||||
{tooltip.items.length === 0 ? (
|
{tooltip.items.length === 0 ? (
|
||||||
|
|||||||
Reference in New Issue
Block a user