feat: add PSA ticket export format and Quick-Start landing page

PSA Export:
- New "PSA / Ticket Note" export format optimized for ConnectWise
- Structured output: Problem, Steps Taken, Resolution, Time Spent, Notes
- Prominent "Copy for Ticket" button on session detail page
- 24 unit tests for PSA export generator

Quick-Start Landing:
- New default landing page with search-first UX
- Auto-focused search bar with debounced tree search
- "Continue Session" cards for active sessions
- "Recent Trees" section from session history
- Home nav item and logo links updated

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
chihlasm
2026-02-08 19:36:51 -05:00
parent f2ae3a51fa
commit 4f8b7dd7ca
11 changed files with 621 additions and 13 deletions

View File

@@ -19,10 +19,11 @@ export function SessionDetailPage() {
const [isLoading, setIsLoading] = useState(true)
const [error, setError] = useState<string | null>(null)
const [isExporting, setIsExporting] = useState(false)
const [exportFormat, setExportFormat] = useState<'markdown' | 'text' | 'html'>(defaultExportFormat)
const [exportFormat, setExportFormat] = useState<'markdown' | 'text' | 'html' | 'psa'>(defaultExportFormat)
const [exportContent, setExportContent] = useState<string | null>(null)
const [showPreview, setShowPreview] = useState(false)
const [copied, setCopied] = useState(false)
const [copiedPsa, setCopiedPsa] = useState(false)
const [showSaveAsTreeModal, setShowSaveAsTreeModal] = useState(false)
const [isSavingTree, setIsSavingTree] = useState(false)
const [showRatingModal, setShowRatingModal] = useState(false)
@@ -81,7 +82,7 @@ export function SessionDetailPage() {
const getFilename = () => {
if (!session) return 'export.txt'
const ext = exportFormat === 'markdown' ? 'md' : exportFormat === 'html' ? 'html' : 'txt'
const ext = exportFormat === 'markdown' ? 'md' : exportFormat === 'html' ? 'html' : 'txt' // psa and text both use .txt
return `session-${session.ticket_number || session.id}.${ext}`
}
@@ -129,6 +130,27 @@ export function SessionDetailPage() {
}
}
const handleCopyForTicket = async () => {
if (!session) return
try {
const options: SessionExport = {
format: 'psa',
include_timestamps: true,
include_tree_info: true,
}
const content = await sessionsApi.export(session.id, options)
if (content) {
await navigator.clipboard.writeText(content)
setCopiedPsa(true)
setTimeout(() => setCopiedPsa(false), 2000)
toast.success('Copied ticket notes to clipboard')
}
} catch (err) {
console.error('Copy for ticket failed:', err)
toast.error('Failed to copy ticket notes')
}
}
const handleDownload = () => {
if (!exportContent || !session) return
const blob = new Blob([exportContent], { type: 'text/plain' })
@@ -273,6 +295,18 @@ export function SessionDetailPage() {
</button>
)}
{/* Copy for Ticket */}
<button
onClick={handleCopyForTicket}
className={cn(
'flex items-center gap-2 rounded-md bg-primary px-4 py-2 text-sm font-medium text-primary-foreground',
'hover:bg-primary/90'
)}
>
{copiedPsa ? <Check className="h-4 w-4" /> : <Copy className="h-4 w-4" />}
{copiedPsa ? 'Copied!' : 'Copy for Ticket'}
</button>
{/* Export Controls */}
<div className="flex items-center gap-2">
<select
@@ -287,6 +321,7 @@ export function SessionDetailPage() {
<option value="markdown">Markdown</option>
<option value="text">Plain Text</option>
<option value="html">HTML</option>
<option value="psa">PSA / Ticket Note</option>
</select>
<button
onClick={handleCopy}