chartboard/TASKS.md
2026-03-30 09:51:33 +05:30

622 lines
37 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 📋 Chart-Board — Phased Work & Task Breakdown
> **Project:** Chart-Board (CodeIgniter 4)
> **Stack:** PHP 8.1 · CI4 · MySQL 8 · Bootstrap 5 · ApexCharts.js · Alpine.js
> **Total Estimated Duration:** ~14 Weeks (Solo) / ~7 Weeks (2-Dev Team)
---
## 📌 Legend
| Symbol | Meaning |
|--------|---------|
| 🔴 | Blocker — must be done before next task |
| 🟡 | Important — high priority |
| 🟢 | Normal priority |
| ⚙️ | Backend task |
| 🎨 | Frontend task |
| 🧪 | Testing task |
| 📄 | Documentation task |
---
## ──────────────────────────────────────────
## PHASE 1 — Project Foundation & Setup
### Estimated Time: 34 Days
## ──────────────────────────────────────────
### 1.1 Environment & Scaffolding
- [x] 🔴 ⚙️ Install CodeIgniter 4 via Composer (`composer create-project codeigniter4/appstarter chart-board`)
- [x] 🔴 ⚙️ Configure `.env` file — `baseURL`, `database.*`, `CI_ENVIRONMENT`
- [X] 🔴 ⚙️ Set up MySQL database `chartboard` with `utf8mb4` charset
- [ ] 🔴 ⚙️ Run the full `chartboard.sql` schema to create all 15+ tables
- [x] 🟡 ⚙️ Configure `app/Config/Database.php` for MySQL connection
- [x] 🟡 ⚙️ Set up `app/Config/App.php` — timezone, base URL, session settings
- [x] 🟢 ⚙️ Configure `app/Config/Cache.php` — file-based cache for dev, Redis for prod
- [x] 🟢 ⚙️ Configure `app/Config/Email.php` — SMTP settings for alerts/verification
- [x] 🟢 ⚙️ Set writable directory permissions (`chmod -R 777 writable/`)
- [x] 🟢 📄 Create `.gitignore` — exclude `.env`, `writable/`, `vendor/`
### 1.2 Front-End Base Setup
- [x] 🔴 🎨 Integrate Bootstrap 5 via CDN or npm build pipeline
- [x] 🔴 🎨 Integrate ApexCharts.js via CDN
- [x] 🟡 🎨 Integrate Alpine.js for lightweight reactivity
- [x] 🟡 🎨 Create base layout file `app/Views/layouts/main.php` — sidebar + topbar + content slot
- [x] 🟡 🎨 Create `app/Views/layouts/auth.php` — centered card layout for login/register
- [x] 🟢 🎨 Set up global CSS variables file `public/assets/css/variables.css`
- [x] 🟢 🎨 Set up global JS file `public/assets/js/app.js` — sidebar toggle, toast, helpers
- [x] 🟢 🎨 Add favicon, logo mark assets to `public/assets/images/`
### 1.3 CI4 Structure Setup
- [x] 🟡 ⚙️ Create base `BaseController.php` — set shared data (user session, workspace)
- [x] 🟡 ⚙️ Create `app/Config/Routes.php` skeleton — group routes by module
- [x] 🟡 ⚙️ Create `AuthFilter.php` — redirect unauthenticated users to login
- [x] 🟡 ⚙️ Create `RoleFilter.php` — check workspace role before allowing access
- [x] 🟢 ⚙️ Create `ApiAuthFilter.php` — validate `Authorization: Bearer` token for API routes
- [x] 🟢 ⚙️ Set up CI4 Encryption service config (`app/Config/Encryption.php`)
- [x] 🟢 ⚙️ Generate encryption key with `php spark key:generate`
---
## ──────────────────────────────────────────
## PHASE 2 — Authentication & User Management
### Estimated Time: 45 Days
## ──────────────────────────────────────────
### 2.1 User Model & Migration
- [x] 🔴 ⚙️ Create `UserModel.php` — CRUD, soft delete, findByEmail, findByApiToken
- [x] 🟡 ⚙️ Create `Migration_CreateUsersTable.php` (already in SQL; create CI4 migration file)
- [x] 🟢 ⚙️ Create `InitialSeeder.php` — seed default super admin + default workspace
### 2.2 Registration
- [x] 🔴 ⚙️ `Auth/RegisterController.php` — show form, validate input, hash password, save user
- [x] 🔴 ⚙️ Generate `verify_token`, send verification email on registration
- [x] 🟡 🎨 `app/Views/auth/register.php` — registration form with name, email, password, confirm password
- [x] 🟡 ⚙️ `Auth/RegisterController::verify()` — handle email verification token link
- [x] 🟢 🎨 Show success flash message after registration
- [ ] 🟢 🧪 Test: register with valid data, duplicate email, weak password
### 2.3 Login & Logout
- [x] 🔴 ⚙️ `Auth/LoginController.php` — validate credentials, check `email_verified`, start session
- [x] 🔴 ⚙️ Store user data in CI4 session: `user_id`, `name`, `email`, `role`
- [x] 🔴 🎨 `app/Views/auth/login.php` — email + password form, remember me checkbox
- [x] 🟡 ⚙️ `Auth/LoginController::logout()` — destroy session, redirect to login
- [x] 🟡 ⚙️ Insert record into `user_sessions` on login, delete on logout
- [ ] 🟢 🧪 Test: wrong password, unverified email, active session redirect
### 2.4 Password Reset
- [x] 🟡 ⚙️ `Auth/PasswordController::forgot()` — generate `reset_token`, set `reset_token_expiry`, send email
- [x] 🟡 ⚙️ `Auth/PasswordController::reset()` — validate token, check expiry, update password
- [x] 🟡 🎨 `app/Views/auth/forgot.php` — email input form
- [x] 🟡 🎨 `app/Views/auth/reset.php` — new password + confirm form
- [ ] 🟢 🧪 Test: expired token, already-used token, mismatched passwords
### 2.5 User Profile
- [x] 🟡 ⚙️ `ProfileController.php` — show profile, update name/avatar, change password
- [x] 🟡 🎨 `app/Views/profile/index.php` — profile card with avatar upload
- [x] 🟡 ⚙️ Handle avatar image upload to `writable/uploads/avatars/`
- [x] 🟡 ⚙️ `ProfileController::generateApiToken()` — create/rotate personal API token, save hashed
- [x] 🟢 🎨 Show/copy API token UI with regenerate button
- [ ] 🟢 🧪 Test: avatar upload size limits, password mismatch, token regeneration
### 2.6 Super Admin — User Management
- [x] 🟡 ⚙️ `Admin/UserController.php` — list all users, activate/deactivate, change role
- [x] 🟡 🎨 `app/Views/admin/users/index.php` — paginated table with search and status filter
- [x] 🟢 🎨 `app/Views/admin/users/edit.php` — edit user role and active status
- [ ] 🟢 🧪 Test: deactivate user blocks login, role change reflects immediately
---
## ──────────────────────────────────────────
## PHASE 3 — Workspace Management
### Estimated Time: 34 Days
## ──────────────────────────────────────────
### 3.1 Workspace CRUD
- [x] 🔴 ⚙️ `WorkspaceController.php` — create, read, update, delete workspaces
- [x] 🔴 ⚙️ `WorkspaceModel.php` — with soft delete, slug generation, owner filter
- [x] 🔴 🎨 `app/Views/workspace/index.php` — workspace list/grid with create button
- [x] 🟡 🎨 `app/Views/workspace/create.php` — name, description, timezone, logo upload form
- [x] 🟡 🎨 `app/Views/workspace/settings.php` — edit workspace details
- [x] 🟡 ⚙️ Auto-generate unique slug from workspace name on creation
- [x] 🟡 ⚙️ On workspace creation, auto-insert creator as `workspace_members` with role `admin`
- [x] 🟢 ⚙️ Soft delete workspace — cascade to members, data sources, charts, dashboards
- [ ] 🟢 🧪 Test: duplicate slug, logo upload, owner-only delete restriction
### 3.2 Members & Invitations
- [x] 🔴 ⚙️ `WorkspaceMemberController.php` — list members, change role, remove member
- [x] 🔴 ⚙️ `WorkspaceInvitationController.php` — send invite email, accept invite, cancel invite
- [x] 🟡 🎨 `app/Views/workspace/members.php` — members table with role dropdown and remove button
- [x] 🟡 🎨 `app/Views/workspace/invite.php` — email + role form, pending invites list
- [x] 🟡 ⚙️ Generate secure `token` for invite, store in `workspace_invitations`, set expiry (48h)
- [x] 🟡 ⚙️ Public route `/invite/{token}` — if user exists log them in; else redirect to register
- [x] 🟢 ⚙️ Prevent inviting existing members, prevent duplicate pending invites
- [ ] 🟢 🧪 Test: expired token, already-accepted token, role change enforcement
### 3.3 Workspace Context Switching
- [x] 🟡 ⚙️ Store `active_workspace_id` in session, set on login/switch
- [x] 🟡 🎨 Workspace switcher dropdown in sidebar — list user's workspaces, highlight active
- [x] 🟢 ⚙️ Middleware: validate user is member of active workspace on every request
- [ ] 🟢 🧪 Test: user with 0 workspaces, switching while on a dashboard page
---
## ──────────────────────────────────────────
## PHASE 4 — Data Source Connections
### Estimated Time: 56 Days
## ──────────────────────────────────────────
### 4.1 Data Source Model & Encryption
- [x] 🔴 ⚙️ `DataSourceModel.php` — CRUD, filter by workspace, soft delete
- [x] 🔴 ⚙️ `Libraries/Encrypter.php` — wrap CI4 Encryption to encrypt/decrypt credentials
- [x] 🔴 ⚙️ Encrypt `password`, `api_auth_value` fields before saving; decrypt on retrieval
- [ ] 🟢 🧪 Test: encrypted values are not plain text in DB, decryption returns correct value
### 4.2 Connection UI
- [x] 🔴 🎨 `app/Views/datasource/index.php` — list all data sources with type icon, status badge
- [x] 🔴 🎨 `app/Views/datasource/create.php` — dynamic form (type selector shows/hides fields)
- [x] 🟡 🎨 Alpine.js: show MySQL/PostgreSQL fields when DB type selected; show API fields for REST API
- [x] 🟡 🎨 `app/Views/datasource/edit.php` — edit form with masked password field
- [x] 🟡 🎨 Connection status badge — Untested / Connected (green) / Failed (red)
- [x] 🟢 🎨 Delete confirmation modal
### 4.3 Connection Drivers
- [x] 🔴 ⚙️ `Libraries/Connectors/MySQLConnector.php` — connect via PDO, run test query `SELECT 1`
- [x] 🔴 ⚙️ `Libraries/Connectors/PostgreSQLConnector.php` — connect via PDO pgsql
- [x] 🟡 ⚙️ `Libraries/Connectors/MongoDBConnector.php` — connect via MongoDB PHP library URI
- [x] 🟡 ⚙️ `Libraries/Connectors/RestApiConnector.php` — cURL GET/POST with auth headers
- [x] 🟡 ⚙️ `Libraries/Connectors/CsvConnector.php` — parse uploaded CSV into in-memory array
- [x] 🟢 ⚙️ `Libraries/ConnectionFactory.php` — factory to return correct connector by type
- [ ] 🟢 🧪 Test: each connector with valid/invalid credentials
### 4.4 Test Connection Endpoint
- [x] 🔴 ⚙️ `POST /datasource/test` (AJAX) — instantiate connector, run test, return JSON `{success, message}`
- [x] 🔴 🎨 "Test Connection" button with spinner; show success/error inline below button
- [x] 🟡 ⚙️ Update `status` and `last_tested_at` in DB after test
- [ ] 🟢 🧪 Test: timeout handling (set cURL timeout 10s), wrong host, wrong credentials
### 4.5 Schema Browser (for Query Builder)
- [x] 🟡 ⚙️ `GET /datasource/{id}/schema` (AJAX) — return tables list and columns per table as JSON
- [ ] 🟡 🎨 Schema sidebar in query builder — collapsible tree: Tables → Columns with types
- [x] 🟢 ⚙️ Cache schema response for 5 minutes per data source
- [ ] 🟢 🧪 Test: DB with 100+ tables, special characters in column names
---
## ──────────────────────────────────────────
## PHASE 5 — Query Builder
### Estimated Time: 56 Days
**Completion note:** Core MVP items are implemented (unified create/edit form, three query modes, multi-filter / multiorder-by visual builder, variables for raw + visual, API headers in `api_params`, preview table + execution log, query cache). Open: CodeMirror editor, datasource schema tree in UI, visual aggregates + GROUP BY controls, chart/dashboard variable wiring (Phase 6), and listed tests.
---
### 5.1 Saved Query Model
- [x] 🔴 ⚙️ `SavedQueryModel.php` — CRUD, filter by workspace and data source, soft delete
- [ ] 🟢 🧪 Test: save and retrieve query with complex JSON config
### 5.2 Visual Query Builder (No-Code)
- [x] 🔴 🎨 Visual mode in `app/Views/query/_form.php` (create/edit) — table name input + comma-separated columns *(no separate `visual.php`; schema dropdown / per-column checkboxes not implemented)*
- [ ] 🔴 🎨 Schema-driven table dropdown + column checkboxes with optional aliases
- [x] 🟡 🎨 Filter builder — add/remove filter rows: field, operator (=, !=, >, <, LIKE, IS NULL, IS NOT NULL), value
- [ ] 🟡 🎨 Aggregate row — apply COUNT/SUM/AVG/MIN/MAX to numeric columns
- [ ] 🟡 🎨 GROUP BY selector — multi-select *(backend `QueryBuilder::toSQL()` supports `group_by[]`; no UI yet)*
- [x] 🟡 🎨 ORDER BY — multiple sort columns, each with ASC/DESC + add/remove rows
- [x] 🟡 🎨 LIMIT input — max rows (default 500); supports `{{variable}}` in visual fields
- [x] 🟡 ⚙️ `QueryBuilder::toSQL()` — convert visual config JSON to safe parameterized SQL
- [x] 🟢 ⚙️ Prevent destructive keywords: block `DROP`, `DELETE`, `UPDATE`, `INSERT`, `TRUNCATE` in generated SQL
- [ ] 🟢 🧪 Test: multi-filter query, aggregate with group by, null filter
### 5.3 Raw SQL Mode
- [ ] 🔴 🎨 CodeMirror 6 editor integration — SQL syntax highlighting, auto-complete (table/column names) *(currently plain textarea in `_form.php`)*
- [x] 🔴 🎨 Mode selector — Raw SQL / Visual builder / API query
- [x] 🟡 ⚙️ SQL safety check before execution — regex/parse to block DDL/DML mutations
- [x] 🟡 ⚙️ `QueryController::execute()` — run sanitized SQL on the selected data source, return JSON results
- [x] 🟢 ⚙️ Enforce query timeout — kill query after 30 seconds
- [ ] 🟢 🧪 Test: malicious SQL injection attempt, timeout simulation, empty result set
### 5.4 Query Variables
- [x] 🔴 ⚙️ `Libraries/QueryVariableParser.php` — scan query string for `{{ var_name }}` pattern using regex, return list of variable names
- [x] 🔴 ⚙️ `Libraries/QueryVariableResolver.php` — resolve system variables (`{{today}}`, `{{now}}`, etc.) and substitute user values via PDO bindings; `resolveTemplateString()` for visual builder identifiers/literals
- [x] 🔴 🎨 Variable panel — Raw SQL + Visual builder: detect / configure / test values; hidden for API mode on create/edit
- [x] 🔴 🎨 Variable config form per detected variable:
- Label (display name shown to end user)
- Type: `text` / `number` / `date` / `date_range` / `select` / `multi_select`
- Default value
- For `select`/`multi_select`: options list (comma-separated or from another query)
- Required toggle
- [x] 🟡 ⚙️ Persist variable definitions in `query_variables` table (per `saved_query_id`) on save/update *(dashboard/chart `display_config` integration pending Phase 6)*
- [ ] 🟡 🎨 Dashboard view — render a variable input widget per variable above each chart that has variables defined:
- `text``<input type="text">`
- `number``<input type="number">`
- `date` → date picker
- `date_range` → dual date range picker
- `select` → dropdown
- `multi_select` → multi-select dropdown with checkboxes
- [ ] 🟡 🎨 On variable value change → re-fetch chart data AJAX with new values, re-render chart without page reload
- [ ] 🟡 ⚙️ Global dashboard filter — if multiple charts share a variable with the same name, a single widget controls all of them simultaneously
- [x] 🟡 ⚙️ Resolve built-in system variables server-side before query execution (no user input needed for these)
- [x] 🟢 🎨 Variable widget in Query Builder preview — show input fields for each detected variable so creator can test values before saving
- [x] 🟢 ⚙️ `multi_select` in raw SQL — `QueryVariableResolver::resolve()` expands array values to multiple `?` placeholders (use inside `IN ({{var}})` in SQL)
- [ ] 🟢 ⚙️ Public shared dashboards — variable widgets still visible and functional for anonymous viewers
- [ ] 🟢 🧪 Test: SQL injection attempt via variable value is blocked by PDO binding; date variable resolves correctly; missing required variable shows validation error; multi-select generates correct `IN` clause
### 5.5 API Query Builder
- [x] 🟡 🎨 API query form — endpoint URL, JSON path, grouped sections (headers + field map with add/remove rows)
- [x] 🟡 🎨 HTTP headers — repeatable rows; persisted in `saved_queries.api_params` as JSON `{ "headers": [...] }`
- [x] 🟡 🎨 JSON path input — e.g. `data.results` to extract nested array
- [x] 🟡 🎨 Field map — source JSON key → column alias rows
- [x] 🟡 ⚙️ `Libraries/ApiConnector::fetch()` — variable substitution on URL, headers, extract by JSON path
- [ ] 🟢 🧪 Test: nested JSON path, missing field graceful fallback, invalid URL
### 5.6 Query Preview & Result Table
- [x] 🔴 🎨 "Run Query" button — AJAX call, show spinner while loading
- [x] 🔴 🎨 Result preview table — first 100 rows, dynamic columns, sortable headers, pagination, CSV download, execution log tab
- [x] 🟡 🎨 Row count badge, execution time badge
- [x] 🟡 🎨 Save flow — name/description/meta on same form as builder; POST to store/update `saved_queries`
- [x] 🟢 🎨 Empty state — centered “No rows returned” message in preview *(illustration asset optional)*
- [ ] 🟢 🧪 Test: 0 rows, 1000+ rows truncated, columns with special characters
### 5.7 Query Cache
- [x] 🟡 ⚙️ After execution, store result JSON in `query_cache` with MD5 cache key and TTL
- [x] 🟡 ⚙️ On next execution, check `query_cache` first; serve from cache if not expired
- [x] 🟢 ⚙️ CI4 Cron task: `DeleteExpiredQueryCache` — run every 30 minutes to purge expired rows
- [ ] 🟢 🧪 Test: cache hit serves faster, cache miss goes to DB, expired cache re-fetches
---
## ──────────────────────────────────────────
## PHASE 6 — Chart Builder
### Estimated Time: 67 Days
**Completion note:** Core MVP is in place: `ChartModel`, `SavedQueryRunner`, `ChartRenderer`, `ChartController`, multi-step builder (saved query first — no separate data-source step), ApexCharts live preview (`preview-query` / `preview-render`), `POST /chart/{id}/data` with CSRF refresh in JSON, chart list with Edit / Duplicate / Delete. Open: `public_token` generation, saved-query modal vs dropdown (dropdown implemented), unsaved-changes guard, table pagination/sort, legend position + custom hex + X-axis date format in UI, client `setInterval` refresh + spinner, search/filter, listed tests.
---
### 6.1 Chart Model
- [x] 🔴 ⚙️ `ChartModel.php` — CRUD, filter by workspace, soft delete
- [ ] 🟡 ⚙️ Public token generator (`is_public` / `public_token`) — pending (sharing phase)
- [ ] 🟢 🧪 Test: create chart with JSON display_config, retrieve and parse correctly
### 6.2 Chart Builder UI — Step Flow
- [x] 🔴 🎨 Multi-step chart builder UI (Step 1: Saved query → Type → Fields → Style → Preview & save)
- [x] 🔴 🎨 Step 1 — Choose saved query + run preview *(data source implied by query; no separate source step)*
- [x] 🔴 🎨 Step 2 — Chart type selector grid (12 types with icons)
- [x] 🟡 🎨 Step 3 — Field mapping: X-axis, Y-axis, Group By, Value (+ combo second metric); columns from preview
- [x] 🟡 🎨 Step 4 — Display settings: title, subtitle, palette presets, legend toggle, data label toggle, Y-axis min/max, number format, grid / smooth / stacked / horizontal bar
- [x] 🟡 🎨 Step navigation — Back / Next / Save; validation before advancing
- [x] 🟡 🎨 Live preview — ApexCharts with real query data (`chart/preview-render`)
- [ ] 🟢 🎨 "Use Saved Query" button — modal to pick query *(dropdown on Step 1 covers pick-from-list)*
- [ ] 🟢 🎨 Unsaved changes warning on browser back/close
### 6.3 Chart Rendering Engine
- [x] 🔴 ⚙️ `Libraries/ChartRenderer.php` — chart config + rows → Apex-compatible options (`buildPayload`)
- [x] 🔴 ⚙️ Renderer: `bar`, `line`, `area`, `pie`, `donut`
- [x] 🟡 ⚙️ Renderer: `scatter`, `kpi_card`, `funnel`, `gauge`, `heatmap`, `combo`
- [ ] 🟡 ⚙️ `table` — paginated HTML table with sort *(basic truncated table only)*
- [x] 🟢 ⚙️ Number formatter — currency (₹/$), percentage, decimals, K/M/B (server + client)
- [ ] 🟢 🧪 Test: each chart type with realistic data, empty data, single-row data
### 6.4 Chart Display Settings
- [x] 🟡 🎨 Color palette presets (Ocean, Forest, Sunset, Mono) *(custom hex input — open)*
- [ ] 🟡 🎨 Legend position selector (top/bottom/left/right/none) in builder UI
- [x] 🟡 🎨 Refresh interval dropdown (Manual / 1 min / 5 min / 15 min / 1 hr) *(1 day option — open)*
- [ ] 🟢 🎨 Date format selector for time-series X-axis (dd/MM, MMM dd, MMM yyyy)
- [x] 🟢 🎨 Stacked bar/area toggle
### 6.5 Chart Auto-Refresh
- [ ] 🟡 🎨 JavaScript: `refresh_interval > 0``setInterval` re-fetch
- [x] 🟡 ⚙️ `POST /chart/{id}/data` — re-run query, JSON render payload *(POST + `variables_json` for CSRF/vars; spec listed GET)*
- [ ] 🟢 🎨 Refresh spinner overlay while loading
- [ ] 🟢 🧪 Test: 1-minute refresh updates data without full page reload
### 6.6 Chart List & Management
- [x] 🟡 🎨 `app/Views/chart/index.php` — card grid, type, last updated, data source + query name
- [x] 🟡 🎨 Chart card actions — Edit, Duplicate, Delete *(Add to Dashboard / Share — Phase 7+)*
- [x] 🟡 ⚙️ `ChartController::duplicate()` — clone with name suffix `(copy)`
- [ ] 🟢 🎨 Search and filter charts by type or data source
- [ ] 🟢 🧪 Test: duplicate preserves all config, delete removes from all dashboards
---
## ──────────────────────────────────────────
## PHASE 7 — Dashboard Builder
### Estimated Time: 67 Days
## ──────────────────────────────────────────
### 7.1 Dashboard Model
- [ ] 🔴 ⚙️ `DashboardModel.php` — CRUD, filter by workspace, soft delete, public token generator
- [ ] 🔴 ⚙️ `DashboardWidgetModel.php` — CRUD widgets per dashboard, store grid position
- [ ] 🟢 🧪 Test: save layout_config JSON and retrieve widget positions correctly
### 7.2 Dashboard List
- [ ] 🔴 🎨 `app/Views/dashboard/index.php` — card grid of dashboards with pinned section at top
- [ ] 🟡 🎨 Dashboard card — name, description, chart count, last updated, share status badge
- [ ] 🟡 🎨 Create Dashboard button — modal with name + description input
- [ ] 🟢 🎨 Pin/Unpin dashboard toggle
- [ ] 🟢 🧪 Test: 0 dashboards empty state, pinned order preserved on reload
### 7.3 Dashboard View Mode
- [ ] 🔴 🎨 `app/Views/dashboard/view.php` — render all widgets in their grid positions
- [ ] 🔴 🎨 Render chart widgets: fetch data via AJAX, render ApexCharts
- [ ] 🟡 🎨 Render text widgets — parse Markdown to HTML using `marked.js`
- [ ] 🟡 🎨 Render image widgets — `<img>` with configurable object-fit
- [ ] 🟡 🎨 Global filter widgets — date range picker and dropdown filter
- [ ] 🟡 ⚙️ When global date filter changes, re-fetch all chart data with new date params injected into queries
- [ ] 🟢 🎨 Fullscreen button — expand dashboard to fill viewport, hide sidebar/topbar
- [ ] 🟢 🧪 Test: mixed widget types, dashboard with 20+ charts, date filter propagation
### 7.4 Dashboard Edit Mode (Drag & Drop)
- [ ] 🔴 🎨 Integrate `gridstack.js` or `Muuri` for drag-and-drop grid layout
- [ ] 🔴 🎨 "Edit Layout" toggle activates draggable/resizable mode on all widgets
- [ ] 🟡 🎨 Resize handles on widget cards — drag corner to resize (min 1×1, max 4×3 units)
- [ ] 🟡 🎨 "Add Widget" button in edit mode — opens modal to pick chart, text, image, or filter widget
- [ ] 🟡 🎨 Remove widget button (✕) visible only in edit mode
- [ ] 🟡 ⚙️ "Save Layout" — AJAX POST grid positions (x, y, w, h) per widget to `dashboard_widgets`
- [ ] 🟡 🎨 "Discard Changes" — reload original layout from DB without saving
- [ ] 🟢 🎨 Widget title override input (optional per-widget title different from chart name)
- [ ] 🟢 🧪 Test: save layout, reload — positions preserved exactly; concurrent edit race condition
### 7.5 Dashboard Settings
- [ ] 🟡 🎨 Dashboard settings panel — name, description, theme (light/dark/system), refresh interval
- [ ] 🟡 ⚙️ `DashboardController::updateSettings()` — update name, theme, refresh
- [ ] 🟢 🎨 Danger zone — delete dashboard with confirmation typing
- [ ] 🟢 🧪 Test: theme toggle persists on reload, refresh interval auto-starts
---
## ──────────────────────────────────────────
## PHASE 8 — Alerts & Notifications
### Estimated Time: 45 Days
## ──────────────────────────────────────────
### 8.1 Alert Model & CRUD
- [ ] 🔴 ⚙️ `AlertModel.php` — CRUD, filter by workspace, soft delete
- [ ] 🔴 ⚙️ `AlertHistoryModel.php` — insert triggered log, fetch recent history per alert
- [ ] 🟡 🎨 `app/Views/alert/index.php` — list alerts with status (OK/Triggered/Muted), last triggered time
- [ ] 🟡 🎨 `app/Views/alert/create.php` — form: chart selector, metric field, condition, threshold, channels
- [ ] 🟡 🎨 `app/Views/alert/edit.php` — edit + mute/unmute toggle
- [ ] 🟢 🧪 Test: create alert with all fields, edit threshold, delete alert
### 8.2 Alert Engine (Background Check)
- [ ] 🔴 ⚙️ `Libraries/AlertEngine.php` — load active alerts, run chart query, compare value to threshold
- [ ] 🔴 ⚙️ CI4 Cron (`php spark alert:check`) — runs every minute via system cron job
- [ ] 🟡 ⚙️ `AlertEngine::evaluate()` — conditions: `gt`, `lt`, `eq`, `gte`, `lte`
- [ ] 🟡 ⚙️ Skip alert if `is_muted_until` is in the future
- [ ] 🟡 ⚙️ On trigger: send Email via CI4 Email library, send Slack message via Webhook HTTP POST
- [ ] 🟡 ⚙️ Log result to `alert_history` — value, channels notified, status (sent/failed/muted)
- [ ] 🟢 ⚙️ Prevent duplicate notifications — if same alert triggered within last 5 minutes, skip
- [ ] 🟢 🧪 Test: threshold breach triggers notification, muted alert is skipped, failed webhook logs error
### 8.3 Alert Notifications UI
- [ ] 🟡 🎨 Alerts sidebar panel (as in POC) — show recent triggered alerts with current value
- [ ] 🟡 🎨 Alert history sub-page — paginated log of all past triggers per alert
- [ ] 🟡 🎨 "Mute for" button — snooze alert for 1h / 4h / 24h
- [ ] 🟢 🎨 Red badge count on sidebar Alerts nav item for active triggered alerts
- [ ] 🟢 🧪 Test: snooze clears badge, alert re-triggers after snooze expires
---
## ──────────────────────────────────────────
## PHASE 9 — Sharing & Embedding
### Estimated Time: 34 Days
## ──────────────────────────────────────────
### 9.1 Public Share Links
- [ ] 🔴 ⚙️ `SharedLinkModel.php` — create token, find by token, increment view count
- [ ] 🔴 ⚙️ `SharingController::generate()` — create record in `shared_links`, return public URL
- [ ] 🔴 ⚙️ Public route `GET /share/{token}` — no auth required, load dashboard/chart view-only
- [ ] 🟡 🎨 Share modal in dashboard/chart view — show public URL with copy button, QR code
- [ ] 🟡 🎨 Optional password field — bcrypt hash stored, prompt on public page if set
- [ ] 🟡 🎨 Optional expiry date picker — after expiry show "This link has expired" page
- [ ] 🟡 ⚙️ `SharingController::revoke()` — set `is_active = 0`, invalidate link
- [ ] 🟢 🎨 View count display in share modal ("Viewed 42 times")
- [ ] 🟢 🧪 Test: password protection, expired link, revoked link, view count increment
### 9.2 iFrame Embed
- [ ] 🟡 🎨 Embed tab in share modal — show iframe HTML snippet with correct URL
- [ ] 🟡 ⚙️ Public share route: set `X-Frame-Options: ALLOWALL` header for embed URLs
- [ ] 🟡 🎨 Embed view — stripped layout (no sidebar/topbar), chart/dashboard only
- [ ] 🟢 🎨 Embed size presets (640×480, 800×600, 1200×800, custom)
- [ ] 🟢 🧪 Test: embed renders in external `<iframe>`, responsive sizing
### 9.3 Export
- [ ] 🟡 🎨 Export chart as PNG — use ApexCharts `chart.dataURI()` + FileSaver.js client-side
- [ ] 🟡 🎨 Export chart data as CSV — AJAX to `GET /chart/{id}/export?format=csv`
- [ ] 🟡 ⚙️ `ChartController::export()` — re-run query, format as CSV using PHP `fputcsv`, stream download
- [ ] 🟡 ⚙️ Export chart data as Excel — use `PhpSpreadsheet` library to generate `.xlsx`
- [ ] 🟢 ⚙️ Log each export to `chart_exports` table
- [ ] 🟢 🧪 Test: CSV column headers match query columns, Excel opens without errors
---
## ──────────────────────────────────────────
## PHASE 10 — REST API
### Estimated Time: 34 Days
## ──────────────────────────────────────────
### 10.1 API Authentication
- [ ] 🔴 ⚙️ `ApiAuthFilter.php` — extract Bearer token from `Authorization` header, find user in `users.api_token`
- [ ] 🔴 ⚙️ Return `401 Unauthorized` JSON if token missing or invalid
- [ ] 🟢 ⚙️ Rate limiter — 60 requests per minute per token using CI4 Throttler
- [ ] 🟢 🧪 Test: valid token, invalid token, missing token, rate limit exceeded returns 429
### 10.2 API Endpoints
- [ ] 🔴 ⚙️ `Api/WorkspaceController``GET /api/v1/workspaces`, `POST /api/v1/workspaces`
- [ ] 🔴 ⚙️ `Api/DataSourceController``GET /api/v1/workspaces/{id}/datasources`, `POST`, `DELETE`
- [ ] 🔴 ⚙️ `Api/ChartController``GET /api/v1/workspaces/{id}/charts`, `POST`, `GET /api/v1/charts/{id}/data`
- [ ] 🟡 ⚙️ `Api/DashboardController``GET`, `POST`, `PUT /api/v1/dashboards/{id}`
- [ ] 🟡 ⚙️ `Api/QueryController``POST /api/v1/datasources/{id}/query` — run ad-hoc query
- [ ] 🟡 ⚙️ `Api/AlertController``GET /api/v1/alerts`, `POST`, `DELETE`
- [ ] 🟢 ⚙️ Consistent JSON response format: `{ success, data, message, errors }`
- [ ] 🟢 📄 Generate Postman collection JSON for all API endpoints
- [ ] 🟢 🧪 Test: each endpoint with valid/invalid workspace membership, missing fields
---
## ──────────────────────────────────────────
## PHASE 11 — Audit Logs
### Estimated Time: 23 Days
## ──────────────────────────────────────────
- [ ] 🔴 ⚙️ `Libraries/AuditLogger.php` — static `log(action, resource_type, resource_id, old, new)` method
- [ ] 🔴 ⚙️ Hook `AuditLogger::log()` into key controller actions: create/update/delete chart, dashboard, data source; login/logout; role change; share link created/revoked
- [ ] 🟡 🎨 `app/Views/audit/index.php` — paginated table of audit logs
- [ ] 🟡 🎨 Filters — by user, by action type, by date range
- [ ] 🟡 🎨 Log detail modal — show `old_value` and `new_value` JSON diff view
- [ ] 🟢 ⚙️ Auto-capture `ip_address` and `user_agent` from CI4 `IncomingRequest`
- [ ] 🟢 ⚙️ CI4 Cron: `PurgeOldAuditLogs` — delete logs older than 90 days (configurable)
- [ ] 🟢 🧪 Test: login creates log entry, chart delete logs old_value, IP captured correctly
---
## ──────────────────────────────────────────
## PHASE 12 — Settings & Configuration
### Estimated Time: 2 Days
## ──────────────────────────────────────────
- [ ] 🟡 ⚙️ `SettingsModel.php` — get/set by key and workspace_id (null = global)
- [ ] 🟡 🎨 `app/Views/settings/workspace.php` — name, logo, timezone, default refresh, default theme
- [ ] 🟡 🎨 `app/Views/settings/notifications.php` — SMTP test button, Slack webhook test button
- [ ] 🟡 🎨 `app/Views/admin/settings.php` (super admin) — allow_registration toggle, max workspaces
- [ ] 🟢 🎨 Theme toggle (light/dark) saved to `users` table or localStorage with CI4 session sync
- [ ] 🟢 🧪 Test: workspace settings persist after logout, global settings affect new registrations
---
## ──────────────────────────────────────────
## PHASE 13 — Polish, Testing & Security
### Estimated Time: 45 Days
## ──────────────────────────────────────────
### 13.1 UI/UX Polish
- [ ] 🟡 🎨 Loading states — skeleton shimmer on all chart cards while data loads
- [ ] 🟡 🎨 Empty states — friendly illustrations for no charts, no data sources, no dashboards
- [ ] 🟡 🎨 Toast notification system — success, error, warning toasts globally
- [ ] 🟡 🎨 Breadcrumb navigation on all pages
- [ ] 🟡 🎨 Responsive layout — sidebar collapses to hamburger on mobile
- [ ] 🟢 🎨 Keyboard shortcuts — `N` = new chart, `D` = go to dashboards, `ESC` = close modal
- [ ] 🟢 🎨 Dark mode — full dark theme toggle applied to all views
- [ ] 🟢 🎨 404 and 500 custom error pages
### 13.2 Security Hardening
- [ ] 🔴 ⚙️ Enable CI4 CSRF protection on all POST forms (`Config/Security.php`)
- [ ] 🔴 ⚙️ Enable CI4 XSS clean on all user inputs via `IncomingRequest::getVar()`
- [ ] 🔴 ⚙️ Validate all user-supplied SQL through allowlist check — block `DROP`, `ALTER`, `GRANT`, etc.
- [ ] 🟡 ⚙️ Set `HttpOnly`, `Secure`, `SameSite=Strict` on session cookie (`Config/Cookie.php`)
- [ ] 🟡 ⚙️ Add Content Security Policy headers via `ResponseTrait` or middleware
- [ ] 🟡 ⚙️ Add rate limiting on login route — block after 5 failed attempts for 15 minutes
- [ ] 🟢 ⚙️ Sanitize all file uploads — allow only `jpg`, `png`, `csv`; validate MIME type server-side
- [ ] 🟢 🧪 Test: CSRF token rejection, XSS payload in chart name, SQL injection in query field
### 13.3 Testing
- [ ] 🟡 🧪 PHPUnit: write feature tests for Auth module (register, login, logout, reset)
- [ ] 🟡 🧪 PHPUnit: write feature tests for Chart CRUD
- [ ] 🟡 🧪 PHPUnit: write feature tests for Dashboard CRUD and widget save
- [ ] 🟡 🧪 PHPUnit: write unit tests for `ChartRenderer`, `QueryBuilder::toSQL()`, `AlertEngine::evaluate()`
- [ ] 🟢 🧪 Browser test (manual): end-to-end user flow — register → create workspace → add data source → build chart → add to dashboard → share
- [ ] 🟢 🧪 Load test: dashboard with 10 charts, each auto-refreshing every 5 minutes — check DB load
---
## ──────────────────────────────────────────
## PHASE 14 — Deployment & Documentation
### Estimated Time: 23 Days
## ──────────────────────────────────────────
### 14.1 Deployment
- [ ] 🔴 ⚙️ Set `CI_ENVIRONMENT = production` in `.env`
- [ ] 🔴 ⚙️ Configure web server (Apache `.htaccess` or Nginx `server {}`) to point root to `public/`
- [ ] 🟡 ⚙️ Set up system cron jobs:
- `* * * * * php /var/www/chart-board/spark alert:check`
- `*/30 * * * * php /var/www/chart-board/spark cache:purge`
- `0 2 * * * php /var/www/chart-board/spark logs:purge`
- [ ] 🟡 ⚙️ Configure Redis for production cache (update `Config/Cache.php`)
- [ ] 🟢 ⚙️ Set up log rotation for `writable/logs/`
- [ ] 🟢 ⚙️ Production MySQL — create read-only user for data source connections
- [ ] 🟢 ⚙️ Set up SSL certificate (Let's Encrypt)
- [ ] 🟢 📄 Create `DEPLOYMENT.md` — server setup steps, Nginx config, cron setup
### 14.2 Documentation
- [ ] 🟡 📄 Complete `README.md` — finalize installation steps, config reference
- [ ] 🟡 📄 `CONTRIBUTING.md` — code style (PSR-12), branch naming, PR checklist
- [ ] 🟢 📄 Postman Collection JSON for full REST API
- [ ] 🟢 📄 In-app help tooltips on query builder and chart builder fields
- [ ] 🟢 📄 Changelog `CHANGELOG.md` — v1.0.0 feature list
---
## 📊 Summary
| Phase | Name | Est. Days | Priority |
|-------|------|-----------|----------|
| 1 | Foundation & Setup | 34 | 🔴 |
| 2 | Auth & User Management | 45 | 🔴 |
| 3 | Workspace Management | 34 | 🔴 |
| 4 | Data Source Connections | 56 | 🔴 |
| 5 | Query Builder | 56 | 🔴 |
| 6 | Chart Builder | 67 | 🔴 |
| 7 | Dashboard Builder | 67 | 🔴 |
| 8 | Alerts & Notifications | 45 | 🟡 |
| 9 | Sharing & Embedding | 34 | 🟡 |
| 10 | REST API | 34 | 🟡 |
| 11 | Audit Logs | 23 | 🟢 |
| 12 | Settings | 2 | 🟢 |
| 13 | Polish, Testing & Security | 45 | 🟡 |
| 14 | Deployment & Docs | 23 | 🟡 |
| **Total** | | **~5668 days** | |
> **Note:** Phases 17 are the critical path (core MVP). Phases 814 are enhancement and hardening layers. A team of 2 developers can parallelize frontend and backend tasks within each phase to cut delivery time roughly in half.
---
*Chart-Board · Task Breakdown v1.0 · Generated for CI4 Build*