diff --git a/frontend/src/pages/SessionDetailPage.tsx b/frontend/src/pages/SessionDetailPage.tsx index 2dd5739b..00adc888 100644 --- a/frontend/src/pages/SessionDetailPage.tsx +++ b/frontend/src/pages/SessionDetailPage.tsx @@ -1,15 +1,17 @@ import { useEffect, useState } from 'react' import { useParams, useNavigate } from 'react-router-dom' -import { Copy, Check, Eye, Save, Share2 } from 'lucide-react' +import { Copy, Check, Eye, Save, Share2, CheckCircle2, AlertTriangle, ArrowUpRight, HelpCircle, Flag } from 'lucide-react' import { sessionsApi } from '@/api/sessions' import { stepsApi } from '@/api/steps' import { ExportPreviewModal } from '@/components/session/ExportPreviewModal' import { SaveSessionAsTreeModal } from '@/components/session/SaveSessionAsTreeModal' import { ShareSessionModal } from '@/components/session/ShareSessionModal' +import { SessionOutcomeModal } from '@/components/session/SessionOutcomeModal' import { SessionTimeline } from '@/components/session/SessionTimeline' import { StepRatingModal } from '@/components/session/StepRatingModal' import { ActionMenu } from '@/components/common/ActionMenu' import type { MenuAction } from '@/components/common/ActionMenu' +import type { SessionOutcome } from '@/types' import { useUserPreferencesStore } from '@/store/userPreferencesStore' import type { Session, SessionExport, SaveAsTreeRequest, Step, RedactionSummary } from '@/types' import { hasRatedSession, markSessionRated } from '@/lib/sessionRatings' @@ -36,6 +38,8 @@ export function SessionDetailPage() { const [isSavingRatings, setIsSavingRatings] = useState(false) const [librarySteps, setLibrarySteps] = useState([]) const [showShareModal, setShowShareModal] = useState(false) + const [showOutcomeModal, setShowOutcomeModal] = useState(false) + const [isCompleting, setIsCompleting] = useState(false) const [maxStepIndex, setMaxStepIndex] = useState(null) const [detailLevel, setDetailLevel] = useState<'standard' | 'full'>('standard') const [includeSummary, setIncludeSummary] = useState(false) @@ -227,6 +231,21 @@ export function SessionDetailPage() { } } + const handleCompleteSession = async (data: { outcome: SessionOutcome; outcome_notes?: string; next_steps?: string }) => { + if (!session) return + setIsCompleting(true) + try { + const updated = await sessionsApi.complete(session.id, data) + setSession(updated) + setShowOutcomeModal(false) + toast.success('Session completed') + } catch { + toast.error('Failed to complete session') + } finally { + setIsCompleting(false) + } + } + const getDefaultTreeName = () => { if (!session) return '' const treeName = session.tree_snapshot?.name || 'Tree' @@ -310,159 +329,157 @@ export function SessionDetailPage() { ) } + // Outcome display config + const OUTCOME_CONFIG: Record = { + resolved: { icon: , color: 'text-emerald-400', bg: 'bg-emerald-500/10', border: 'border-emerald-500/20' }, + workaround: { icon: , color: 'text-amber-400', bg: 'bg-amber-500/10', border: 'border-amber-500/20' }, + escalated: { icon: , color: 'text-red-400', bg: 'bg-red-500/10', border: 'border-red-500/20' }, + unresolved: { icon: , color: 'text-muted-foreground', bg: 'bg-muted', border: 'border-border' }, + } + const outcomeConfig = session.outcome ? OUTCOME_CONFIG[session.outcome] : null + return (
- {/* Header */} -
-
-
- -

- {session.ticket_number || 'Session Details'} -

-
- navigate('/sessions')} + className="mb-4 text-sm text-muted-foreground hover:text-foreground" + > + ← Back to sessions + + + {/* Page title row */} +
+
+

+ {session.ticket_number || 'Session Details'} +

+

+ {session.tree_snapshot?.name} + {session.client_name && <> · Client: {session.client_name}} + {' · '}{new Date(session.started_at).toLocaleDateString(undefined, { month: 'short', day: 'numeric', year: 'numeric' })} +

+
+ setShowShareModal(true) }, + ...(session.completed_at ? [{ label: 'Save as Tree', icon: Save, onClick: () => setShowSaveAsTreeModal(true) }] as MenuAction[] : []), + ]} + /> +
+ + {/* Session summary card */} + {session.completed_at && outcomeConfig ? ( +
+
+
+ {outcomeConfig.icon} +
+
+ {outcomeLabel} + · {getTotalDuration()} +
+ {session.outcome_notes && ( +

{session.outcome_notes}

+ )} + {session.next_steps && ( +
+ Next Steps +

{session.next_steps}

+
)} - > - - {session.completed_at ? 'Completed' : 'In Progress'} - - {session.client_name && Client: {session.client_name}} - {session.completed_at && ( - - Duration: {getTotalDuration()} - - )} - {outcomeLabel && ( - - Outcome: {outcomeLabel} - - )} -
- {session.outcome_notes && ( -

Outcome Notes: {session.outcome_notes}

- )} - {session.next_steps && ( -
- Next Steps: -

{session.next_steps}

- )} -
- - {/* Actions */} -
- setShowShareModal(true), - }, - ...(session.completed_at ? [{ - label: 'Save as Tree', - icon: Save, - onClick: () => setShowSaveAsTreeModal(true), - }] as MenuAction[] : []), - ]} - /> - - {/* Copy for Ticket */} +
+ {/* Primary action: Copy for Ticket */} - - {/* Export Controls */} -
- - {session.decisions.length > 1 && ( - - )} - - - -
+ ) : !session.completed_at ? ( + /* In-progress banner */ +
+
+ +
+

Session in progress

+

Set an outcome to finalize this session and generate documentation.

+
+
+ +
+ ) : null} + + {/* Export toolbar (secondary) */} +
+ + {session.decisions.length > 1 && ( + + )} + + + + {/* Copy for ticket (secondary position when session is complete) */} + {session.completed_at && ( + + )}
{/* Timeline / Step Checklist */} @@ -513,6 +530,14 @@ export function SessionDetailPage() { isOpen={showShareModal} onClose={() => setShowShareModal(false)} /> + + {/* Complete Session Modal (in-progress sessions) */} + setShowOutcomeModal(false)} + onSubmit={handleCompleteSession} + isSubmitting={isCompleting} + />
) }