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:
@@ -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}
|
||||
|
||||
Reference in New Issue
Block a user