@@ -14,11 +14,12 @@ import EventTooltip from './EventTooltip'
|
|||||||
import WorkloadHeatmap from './WorkloadHeatmap'
|
import WorkloadHeatmap from './WorkloadHeatmap'
|
||||||
|
|
||||||
export default function MainCalendar({ onCalendarReady }) {
|
export default function MainCalendar({ onCalendarReady }) {
|
||||||
const calRef = useRef(null)
|
const calRef = useRef(null)
|
||||||
|
const wrapperRef = useRef(null)
|
||||||
const { projects, updateDeliverable: storeUpdate, removeDeliverable } = useProjectStore()
|
const { projects, updateDeliverable: storeUpdate, removeDeliverable } = useProjectStore()
|
||||||
const openFocus = useFocusStore(s => s.openFocus)
|
const openFocus = useFocusStore(s => s.openFocus)
|
||||||
const { showHeatmap, toggleHeatmap } = useUIStore()
|
const { showHeatmap, toggleHeatmap } = useUIStore()
|
||||||
const addToast = useToastStore(s => s.addToast)
|
const addToast = useToastStore(s => s.addToast)
|
||||||
|
|
||||||
const [modal, setModal] = useState({ open: false, deliverable: null, defaultDate: '' })
|
const [modal, setModal] = useState({ open: false, deliverable: null, defaultDate: '' })
|
||||||
const [contextMenu, setContextMenu] = useState(null)
|
const [contextMenu, setContextMenu] = useState(null)
|
||||||
@@ -31,6 +32,18 @@ export default function MainCalendar({ onCalendarReady }) {
|
|||||||
}
|
}
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
|
// ResizeObserver: call updateSize() on every frame the container changes width
|
||||||
|
// during the sidebar CSS transition so FullCalendar reflows smoothly
|
||||||
|
useEffect(() => {
|
||||||
|
const el = wrapperRef.current
|
||||||
|
if (!el) return
|
||||||
|
const ro = new ResizeObserver(() => {
|
||||||
|
calRef.current?.getApi().updateSize()
|
||||||
|
})
|
||||||
|
ro.observe(el)
|
||||||
|
return () => ro.disconnect()
|
||||||
|
}, [])
|
||||||
|
|
||||||
const events = projects.flatMap(p =>
|
const events = projects.flatMap(p =>
|
||||||
(p.deliverables || []).map(d => ({
|
(p.deliverables || []).map(d => ({
|
||||||
id: String(d.id),
|
id: String(d.id),
|
||||||
@@ -69,12 +82,12 @@ export default function MainCalendar({ onCalendarReady }) {
|
|||||||
})
|
})
|
||||||
}, [storeUpdate, addToast])
|
}, [storeUpdate, addToast])
|
||||||
|
|
||||||
// Click empty date — open add modal
|
// Click empty date - open add modal
|
||||||
const handleDateClick = useCallback(({ dateStr }) => {
|
const handleDateClick = useCallback(({ dateStr }) => {
|
||||||
setModal({ open: true, deliverable: null, defaultDate: dateStr.substring(0, 10) })
|
setModal({ open: true, deliverable: null, defaultDate: dateStr.substring(0, 10) })
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
// Date range drag-select — pre-fill modal with start date
|
// Date range drag-select - pre-fill modal with start date
|
||||||
const handleSelect = useCallback(({ startStr }) => {
|
const handleSelect = useCallback(({ startStr }) => {
|
||||||
setModal({ open: true, deliverable: null, defaultDate: startStr.substring(0, 10) })
|
setModal({ open: true, deliverable: null, defaultDate: startStr.substring(0, 10) })
|
||||||
}, [])
|
}, [])
|
||||||
@@ -107,11 +120,11 @@ export default function MainCalendar({ onCalendarReady }) {
|
|||||||
setContextMenu({
|
setContextMenu({
|
||||||
x: e.clientX, y: e.clientY,
|
x: e.clientX, y: e.clientY,
|
||||||
items: [
|
items: [
|
||||||
{ icon: '✎', label: 'Edit Deliverable', action: () => setModal({ open: true, deliverable, defaultDate: '' }) },
|
{ icon: '\u2714\ufe0e', label: 'Edit Deliverable', action: () => setModal({ open: true, deliverable, defaultDate: '' }) },
|
||||||
{ icon: '◎', label: 'Open Focus View', action: () => openFocus(projectId, deliverableId) },
|
{ icon: '\u2756', label: 'Open Focus View', action: () => openFocus(projectId, deliverableId) },
|
||||||
...(project?.drive_url ? [{ icon: '⬡', label: 'Open Drive Folder', action: () => window.open(project.drive_url, '_blank') }] : []),
|
...(project?.drive_url ? [{ icon: '\u2b21', label: 'Open Drive Folder', action: () => window.open(project.drive_url, '_blank') }] : []),
|
||||||
{ separator: true },
|
{ separator: true },
|
||||||
{ icon: '✕', label: 'Delete Deliverable', danger: true,
|
{ icon: '\u2715', label: 'Delete Deliverable', danger: true,
|
||||||
action: async () => {
|
action: async () => {
|
||||||
if (window.confirm(`Delete "${deliverable.title}"?`)) {
|
if (window.confirm(`Delete "${deliverable.title}"?`)) {
|
||||||
await deleteDeliverable(deliverableId); removeDeliverable(deliverableId)
|
await deleteDeliverable(deliverableId); removeDeliverable(deliverableId)
|
||||||
@@ -133,7 +146,7 @@ export default function MainCalendar({ onCalendarReady }) {
|
|||||||
? 'bg-gold text-surface border-gold'
|
? 'bg-gold text-surface border-gold'
|
||||||
: 'bg-surface-elevated border-surface-border text-text-muted hover:border-gold/40 hover:text-gold'
|
: 'bg-surface-elevated border-surface-border text-text-muted hover:border-gold/40 hover:text-gold'
|
||||||
}`}>
|
}`}>
|
||||||
{showHeatmap ? '← Calendar' : '⬡ Heatmap'}
|
{showHeatmap ? '\u2190 Calendar' : '\u2b21 Heatmap'}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -142,7 +155,7 @@ export default function MainCalendar({ onCalendarReady }) {
|
|||||||
{showHeatmap ? (
|
{showHeatmap ? (
|
||||||
<WorkloadHeatmap />
|
<WorkloadHeatmap />
|
||||||
) : (
|
) : (
|
||||||
<div className="h-full p-4 pt-2">
|
<div ref={wrapperRef} className="h-full p-4 pt-2">
|
||||||
<FullCalendar
|
<FullCalendar
|
||||||
ref={calRef}
|
ref={calRef}
|
||||||
plugins={[dayGridPlugin, timeGridPlugin, interactionPlugin]}
|
plugins={[dayGridPlugin, timeGridPlugin, interactionPlugin]}
|
||||||
|
|||||||
Reference in New Issue
Block a user