# πŸ“‹ 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: 3–4 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: 4–5 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: 3–4 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: 5–6 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: 5–6 Days **Completion note:** Core MVP items are implemented (unified create/edit form, three query modes, multi-filter / multi–order-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` β†’ `` - `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: 6–7 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: 6–7 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 β€” `` 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: 4–5 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: 3–4 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 `