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:
@@ -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">→</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">
|
||||
|
||||
@@ -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 */}
|
||||
|
||||
@@ -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'}
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user