Add files via upload
This commit is contained in:
@@ -5,14 +5,17 @@ import timeGridPlugin from '@fullcalendar/timegrid'
|
||||
import interactionPlugin from '@fullcalendar/interaction'
|
||||
import useProjectStore from '../../store/useProjectStore'
|
||||
import useFocusStore from '../../store/useFocusStore'
|
||||
import { updateDeliverable } from '../../api/deliverables'
|
||||
import { updateDeliverable, deleteDeliverable } from '../../api/deliverables'
|
||||
import DeliverableModal from '../Deliverables/DeliverableModal'
|
||||
import ContextMenu from '../UI/ContextMenu'
|
||||
|
||||
export default function MainCalendar() {
|
||||
const calRef = useRef(null)
|
||||
const { projects, updateDeliverable: storeUpdate } = useProjectStore()
|
||||
const { projects, updateDeliverable: storeUpdate, removeDeliverable } = useProjectStore()
|
||||
const openFocus = useFocusStore(s => s.openFocus)
|
||||
const [modal, setModal] = useState({ open: false, deliverable: null, defaultDate: '' })
|
||||
|
||||
const [modal, setModal] = useState({ open: false, deliverable: null, defaultDate: '' })
|
||||
const [contextMenu, setContextMenu] = useState(null)
|
||||
|
||||
const events = projects.flatMap(p =>
|
||||
(p.deliverables || []).map(d => ({
|
||||
@@ -26,22 +29,78 @@ export default function MainCalendar() {
|
||||
}))
|
||||
)
|
||||
|
||||
const handleEventDrop = useCallback(async ({ event }) => {
|
||||
const { deliverableId } = event.extendedProps
|
||||
storeUpdate(await updateDeliverable(deliverableId, { due_date: event.startStr.substring(0,10) }))
|
||||
}, [storeUpdate])
|
||||
const getDeliverable = (projectId, deliverableId) => {
|
||||
const p = projects.find(p => p.id === projectId)
|
||||
return { project: p, deliverable: p?.deliverables.find(d => d.id === deliverableId) }
|
||||
}
|
||||
|
||||
// Single click → Focus View
|
||||
const handleEventClick = useCallback(({ event }) => {
|
||||
const { deliverableId, projectId } = event.extendedProps
|
||||
openFocus(projectId, deliverableId)
|
||||
}, [openFocus])
|
||||
|
||||
const handleDateClick = useCallback(({ dateStr }) => {
|
||||
setModal({ open: true, deliverable: null, defaultDate: dateStr.substring(0,10) })
|
||||
// Drag-and-drop → patch date
|
||||
const handleEventDrop = useCallback(async ({ event }) => {
|
||||
const { deliverableId } = event.extendedProps
|
||||
storeUpdate(await updateDeliverable(deliverableId, { due_date: event.startStr.substring(0, 10) }))
|
||||
}, [storeUpdate])
|
||||
|
||||
// Click empty date → add deliverable
|
||||
const handleDateClick = useCallback(({ dateStr }) => {
|
||||
setModal({ open: true, deliverable: null, defaultDate: dateStr.substring(0, 10) })
|
||||
}, [])
|
||||
|
||||
// Attach dblclick + contextmenu to each event element after mount
|
||||
const handleEventDidMount = useCallback(({ event, el }) => {
|
||||
const { deliverableId, projectId } = event.extendedProps
|
||||
|
||||
// Double-click → open edit modal directly
|
||||
el.addEventListener('dblclick', (e) => {
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
const { deliverable } = getDeliverable(projectId, deliverableId)
|
||||
if (deliverable) setModal({ open: true, deliverable, defaultDate: '' })
|
||||
})
|
||||
|
||||
// Right-click → context menu
|
||||
el.addEventListener('contextmenu', (e) => {
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
const { project, deliverable } = getDeliverable(projectId, deliverableId)
|
||||
if (!deliverable) return
|
||||
setContextMenu({
|
||||
x: e.clientX, y: e.clientY,
|
||||
items: [
|
||||
{
|
||||
icon: '✎', label: 'Edit Deliverable',
|
||||
action: () => setModal({ open: true, deliverable, defaultDate: '' }),
|
||||
},
|
||||
{
|
||||
icon: '◎', label: 'Open Focus View',
|
||||
action: () => openFocus(projectId, deliverableId),
|
||||
},
|
||||
...(project?.drive_url ? [{
|
||||
icon: '⬡', label: 'Open Drive Folder',
|
||||
action: () => window.open(project.drive_url, '_blank'),
|
||||
}] : []),
|
||||
{ separator: true },
|
||||
{
|
||||
icon: '✕', label: 'Delete Deliverable', danger: true,
|
||||
action: async () => {
|
||||
if (window.confirm(`Delete "${deliverable.title}"?`)) {
|
||||
await deleteDeliverable(deliverableId)
|
||||
removeDeliverable(deliverableId)
|
||||
}
|
||||
},
|
||||
},
|
||||
],
|
||||
})
|
||||
})
|
||||
}, [projects, openFocus])
|
||||
|
||||
return (
|
||||
<div className="h-full flex flex-col bg-surface p-4">
|
||||
<div className="h-full flex flex-col bg-surface p-4" onContextMenu={e => e.preventDefault()}>
|
||||
<div className="flex-1 overflow-hidden">
|
||||
<FullCalendar
|
||||
ref={calRef}
|
||||
@@ -53,18 +112,29 @@ export default function MainCalendar() {
|
||||
eventDrop={handleEventDrop}
|
||||
eventClick={handleEventClick}
|
||||
dateClick={handleDateClick}
|
||||
eventDidMount={handleEventDidMount}
|
||||
height="100%"
|
||||
dayMaxEvents={4}
|
||||
eventDisplay="block"
|
||||
displayEventTime={false}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<DeliverableModal
|
||||
isOpen={modal.open}
|
||||
onClose={() => setModal({ open: false, deliverable: null, defaultDate: '' })}
|
||||
deliverable={modal.deliverable}
|
||||
defaultDate={modal.defaultDate}
|
||||
/>
|
||||
|
||||
{contextMenu && (
|
||||
<ContextMenu
|
||||
x={contextMenu.x}
|
||||
y={contextMenu.y}
|
||||
items={contextMenu.items}
|
||||
onClose={() => setContextMenu(null)}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user