diff --git a/components/swift/artifacts/artifact-area.tsx b/components/swift/artifacts/artifact-area.tsx index 9ba6763..85e4bcc 100644 --- a/components/swift/artifacts/artifact-area.tsx +++ b/components/swift/artifacts/artifact-area.tsx @@ -1,85 +1,30 @@ 'use client'; /** - * Artifact Area (v3.0) + * Artifact Area (v3.0 + E4) * - * Displays active blocks based on activeBlock state. - * Shows placeholder when no block is active. + * Displays artifacts using ArtifactContainer with tab support. + * Manages multiple artifacts with lifecycle actions. * - * Epic: E1 (Foundation), E3 (Chat API & Medical Scribe) - * Stories: E1.S3 (Placeholder componenten), E3.S6 (Artifact opening from chat) + * Epic: E1 (Foundation), E3 (Chat API), E4 (Artifact Area & Tabs) + * Stories: E1.S3, E3.S6, E4.S1, E4.S2 */ import { useSwiftStore } from '@/stores/swift-store'; -import { DagnotatieBlock } from '../blocks/dagnotitie-block'; -import { ZoekenBlock } from '../blocks/zoeken-block'; -import { OverdrachtBlock } from '../blocks/overdracht-block'; -import { FallbackPicker } from '../blocks/fallback-picker'; +import { ArtifactContainer } from './artifact-container'; export function ArtifactArea() { - const activeBlock = useSwiftStore((s) => s.activeBlock); - const prefillData = useSwiftStore((s) => s.prefillData); + const openArtifacts = useSwiftStore((s) => s.openArtifacts); + const activeArtifactId = useSwiftStore((s) => s.activeArtifactId); + const switchArtifact = useSwiftStore((s) => s.switchArtifact); + const closeArtifact = useSwiftStore((s) => s.closeArtifact); - // Render active block - if (activeBlock) { - return ( -
- {activeBlock === 'dagnotitie' && } - {activeBlock === 'zoeken' && } - {activeBlock === 'overdracht' && } - {activeBlock === 'fallback' && } -
- ); - } - - // Placeholder when no block is active return ( -
-
-
πŸ“‹
-

- Artifacts verschijnen hier -

-

- Wanneer je een actie vraagt in de chat, verschijnt het bijbehorende formulier of overzicht hier. -

- -
-
-
- πŸ“ -
-
-

Dagnotitie

-

Voor snelle notities tijdens de dienst

-
-
- -
-
- πŸ” -
-
-

PatiΓ«nt Zoeken

-

Zoek en selecteer patiΓ«nten

-
-
- -
-
- πŸ”„ -
-
-

Overdracht

-

Dienst overdracht met AI-samenvatting

-
-
-
- -

- Vraag me iets in de chat om te beginnen! -

