From c0ebbdbe20deec687721e2830b157973115fd01b Mon Sep 17 00:00:00 2001 From: gbanyan Date: Fri, 13 Feb 2026 12:59:03 +0800 Subject: [PATCH] feat(03-01): add expandable note history panel with search MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Install @alpinejs/collapse plugin for smooth expand/collapse animation - Fix controller ordering: notes now explicitly ordered newest first via latest('created_at') - Note count badge is now clickable button to toggle history panel - Add expansion panel row with loading state, search filter, empty state - Search filters notes by content or author name (client-side) - Panel collapses cleanly, search query resets on close - Cache sync: new notes from inline form appear in history immediately - Display format: author name and formatted datetime (YYYY年MM月DD日 HH:mm) - Empty state shows '尚無備註', no-results shows '找不到符合的備忘錄' --- .../Admin/MemberNoteController.php | 2 +- package-lock.json | 9 ++ package.json | 3 + resources/js/app.js | 2 + resources/views/admin/members/index.blade.php | 114 +++++++++++++++++- 5 files changed, 125 insertions(+), 5 deletions(-) diff --git a/app/Http/Controllers/Admin/MemberNoteController.php b/app/Http/Controllers/Admin/MemberNoteController.php index d664883..efd291a 100644 --- a/app/Http/Controllers/Admin/MemberNoteController.php +++ b/app/Http/Controllers/Admin/MemberNoteController.php @@ -15,7 +15,7 @@ class MemberNoteController extends Controller */ public function index(Member $member) { - $notes = $member->notes()->with('author')->get(); + $notes = $member->notes()->with('author:id,name')->latest('created_at')->get(); return response()->json(['notes' => $notes]); } diff --git a/package-lock.json b/package-lock.json index 130093d..7857184 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4,6 +4,9 @@ "requires": true, "packages": { "": { + "dependencies": { + "@alpinejs/collapse": "^3.15.8" + }, "devDependencies": { "@tailwindcss/forms": "^0.5.2", "alpinejs": "^3.4.2", @@ -28,6 +31,12 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/@alpinejs/collapse": { + "version": "3.15.8", + "resolved": "https://registry.npmjs.org/@alpinejs/collapse/-/collapse-3.15.8.tgz", + "integrity": "sha512-zZhD8DHdHuzGFe8+cHNH99K//oFutzKwcy6vagydb3KFlTzmqxTnHZo5sSV81lAazhV7qKsYCKtNV14tR9QkJw==", + "license": "MIT" + }, "node_modules/@esbuild/aix-ppc64": { "version": "0.21.5", "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", diff --git a/package.json b/package.json index 31208d1..5209566 100644 --- a/package.json +++ b/package.json @@ -14,5 +14,8 @@ "postcss": "^8.4.31", "tailwindcss": "^3.1.0", "vite": "^5.0.0" + }, + "dependencies": { + "@alpinejs/collapse": "^3.15.8" } } diff --git a/resources/js/app.js b/resources/js/app.js index a8093be..fb7bf52 100644 --- a/resources/js/app.js +++ b/resources/js/app.js @@ -1,7 +1,9 @@ import './bootstrap'; import Alpine from 'alpinejs'; +import collapse from '@alpinejs/collapse'; +Alpine.plugin(collapse); window.Alpine = Alpine; Alpine.start(); diff --git a/resources/views/admin/members/index.blade.php b/resources/views/admin/members/index.blade.php index 152347e..0f35e49 100644 --- a/resources/views/admin/members/index.blade.php +++ b/resources/views/admin/members/index.blade.php @@ -193,22 +193,30 @@ @forelse ($members as $member) - notes_count ?? 0 }}, + historyOpen: false, + notes: [], + notesLoaded: false, + isLoadingNotes: false, + searchQuery: '', async submitNote() { this.isSubmitting = true; this.errors = {}; try { - await axios.post('{{ route('admin.members.notes.store', $member) }}', { + const response = await axios.post('{{ route('admin.members.notes.store', $member) }}', { content: this.noteContent }); this.noteCount++; this.noteContent = ''; this.noteFormOpen = false; + if (this.notesLoaded) { + this.notes.unshift(response.data.note); + } } catch (error) { if (error.response && error.response.status === 422) { this.errors = error.response.data.errors || {}; @@ -216,8 +224,47 @@ } finally { this.isSubmitting = false; } + }, + 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; } }"> + @@ -255,9 +302,13 @@
- +