• Administrator's avatar
    refactor(story): decompose StoryView 763→173 lines — Candidate B · 7f6e1ee8
    Administrator authored
    
    
    StoryView was the last "master-data" god-view whose script section
    still owned state, fetch, CRUD methods, table config, and three dialog
    templates in one file. Mirrors the Candidate 2 TaskView decomposition
    pattern.
    
    - src/components/story/StoryTable.vue        — v-data-table + three
                                                    Map<id, name> computeds
                                                    for O(1) display lookup
                                                    (replaces O(n^2) find);
                                                    emits @edit / @delete
    - src/components/story/StoryEditDialog.vue   — create+edit merged via
                                                    nullable `story` prop;
                                                    owns save flow + inline
                                                    error alert
    - src/components/story/StoryDeleteDialog.vue — owns deleteStory call,
                                                    isDeleting, inline
                                                    error; preserves the
                                                    existing 48-line UX
    - src/composables/useFlashMessage.js         — { message, show, clear }
                                                    with built-in 3s
                                                    auto-dismiss
    - src/__tests__/composables/useFlashMessage.spec.js — 13 tests
                                                           (fake timers)
    
    Key design choices (settled via /grilling before implementation):
    
    1. Dialog seam shape is explicit props+events — :open, :story,
       @close, @saved, @deleted. Not v-model. Keeps multiple emits
       semantically distinct.
    
    2. Lookup data (students/subjects/terms) lives in StoryView as raw
       refs and is passed down to children. No useMasterData composable —
       that scope belongs to Candidate A (useCrudList).
    
    3. StoryEditDialog handles create+edit (TaskEditDialog pattern), with
       @saved carrying the backend response so the parent can do
       { ...existing, ...updated } merge and pick up audit fields
       (created_at, last_updated_at, etc.).
    
    4. StoryDeleteDialog owns the delete flow internally — parent just
       wires @deleted(id) to a list splice. The existing
       "close-edit-after-delete" contract is preserved via the parent's
       onStoryDeleted handler.
    
    5. Row-level delete is now in StoryTable (mdi-delete icon next to
       edit). Replaces the awkward "open edit -> click delete inside edit
       dialog -> confirm" three-click path with a two-click one.
    
    6. console.log calls in StoryView removed (match TaskEditDialog's
       console.error-only pattern; CLAUDE.md says services log heavily,
       not components).
    
    StoryView is now a thin orchestrator: refs, two fetch functions,
    open/close handlers, two merge handlers. fetchStories still depends
    on fetchRelatedData completing first (sequential await, NOT
    Promise.all — see comment in onMounted). Tests 178 -> 191.
    Co-Authored-By: default avatarClaude <noreply@anthropic.com>
    7f6e1ee8
StoryView.vue 5.02 KB