- 19 Jul, 2026 2 commits
-
-
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:Claude <noreply@anthropic.com>
-
Administrator authored
The router beforeEach guard was a 40-line inline function mixing auth state checks, LAN bypass, and route resolution. Pulled the decision logic into src/utils/authGuard.js — a pure function checkAuth(to, authState) → {allowed, reason, redirect?} — that takes the auth store as a plain object and returns a verdict. No Pinia, no vue-router, no DOM imports. Trivially testable (21 spec covering LAN bypass, already-authed-on-login, refresh-failed, etc.). Router guard shrinks to 5 lines; AppLayout drops a redundant router.push('/login') (auth.logout already navigates internally); 5 master-data views drop redundant onMounted initializeAuth() calls (initializeAuth now runs once in main.js at boot). Co-Authored-By:Claude <noreply@anthropic.com>
-
- 06 Jul, 2026 1 commit
-
-
Administrator authored
-
- 04 Sep, 2025 1 commit
-
-
Administrator authored
-