Add files via upload
This commit is contained in:
406
README.md
406
README.md
@@ -16,9 +16,11 @@
|
||||
- [Project Structure](#project-structure)
|
||||
- [Features](#features)
|
||||
- [Interaction Reference](#interaction-reference)
|
||||
- [Keyboard Shortcuts](#keyboard-shortcuts)
|
||||
- [API Reference](#api-reference)
|
||||
- [Component Architecture](#component-architecture)
|
||||
- [Unraid Installation](#unraid-installation)
|
||||
- [Adding Your Logo](#adding-your-logo)
|
||||
- [Updating FabDash on Unraid](#updating-fabdash-on-unraid)
|
||||
- [Local Development](#local-development)
|
||||
- [Environment Variables](#environment-variables)
|
||||
@@ -29,7 +31,7 @@
|
||||
|
||||
## 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 timeline Focus View, and a rich right-click context menu system. All wrapped in a dark/gold UI and deployed as a single Docker container with no external dependencies.
|
||||
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.
|
||||
|
||||
---
|
||||
|
||||
@@ -42,17 +44,20 @@ FabDash is a self-hosted, full-stack project management dashboard built for fabr
|
||||
| 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) |
|
||||
| @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 events |
|
||||
| react-chrono | 2.6.0 | Focus View timeline (pinned — last React 18 compatible) |
|
||||
| @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 |
|
||||
| date-fns | ^3.6.0 | Date formatting and calculations |
|
||||
|
||||
> **Note:** All FullCalendar packages are pinned to exactly `6.1.15` to prevent version mismatch errors during Docker builds. `react-chrono` is pinned to `2.6.0` — versions ≥ 2.7.0 require React 19.
|
||||
> **Dependency notes:**
|
||||
> - All FullCalendar packages are pinned to exactly `6.1.15` (no `^`) to prevent version mismatch build errors
|
||||
> - `react-chrono` is pinned to `2.6.0` — versions ≥ 2.7.0 require React 19 which breaks the build
|
||||
> - `npm install --legacy-peer-deps` is used in the Dockerfile and local dev to handle peer dependency conflicts
|
||||
|
||||
### Backend
|
||||
|
||||
@@ -67,8 +72,8 @@ FabDash is a self-hosted, full-stack project management dashboard built for fabr
|
||||
### Database
|
||||
|
||||
- **SQLite** — Zero-config, file-based persistence at `/app/data/fabdash.db`
|
||||
- Mounted as a named Docker volume so data survives all container restarts and rebuilds
|
||||
- **Inline migrations** — New columns are applied automatically on startup via `_run_migrations()` in `app/__init__.py`; no manual migration commands required
|
||||
- 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()` in `app/__init__.py`; no `flask db upgrade` commands needed
|
||||
|
||||
---
|
||||
|
||||
@@ -76,7 +81,7 @@ FabDash is a self-hosted, full-stack project management dashboard built for fabr
|
||||
|
||||
```
|
||||
fabdash/
|
||||
├── Dockerfile
|
||||
├── Dockerfile # Multi-stage: Node (React build) → Python (Flask)
|
||||
├── docker-compose.yml
|
||||
├── .env.example
|
||||
├── .gitignore
|
||||
@@ -87,53 +92,61 @@ fabdash/
|
||||
│ ├── config.py
|
||||
│ ├── requirements.txt
|
||||
│ └── app/
|
||||
│ ├── __init__.py # App factory + inline migrations
|
||||
│ ├── __init__.py # App factory + inline schema migrations
|
||||
│ ├── extensions.py
|
||||
│ ├── models.py # Project + Deliverable models
|
||||
│ ├── models.py # Project + Deliverable ORM models
|
||||
│ └── routes/
|
||||
│ ├── __init__.py
|
||||
│ ├── projects.py
|
||||
│ ├── projects.py # CRUD + drive_url support
|
||||
│ └── deliverables.py
|
||||
│
|
||||
└── frontend/
|
||||
├── package.json
|
||||
├── vite.config.js # optimizeDeps for FullCalendar
|
||||
├── 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
|
||||
├── App.jsx # Sidebar layout, keyboard shortcuts, toast container
|
||||
├── api/
|
||||
│ ├── projects.js
|
||||
│ └── deliverables.js
|
||||
├── components/
|
||||
│ ├── Calendar/
|
||||
│ │ └── MainCalendar.jsx # dblclick + right-click on events
|
||||
│ │ ├── 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
|
||||
│ │ ├── ProjectCard.jsx # dblclick + right-click, Drive link
|
||||
│ │ └── ProjectModal.jsx # Drive URL input field
|
||||
│ │ ├── 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 # Click any card to activate
|
||||
│ │ └── DeliverableCard.jsx # dblclick + right-click, status menu
|
||||
│ │ ├── 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
|
||||
│ ├── Modal.jsx # Animated enter transition
|
||||
│ ├── Drawer.jsx
|
||||
│ └── ContextMenu.jsx # Floating right-click menu
|
||||
│ ├── ContextMenu.jsx # Floating right-click menu
|
||||
│ └── Toast.jsx # 30-second undo toast with countdown ring
|
||||
├── store/
|
||||
│ ├── useProjectStore.js
|
||||
│ └── useFocusStore.js # setActiveDeliverable action
|
||||
│ ├── useFocusStore.js # setActiveDeliverable action
|
||||
│ ├── useUIStore.js # sidebarOpen, sidebarTab, showHeatmap
|
||||
│ └── useToastStore.js # Toast queue management
|
||||
├── utils/
|
||||
│ ├── dateHelpers.js
|
||||
│ └── statusHelpers.js
|
||||
└── styles/
|
||||
└── globals.css
|
||||
└── globals.css # FullCalendar dark theme, slide-up animation
|
||||
```
|
||||
|
||||
---
|
||||
@@ -142,67 +155,86 @@ fabdash/
|
||||
|
||||
### Calendar
|
||||
|
||||
- **Large full-width calendar** — Month, Week, and Day views via FullCalendar
|
||||
- **Drag-and-drop rescheduling** — Move any deliverable to a new date; backend patches immediately
|
||||
- **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 press `B` to 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** — Add unlimited deliverables per project, each with its own title, due date, and status
|
||||
- **Inline creation** — Add all deliverables while creating the project in a single form submission
|
||||
- **Color coding** — 12-swatch palette + custom hex input; color appears on calendar events and sidebar cards
|
||||
- **Google Drive link** — Optional Drive folder URL stored per project; appears as a button on the project card and in context menus
|
||||
- **Double-click project header** — Opens Edit Project modal instantly
|
||||
- **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 detection** — Any deliverable whose due date has passed is automatically returned as `overdue` by the API unless it is marked `completed`. Logic is computed at read time — no cron job needed
|
||||
- **Auto-overdue** — Any deliverable whose due date has passed is returned as `overdue` by the API unless marked `completed`. 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** — Triggered by clicking any calendar event or sidebar deliverable
|
||||
- **Horizontal timeline** — All deliverables for the project rendered in date order
|
||||
- **Active card highlighting** — Clicked deliverable glows in gold with a "Selected" badge
|
||||
- **Click any card** — Switches the active/selected card without closing the drawer
|
||||
- **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), Edit, and Delete
|
||||
- **Right-click any card** — Context menu with quick status change (all 4 statuses inline), Edit, and Delete
|
||||
|
||||
### Right-Click Context Menu
|
||||
|
||||
A custom floating `ContextMenu` component appears at the cursor position and auto-adjusts to stay within the viewport. It closes on outside click or `Escape`. Available everywhere:
|
||||
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*, Delete |
|
||||
| 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 |
|
||||
| Focus View card | Edit Deliverable, Mark Upcoming / In Progress / Completed / Overdue, Delete |
|
||||
|
||||
*Only shown when a Drive URL is set on the project.
|
||||
*Only shown when a Google Drive URL is set on the project.
|
||||
|
||||
### Theme & Design System
|
||||
### Undo Toast
|
||||
|
||||
```js
|
||||
// tailwind.config.js — custom tokens
|
||||
colors: {
|
||||
gold: '#C9A84C', // Primary accent
|
||||
'gold-light': '#E8C96A',
|
||||
'gold-muted': '#8A6E2F',
|
||||
surface: '#111111', // Page background
|
||||
'surface-raised': '#1A1A1A', // Sidebar, drawer
|
||||
'surface-elevated': '#242424', // Cards
|
||||
'surface-border': '#2E2E2E', // Borders
|
||||
'text-primary': '#F5F5F5',
|
||||
'text-muted': '#888888',
|
||||
}
|
||||
```
|
||||
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 `requestAnimationFrame` for reliable transitions
|
||||
- **Sidebar slide** — `transition-[width] duration-300` for smooth collapse/expand
|
||||
- **Toast slide-up** — `@keyframes slide-up` animation in `globals.css`
|
||||
- **Heatmap cells** — `hover:scale-125` on each cell for tactile feedback
|
||||
|
||||
---
|
||||
|
||||
@@ -211,11 +243,27 @@ colors: {
|
||||
| Location | Single Click | Double Click | Right Click |
|
||||
|---|---|---|---|
|
||||
| Calendar event | Open Focus View | Edit Deliverable modal | Context menu |
|
||||
| Calendar empty cell | Add Deliverable (pre-filled date) | — | — |
|
||||
| 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 |
|
||||
| Focus View active card Edit button | Edit Deliverable modal | — | — |
|
||||
| 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.
|
||||
|
||||
---
|
||||
|
||||
@@ -240,19 +288,19 @@ colors: {
|
||||
| `PATCH` | `/api/deliverables/:id` | Update title, due_date, or status |
|
||||
| `DELETE` | `/api/deliverables/:id` | Delete deliverable |
|
||||
|
||||
**Example — Create Project:**
|
||||
**Example — Create Project with Drive link:**
|
||||
|
||||
```json
|
||||
POST /api/projects
|
||||
{
|
||||
"name": "CODA",
|
||||
"color": "#4A90D9",
|
||||
"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-11", "status": "upcoming" }
|
||||
{ "title": "Fabricate Shelter Assembly Box", "due_date": "2026-03-10", "status": "upcoming" }
|
||||
]
|
||||
}
|
||||
```
|
||||
@@ -263,24 +311,39 @@ POST /api/projects
|
||||
|
||||
```
|
||||
App
|
||||
├── Sidebar
|
||||
│ ├── ProjectList
|
||||
│ │ └── ProjectCard (× N) ← dblclick + right-click
|
||||
│ │ ├── DeliverableRow (× N) ← dblclick + right-click
|
||||
│ │ ├── DeliverableModal (local)
|
||||
│ │ └── ContextMenu (local)
|
||||
│ └── Button ["+ Project"]
|
||||
├── <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
|
||||
│
|
||||
├── MainCalendar ← FullCalendar + eventDidMount
|
||||
│ ├── FullCalendar events ← dblclick + right-click via DOM
|
||||
├── 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)
|
||||
├── FocusTimeline
|
||||
│ └── DeliverableCard (× N) ← click=select, dblclick=edit, right-click=menu
|
||||
│ └── ContextMenu (local)
|
||||
└── DeliverableModal
|
||||
├── 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
|
||||
```
|
||||
|
||||
---
|
||||
@@ -308,16 +371,27 @@ cd fabdash
|
||||
|
||||
---
|
||||
|
||||
### Step 3 — Build the Docker Image
|
||||
### Step 3 — Add Your Logo (Optional)
|
||||
|
||||
Builds the image on your Unraid server and tags it `fabdash:latest`.
|
||||
First build takes 3–5 minutes (downloads Node + Python layers, compiles React).
|
||||
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
|
||||
|
||||
```bash
|
||||
docker build -t fabdash:latest .
|
||||
```
|
||||
|
||||
Verify the image exists:
|
||||
First build takes 3–5 minutes (downloads Node + Python base layers, compiles React).
|
||||
|
||||
Verify the image:
|
||||
|
||||
```bash
|
||||
docker images | grep fabdash
|
||||
@@ -325,7 +399,7 @@ docker images | grep fabdash
|
||||
|
||||
---
|
||||
|
||||
### Step 4 — Create the Data Directory
|
||||
### Step 5 — Create the Data Directory
|
||||
|
||||
```bash
|
||||
mkdir -p /mnt/user/appdata/fabdash/data
|
||||
@@ -333,11 +407,10 @@ mkdir -p /mnt/user/appdata/fabdash/data
|
||||
|
||||
---
|
||||
|
||||
### Step 5 — Add the Container via Unraid Docker GUI
|
||||
### Step 6 — Add the Container via Unraid Docker GUI
|
||||
|
||||
1. Go to the **Docker** tab in Unraid
|
||||
2. Click **Add Container** at the bottom
|
||||
3. Toggle **Advanced View** in the top-right of the form
|
||||
1. Go to the **Docker** tab → **Add Container**
|
||||
2. Toggle **Advanced View** in the top-right of the form
|
||||
|
||||
#### Basic Settings
|
||||
|
||||
@@ -352,7 +425,7 @@ mkdir -p /mnt/user/appdata/fabdash/data
|
||||
|
||||
#### Port Mapping
|
||||
|
||||
Click **Add another Path, Port, Variable, Label or Device** → **Port**:
|
||||
**Add → Port:**
|
||||
|
||||
| Field | Value |
|
||||
|---|---|
|
||||
@@ -361,11 +434,11 @@ Click **Add another Path, Port, Variable, Label or Device** → **Port**:
|
||||
| **Host Port** | `8080` |
|
||||
| **Connection Type** | `TCP` |
|
||||
|
||||
> Change Host Port if `8080` is already in use. Access FabDash at `http://YOUR_UNRAID_IP:8080`
|
||||
> Change Host Port if `8080` is already in use. Access at `http://YOUR_UNRAID_IP:8080`
|
||||
|
||||
#### Volume / Path Mapping
|
||||
|
||||
Click **Add another Path, Port, Variable, Label or Device** → **Path**:
|
||||
**Add → Path:**
|
||||
|
||||
| Field | Value |
|
||||
|---|---|
|
||||
@@ -374,11 +447,11 @@ Click **Add another Path, Port, Variable, Label or Device** → **Path**:
|
||||
| **Host Path** | `/mnt/user/appdata/fabdash/data` |
|
||||
| **Access Mode** | `Read/Write` |
|
||||
|
||||
> Your database (`fabdash.db`) lives here and survives all rebuilds and reboots.
|
||||
> Your database (`fabdash.db`) lives here and persists across all rebuilds and reboots.
|
||||
|
||||
#### Environment Variables
|
||||
|
||||
Add each as **Variable**:
|
||||
**Add → Variable** for each:
|
||||
|
||||
| Name | Key | Value |
|
||||
|---|---|---|
|
||||
@@ -400,23 +473,19 @@ cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w 48 | head -n 1
|
||||
|
||||
---
|
||||
|
||||
### Step 6 — Apply and Launch
|
||||
### Step 7 — Apply and Launch
|
||||
|
||||
Click **Apply**. The container starts using your locally built image.
|
||||
|
||||
Open your browser: **`http://YOUR_UNRAID_IP:8080`**
|
||||
Click **Apply**. Open your browser: **`http://YOUR_UNRAID_IP:8080`**
|
||||
|
||||
---
|
||||
|
||||
### Step 7 — Verify (Optional)
|
||||
|
||||
Check logs from the Unraid Docker tab → click `fabdash` → **Logs**, or via SSH:
|
||||
### Step 8 — Verify (Optional)
|
||||
|
||||
```bash
|
||||
docker logs fabdash --tail 50
|
||||
```
|
||||
|
||||
A healthy startup looks like:
|
||||
A healthy startup:
|
||||
|
||||
```
|
||||
[INFO] Starting gunicorn 23.0.0
|
||||
@@ -426,9 +495,35 @@ A healthy startup looks like:
|
||||
|
||||
---
|
||||
|
||||
## Adding Your Logo
|
||||
|
||||
FabDash supports a custom square logo that appears in two places:
|
||||
|
||||
1. **Sidebar header** — Next to the "FabDash" wordmark (32×32px, `object-contain`)
|
||||
2. **Collapsed sidebar toggle** — Replaces the `▶` arrow with a branded square tile (32×32px)
|
||||
|
||||
**To add your logo:**
|
||||
|
||||
```bash
|
||||
# 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 code and rebuild
|
||||
### Step 1 — Pull latest and rebuild
|
||||
|
||||
```bash
|
||||
ssh root@YOUR_UNRAID_IP
|
||||
@@ -439,10 +534,9 @@ docker build -t fabdash:latest .
|
||||
|
||||
### Step 2 — Restart via Unraid Docker GUI
|
||||
|
||||
Go to **Docker** tab → click the `fabdash` container icon → **Restart**
|
||||
**Docker** tab → click `fabdash` container icon → **Restart**
|
||||
|
||||
The container reloads with the new image. Your database is untouched.
|
||||
New schema columns (if any) are applied automatically on startup.
|
||||
Your database is untouched. New schema columns apply automatically on startup.
|
||||
|
||||
### Step 3 — Verify
|
||||
|
||||
@@ -454,8 +548,6 @@ docker logs fabdash --tail 20
|
||||
|
||||
## Data Backup
|
||||
|
||||
Your database lives at:
|
||||
|
||||
```
|
||||
/mnt/user/appdata/fabdash/data/fabdash.db
|
||||
```
|
||||
@@ -467,7 +559,7 @@ cp /mnt/user/appdata/fabdash/data/fabdash.db \
|
||||
/mnt/user/backups/fabdash-$(date +%Y%m%d-%H%M).db
|
||||
```
|
||||
|
||||
**Automated backup:** Use the **Appdata Backup/Restore** plugin from Unraid Community Applications to schedule regular backups of `/mnt/user/appdata/fabdash/`.
|
||||
**Automated:** Use the **Appdata Backup/Restore** plugin from Unraid Community Applications.
|
||||
|
||||
---
|
||||
|
||||
@@ -478,7 +570,7 @@ cp /mnt/user/appdata/fabdash/data/fabdash.db \
|
||||
```bash
|
||||
cd backend
|
||||
python -m venv venv
|
||||
source venv/bin/activate # Windows: venv\Scripts\activate
|
||||
source venv/bin/activate
|
||||
pip install -r requirements.txt
|
||||
export FLASK_ENV=development
|
||||
flask run --port 5000
|
||||
@@ -489,10 +581,12 @@ flask run --port 5000
|
||||
```bash
|
||||
cd frontend
|
||||
npm install --legacy-peer-deps
|
||||
npm run dev # http://localhost:5173
|
||||
npm run dev # http://localhost:5173
|
||||
```
|
||||
|
||||
Vite proxies all `/api/*` requests to Flask on port 5000 via `vite.config.js`. No CORS configuration needed in development.
|
||||
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.
|
||||
|
||||
---
|
||||
|
||||
@@ -501,8 +595,8 @@ Vite proxies all `/api/*` requests to Flask on port 5000 via `vite.config.js`. N
|
||||
| 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 and auto-reload |
|
||||
| `DATABASE_URL` | No | `sqlite:////app/data/fabdash.db` | Full SQLite path (4 slashes = absolute path) |
|
||||
| `FLASK_ENV` | No | `production` | Set to `development` for debug mode |
|
||||
| `DATABASE_URL` | No | `sqlite:////app/data/fabdash.db` | SQLite path (4 slashes = absolute path) |
|
||||
|
||||
---
|
||||
|
||||
@@ -510,12 +604,12 @@ Vite proxies all `/api/*` requests to Flask on port 5000 via `vite.config.js`. N
|
||||
|
||||
```sql
|
||||
CREATE TABLE projects (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
name TEXT NOT NULL,
|
||||
color TEXT NOT NULL DEFAULT '#C9A84C',
|
||||
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
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE TABLE deliverables (
|
||||
@@ -532,22 +626,41 @@ CREATE INDEX idx_deliverables_project ON deliverables(project_id);
|
||||
CREATE INDEX idx_deliverables_due_date ON deliverables(due_date);
|
||||
```
|
||||
|
||||
> `drive_url` is applied to existing databases automatically via the inline migration in `app/__init__.py` — no manual SQL required.
|
||||
> `drive_url` is added to existing databases automatically at startup via `_run_migrations()` in `app/__init__.py`.
|
||||
|
||||
### Auto-Overdue Logic
|
||||
|
||||
The `Deliverable.effective_status()` method is called on every API read:
|
||||
|
||||
```python
|
||||
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 → returns `overdue` to the frontend
|
||||
- `completed` deliverables are never auto-downgraded regardless of date
|
||||
- The stored database value is never modified by this logic
|
||||
- Past-due + not completed → `overdue`
|
||||
- `completed` is never auto-downgraded, even if the date has passed
|
||||
- Stored `status` value is preserved; this is read-only logic
|
||||
|
||||
### Inline Migration Pattern
|
||||
|
||||
New columns are applied to existing databases automatically on app startup:
|
||||
|
||||
```python
|
||||
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.
|
||||
|
||||
---
|
||||
|
||||
@@ -563,35 +676,44 @@ def effective_status(self):
|
||||
- [x] Color-coded projects (12-swatch palette + custom hex)
|
||||
- [x] Google Drive folder link per project
|
||||
- [x] Deliverable Focus View — slide-up drawer, horizontal timeline
|
||||
- [x] Click any Focus View card to activate/select it
|
||||
- [x] Active deliverable gold highlight in Focus View
|
||||
- [x] Click any Focus View card to activate; scale-105 clip fix
|
||||
- [x] Active deliverable gold highlight with "Selected" badge
|
||||
- [x] Status badges — Upcoming / In Progress / Completed / Overdue
|
||||
- [x] Auto-overdue detection (computed at API read time, no cron job)
|
||||
- [x] Right-click context menus — calendar events, sidebar rows, Focus View cards
|
||||
- [x] Double-click shortcuts — events, sidebar rows, project header, Focus View cards
|
||||
- [x] Quick status change from Focus View right-click menu
|
||||
- [x] 30-second undo toast with circular countdown ring after drag-and-drop
|
||||
- [x] Hover tooltip on calendar events (project, title, date, status)
|
||||
- [x] Collapsible sidebar with branded logo tile when collapsed
|
||||
- [x] Custom logo support — `frontend/public/logo.png`
|
||||
- [x] Animated modal enter transitions (backdrop + scale + translateY)
|
||||
- [x] Keyboard shortcuts — N, B, ← →, T, Esc
|
||||
- [x] Keyboard shortcut legend pinned to sidebar footer
|
||||
- [x] Workload heatmap — 20-week density grid with stat cards
|
||||
- [x] Date range drag-select on calendar
|
||||
- [x] ISO week numbers on calendar
|
||||
- [x] Agenda / Upcoming panel in sidebar (next 60 days)
|
||||
- [x] Empty state SVG illustration with keyboard hint
|
||||
- [x] Flask REST API + SQLite persistence
|
||||
- [x] Cascade delete (project → deliverables)
|
||||
- [x] Inline schema migrations (no manual flask db upgrade needed)
|
||||
- [x] Single Docker container deployment (multi-stage build)
|
||||
- [x] Inline schema migrations (auto-applied on startup)
|
||||
- [x] Multi-stage Docker build (Node → Python, single container)
|
||||
- [x] Unraid installation — local build + Docker GUI management
|
||||
|
||||
### v1.1 — Polish & UX
|
||||
### v1.1 — Polish & UX *(in progress)*
|
||||
|
||||
- [ ] Keyboard shortcuts (`N` = new project, `Esc` = close modal/drawer, arrow keys = calendar nav)
|
||||
- [ ] 30-second undo toast for drag-and-drop moves
|
||||
- [ ] Animated modal and drawer enter/exit transitions
|
||||
- [ ] Hover tooltip on calendar events (preview without opening Focus View)
|
||||
- [ ] Responsive layout with collapsible sidebar
|
||||
- [ ] Empty state illustrations
|
||||
- [ ] 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
|
||||
|
||||
- [ ] Agenda sidebar showing all upcoming deliverables across projects
|
||||
- [ ] Date range selection for bulk deliverable creation
|
||||
- [ ] "Today" jump button
|
||||
- [ ] Week numbers in calendar header
|
||||
- [ ] Mini calendar thumbnail for quick date navigation
|
||||
- [ ] 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
|
||||
|
||||
@@ -611,17 +733,17 @@ def effective_status(self):
|
||||
|
||||
### v2.2 — Advanced Views
|
||||
|
||||
- [ ] Gantt view alternate layout
|
||||
- [ ] Gantt view
|
||||
- [ ] Kanban board (columns by status)
|
||||
- [ ] Cross-project timeline view
|
||||
- [ ] Workload heatmap — deliverable density per day
|
||||
- [ ] 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 based on project cadence
|
||||
- [ ] AI-assisted scheduling suggestions
|
||||
- [ ] Conflict detection — flag overloaded days
|
||||
- [ ] Natural language deliverable input ("Add final draft due next Friday to CODA")
|
||||
- [ ] Natural language deliverable input
|
||||
|
||||
---
|
||||
|
||||
|
||||
Reference in New Issue
Block a user