From 41369fd8e1883c4fde13989f233ab5892dcab744 Mon Sep 17 00:00:00 2001 From: jasonMPM Date: Thu, 5 Mar 2026 16:45:27 -0600 Subject: [PATCH] Revert "Add files via upload" --- README.md | 5 +- frontend/index.html | 2 - .../src/components/Calendar/MainCalendar.jsx | 184 +++++++++++++++++- 3 files changed, 185 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 8ad933c..134e361 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,6 @@ -# FABDASH +# FabDash **Fabrication Dashboard** — A sleek, modern project management & scheduling application built for fabrication workflows. -Repo: https://github.com/jasonMPM/fabdash ![Version](https://img.shields.io/badge/version-1.0.0-gold) ![Stack](https://img.shields.io/badge/stack-React%20%2B%20Flask%20%2B%20SQLite-informational) @@ -748,4 +747,4 @@ To add a new column in a future version, append its `ALTER TABLE` statement to t --- -*Built with intention. No subscriptions. No bloat. Just your fabrication workflow.* \ No newline at end of file +*Built with intention. No subscriptions. No bloat. Just your fabrication workflow.* diff --git a/frontend/index.html b/frontend/index.html index 2cd8bc8..4537fe6 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -5,8 +5,6 @@ FABDASH - -
diff --git a/frontend/src/components/Calendar/MainCalendar.jsx b/frontend/src/components/Calendar/MainCalendar.jsx index 636aeef..2e90d98 100644 --- a/frontend/src/components/Calendar/MainCalendar.jsx +++ b/frontend/src/components/Calendar/MainCalendar.jsx @@ -1 +1,183 @@ -// ...file truncated in this snippet — only the toolbar area is shown changed +import { useRef, useState, useCallback, useEffect } from 'react' +import FullCalendar from '@fullcalendar/react' +import dayGridPlugin from '@fullcalendar/daygrid' +import timeGridPlugin from '@fullcalendar/timegrid' +import interactionPlugin from '@fullcalendar/interaction' +import useProjectStore from '../../store/useProjectStore' +import useFocusStore from '../../store/useFocusStore' +import useUIStore from '../../store/useUIStore' +import useToastStore from '../../store/useToastStore' +import { updateDeliverable, deleteDeliverable } from '../../api/deliverables' +import DeliverableModal from '../Deliverables/DeliverableModal' +import ContextMenu from '../UI/ContextMenu' +import EventTooltip from './EventTooltip' +import WorkloadHeatmap from './WorkloadHeatmap' + +export default function MainCalendar({ onCalendarReady }) { + const calRef = useRef(null) + const { projects, updateDeliverable: storeUpdate, removeDeliverable } = useProjectStore() + const openFocus = useFocusStore(s => s.openFocus) + const { showHeatmap, toggleHeatmap } = useUIStore() + const addToast = useToastStore(s => s.addToast) + + const [modal, setModal] = useState({ open: false, deliverable: null, defaultDate: '' }) + const [contextMenu, setContextMenu] = useState(null) + const [tooltip, setTooltip] = useState(null) + + // Expose calendar API to App.jsx for keyboard shortcuts + useEffect(() => { + if (calRef.current && onCalendarReady) { + onCalendarReady(calRef.current.getApi()) + } + }, []) + + const events = projects.flatMap(p => + (p.deliverables || []).map(d => ({ + id: String(d.id), + title: `${p.name}: ${d.title}`, + start: d.due_date, + allDay: true, + backgroundColor: p.color, + borderColor: p.color, + extendedProps: { deliverableId: d.id, projectId: p.id }, + })) + ) + + const getCtx = (projectId, deliverableId) => { + const project = projects.find(p => p.id === projectId) + const deliverable = project?.deliverables.find(d => d.id === deliverableId) + return { project, deliverable } + } + + const handleEventClick = useCallback(({ event }) => { + const { deliverableId, projectId } = event.extendedProps + openFocus(projectId, deliverableId) + }, [openFocus]) + + // Drag-and-drop with 30-second undo toast + const handleEventDrop = useCallback(async ({ event, oldEvent }) => { + const { deliverableId } = event.extendedProps + const newDate = event.startStr.substring(0, 10) + const oldDate = oldEvent.startStr.substring(0, 10) + storeUpdate(await updateDeliverable(deliverableId, { due_date: newDate })) + addToast({ + message: `Moved to ${newDate}`, + duration: 30, + undoFn: async () => { + storeUpdate(await updateDeliverable(deliverableId, { due_date: oldDate })) + }, + }) + }, [storeUpdate, addToast]) + + // Click empty date — open add modal + const handleDateClick = useCallback(({ dateStr }) => { + setModal({ open: true, deliverable: null, defaultDate: dateStr.substring(0, 10) }) + }, []) + + // Date range drag-select — pre-fill modal with start date + const handleSelect = useCallback(({ startStr }) => { + setModal({ open: true, deliverable: null, defaultDate: startStr.substring(0, 10) }) + }, []) + + // Attach dblclick + contextmenu + tooltip via eventDidMount + const handleEventDidMount = useCallback(({ event, el }) => { + const { deliverableId, projectId } = event.extendedProps + + el.addEventListener('mouseenter', (e) => { + const { project, deliverable } = getCtx(projectId, deliverableId) + setTooltip({ x: e.clientX, y: e.clientY, project, deliverable }) + }) + el.addEventListener('mouseleave', () => setTooltip(null)) + el.addEventListener('mousemove', (e) => { + setTooltip(prev => prev ? { ...prev, x: e.clientX, y: e.clientY } : null) + }) + + el.addEventListener('dblclick', (e) => { + e.preventDefault(); e.stopPropagation() + setTooltip(null) + const { deliverable } = getCtx(projectId, deliverableId) + if (deliverable) setModal({ open: true, deliverable, defaultDate: '' }) + }) + + el.addEventListener('contextmenu', (e) => { + e.preventDefault(); e.stopPropagation() + setTooltip(null) + const { project, deliverable } = getCtx(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 ( +
e.preventDefault()}> + {/* View toggle toolbar */} +
+ +
+ + {/* Main content area */} +
+ {showHeatmap ? ( + + ) : ( +
+ +
+ )} +
+ + setModal({ open: false, deliverable: null, defaultDate: '' })} + deliverable={modal.deliverable} + defaultDate={modal.defaultDate} + /> + + {contextMenu && ( + setContextMenu(null)} /> + )} + + +
+ ) +}