Add files via upload
This commit is contained in:
34
frontend/src/components/FocusView/DeliverableCard.jsx
Normal file
34
frontend/src/components/FocusView/DeliverableCard.jsx
Normal file
@@ -0,0 +1,34 @@
|
||||
import Badge from '../UI/Badge'
|
||||
import { formatDate } from '../../utils/dateHelpers'
|
||||
|
||||
export default function DeliverableCard({ deliverable, isActive, index, projectColor, onEdit }) {
|
||||
return (
|
||||
<div
|
||||
onClick={() => isActive && onEdit && onEdit(deliverable)}
|
||||
className={`relative flex flex-col gap-2 rounded-xl border p-4 min-w-[190px] max-w-[230px] flex-shrink-0 transition-all duration-300 select-none
|
||||
${isActive
|
||||
? 'border-gold bg-surface-elevated shadow-gold scale-105 ring-2 ring-gold/30 cursor-pointer'
|
||||
: 'border-surface-border bg-surface cursor-default'
|
||||
}`}
|
||||
>
|
||||
{isActive && (
|
||||
<div className="absolute -top-3 left-1/2 -translate-x-1/2 bg-gold text-surface text-[9px] font-black px-2.5 py-0.5 rounded-full tracking-widest uppercase">
|
||||
Selected
|
||||
</div>
|
||||
)}
|
||||
<div className="flex items-center gap-1.5">
|
||||
<div className="w-2 h-2 rounded-full flex-shrink-0" style={{ backgroundColor: projectColor }} />
|
||||
<span className={`text-[10px] font-semibold uppercase tracking-widest ${isActive ? 'text-gold' : 'text-text-muted/60'}`}>
|
||||
Deliverable {index + 1}
|
||||
</span>
|
||||
</div>
|
||||
<p className={`text-sm font-semibold leading-snug ${isActive ? 'text-text-primary' : 'text-text-muted'}`}>
|
||||
{deliverable.title}
|
||||
</p>
|
||||
<p className={`text-xs font-mono ${isActive ? 'text-gold' : 'text-text-muted/50'}`}>
|
||||
{formatDate(deliverable.due_date)}
|
||||
</p>
|
||||
<Badge status={deliverable.status} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
37
frontend/src/components/FocusView/FocusDrawer.jsx
Normal file
37
frontend/src/components/FocusView/FocusDrawer.jsx
Normal file
@@ -0,0 +1,37 @@
|
||||
import { useState } from 'react'
|
||||
import Drawer from '../UI/Drawer'
|
||||
import FocusTimeline from './FocusTimeline'
|
||||
import DeliverableModal from '../Deliverables/DeliverableModal'
|
||||
import useFocusStore from '../../store/useFocusStore'
|
||||
import useProjectStore from '../../store/useProjectStore'
|
||||
|
||||
export default function FocusDrawer() {
|
||||
const { isOpen, projectId, activeDeliverableId, closeFocus } = useFocusStore()
|
||||
const getProjectById = useProjectStore(s => s.getProjectById)
|
||||
const [editDel, setEditDel] = useState(null)
|
||||
const [showModal, setShowModal] = useState(false)
|
||||
|
||||
const project = projectId ? getProjectById(projectId) : null
|
||||
|
||||
const handleEdit = (d) => { setEditDel(d); setShowModal(true) }
|
||||
|
||||
return (
|
||||
<>
|
||||
<Drawer isOpen={isOpen} onClose={closeFocus}>
|
||||
{project && (
|
||||
<FocusTimeline
|
||||
project={project}
|
||||
activeDeliverableId={activeDeliverableId}
|
||||
onEditDeliverable={handleEdit}
|
||||
/>
|
||||
)}
|
||||
</Drawer>
|
||||
<DeliverableModal
|
||||
isOpen={showModal}
|
||||
onClose={() => { setShowModal(false); setEditDel(null) }}
|
||||
deliverable={editDel}
|
||||
projectId={projectId}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
}
|
||||
39
frontend/src/components/FocusView/FocusTimeline.jsx
Normal file
39
frontend/src/components/FocusView/FocusTimeline.jsx
Normal file
@@ -0,0 +1,39 @@
|
||||
import DeliverableCard from './DeliverableCard'
|
||||
|
||||
export default function FocusTimeline({ project, activeDeliverableId, onEditDeliverable }) {
|
||||
const sorted = [...(project.deliverables || [])].sort((a, b) => new Date(a.due_date) - new Date(b.due_date))
|
||||
return (
|
||||
<div className="px-6 pb-6 pt-5">
|
||||
<div className="flex items-center gap-2 mb-5">
|
||||
<div className="w-3 h-3 rounded-full flex-shrink-0" style={{ backgroundColor: project.color }} />
|
||||
<h3 className="text-gold font-bold text-base tracking-wide">{project.name}</h3>
|
||||
{project.description && (
|
||||
<span className="text-text-muted text-xs">— {project.description}</span>
|
||||
)}
|
||||
<span className="ml-auto text-text-muted/50 text-xs">{sorted.length} deliverable{sorted.length !== 1 ? 's' : ''}</span>
|
||||
</div>
|
||||
<div className="flex items-center overflow-x-auto pb-3 gap-0">
|
||||
{sorted.map((d, i) => (
|
||||
<div key={d.id} className="flex items-center flex-shrink-0">
|
||||
<DeliverableCard
|
||||
deliverable={d}
|
||||
isActive={d.id === activeDeliverableId}
|
||||
index={i}
|
||||
projectColor={project.color}
|
||||
onEdit={onEditDeliverable}
|
||||
/>
|
||||
{i < sorted.length - 1 && (
|
||||
<div className="flex items-center flex-shrink-0 px-1">
|
||||
<div className="h-px w-6 bg-surface-border" />
|
||||
<span className="text-surface-border text-xs">▶</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
{sorted.length === 0 && (
|
||||
<p className="text-text-muted text-sm italic">No deliverables yet.</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user