From edb0e3a5398d5c1033087df3910e0058782e1b88 Mon Sep 17 00:00:00 2001 From: jasonMPM Date: Thu, 5 Mar 2026 15:39:21 -0600 Subject: [PATCH] Add files via upload --- frontend/src/App.jsx | 38 +++- .../src/components/Calendar/AgendaPanel.jsx | 81 +++++++++ .../src/components/Calendar/EventTooltip.jsx | 23 +++ .../src/components/Calendar/MainCalendar.jsx | 165 ++++++++++------- .../components/Calendar/WorkloadHeatmap.jsx | 166 ++++++++++++++++++ .../src/components/Projects/ProjectList.jsx | 76 ++++++-- frontend/src/components/UI/Modal.jsx | 22 ++- frontend/src/components/UI/Toast.jsx | 50 ++++++ frontend/src/store/useToastStore.js | 15 ++ frontend/src/store/useUIStore.js | 13 ++ frontend/src/styles/globals.css | 47 ++--- 11 files changed, 592 insertions(+), 104 deletions(-) create mode 100644 frontend/src/components/Calendar/AgendaPanel.jsx create mode 100644 frontend/src/components/Calendar/EventTooltip.jsx create mode 100644 frontend/src/components/Calendar/WorkloadHeatmap.jsx create mode 100644 frontend/src/components/UI/Toast.jsx create mode 100644 frontend/src/store/useToastStore.js create mode 100644 frontend/src/store/useUIStore.js diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index e475fc2..7824120 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -1,12 +1,17 @@ -import { useEffect } from 'react' +import { useEffect, useRef } from 'react' import ProjectList from './components/Projects/ProjectList' import MainCalendar from './components/Calendar/MainCalendar' import FocusDrawer from './components/FocusView/FocusDrawer' +import ToastContainer from './components/UI/Toast' import { fetchProjects } from './api/projects' import useProjectStore from './store/useProjectStore' +import useUIStore from './store/useUIStore' export default function App() { const { setProjects, setLoading } = useProjectStore() + const { sidebarOpen, toggleSidebar } = useUIStore() + const calApiRef = useRef(null) + const newProjectFn = useRef(null) useEffect(() => { setLoading(true) @@ -15,15 +20,40 @@ export default function App() { .catch(() => setLoading(false)) }, []) + // Global keyboard shortcuts + useEffect(() => { + const handler = (e) => { + if (['INPUT','TEXTAREA','SELECT'].includes(e.target.tagName)) return + if (e.key === 'n' || e.key === 'N') { e.preventDefault(); newProjectFn.current?.() } + if (e.key === 'b' || e.key === 'B') toggleSidebar() + if (e.key === 'ArrowLeft' && calApiRef.current) calApiRef.current.prev() + if (e.key === 'ArrowRight' && calApiRef.current) calApiRef.current.next() + if ((e.key === 't' || e.key === 'T') && calApiRef.current) calApiRef.current.today() + } + document.addEventListener('keydown', handler) + return () => document.removeEventListener('keydown', handler) + }, [toggleSidebar]) + return (
-
) } diff --git a/frontend/src/components/Calendar/AgendaPanel.jsx b/frontend/src/components/Calendar/AgendaPanel.jsx new file mode 100644 index 0000000..49a55b2 --- /dev/null +++ b/frontend/src/components/Calendar/AgendaPanel.jsx @@ -0,0 +1,81 @@ +import { useMemo } from 'react' +import { format, parseISO, isAfter, isBefore, addDays, startOfDay, isToday } from 'date-fns' +import useProjectStore from '../../store/useProjectStore' +import useFocusStore from '../../store/useFocusStore' +import Badge from '../UI/Badge' + +export default function AgendaPanel() { + const projects = useProjectStore(s => s.projects) + const openFocus = useFocusStore(s => s.openFocus) + + const grouped = useMemo(() => { + const today = startOfDay(new Date()) + const cutoff = addDays(today, 60) + const items = [] + + projects.forEach(p => { + (p.deliverables || []).forEach(d => { + const dt = parseISO(d.due_date) + if (!isBefore(dt, today) && !isAfter(dt, cutoff)) { + items.push({ ...d, projectName: p.name, projectColor: p.color, projectId: p.id }) + } + }) + }) + + items.sort((a, b) => new Date(a.due_date) - new Date(b.due_date)) + + const map = {} + items.forEach(item => { + if (!map[item.due_date]) map[item.due_date] = [] + map[item.due_date].push(item) + }) + return map + }, [projects]) + + const dates = Object.keys(grouped).sort() + + if (dates.length === 0) { + return ( +
+
+ + + + + + +
+

All clear for 60 days

+

No upcoming deliverables

+
+ ) + } + + return ( +
+

+ Next 60 days · {dates.length} due date{dates.length !== 1 ? 's' : ''} +

+ {dates.map(date => ( +
+
+ + {isToday(parseISO(date)) ? 'Today' : format(parseISO(date), 'EEE, MMM d')} + +
+ {grouped[date].map(d => ( + + ))} +
+ ))} +
+ ) +} diff --git a/frontend/src/components/Calendar/EventTooltip.jsx b/frontend/src/components/Calendar/EventTooltip.jsx new file mode 100644 index 0000000..2bd3b87 --- /dev/null +++ b/frontend/src/components/Calendar/EventTooltip.jsx @@ -0,0 +1,23 @@ +import Badge from '../UI/Badge' +import { formatDate } from '../../utils/dateHelpers' + +export default function EventTooltip({ tooltip }) { + if (!tooltip) return null + const { x, y, project, deliverable } = tooltip + const tx = Math.min(x + 14, window.innerWidth - 230) + const ty = Math.max(y - 90, 8) + return ( +
+
+
+ {project?.name} +
+

{deliverable?.title}

+
+ {formatDate(deliverable?.due_date)} + +
+
+ ) +} diff --git a/frontend/src/components/Calendar/MainCalendar.jsx b/frontend/src/components/Calendar/MainCalendar.jsx index 72df263..2e90d98 100644 --- a/frontend/src/components/Calendar/MainCalendar.jsx +++ b/frontend/src/components/Calendar/MainCalendar.jsx @@ -1,21 +1,35 @@ -import { useRef, useState, useCallback } from 'react' +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() { +export default function MainCalendar({ onCalendarReady }) { const calRef = useRef(null) const { projects, updateDeliverable: storeUpdate, removeDeliverable } = useProjectStore() - const openFocus = useFocusStore(s => s.openFocus) + 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 => ({ @@ -29,70 +43,80 @@ export default function MainCalendar() { })) ) - const getDeliverable = (projectId, deliverableId) => { - const p = projects.find(p => p.id === projectId) - return { project: p, deliverable: p?.deliverables.find(d => d.id === deliverableId) } + const getCtx = (projectId, deliverableId) => { + const project = projects.find(p => p.id === projectId) + const deliverable = project?.deliverables.find(d => d.id === deliverableId) + return { project, deliverable } } - // Single click → Focus View const handleEventClick = useCallback(({ event }) => { const { deliverableId, projectId } = event.extendedProps openFocus(projectId, deliverableId) }, [openFocus]) - // Drag-and-drop → patch date - const handleEventDrop = useCallback(async ({ event }) => { + // Drag-and-drop with 30-second undo toast + const handleEventDrop = useCallback(async ({ event, oldEvent }) => { const { deliverableId } = event.extendedProps - storeUpdate(await updateDeliverable(deliverableId, { due_date: event.startStr.substring(0, 10) })) - }, [storeUpdate]) + 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 → add deliverable + // Click empty date — open add modal const handleDateClick = useCallback(({ dateStr }) => { setModal({ open: true, deliverable: null, defaultDate: dateStr.substring(0, 10) }) }, []) - // Attach dblclick + contextmenu to each event element after mount + // 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 - // Double-click → open edit modal directly + 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() - const { deliverable } = getDeliverable(projectId, deliverableId) + e.preventDefault(); e.stopPropagation() + setTooltip(null) + const { deliverable } = getCtx(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) + 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'), - }] : []), + { 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, + { icon: '✕', label: 'Delete Deliverable', danger: true, action: async () => { if (window.confirm(`Delete "${deliverable.title}"?`)) { - await deleteDeliverable(deliverableId) - removeDeliverable(deliverableId) + await deleteDeliverable(deliverableId); removeDeliverable(deliverableId) } - }, + } }, ], }) @@ -100,24 +124,46 @@ export default function MainCalendar() { }, [projects, openFocus]) return ( -
e.preventDefault()}> +
e.preventDefault()}> + {/* View toggle toolbar */} +
+ +
+ + {/* Main content area */}
- + {showHeatmap ? ( + + ) : ( +
+ +
+ )}
{contextMenu && ( - setContextMenu(null)} - /> + setContextMenu(null)} /> )} + +
) } diff --git a/frontend/src/components/Calendar/WorkloadHeatmap.jsx b/frontend/src/components/Calendar/WorkloadHeatmap.jsx new file mode 100644 index 0000000..3cd383f --- /dev/null +++ b/frontend/src/components/Calendar/WorkloadHeatmap.jsx @@ -0,0 +1,166 @@ +import { useMemo, useState } from 'react' +import { format, startOfWeek, addDays, addWeeks, isSameDay, parseISO, isToday } from 'date-fns' +import useProjectStore from '../../store/useProjectStore' +import useFocusStore from '../../store/useFocusStore' +import Badge from '../UI/Badge' + +const WEEKS = 20 +const DAY_INIT = ['M','T','W','T','F','S','S'] + +function getCellStyle(count) { + if (count === 0) return 'bg-surface border-surface-border' + if (count === 1) return 'bg-gold/25 border-gold/40' + if (count === 2) return 'bg-gold/55 border-gold/70' + return 'bg-gold border-gold shadow-gold' +} + +export default function WorkloadHeatmap() { + const projects = useProjectStore(s => s.projects) + const openFocus = useFocusStore(s => s.openFocus) + const [tooltip, setTooltip] = useState(null) + + const { weeks, stats } = useMemo(() => { + const start = startOfWeek(addWeeks(new Date(), -10), { weekStartsOn: 1 }) + const map = {} + projects.forEach(p => { + (p.deliverables || []).forEach(d => { + if (!map[d.due_date]) map[d.due_date] = [] + map[d.due_date].push({ deliverable: d, project: p }) + }) + }) + + const grid = Array.from({ length: WEEKS }, (_, wi) => + Array.from({ length: 7 }, (_, di) => { + const date = addDays(start, wi * 7 + di) + const key = format(date, 'yyyy-MM-dd') + return { date, key, items: map[key] || [] } + }) + ) + + const all = projects.flatMap(p => p.deliverables || []) + const stats = { + total: all.length, + overdue: all.filter(d => d.status === 'overdue').length, + in_progress: all.filter(d => d.status === 'in_progress').length, + completed: all.filter(d => d.status === 'completed').length, + upcoming: all.filter(d => d.status === 'upcoming').length, + } + return { weeks: grid, stats } + }, [projects]) + + const monthLabels = useMemo(() => { + const labels = []; let last = -1 + weeks.forEach((week, wi) => { + const m = week[0].date.getMonth() + if (m !== last) { labels.push({ wi, label: format(week[0].date, 'MMM') }); last = m } + }) + return labels + }, [weeks]) + + const CELL = 20, GAP = 3 + + return ( +
+ {/* Header */} +
+
+

Workload Heatmap

+

{WEEKS} weeks of deliverable density

+
+
+ Less + {['bg-surface border-surface-border','bg-gold/25 border-gold/40','bg-gold/55 border-gold/70','bg-gold border-gold'].map((c,i) => ( +
+ ))} + More +
+
+ + {/* Stat cards */} +
+ {[ + { label: 'Total', value: stats.total, color: 'text-text-primary' }, + { label: 'Upcoming', value: stats.upcoming, color: 'text-blue-400' }, + { label: 'In Progress', value: stats.in_progress, color: 'text-amber-400' }, + { label: 'Completed', value: stats.completed, color: 'text-green-400' }, + { label: 'Overdue', value: stats.overdue, color: 'text-red-400' }, + ].map(({ label, value, color }) => ( +
+

{value}

+

{label}

+
+ ))} +
+ + {/* Heatmap grid */} +
+ {/* Day labels */} +
+ {DAY_INIT.map((d, i) => ( +
{d}
+ ))} +
+ + {/* Grid */} +
+ {/* Month labels */} +
+ {monthLabels.map(({ wi, label }) => ( + + {label} + + ))} +
+ {/* Week columns */} +
+ {weeks.map((week, wi) => ( +
+ {week.map(({ date, key, items }) => ( +
items.length > 0 && openFocus(items[0].project.id, items[0].deliverable.id)} + onMouseEnter={(e) => setTooltip({ x: e.clientX, y: e.clientY, date, items })} + onMouseLeave={() => setTooltip(null)} + /> + ))} +
+ ))} +
+
+
+ + {/* Tooltip */} + {tooltip && ( +
+

+ {isToday(tooltip.date) ? 'Today — ' : ''}{format(tooltip.date, 'EEE, MMM d, yyyy')} +

+ {tooltip.items.length === 0 ? ( +

No deliverables

+ ) : ( +
+ {tooltip.items.slice(0, 5).map(({ deliverable, project }) => ( +
+
+
+

{deliverable.title}

+

{project.name}

+
+
+ ))} + {tooltip.items.length > 5 && ( +

+{tooltip.items.length - 5} more

+ )} +
+ )} +
+ )} +
+ ) +} diff --git a/frontend/src/components/Projects/ProjectList.jsx b/frontend/src/components/Projects/ProjectList.jsx index dc7af49..a7be12a 100644 --- a/frontend/src/components/Projects/ProjectList.jsx +++ b/frontend/src/components/Projects/ProjectList.jsx @@ -1,14 +1,19 @@ -import { useState } from 'react' +import { useState, useEffect } from 'react' import ProjectCard from './ProjectCard' import ProjectModal from './ProjectModal' import Button from '../UI/Button' +import AgendaPanel from '../Calendar/AgendaPanel' import useProjectStore from '../../store/useProjectStore' +import useUIStore from '../../store/useUIStore' import { deleteProject } from '../../api/projects' -export default function ProjectList() { - const { projects, removeProject } = useProjectStore() - const [showModal, setShowModal] = useState(false) - const [editing, setEditing] = useState(null) +export default function ProjectList({ onRegisterNewProject }) { + const { projects, removeProject } = useProjectStore() + const { sidebarTab, setSidebarTab } = useUIStore() + const [showModal, setShowModal] = useState(false) + const [editing, setEditing] = useState(null) + + useEffect(() => { onRegisterNewProject?.(() => setShowModal(true)) }, [onRegisterNewProject]) const handleEdit = (p) => { setEditing(p); setShowModal(true) } const handleDelete = async (p) => { @@ -20,21 +25,64 @@ export default function ProjectList() { return (
+ {/* Header */}

FabDash

-
- {projects.length === 0 && ( -
-

No projects yet.

-

Click "+ Project" to begin.

-
- )} - {projects.map(p => ( - + + {/* Tab toggle */} +
+ {[['projects','Projects'],['agenda','Upcoming']].map(([key, label]) => ( + ))}
+ + {/* Content */} +
+ {sidebarTab === 'projects' ? ( +
+ {projects.length === 0 ? ( +
+
+ + + + + + + + +
+

No projects yet

+

+ Press N or click + Project +

+
+ ) : ( + projects.map(p => ( + + )) + )} +
+ ) : ( + + )} +
+ + {/* Keyboard shortcuts legend */} +
+ {[['N','New project'],['B','Toggle sidebar'],['←→','Navigate'],['T','Today']].map(([key, desc]) => ( + + {key} + {desc} + + ))} +
+
) diff --git a/frontend/src/components/UI/Modal.jsx b/frontend/src/components/UI/Modal.jsx index dd2c982..2929d89 100644 --- a/frontend/src/components/UI/Modal.jsx +++ b/frontend/src/components/UI/Modal.jsx @@ -1,16 +1,28 @@ -import { useEffect } from 'react' -export default function Modal({ isOpen, onClose, title, children, size='md' }) { +import { useEffect, useState } from 'react' + +export default function Modal({ isOpen, onClose, title, children, size = 'md' }) { + const [visible, setVisible] = useState(false) + + useEffect(() => { + if (isOpen) requestAnimationFrame(() => setVisible(true)) + else setVisible(false) + }, [isOpen]) + useEffect(() => { const h = (e) => { if (e.key === 'Escape') onClose() } if (isOpen) document.addEventListener('keydown', h) return () => document.removeEventListener('keydown', h) }, [isOpen, onClose]) + if (!isOpen) return null - const sizes = { sm:'max-w-md', md:'max-w-lg', lg:'max-w-2xl', xl:'max-w-4xl' } + const sizes = { sm: 'max-w-md', md: 'max-w-lg', lg: 'max-w-2xl', xl: 'max-w-4xl' } + return (
-
-
+
+

{title}

diff --git a/frontend/src/components/UI/Toast.jsx b/frontend/src/components/UI/Toast.jsx new file mode 100644 index 0000000..f1c8fda --- /dev/null +++ b/frontend/src/components/UI/Toast.jsx @@ -0,0 +1,50 @@ +import { useEffect, useState } from 'react' +import useToastStore from '../../store/useToastStore' + +function ToastItem({ toast }) { + const removeToast = useToastStore(s => s.removeToast) + const [secs, setSecs] = useState(toast.duration) + + useEffect(() => { + const t = setInterval(() => setSecs(s => { + if (s <= 1) { clearInterval(t); removeToast(toast.id); return 0 } + return s - 1 + }), 1000) + return () => clearInterval(t) + }, []) + + return ( +
+ {toast.message} + {toast.undoFn && ( + + )} +
+
+ + + + + {secs} +
+ +
+
+ ) +} + +export default function ToastContainer() { + const toasts = useToastStore(s => s.toasts) + if (!toasts.length) return null + return ( +
+ {toasts.map(t => ( +
+ ))} +
+ ) +} diff --git a/frontend/src/store/useToastStore.js b/frontend/src/store/useToastStore.js new file mode 100644 index 0000000..9ff88a0 --- /dev/null +++ b/frontend/src/store/useToastStore.js @@ -0,0 +1,15 @@ +import { create } from 'zustand' + +let _id = 0 + +const useToastStore = create((set) => ({ + toasts: [], + addToast: ({ message, undoFn, duration = 30 }) => { + const id = ++_id + set(s => ({ toasts: [...s.toasts, { id, message, undoFn, duration }] })) + return id + }, + removeToast: (id) => set(s => ({ toasts: s.toasts.filter(t => t.id !== id) })), +})) + +export default useToastStore diff --git a/frontend/src/store/useUIStore.js b/frontend/src/store/useUIStore.js new file mode 100644 index 0000000..8addc01 --- /dev/null +++ b/frontend/src/store/useUIStore.js @@ -0,0 +1,13 @@ +import { create } from 'zustand' + +const useUIStore = create((set) => ({ + sidebarOpen: true, + sidebarTab: 'projects', // 'projects' | 'agenda' + showHeatmap: false, + + toggleSidebar: () => set(s => ({ sidebarOpen: !s.sidebarOpen })), + setSidebarTab: (tab) => set({ sidebarTab: tab }), + toggleHeatmap: () => set(s => ({ showHeatmap: !s.showHeatmap })), +})) + +export default useUIStore diff --git a/frontend/src/styles/globals.css b/frontend/src/styles/globals.css index cd689e1..afb6a5b 100644 --- a/frontend/src/styles/globals.css +++ b/frontend/src/styles/globals.css @@ -8,12 +8,17 @@ body { background-color: #111111; color: #F5F5F5; font-family: 'Inter', system-ui, sans-serif; - margin: 0; - padding: 0; - overflow: hidden; + margin: 0; padding: 0; overflow: hidden; } -/* ── FullCalendar dark theme overrides ── */ +/* ── Animations ── */ +@keyframes slide-up { + from { opacity: 0; transform: translateY(12px); } + to { opacity: 1; transform: translateY(0); } +} +.animate-slide-up { animation: slide-up 0.2s ease-out forwards; } + +/* ── FullCalendar dark theme ── */ .fc { --fc-border-color: #2E2E2E; --fc-button-bg-color: #1A1A1A; @@ -27,7 +32,6 @@ body { --fc-neutral-bg-color: #1A1A1A; --fc-event-border-color: transparent; } - .fc-theme-standard td, .fc-theme-standard th, .fc-theme-standard .fc-scrollgrid { border-color: #2E2E2E !important; } @@ -47,27 +51,30 @@ body { } .fc-daygrid-event { - border-radius: 4px !important; - font-size: 0.72rem !important; - font-weight: 600 !important; - cursor: pointer !important; - padding: 1px 5px !important; + border-radius: 4px !important; font-size: 0.72rem !important; + font-weight: 600 !important; cursor: pointer !important; padding: 1px 5px !important; } - .fc-event-title { color: #111111 !important; font-weight: 700 !important; } .fc-day-today .fc-daygrid-day-number { - background-color: #C9A84C !important; - color: #111111 !important; - border-radius: 50% !important; - width: 26px !important; - height: 26px !important; - display: flex !important; - align-items: center !important; - justify-content: center !important; + background-color: #C9A84C !important; color: #111111 !important; + border-radius: 50% !important; width: 26px !important; height: 26px !important; + display: flex !important; align-items: center !important; justify-content: center !important; } -/* ── Scrollbar ── */ +/* Week number column */ +.fc-timegrid-axis.fc-scrollgrid-shrink, +.fc-daygrid-week-number { + color: #C9A84C !important; + font-size: 0.65rem !important; + opacity: 0.6; + text-decoration: none !important; +} + +/* Selection highlight */ +.fc-highlight { background: rgba(201, 168, 76, 0.12) !important; } + +/* Scrollbar */ ::-webkit-scrollbar { width: 5px; height: 5px; } ::-webkit-scrollbar-track { background: #1A1A1A; } ::-webkit-scrollbar-thumb { background: #2E2E2E; border-radius: 3px; }