feat: add supporting data capture, PDF export, and branding settings UI

- API clients for supporting data CRUD and team branding
- AddSupportingDataModal with text snippet and screenshot tabs (paste + upload)
- SupportingDataPanel collapsible section integrated into both session runners
- ExportPreviewModal updated with PDF format and server-side download flow
- BrandingSettings component for company name and logo management
- Expose team_id in UserResponse schema for branding endpoint access

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
chihlasm
2026-03-17 02:03:40 -04:00
parent b72eb56b7f
commit 2339787499
13 changed files with 857 additions and 54 deletions

View File

@@ -2,6 +2,7 @@ import { useEffect, useState } from 'react'
import { Link } from 'react-router-dom'
import { Building2, Users, Mail, Crown, Loader2, AlertCircle, Check, X, Settings, FolderTree, Server, RefreshCw, MessageSquareText, UserCog, AlertTriangle, Clock, Plug } from 'lucide-react'
import { PageMeta } from '@/components/common/PageMeta'
import { BrandingSettings } from '@/components/settings/BrandingSettings'
import { accountsApi } from '@/api/accounts'
import type { Account, AccountMember, AccountInvite } from '@/types'
import { TransferOwnershipModal } from '@/components/account/TransferOwnershipModal'
@@ -21,6 +22,7 @@ export function AccountSettingsPage() {
const { isAccountOwner } = usePermissions()
const { plan, limits, usage } = useSubscription()
const { defaultExportFormat, setDefaultExportFormat } = useUserPreferencesStore()
const user = useAuthStore((s) => s.user)
const subscription = useAuthStore((s) => s.subscription)
const [account, setAccount] = useState<Account | null>(null)
@@ -587,6 +589,11 @@ export function AccountSettingsPage() {
<span className="text-muted-foreground group-hover:text-foreground transition-colors">&rarr;</span>
</Link>
{/* Branding Section (owners only) */}
{isAccountOwner && user?.team_id && (
<BrandingSettings teamId={user.team_id} />
)}
{/* Preferences Section */}
<div className="bg-card border border-border rounded-xl p-4 sm:p-6">
<div className="flex items-center gap-2">

View File

@@ -25,6 +25,7 @@ import type { CustomStepDraft } from '@/components/step-library/CustomStepModal'
import { PostStepActionModal } from '@/components/session/PostStepActionModal'
import { CopilotPanel } from '@/components/copilot/CopilotPanel'
import { CopilotToggle } from '@/components/copilot/CopilotToggle'
import { SupportingDataPanel } from '@/components/session/SupportingDataPanel'
import { integrationsApi, sessionPsaApi } from '@/api/integrations'
import { TicketPickerModal } from '@/components/session/TicketPickerModal'
import { TicketLinkIndicator } from '@/components/session/TicketLinkIndicator'
@@ -792,6 +793,13 @@ export function ProceduralNavigationPage() {
<StepFeedback stepId={currentStep.id} sessionId={session.id} />
</div>
)}
{/* Supporting Data */}
{session && (
<div className="mt-4">
<SupportingDataPanel sessionId={session.id} />
</div>
)}
</div>
{/* AI Copilot - in-flow panel */}

View File

@@ -32,7 +32,7 @@ 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' | 'psa'>(defaultExportFormat)
const [exportFormat, setExportFormat] = useState<'markdown' | 'text' | 'html' | 'psa' | 'pdf'>(defaultExportFormat)
const [exportContent, setExportContent] = useState<string | null>(null)
const [showPreview, setShowPreview] = useState(false)
const [copied, setCopied] = useState(false)
@@ -51,6 +51,7 @@ export function SessionDetailPage() {
const [redactionMode, setRedactionMode] = useState<'none' | 'mask'>('none')
const [redactionSummary, setRedactionSummary] = useState<RedactionSummary | null>(null)
const [isGeneratingFlow, setIsGeneratingFlow] = useState(false)
const [pdfLoading, setPdfLoading] = useState(false)
useEffect(() => {
if (id) {
@@ -223,6 +224,31 @@ export function SessionDetailPage() {
}
}
const handleDownloadPdf = async () => {
if (!session) return
setPdfLoading(true)
try {
const { apiClient } = await import('@/api/client')
const response = await apiClient.post(
`/sessions/${session.id}/export`,
{ format: 'pdf' },
{ responseType: 'blob' }
)
const url = URL.createObjectURL(response.data)
const a = document.createElement('a')
a.href = url
a.download = `session-export-${session.id}.pdf`
a.click()
URL.revokeObjectURL(url)
analytics.exportGenerated({ session_id: session.id, format: 'pdf' })
} catch (error) {
console.error('PDF export failed:', error)
toast.error('Failed to generate PDF export')
} finally {
setPdfLoading(false)
}
}
const handleSaveAsTree = async (data: SaveAsTreeRequest) => {
if (!session) return
setIsSavingTree(true)
@@ -478,6 +504,7 @@ export function SessionDetailPage() {
<option value="text">Plain Text</option>
<option value="html">HTML</option>
<option value="psa">PSA / Ticket Note</option>
<option value="pdf">PDF</option>
</select>
{session.decisions.length > 1 && (
<select
@@ -546,6 +573,8 @@ export function SessionDetailPage() {
filename={getFilename()}
format={exportFormat}
onDownload={handleDownload}
onDownloadPdf={handleDownloadPdf}
pdfLoading={pdfLoading}
includeSummary={includeSummary}
onToggleSummary={handleToggleSummary}
redactionEnabled={redactionMode === 'mask'}

View File

@@ -21,6 +21,7 @@ import { StepFeedback } from '@/components/session/StepFeedback'
import { buildSessionShareUrl, getLatestActiveShareForSession } from '@/lib/sessionShare'
import { CopilotPanel } from '@/components/copilot/CopilotPanel'
import { CopilotToggle } from '@/components/copilot/CopilotToggle'
import { SupportingDataPanel } from '@/components/session/SupportingDataPanel'
import { Button } from '@/components/ui/Button'
import { integrationsApi, sessionPsaApi } from '@/api/integrations'
import { TicketPickerModal } from '@/components/session/TicketPickerModal'
@@ -1204,6 +1205,13 @@ export function TreeNavigationPage() {
/>
</div>
{/* Supporting Data */}
{session && (
<div className="mt-4">
<SupportingDataPanel sessionId={session.id} />
</div>
)}
{/* Back Button */}
{pathTaken.length > 1 && (
<button