fix: remove redundant top-row heatmaps, keep labeled status rows only
FABDASH
Fabrication Dashboard — A sleek, modern project management & scheduling application built for fabrication workflows. Repo: https://github.com/jasonMPM/fabdash
Table of Contents
- Overview
- Tech Stack
- Project Structure
- Features
- Interaction Reference
- Keyboard Shortcuts
- API Reference
- Component Architecture
- Unraid Installation
- Adding Your Logo
- Updating FabDash on Unraid
- Local Development
- Environment Variables
- Database Schema
- Roadmap
Overview
FabDash is a self-hosted, full-stack project management dashboard built for fabrication teams. It features a large interactive calendar, multi-deliverable project tracking, drag-and-drop scheduling, a per-project Focus View timeline, workload heatmap, agenda panel, right-click context menus, keyboard shortcuts, and a collapsible branded sidebar — all wrapped in a dark/gold UI and deployed as a single Docker container with no external dependencies.
Tech Stack
Frontend
| Package | Version | Purpose |
|---|---|---|
| React | ^18.3.1 | UI framework |
| Vite | ^5.4.11 | Build tool and dev server |
| Tailwind CSS | ^3.4.15 | Utility-first styling |
| @fullcalendar/core | 6.1.15 | FullCalendar core (pinned — version must match all FC packages) |
| @fullcalendar/react | 6.1.15 | Calendar component |
| @fullcalendar/daygrid | 6.1.15 | Month/week grid view |
| @fullcalendar/timegrid | 6.1.15 | Time-slot view |
| @fullcalendar/interaction | 6.1.15 | Drag-and-drop, click, select events |
| react-chrono | 2.6.0 | Focus View timeline (pinned — v2.7+ requires React 19) |
| Zustand | ^4.5.5 | Global state management |
| Axios | ^1.7.9 | HTTP client |
| date-fns | ^3.6.0 | Date formatting and calculations |
Dependency notes:
- All FullCalendar packages are pinned to exactly
6.1.15(no^) to prevent version mismatch build errorsreact-chronois pinned to2.6.0— versions ≥ 2.7.0 require React 19 which breaks the buildnpm install --legacy-peer-depsis used in the Dockerfile and local dev to handle peer dependency conflicts
Backend
| Package | Version | Purpose |
|---|---|---|
| Flask | 3.1.0 | REST API + static file serving |
| Flask-SQLAlchemy | 3.1.1 | ORM for SQLite |
| Flask-Migrate | 4.0.7 | Schema migration support |
| Flask-CORS | 5.0.0 | Cross-origin support (dev) |
| Gunicorn | 23.0.0 | Production WSGI server |
Database
- SQLite — Zero-config, file-based persistence at
/app/data/fabdash.db - Mounted as a Docker volume so data survives all container restarts and image rebuilds
- Inline migrations — New columns are applied automatically on startup via
_run_migrations()inapp/__init__.py; noflask db upgradecommands needed
Project Structure
fabdash/
├── Dockerfile # Multi-stage: Node (React build) → Python (Flask)
├── docker-compose.yml
├── .env.example
├── .gitignore
├── README.md
│
├── backend/
│ ├── run.py
│ ├── config.py
│ ├── requirements.txt
│ └── app/
│ ├── __init__.py # App factory + inline schema migrations
│ ├── extensions.py
│ ├── models.py # Project + Deliverable ORM models
│ └── routes/
│ ├── __init__.py
│ ├── projects.py # CRUD + drive_url support
│ └── deliverables.py
│
└── frontend/
├── package.json # react-chrono@2.6.0, @fullcalendar/* pinned to 6.1.15
├── vite.config.js # optimizeDeps for FullCalendar, /api proxy
├── tailwind.config.js
├── postcss.config.js
├── index.html
└── public/
└── logo.png # ← Drop your square logo here
└── src/
├── main.jsx
├── App.jsx # Sidebar layout, keyboard shortcuts, toast container
├── api/
│ ├── projects.js
│ └── deliverables.js
├── components/
│ ├── Calendar/
│ │ ├── MainCalendar.jsx # Events, drag-drop, tooltip, select, heatmap toggle
│ │ ├── EventTooltip.jsx # Hover preview card
│ │ ├── AgendaPanel.jsx # Upcoming deliverables (next 60 days)
│ │ └── WorkloadHeatmap.jsx # 20-week density heatmap + stat cards
│ ├── Projects/
│ │ ├── ProjectList.jsx # Logo header, tab toggle, empty state
│ │ ├── ProjectCard.jsx # Drive link, dblclick, right-click
│ │ └── ProjectModal.jsx # Drive URL field
│ ├── Deliverables/
│ │ └── DeliverableModal.jsx
│ ├── FocusView/
│ │ ├── FocusDrawer.jsx
│ │ ├── FocusTimeline.jsx # px padding fix for scale-105 clip
│ │ └── DeliverableCard.jsx # Click=select, dblclick=edit, right-click=menu
│ └── UI/
│ ├── Button.jsx
│ ├── Badge.jsx
│ ├── Modal.jsx # Animated enter transition
│ ├── Drawer.jsx
│ ├── ContextMenu.jsx # Floating right-click menu
│ └── Toast.jsx # 30-second undo toast with countdown ring
├── store/
│ ├── useProjectStore.js
│ ├── useFocusStore.js # setActiveDeliverable action
│ ├── useUIStore.js # sidebarOpen, sidebarTab, showHeatmap
│ └── useToastStore.js # Toast queue management
├── utils/
│ ├── dateHelpers.js
│ └── statusHelpers.js
└── styles/
└── globals.css # FullCalendar dark theme, slide-up animation
Features
Calendar
- Full-width calendar — Month, Week, and Day views via FullCalendar
- Week numbers — ISO week numbers displayed in gold on the left edge
- Drag-and-drop rescheduling — Move any deliverable to a new date; backend patches immediately with a 30-second undo toast
- Date range drag-select — Click and drag across multiple days to open Add Deliverable modal pre-filled with the start date
- Click empty date cell — Opens Add Deliverable modal pre-filled with that date
- Hover tooltip — Shows project name, deliverable title, due date, and status badge without clicking
- Single-click event — Opens Deliverable Focus View
- Double-click event — Opens Edit Deliverable modal directly
- Right-click event — Context menu (Edit, Focus View, Open Drive, Delete)
Workload Heatmap
Toggle between the calendar and heatmap with the ⬡ Heatmap button in the top-right of the main area.
- 20-week grid — Mon–Sun rows, week columns, centered 10 weeks before and after today
- Color intensity — Cells shade from dark (no deliverables) to full gold (3+ deliverables)
- Today ring — Current day highlighted with a white ring
- Hover tooltip — Shows date + all deliverable titles and project names for that day (up to 5, with overflow count)
- Click cell — Opens Focus View for the first deliverable on that day
- Stat cards — Total / Upcoming / In Progress / Completed / Overdue counts across all projects
Sidebar
- Collapsible — Click the
◀tab or pressBto collapse. The toggle becomes a branded square tile showing your logo - Projects tab — Project cards with deliverable rows; empty state SVG illustration with keyboard hint
- Upcoming tab — Agenda panel showing all deliverables due in the next 60 days, grouped by date with sticky headers; Today highlighted in gold
- Keyboard shortcut legend — Pinned at the bottom of the sidebar
Projects
- Multi-deliverable projects — Unlimited deliverables per project, each with its own title, due date, and status
- Inline creation — Add all deliverables in a single form while creating the project
- Color coding — 12-swatch palette + custom hex input
- Google Drive link — Optional Drive folder URL stored per project; shows as a Drive button on the card and appears in context menus
- Double-click project header — Opens Edit Project modal
- Right-click project header — Context menu (Edit, Open Drive, Delete)
Deliverables
- Status tracking — Upcoming / In Progress / Completed / Overdue
- Auto-overdue — Any deliverable whose due date has passed is returned as
overdueby the API unless markedcompleted. Computed at read time — no cron job required - Double-click sidebar row — Opens Edit Deliverable modal
- Right-click sidebar row — Context menu (Edit, Focus View, Delete)
Deliverable Focus View
- Slide-up drawer — Opens from bottom of screen when clicking any calendar event or sidebar row
- Horizontal timeline — All deliverables for the project rendered in chronological order
- Active card — Highlighted in gold with a "Selected" badge floating above; properly padded so scale-105 transform never clips against scroll container edges
- Click any card — Switches the active/selected card
- Double-click any card — Opens Edit Deliverable modal
- Right-click any card — Context menu with quick status change (all 4 statuses inline), Edit, and Delete
Right-Click Context Menu
Custom floating ContextMenu component auto-adjusts to stay within the viewport. Closes on outside click or Escape.
| Trigger | Menu Items |
|---|---|
| Calendar event | Edit Deliverable, Open Focus View, Open Drive Folder*, Delete |
| Sidebar project header | Edit Project, Open Drive*, Delete Project |
| Sidebar deliverable row | Edit Deliverable, Open Focus View, Delete |
| Focus View card | Edit Deliverable, Mark Upcoming / In Progress / Completed / Overdue, Delete |
*Only shown when a Google Drive URL is set on the project.
Undo Toast
After a drag-and-drop rescheduling, a toast appears at the bottom of the screen with:
- The new date
- An Undo button that patches back to the original date
- A circular SVG countdown ring showing seconds remaining (30s default)
- Auto-dismisses when the timer expires
Animations & Polish
- Modal enter — Backdrop fades in, dialog scales up from 95% with a translateY, using
requestAnimationFramefor reliable transitions - Sidebar slide —
transition-[width] duration-300for smooth collapse/expand - Toast slide-up —
@keyframes slide-upanimation inglobals.css - Heatmap cells —
hover:scale-125on each cell for tactile feedback
Interaction Reference
| Location | Single Click | Double Click | Right Click |
|---|---|---|---|
| Calendar event | Open Focus View | Edit Deliverable modal | Context menu |
| Calendar empty cell | Add Deliverable (date pre-filled) | — | — |
| Calendar date range drag | Add Deliverable (start date pre-filled) | — | — |
| Sidebar project header | — | Edit Project modal | Context menu |
| Sidebar deliverable row | Open Focus View | Edit Deliverable modal | Context menu |
| Focus View card | Select / activate | Edit Deliverable modal | Context menu |
| Heatmap cell (with deliverables) | Open Focus View for first deliverable | — | — |
Keyboard Shortcuts
| Key | Action |
|---|---|
N |
Open New Project modal |
B |
Toggle sidebar open / collapsed |
← |
Calendar — previous month/week/day |
→ |
Calendar — next month/week/day |
T |
Calendar — jump to today |
Esc |
Close any open modal or drawer |
Shortcuts are blocked when focus is inside an input, textarea, or select field.
API Reference
Projects
| Method | Endpoint | Description |
|---|---|---|
GET |
/api/projects |
List all projects with nested deliverables |
POST |
/api/projects |
Create project (with optional inline deliverables) |
GET |
/api/projects/:id |
Get single project |
PATCH |
/api/projects/:id |
Update name, color, description, or drive_url |
DELETE |
/api/projects/:id |
Delete project + cascade deliverables |
Deliverables
| Method | Endpoint | Description |
|---|---|---|
GET |
/api/deliverables?project_id=:id |
List deliverables for a project |
POST |
/api/deliverables |
Create deliverable |
PATCH |
/api/deliverables/:id |
Update title, due_date, or status |
DELETE |
/api/deliverables/:id |
Delete deliverable |
Example — Create Project with Drive link:
POST /api/projects
{
"name": "Tucson",
"color": "#E67E22",
"description": "ADA Call Column and Shelter Equipment Box",
"drive_url": "https://drive.google.com/drive/folders/your-folder-id",
"deliverables": [
{ "title": "Cut Extrusion to Length", "due_date": "2026-03-05", "status": "in_progress" },
{ "title": "Cut Faceplates and Top Plates", "due_date": "2026-03-06", "status": "upcoming" },
{ "title": "Fabricate Shelter Assembly Box", "due_date": "2026-03-10", "status": "upcoming" }
]
}
Component Architecture
App
├── <aside> Sidebar (collapsible, w-72 ↔ w-0)
│ └── ProjectList
│ ├── Header: [logo] FabDash + [+ Project]
│ ├── Tabs: [Projects] [Upcoming]
│ ├── ProjectCard (× N) ← dblclick header, right-click
│ │ ├── DeliverableRow (× N) ← click, dblclick, right-click
│ │ ├── DeliverableModal (local)
│ │ └── ContextMenu (local)
│ ├── AgendaPanel ← Upcoming tab
│ ├── Keyboard shortcut legend
│ └── ProjectModal
│
├── Sidebar toggle button
│ ├── Open: ◀ arrow tab
│ └── Collapsed: [logo] square tile + ▶
│
├── <main> MainCalendar
│ ├── [⬡ Heatmap] toggle button
│ ├── FullCalendar ← eventDidMount: tooltip, dblclick, right-click
│ │ └── Events (drag, click, select)
│ ├── WorkloadHeatmap (toggled)
│ ├── EventTooltip (hover)
│ ├── DeliverableModal
│ └── ContextMenu
│
├── FocusDrawer (slide-up overlay)
│ ├── FocusTimeline
│ │ └── DeliverableCard (× N) ← click=select, dblclick=edit, right-click
│ │ └── ContextMenu (local)
│ └── DeliverableModal
│
└── ToastContainer (fixed bottom-center)
└── ToastItem (× N) — countdown ring + Undo
Unraid Installation
FabDash is installed by cloning the repo and building the Docker image locally on your Unraid server via SSH, then managing the container through the Unraid Docker GUI.
Step 1 — Enable SSH on Unraid
Go to Settings → Management Access and confirm SSH is enabled. Note your Unraid server's local IP address from the dashboard header.
Step 2 — SSH into Unraid and Clone the Repo
ssh root@YOUR_UNRAID_IP
cd /mnt/user/appdata
git clone https://github.com/yourname/fabdash.git
cd fabdash
Step 3 — Add Your Logo (Optional)
Place your square logo file at:
frontend/public/logo.png
Supported formats: .png, .jpg, .svg, .webp — rename to logo.png. If omitted, the sidebar header shows text only and the collapsed toggle shows "FD".
Step 4 — Build the Docker Image
docker build -t fabdash:latest .
First build takes 3–5 minutes (downloads Node + Python base layers, compiles React).
Verify the image:
docker images | grep fabdash
Step 5 — Create the Data Directory
mkdir -p /mnt/user/appdata/fabdash/data
Step 6 — Add the Container via Unraid Docker GUI
- Go to the Docker tab → Add Container
- Toggle Advanced View in the top-right of the form
Basic Settings
| Field | Value |
|---|---|
| Name | fabdash |
| Repository | fabdash:latest |
| Docker Hub URL | (leave blank — local image) |
| Network Type | Bridge |
| Console shell command | bash |
| Privileged | Off |
Port Mapping
Add → Port:
| Field | Value |
|---|---|
| Name | Web UI |
| Container Port | 8080 |
| Host Port | 8080 |
| Connection Type | TCP |
Change Host Port if
8080is already in use. Access athttp://YOUR_UNRAID_IP:8080
Volume / Path Mapping
Add → Path:
| Field | Value |
|---|---|
| Name | AppData |
| Container Path | /app/data |
| Host Path | /mnt/user/appdata/fabdash/data |
| Access Mode | Read/Write |
Your database (
fabdash.db) lives here and persists across all rebuilds and reboots.
Environment Variables
Add → Variable for each:
| Name | Key | Value |
|---|---|---|
| Secret Key | SECRET_KEY |
(generate below) |
| Flask Environment | FLASK_ENV |
production |
| Database URL | DATABASE_URL |
sqlite:////app/data/fabdash.db |
Generate a strong SECRET_KEY from the Unraid terminal:
cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w 48 | head -n 1
Extra Parameters
| Field | Value |
|---|---|
| Extra Parameters | --restart unless-stopped |
Step 7 — Apply and Launch
Click Apply. Open your browser: http://YOUR_UNRAID_IP:8080
Step 8 — Verify (Optional)
docker logs fabdash --tail 50
A healthy startup:
[INFO] Starting gunicorn 23.0.0
[INFO] Listening at: http://0.0.0.0:8080
[INFO] Worker booting (pid: ...)
Adding Your Logo
FabDash supports a custom square logo that appears in two places:
- Sidebar header — Next to the "FabDash" wordmark (32×32px,
object-contain) - Collapsed sidebar toggle — Replaces the
▶arrow with a branded square tile (32×32px)
To add your logo:
# Copy your logo into the public directory
cp /path/to/your/logo.png /mnt/user/appdata/fabdash/frontend/public/logo.png
# Rebuild and restart
cd /mnt/user/appdata/fabdash
docker build -t fabdash:latest .
Then restart the container from the Unraid Docker GUI.
Supported formats: .png, .jpg, .svg, .webp — file must be named logo.png
If the file is missing, both image elements silently hide themselves via onError — no broken image icons, no errors.
Updating FabDash on Unraid
Step 1 — Pull latest and rebuild
ssh root@YOUR_UNRAID_IP
cd /mnt/user/appdata/fabdash
git pull
docker build -t fabdash:latest .
Step 2 — Restart via Unraid Docker GUI
Docker tab → click fabdash container icon → Restart
Your database is untouched. New schema columns apply automatically on startup.
Step 3 — Verify
docker logs fabdash --tail 20
Data Backup
/mnt/user/appdata/fabdash/data/fabdash.db
Manual backup:
cp /mnt/user/appdata/fabdash/data/fabdash.db \
/mnt/user/backups/fabdash-$(date +%Y%m%d-%H%M).db
Automated: Use the Appdata Backup/Restore plugin from Unraid Community Applications.
Local Development
Terminal 1 — Flask backend:
cd backend
python -m venv venv
source venv/bin/activate
pip install -r requirements.txt
export FLASK_ENV=development
flask run --port 5000
Terminal 2 — React frontend:
cd frontend
npm install --legacy-peer-deps
npm run dev # http://localhost:5173
Vite proxies all /api/* requests to Flask on port 5000 automatically via vite.config.js.
To use your logo in development, place it at frontend/public/logo.png — Vite serves public/ at the root URL automatically.
Environment Variables
| Variable | Required | Default | Description |
|---|---|---|---|
SECRET_KEY |
Yes | — | Flask session secret. Use a long random string |
FLASK_ENV |
No | production |
Set to development for debug mode |
DATABASE_URL |
No | sqlite:////app/data/fabdash.db |
SQLite path (4 slashes = absolute path) |
Database Schema
CREATE TABLE projects (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
color TEXT NOT NULL DEFAULT '#C9A84C',
description TEXT,
drive_url VARCHAR(500),
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE deliverables (
id INTEGER PRIMARY KEY AUTOINCREMENT,
project_id INTEGER NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
title TEXT NOT NULL,
due_date DATE NOT NULL,
status TEXT NOT NULL DEFAULT 'upcoming'
CHECK(status IN ('upcoming','in_progress','completed','overdue')),
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX idx_deliverables_project ON deliverables(project_id);
CREATE INDEX idx_deliverables_due_date ON deliverables(due_date);
drive_urlis added to existing databases automatically at startup via_run_migrations()inapp/__init__.py.
Auto-Overdue Logic
def effective_status(self):
"""Computed at read time — never modifies the database."""
if self.status != 'completed' and self.due_date < date.today():
return 'overdue'
return self.status
- Past-due + not completed →
overdue completedis never auto-downgraded, even if the date has passed- Stored
statusvalue is preserved; this is read-only logic
Inline Migration Pattern
New columns are applied to existing databases automatically on app startup:
def _run_migrations():
migrations = [
'ALTER TABLE projects ADD COLUMN drive_url VARCHAR(500)',
]
with db.engine.connect() as conn:
for stmt in migrations:
try:
conn.execute(text(stmt))
conn.commit()
except Exception:
pass # Column already exists — safe to ignore
To add a new column in a future version, append its ALTER TABLE statement to the migrations list.
Roadmap
v1.0 — Core Release (current)
- Dark/gold Tailwind design system
- FullCalendar — Month, Week, and Day views
- Drag-and-drop deliverable rescheduling
- Multi-deliverable project creation with inline rows
- Add / Edit / Delete for projects and deliverables
- Color-coded projects (12-swatch palette + custom hex)
- Google Drive folder link per project
- Deliverable Focus View — slide-up drawer, horizontal timeline
- Click any Focus View card to activate; scale-105 clip fix
- Active deliverable gold highlight with "Selected" badge
- Status badges — Upcoming / In Progress / Completed / Overdue
- Auto-overdue detection (computed at API read time, no cron job)
- Right-click context menus — calendar events, sidebar rows, Focus View cards
- Double-click shortcuts — events, sidebar rows, project header, Focus View cards
- Quick status change from Focus View right-click menu
- 30-second undo toast with circular countdown ring after drag-and-drop
- Hover tooltip on calendar events (project, title, date, status)
- Collapsible sidebar with branded logo tile when collapsed
- Custom logo support —
frontend/public/logo.png - Animated modal enter transitions (backdrop + scale + translateY)
- Keyboard shortcuts — N, B, ← →, T, Esc
- Keyboard shortcut legend pinned to sidebar footer
- Workload heatmap — 20-week density grid with stat cards
- Date range drag-select on calendar
- ISO week numbers on calendar
- Agenda / Upcoming panel in sidebar (next 60 days)
- Empty state SVG illustration with keyboard hint
- Flask REST API + SQLite persistence
- Cascade delete (project → deliverables)
- Inline schema migrations (auto-applied on startup)
- Multi-stage Docker build (Node → Python, single container)
- Unraid installation — local build + Docker GUI management
v1.1 — Polish & UX (in progress)
- Animated modal and drawer exit transitions
- Drag-and-drop between projects (reassign deliverable)
- Mini calendar in sidebar for quick date navigation
- Responsive mobile layout
v1.2 — Calendar Enhancements
- Recurring deliverables (weekly, bi-weekly, monthly)
- Bulk status update from heatmap selection
- Print / export calendar view to PDF
- Custom calendar event display (show status color, not just project color)
v2.0 — Auth & Multi-user
- User login (Flask-Login + JWT)
- Multi-user support with project ownership
- Role-based access per project (Owner / Editor / Viewer)
- Activity log per project
- Comment threads on deliverables
v2.1 — Notifications & Integrations
- In-app notification center for approaching due dates
- Email reminders at configurable intervals (Flask-Mail)
- iCal / Google Calendar export per project
- Slack webhook for deliverable status changes
- CSV import/export for bulk project setup
v2.2 — Advanced Views
- Gantt view
- Kanban board (columns by status)
- Cross-project timeline
- Heatmap drill-down — click cell to expand day detail panel
- Archived projects with searchable history
v3.0 — Intelligence Layer
- AI-assisted scheduling suggestions
- Conflict detection — flag overloaded days
- Natural language deliverable input
Built with intention. No subscriptions. No bloat. Just your fabrication workflow.