-
-
+ ); } diff --git a/components/swift/command-center/command-center.tsx b/components/swift/command-center/command-center.tsx index 3f7673f..6f6f3ad 100644 --- a/components/swift/command-center/command-center.tsx +++ b/components/swift/command-center/command-center.tsx @@ -22,18 +22,19 @@ import { ContextBar } from './context-bar'; import { OfflineBanner } from './offline-banner'; import { ChatPanel } from '../chat/chat-panel'; import { ArtifactArea } from '../artifacts/artifact-area'; +import { getArtifactTitle } from '../artifacts/artifact-container'; export function CommandCenter() { - const { closeBlock, activeBlock, openBlock, pendingAction, setPendingAction } = useSwiftStore(); + const { closeAllArtifacts, openArtifacts, openArtifact, pendingAction, setPendingAction } = useSwiftStore(); const inputRef = useRef(null); // Global keyboard shortcuts const handleKeyDown = useCallback( (e: KeyboardEvent) => { - // Escape: close active block - if (e.key === 'Escape' && activeBlock) { + // Escape: close all artifacts + if (e.key === 'Escape' && openArtifacts.length > 0) { e.preventDefault(); - closeBlock(); + closeAllArtifacts(); } // Cmd/Ctrl + K: focus input (chat input in v3.0) @@ -42,7 +43,7 @@ export function CommandCenter() { inputRef.current?.focus(); } }, - [activeBlock, closeBlock] + [openArtifacts, closeAllArtifacts] ); useEffect(() => { @@ -50,7 +51,7 @@ export function CommandCenter() { return () => window.removeEventListener('keydown', handleKeyDown); }, [handleKeyDown]); - // E3.S6: Handle pending actions from chat (artifact opening) + // E3.S6 + E4.S2: Handle pending actions from chat (artifact opening) useEffect(() => { if (!pendingAction) return; @@ -60,10 +61,17 @@ export function CommandCenter() { if (pendingAction.artifact) { const { type, prefill } = pendingAction.artifact; - console.log('[CommandCenter] Opening artifact:', type, 'with prefill:', prefill); + // Generate title for the artifact + const title = getArtifactTitle(type, prefill); - // Open the block with prefill data - openBlock(type, prefill); + console.log('[CommandCenter] Opening artifact:', type, title); + + // Open the artifact (E4.S2 - new artifact system) + openArtifact({ + type, + prefill, + title, + }); // Clear pending action after processing setPendingAction(null); @@ -71,7 +79,7 @@ export function CommandCenter() { console.log('[CommandCenter] Action has no artifact, skipping'); setPendingAction(null); } - }, [pendingAction, openBlock, setPendingAction]); + }, [pendingAction, openArtifact, setPendingAction]); return (
diff --git a/docs/swift/bouwplan-swift-v3.md b/docs/swift/bouwplan-swift-v3.md index 73be7a1..6a64529 100644 --- a/docs/swift/bouwplan-swift-v3.md +++ b/docs/swift/bouwplan-swift-v3.md @@ -222,12 +222,12 @@ const useChatStore = create((set) => ({ | E1 | Foundation - Split-screen | Layout naar 40/60 split | βœ… **Compleet** | 3/3 | 12 SP | E1.S1 geskipt (geen feature flag) | | E2 | Chat Panel & Messages | Chat UI zonder AI | βœ… **Compleet** | 5/5 | 13 SP | Scrolling, input, shortcuts | | E3 | Chat API & Medical Scribe | AI conversatie werkend | βœ… **Compleet** | 6/6 | 21 SP | Artifact opening werkend! | -| E4 | Artifact Area & Tabs | Meerdere artifacts mogelijk | ⏳ In Progress | 1/4 | 13 SP | Container & tabs structure | +| E4 | Artifact Area & Tabs | Meerdere artifacts mogelijk | ⏳ In Progress | 2/4 | 13 SP | Lifecycle management werkend | | E5 | AI-Filtering & Polish | Psychiater filtering, polish | ⏳ To Do | 0/5 | 13 SP | Week 6 | | E6 | Testing & Refinement | QA, bugs, performance | ⏳ To Do | 0/4 | 8 SP | Week 7-8 | **Totaal:** 31 stories, **85 Story Points** (~7 weken Γ  12 SP/week) -**Voortgang:** βœ… 17/31 stories compleet, 2 geskipt (53 SP / 85 SP = **62%**) +**Voortgang:** βœ… 18/31 stories compleet, 2 geskipt (56 SP / 85 SP = **66%**) **Belangrijk:** - ⚠️ Voer niet in 1x het volledige plan uit. Bouw per epic en per story. @@ -613,7 +613,7 @@ E3.S5 (useChatStream hook) is geskipt omdat: | Story ID | Beschrijving | Acceptatiecriteria | Status | Afhankelijkheden | Story Points | |----------|--------------|---------------------|--------|------------------|--------------| | E4.S1 | ArtifactContainer component | Wrapper met tabs bovenaan, max 3 artifacts | βœ… **Compleet** | E3.S6 | 5 | -| E4.S2 | Artifact lifecycle management | Open/close/switch tussen artifacts in store | ⏳ | E4.S1 | 3 | +| E4.S2 | Artifact lifecycle management | Open/close/switch tussen artifacts in store | βœ… **Compleet** | E4.S1 | 3 | | E4.S3 | Slide-in animatie | Artifact slide-in van rechts (200ms ease-out) | ⏳ | E4.S2 | 2 | | E4.S4 | Placeholder state | "Artifacts verschijnen hier" met voorbeelden | ⏳ | E4.S3 | 3 | @@ -739,7 +739,27 @@ function ArtifactPlaceholder() { **Git Commits:** - (to be committed) β€” E4.S1 (ArtifactContainer component) -**Note:** E4.S1 definieert de structuur. E4.S2 voegt store state toe (openArtifacts[], activeArtifactId, actions). +**Deliverables (E4.S2 compleet):** +- βœ… `stores/swift-store.ts` β€” openArtifacts[], activeArtifactId state +- βœ… Store actions: openArtifact(), closeArtifact(), switchArtifact(), closeAllArtifacts() +- βœ… openArtifact: Auto-generate ID + timestamp, max 3 logic (remove oldest) +- βœ… closeArtifact: Filter out artifact, auto-switch to last remaining +- βœ… switchArtifact: Verify exists, set activeArtifactId +- βœ… closeAllArtifacts: Clear all artifacts + active ID +- βœ… Console logging: "[Store] Opening artifact:", "[Store] Closing artifact:", etc. +- βœ… ArtifactArea: Refactored to use ArtifactContainer +- βœ… ArtifactArea: Pass store actions (switchArtifact, closeArtifact) +- βœ… CommandCenter: openArtifact() i.p.v. openBlock() +- βœ… CommandCenter: getArtifactTitle() voor artifact titles +- βœ… CommandCenter: Escape key β†’ closeAllArtifacts() +- βœ… Integration: Chat β†’ Action β†’ openArtifact β†’ Tabs + Container +- βœ… Build succesvol zonder errors + +**Git Commits:** +- `855744c` β€” E4.S1 (ArtifactContainer component) +- (to be committed) β€” E4.S2 (Artifact lifecycle management) + +**Note:** E4.S1 + E4.S2 maken het artifact systeem volledig functioneel. E4.S3 voegt animaties toe. **Deliverable Epic 4 complete:** Meerdere artifacts mogelijk, smooth transitions, placeholder state diff --git a/stores/swift-store.ts b/stores/swift-store.ts index 5881848..9c128b7 100644 --- a/stores/swift-store.ts +++ b/stores/swift-store.ts @@ -92,15 +92,25 @@ interface SwiftStore { isStreaming: boolean; pendingAction: ChatAction | null; + // Artifact state (E4 - Multiple artifacts support) + openArtifacts: Artifact[]; + activeArtifactId: string | null; + // Context actions setActivePatient: (patient: Patient | null) => void; setShift: (shift: ShiftType) => void; - // Block actions + // Block actions (legacy - will be replaced by artifact actions) openBlock: (type: BlockType, prefill?: BlockPrefillData) => void; closeBlock: () => void; setBlockLoading: (loading: boolean) => void; + // Artifact actions (E4.S2) + openArtifact: (artifact: Omit) => void; + closeArtifact: (id: string) => void; + switchArtifact: (id: string) => void; + closeAllArtifacts: () => void; + // Input actions setInputValue: (value: string) => void; setVoiceActive: (active: boolean) => void; @@ -143,6 +153,9 @@ const initialState = { chatMessages: [], isStreaming: false, pendingAction: null, + // Artifact state (E4) + openArtifacts: [], + activeArtifactId: null, }; // Create the store @@ -264,6 +277,89 @@ export const useSwiftStore = create()( setPendingAction: (action) => set({ pendingAction: action }, false, 'setPendingAction'), + // Artifact actions (E4.S2) + openArtifact: (artifactData) => { + set( + (state) => { + // Create artifact with unique ID and timestamp + const newArtifact: Artifact = { + ...artifactData, + id: crypto.randomUUID(), + createdAt: new Date(), + }; + + let artifacts = [...state.openArtifacts]; + + // Max 3 artifacts - remove oldest if at capacity + if (artifacts.length >= 3) { + console.log('[Store] Max 3 artifacts reached, removing oldest'); + artifacts = artifacts.slice(1); // Remove first (oldest) + } + + console.log('[Store] Opening artifact:', newArtifact.type, newArtifact.title); + + return { + openArtifacts: [...artifacts, newArtifact], + activeArtifactId: newArtifact.id, + }; + }, + false, + 'openArtifact' + ); + }, + + closeArtifact: (id) => { + set( + (state) => { + const artifacts = state.openArtifacts.filter((a) => a.id !== id); + + // If closing active artifact, switch to last remaining artifact + let newActiveId = state.activeArtifactId; + if (state.activeArtifactId === id) { + newActiveId = artifacts.length > 0 ? artifacts[artifacts.length - 1].id : null; + } + + console.log('[Store] Closing artifact:', id, 'New active:', newActiveId); + + return { + openArtifacts: artifacts, + activeArtifactId: newActiveId, + }; + }, + false, + 'closeArtifact' + ); + }, + + switchArtifact: (id) => { + set( + (state) => { + // Verify artifact exists + const exists = state.openArtifacts.some((a) => a.id === id); + if (!exists) { + console.warn('[Store] Cannot switch to artifact:', id, '(not found)'); + return state; + } + + console.log('[Store] Switching to artifact:', id); + return { activeArtifactId: id }; + }, + false, + 'switchArtifact' + ); + }, + + closeAllArtifacts: () => { + set( + { + openArtifacts: [], + activeArtifactId: null, + }, + false, + 'closeAllArtifacts' + ); + }, + // Reset reset: () => set(initialState, false, 'reset'), }),