Files
fabdash/frontend/src/components/FocusView/DeliverableCard.jsx

85 lines
3.0 KiB
React
Raw Normal View History

2026-03-05 15:19:38 -06:00
import { useState } from 'react'
2026-03-05 12:13:22 -06:00
import Badge from '../UI/Badge'
import { formatDate } from '../../utils/dateHelpers'
2026-03-05 15:19:38 -06:00
import ContextMenu from '../UI/ContextMenu'
import { updateDeliverable, deleteDeliverable } from '../../api/deliverables'
import useProjectStore from '../../store/useProjectStore'
import { STATUS_OPTIONS } from '../../utils/statusHelpers'
2026-03-05 12:13:22 -06:00
2026-03-05 13:17:36 -06:00
export default function DeliverableCard({ deliverable, isActive, index, projectColor, onSelect, onEdit }) {
2026-03-05 15:19:38 -06:00
const { updateDeliverable: storeUpdate, removeDeliverable } = useProjectStore()
const [ctxMenu, setCtxMenu] = useState(null)
const handleContextMenu = (e) => {
e.preventDefault()
e.stopPropagation()
setCtxMenu({
2026-03-06 00:32:38 -06:00
x: e.clientX,
y: e.clientY,
2026-03-05 15:19:38 -06:00
items: [
2026-03-06 00:38:31 -06:00
{
icon: '✎',
label: 'Edit Deliverable',
highlight: true,
action: () => onEdit(deliverable),
},
2026-03-05 15:19:38 -06:00
{ separator: true },
...STATUS_OPTIONS.map(s => ({
2026-03-06 00:38:31 -06:00
icon: s.value === deliverable.status ? '●' : '○',
2026-03-05 15:19:38 -06:00
label: `Mark ${s.label}`,
action: async () => {
storeUpdate(await updateDeliverable(deliverable.id, { status: s.value }))
},
})),
{ separator: true },
{
2026-03-06 00:38:31 -06:00
icon: '✕',
2026-03-06 00:32:38 -06:00
label: 'Delete Deliverable',
danger: true,
2026-03-05 15:19:38 -06:00
action: async () => {
if (window.confirm(`Delete "${deliverable.title}"?`)) {
await deleteDeliverable(deliverable.id)
removeDeliverable(deliverable.id)
}
},
},
],
})
}
2026-03-05 12:13:22 -06:00
return (
2026-03-05 15:19:38 -06:00
<>
<div
onClick={() => onSelect(deliverable.id)}
onDoubleClick={(e) => { e.stopPropagation(); onEdit(deliverable) }}
onContextMenu={handleContextMenu}
2026-03-06 00:32:38 -06:00
title="Click: Select · Double-click: Edit · Right-click: Menu"
className={`relative flex flex-col gap-2 rounded-xl border p-4 min-w-[190px] max-w-[230px] flex-shrink-0 cursor-pointer transition-all duration-200 select-none mt-4 ${
isActive
2026-03-05 15:19:38 -06:00
? 'border-gold bg-surface-elevated shadow-gold scale-105 ring-2 ring-gold/30'
: 'border-surface-border bg-surface hover:border-gold/40 hover:bg-surface-elevated/60'
2026-03-06 00:32:38 -06:00
}`}
2026-03-05 15:19:38 -06:00
>
{isActive && (
2026-03-06 00:32:38 -06:00
<div className="absolute -top-5 left-1/2 -translate-x-1/2 text-[9px] font-bold tracking-widest uppercase text-gold/80 whitespace-nowrap">
2026-03-05 15:19:38 -06:00
Selected
</div>
)}
2026-03-06 00:32:38 -06:00
<div className="text-[10px] text-text-muted/50 font-mono">Deliverable {index + 1}</div>
<div className="text-sm font-semibold text-text-primary leading-snug line-clamp-3">{deliverable.title}</div>
<div className="text-xs text-text-muted/70 mt-auto pt-1">{formatDate(deliverable.due_date)}</div>
2026-03-05 15:19:38 -06:00
<Badge status={deliverable.status} />
2026-03-05 12:13:22 -06:00
</div>
2026-03-05 15:19:38 -06:00
{ctxMenu && (
2026-03-06 00:38:31 -06:00
<ContextMenu
x={ctxMenu.x}
y={ctxMenu.y}
items={ctxMenu.items}
onClose={() => setCtxMenu(null)}
/>
2026-03-05 13:17:36 -06:00
)}
2026-03-05 15:19:38 -06:00
</>
2026-03-05 12:13:22 -06:00
)
}