From 14bab518dd46de4336328987c960d48d33bbc471 Mon Sep 17 00:00:00 2001 From: gbanyan Date: Fri, 13 Feb 2026 12:53:17 +0800 Subject: [PATCH] docs(03): create phase plan for note history display --- .planning/ROADMAP.md | 8 +- .../03-note-history-display/03-01-PLAN.md | 469 ++++++++++++++++++ 2 files changed, 473 insertions(+), 4 deletions(-) create mode 100644 .planning/phases/03-note-history-display/03-01-PLAN.md diff --git a/.planning/ROADMAP.md b/.planning/ROADMAP.md index 3d3ce4e..840f0a9 100644 --- a/.planning/ROADMAP.md +++ b/.planning/ROADMAP.md @@ -2,7 +2,7 @@ ## Overview -This roadmap delivers inline note-taking capabilities for the Taiwan NPO admin member list, enabling quick annotation without page navigation. The implementation follows a foundation-first approach: database schema and backend API (Phase 1), followed by inline quick-add UI delivering core value (Phase 2), and concluding with full note history and display features (Phase 3). All work leverages the existing Laravel 10 + Alpine.js + Tailwind stack with zero new dependencies. +This roadmap delivers inline note-taking capabilities for the Taiwan NPO admin member list, enabling quick annotation without page navigation. The implementation follows a foundation-first approach: database schema and backend API (Phase 1), followed by inline quick-add UI delivering core value (Phase 2), and concluding with full note history and display features (Phase 3). All work leverages the existing Laravel 10 + Alpine.js + Tailwind stack (Phase 3 adds @alpinejs/collapse, the official Alpine.js collapse plugin). ## Phases @@ -73,10 +73,10 @@ Plans: 4. Admin can filter/search notes by text content within a member's note history 5. Expanded panel collapses cleanly without affecting other member rows -**Plans**: TBD +**Plans:** 1 plan Plans: -- [ ] TBD (will be defined in plan-phase) +- [ ] 03-01-PLAN.md — Expandable note history panel with search, collapse plugin, controller ordering fix, and feature tests ## Progress @@ -87,7 +87,7 @@ Phases execute in numeric order: 1 → 2 → 3 |-------|----------------|--------|-----------| | 1. Database Schema & Backend API | 2/2 | ✓ Complete | 2026-02-13 | | 2. Inline Quick-Add UI | 1/1 | ✓ Complete | 2026-02-13 | -| 3. Note History & Display | 0/TBD | Not started | - | +| 3. Note History & Display | 0/1 | Not started | - | --- *Roadmap created: 2026-02-13* diff --git a/.planning/phases/03-note-history-display/03-01-PLAN.md b/.planning/phases/03-note-history-display/03-01-PLAN.md new file mode 100644 index 0000000..552d481 --- /dev/null +++ b/.planning/phases/03-note-history-display/03-01-PLAN.md @@ -0,0 +1,469 @@ +--- +phase: 03-note-history-display +plan: 01 +type: execute +wave: 1 +depends_on: [] +files_modified: + - resources/js/app.js + - resources/views/admin/members/index.blade.php + - app/Http/Controllers/Admin/MemberNoteController.php + - tests/Feature/Admin/MemberNoteTest.php + - package.json +autonomous: true + +must_haves: + truths: + - "Admin clicks note count badge and an inline panel expands below the row showing all notes for that member" + - "Notes display newest first with author name and formatted datetime (YYYY年MM月DD日 HH:mm)" + - "Panel shows '尚無備註' when member has no notes" + - "Admin can type in a search field to filter notes by text content or author name" + - "Panel collapses cleanly when badge is clicked again, search query resets, other rows are unaffected" + - "After adding a note via inline form, the history panel (if previously opened) shows the new note immediately without re-fetching" + artifacts: + - path: "resources/js/app.js" + provides: "Alpine.js collapse plugin registration" + contains: "Alpine.plugin(collapse)" + - path: "resources/views/admin/members/index.blade.php" + provides: "Expandable note history panel with search" + contains: "toggleHistory" + - path: "app/Http/Controllers/Admin/MemberNoteController.php" + provides: "Notes endpoint with newest-first ordering and eager-loaded author" + contains: "latest" + - path: "tests/Feature/Admin/MemberNoteTest.php" + provides: "Tests verifying ordering, empty state, and search-related data" + key_links: + - from: "resources/views/admin/members/index.blade.php" + to: "GET /admin/members/{member}/notes" + via: "axios.get in Alpine.js toggleHistory() method" + pattern: "admin.members.notes.index" + - from: "resources/views/admin/members/index.blade.php" + to: "resources/js/app.js" + via: "Alpine.plugin(collapse) enables x-collapse directive" + pattern: "x-collapse" + - from: "resources/views/admin/members/index.blade.php" + to: "submitNote → notes.unshift" + via: "After note creation, new note injected into cached notes array" + pattern: "this.notes.unshift" +--- + + +Add expandable note history panel to member list with search filtering, complete the note feature. + +Purpose: Admins can view full note history for any member by clicking the note count badge, filter notes by content, and see notes displayed with author attribution and timestamps — all inline without leaving the member list page. + +Output: Working expandable panel with lazy-loaded notes, client-side search, empty state, and proper cache sync with the inline add form. + + + +@/Users/gbanyan/.claude/get-shit-done/workflows/execute-plan.md +@/Users/gbanyan/.claude/get-shit-done/templates/summary.md + + + +@.planning/PROJECT.md +@.planning/ROADMAP.md +@.planning/STATE.md +@.planning/phases/03-note-history-display/03-RESEARCH.md +@.planning/phases/02-inline-quick-add-ui/02-01-SUMMARY.md +@resources/views/admin/members/index.blade.php +@resources/js/app.js +@app/Http/Controllers/Admin/MemberNoteController.php +@tests/Feature/Admin/MemberNoteTest.php + + + + + + Task 1: Install collapse plugin, fix controller ordering, build expandable history panel with search + + package.json + resources/js/app.js + app/Http/Controllers/Admin/MemberNoteController.php + resources/views/admin/members/index.blade.php + + +**Step 1: Install @alpinejs/collapse** + +Run `npm install @alpinejs/collapse` to add the plugin. + +**Step 2: Register collapse plugin in app.js** + +In `resources/js/app.js`, import and register the collapse plugin BEFORE `Alpine.start()`: + +```javascript +import './bootstrap'; +import Alpine from 'alpinejs'; +import collapse from '@alpinejs/collapse'; + +Alpine.plugin(collapse); +window.Alpine = Alpine; +Alpine.start(); +``` + +Run `npm run build` to verify the build succeeds. + +**Step 3: Fix controller ordering** + +In `app/Http/Controllers/Admin/MemberNoteController.php`, update the `index` method to order notes newest first: + +Change: +```php +$notes = $member->notes()->with('author')->get(); +``` +To: +```php +$notes = $member->notes()->with('author:id,name')->latest('created_at')->get(); +``` + +This fixes a latent bug where ordering was only working by coincidence (SQLite insertion order). Also narrows author eager load to only `id` and `name` fields. + +**Step 4: Extend Alpine.js x-data scope in member row** + +In `resources/views/admin/members/index.blade.php`, extend the existing per-row `x-data` object to add history panel state. The existing `x-data` on the `` (line ~196) currently has `noteFormOpen`, `noteContent`, `isSubmitting`, `errors`, `noteCount`, and `submitNote()`. Add these new properties and methods: + +New state properties (add after `noteCount`): +- `historyOpen: false` — controls panel visibility +- `notes: []` — cached note data array +- `notesLoaded: false` — tracks if notes have been fetched +- `isLoadingNotes: false` — loading spinner state +- `searchQuery: ''` — search input value + +New methods: + +```javascript +toggleHistory() { + this.historyOpen = !this.historyOpen; + if (!this.historyOpen) { + this.searchQuery = ''; + } + if (this.historyOpen && !this.notesLoaded) { + this.loadNotes(); + } +}, +async loadNotes() { + this.isLoadingNotes = true; + try { + const response = await axios.get('{{ route("admin.members.notes.index", $member) }}'); + this.notes = response.data.notes; + this.notesLoaded = true; + } catch (error) { + console.error('Failed to load notes:', error); + } finally { + this.isLoadingNotes = false; + } +}, +get filteredNotes() { + if (!this.searchQuery.trim()) return this.notes; + const query = this.searchQuery.toLowerCase(); + return this.notes.filter(note => + note.content.toLowerCase().includes(query) || + note.author.name.toLowerCase().includes(query) + ); +}, +formatDateTime(dateString) { + const date = new Date(dateString); + const year = date.getFullYear(); + const month = String(date.getMonth() + 1).padStart(2, '0'); + const day = String(date.getDate()).padStart(2, '0'); + const hours = String(date.getHours()).padStart(2, '0'); + const minutes = String(date.getMinutes()).padStart(2, '0'); + return year + '年' + month + '月' + day + '日 ' + hours + ':' + minutes; +} +``` + +**Step 5: Update submitNote() for cache sync** + +Inside the existing `submitNote()` method's success block (after `this.noteCount++`), add cache sync logic: + +```javascript +// After noteCount++, noteContent = '', noteFormOpen = false: +if (this.notesLoaded) { + this.notes.unshift(response.data.note); +} +``` + +This ensures the history panel (if already opened) shows the new note immediately. The `response.data.note` already includes `author` from the store endpoint (Phase 1 returns `$note->load('author')`). + +**Step 6: Make the note count badge clickable** + +Replace the existing static `` badge in the 備忘錄 column with a clickable ` +``` + +Keep the existing pencil icon toggle button for the add form unchanged. + +**Step 7: Add expansion panel as a separate `` after the main row** + +Immediately after the closing `` of the main member row (before the `@empty` directive), add a new `` for the expansion panel. This `` must be OUTSIDE the main row's `` but needs to share Alpine.js state. + +IMPORTANT: The expansion panel `` cannot access the main row's `x-data` if it's on a sibling ``. To solve this, wrap BOTH the main `` and the expansion `` in a `