Files
policy-ui/app/pages/claims/[id].vue
Jordan Weingarten 67482f6629 WIP jordan
2026-04-16 11:11:44 -05:00

1199 lines
54 KiB
Vue

<script setup lang="ts">
import {
MOCK_CLAIM_DETAILS, CARRIER_STATUS_LABELS, WORKFLOW_STATUS_LABELS,
PRIORITY_LABELS, TASK_STATUS_LABELS, DOC_CATEGORY_LABELS,
slaColor, fmtClaimMoney,
type ClaimDetail, type ClaimTask, type ClaimCommEntry, type ClaimDocument,
type CommType, type DocCategory, type TaskStatus, type GeneratedFormStatus,
} from '~/data/mock-claims'
definePageMeta({ ssr: false })
usePageTitle('Claim Detail')
const route = useRoute()
const claimId = route.params.id as string
const claim = ref<ClaimDetail | null>(MOCK_CLAIM_DETAILS[claimId] ?? null)
// ── Tabs ──────────────────────────────────────────────────────────────────────
type TabId = 'overview' | 'tasks' | 'communications' | 'documents' | 'financial'
const activeTab = ref<TabId>('overview')
const tabs: { id: TabId; label: string; icon: string }[] = [
{ id: 'overview', label: 'Overview', icon: 'i-heroicons-squares-2x2' },
{ id: 'tasks', label: 'Tasks', icon: 'i-heroicons-clipboard-document-check' },
{ id: 'communications', label: 'Communications', icon: 'i-heroicons-chat-bubble-left-right' },
{ id: 'documents', label: 'Documents', icon: 'i-heroicons-folder-open' },
{ id: 'financial', label: 'Financial', icon: 'i-heroicons-banknotes' },
]
// ── Helpers ────────────────────────────────────────────────────────────────────
const fmtMoney = (n: number) => '$' + n.toLocaleString('en-US', { minimumFractionDigits: 0 })
const fmtDate = (d: string) => {
if (!d) return '—'
return new Date(d).toLocaleDateString('en-US', { day: '2-digit', month: 'short', year: 'numeric' })
}
const fmtDateTime = (d: string) => {
if (!d) return '—'
return new Date(d).toLocaleDateString('en-US', { day: '2-digit', month: 'short', year: 'numeric', hour: '2-digit', minute: '2-digit' })
}
const carrierStatusClass = (s: string) => {
const map: Record<string, string> = {
fnol_submitted: 'cl-cs-fnol', acknowledged: 'cl-cs-ack', investigation: 'cl-cs-inv',
documentation_pending: 'cl-cs-doc', reserved: 'cl-cs-rsv', negotiation: 'cl-cs-neg',
settlement_offered: 'cl-cs-set', closed: 'cl-cs-closed',
}
return map[s] ?? ''
}
const priorityClass = (p: string) => {
const map: Record<string, string> = { critical: 'cl-pri-critical', high: 'cl-pri-high', medium: 'cl-pri-medium', low: 'cl-pri-low' }
return map[p] ?? ''
}
const taskStatusClass = (s: TaskStatus) => {
const map: Record<string, string> = { open: 'cl-ts-open', in_progress: 'cl-ts-progress', overdue: 'cl-ts-overdue', done: 'cl-ts-done' }
return map[s] ?? ''
}
const commIcon = (t: CommType) => {
const map: Record<string, string> = { email: 'i-heroicons-envelope', call: 'i-heroicons-phone', note: 'i-heroicons-pencil-square', system: 'i-heroicons-cog-6-tooth' }
return map[t] ?? 'i-heroicons-chat-bubble-left'
}
const commTypeLabel = (t: CommType) => {
const map: Record<string, string> = { email: 'Email', call: 'Call', note: 'Note', system: 'System' }
return map[t] ?? t
}
const formStatusClass = (s: GeneratedFormStatus) => {
const map: Record<string, string> = { draft: 'cl-fs-draft', ready_for_signature: 'cl-fs-ready', signed: 'cl-fs-signed', submitted: 'cl-fs-submitted' }
return map[s] ?? ''
}
const formStatusLabel = (s: GeneratedFormStatus) => {
const map: Record<string, string> = { draft: 'Draft', ready_for_signature: 'Ready for Signature', signed: 'Signed', submitted: 'Submitted' }
return map[s] ?? s
}
// ── Party filter (for communications) ─────────────────────────────────────────
const activePartyId = ref<string | null>(null)
const filteredComms = computed(() => {
if (!claim.value) return []
let comms = [...claim.value.communications]
if (activePartyId.value) comms = comms.filter(c => c.partyId === activePartyId.value)
if (commTypeFilter.value) comms = comms.filter(c => c.type === commTypeFilter.value)
return comms.sort((a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime())
})
const commTypeFilter = ref<CommType | null>(null)
// ── Tasks ─────────────────────────────────────────────────────────────────────
const sortedTasks = computed(() => {
if (!claim.value) return []
return [...claim.value.tasks].sort((a, b) => {
const order: Record<TaskStatus, number> = { overdue: 0, open: 1, in_progress: 2, done: 3 }
return (order[a.status] ?? 9) - (order[b.status] ?? 9)
})
})
const openActions = computed(() => {
if (!claim.value) return []
return claim.value.tasks.filter(t => t.status === 'open' || t.status === 'overdue' || t.status === 'in_progress')
})
// ── Documents ─────────────────────────────────────────────────────────────────
const docsByCategory = computed(() => {
if (!claim.value) return []
const cats: DocCategory[] = ['fnol', 'evidence', 'estimates', 'correspondence', 'settlement']
return cats.map(cat => ({
category: cat,
label: DOC_CATEGORY_LABELS[cat],
docs: claim.value!.documents.filter(d => d.category === cat),
requiredCount: claim.value!.documents.filter(d => d.category === cat && d.required).length,
receivedCount: claim.value!.documents.filter(d => d.category === cat && d.required && d.received).length,
}))
})
// ── Financial ─────────────────────────────────────────────────────────────────
const reserveEntries = computed(() => claim.value?.financials.filter(f => f.type === 'reserve_change') ?? [])
const paymentEntries = computed(() => claim.value?.financials.filter(f => f.type === 'payment') ?? [])
const subrogationEntries = computed(() => claim.value?.financials.filter(f => f.type === 'subrogation') ?? [])
const expenseEntries = computed(() => claim.value?.financials.filter(f => f.type === 'expense') ?? [])
const financialSummary = computed(() => {
if (!claim.value) return { reserved: 0, paid: 0, subrogated: 0, expenses: 0, net: 0 }
const reserved = claim.value.reservedAmount
const paid = claim.value.paidAmount
const subrogated = subrogationEntries.value.reduce((s, e) => s + e.amount, 0)
const expenses = expenseEntries.value.reduce((s, e) => s + e.amount, 0)
return { reserved, paid, subrogated, expenses, net: paid - subrogated + expenses }
})
// ── AI Recap ──────────────────────────────────────────────────────────────────
const aiRecapOpen = ref(true)
// ── Quick compose ─────────────────────────────────────────────────────────────
const composeText = ref('')
const composeType = ref<CommType>('note')
// ── Intake action button ──────────────────────────────────────────────────────
const intakeActionLabel = computed(() => {
if (!claim.value) return ''
const map: Record<string, string> = {
not_sent: 'Send Intake Form',
sent: 'Intake Pending',
in_progress: 'Intake In Progress',
completed: 'Generate Forms',
}
return map[claim.value.intakeStatus] ?? ''
})
const intakeActionIcon = computed(() => {
if (!claim.value) return ''
const map: Record<string, string> = {
not_sent: 'i-heroicons-paper-airplane',
sent: 'i-heroicons-clock',
in_progress: 'i-heroicons-arrow-path',
completed: 'i-heroicons-document-duplicate',
}
return map[claim.value.intakeStatus] ?? ''
})
const intakeActionDisabled = computed(() => {
return claim.value?.intakeStatus === 'sent' || claim.value?.intakeStatus === 'in_progress'
})
// ── Action handlers ──────────────────────────────────────────────────────────
const toast = useToast()
function handleGenerateForms() {
toast.add({ title: 'Generating carrier forms…', description: `Forms for claim ${claimId} are being prepared.`, color: 'green' })
}
function handleIntakeAction() {
if (!claim.value) return
if (claim.value.intakeStatus === 'not_sent') {
toast.add({ title: 'Intake form sent', description: `Sent to ${claim.value.customerName} via email.`, color: 'green' })
} else if (claim.value.intakeStatus === 'completed') {
handleGenerateForms()
}
}
// ── Reserve chart ─────────────────────────────────────────────────────────────
const maxReserve = computed(() => {
if (!claim.value) return 1
return Math.max(...claim.value.reserveHistory.map(r => r.amount), 1)
})
</script>
<template>
<div class="cl-root mx-auto max-w-6xl pb-12">
<!-- Not found state -->
<template v-if="!claim">
<div class="cl-not-found">
<UIcon name="i-heroicons-exclamation-triangle" class="w-8 h-8" />
<h2>Claim not found</h2>
<p>No claim with ID <strong>{{ claimId }}</strong> exists.</p>
<NuxtLink to="/claims" class="cl-back-link">Back to Claims</NuxtLink>
</div>
</template>
<template v-else>
<!-- Back link -->
<NuxtLink to="/claims" class="cl-back-link">
<UIcon name="i-heroicons-arrow-left" class="w-3.5 h-3.5" />
Back to Claims
</NuxtLink>
<!-- HEADER -->
<div class="cl-detail-header">
<div class="cl-header-left">
<div class="cl-title-row">
<h1 class="cl-title">{{ claim.id }}</h1>
<span class="cl-carrier-pill" :class="carrierStatusClass(claim.carrierStatus)">
{{ CARRIER_STATUS_LABELS[claim.carrierStatus] }}
</span>
<span class="cl-workflow-pill">
{{ WORKFLOW_STATUS_LABELS[claim.workflowStatus] }}
</span>
<span class="cl-priority-badge" :class="priorityClass(claim.priority)">
{{ PRIORITY_LABELS[claim.priority] }}
</span>
</div>
<div class="cl-subtitle">
<NuxtLink :to="`/customers/${claim.customerId}`" class="cl-subtitle-link">{{ claim.customerName }}</NuxtLink>
<span class="cl-sep">/</span>
<NuxtLink :to="`/policies/${claim.policyId}`" class="cl-subtitle-link">{{ claim.policyNumber }}</NuxtLink>
<span class="cl-sep">/</span>
<span>{{ claim.carrier }}</span>
<span class="cl-sep">/</span>
<span>{{ claim.lob }}</span>
</div>
<div class="cl-meta-row">
<span>Filed {{ fmtDate(claim.dateFiled) }}</span>
<span class="cl-sep">·</span>
<span :class="claim.daysOpen > 30 ? 'cl-days-warn' : ''">{{ claim.daysOpen }} days open</span>
<span class="cl-sep">·</span>
<span>{{ claim.type }}</span>
<span class="cl-sep">·</span>
<span>Handler: <strong>{{ claim.handler }}</strong></span>
</div>
</div>
<div class="cl-header-right">
<button
class="cl-intake-btn"
:class="{ 'cl-intake-btn-disabled': intakeActionDisabled }"
:disabled="intakeActionDisabled"
@click="handleIntakeAction"
>
<UIcon :name="intakeActionIcon" class="w-4 h-4" />
{{ intakeActionLabel }}
</button>
</div>
</div>
<!-- PARTIES STRIP -->
<div class="cl-party-strip">
<button
class="cl-party-chip"
:class="{ 'cl-party-active': !activePartyId }"
@click="activePartyId = null"
>
<span class="cl-party-avatar">All</span>
<span class="cl-party-name">All Parties</span>
</button>
<button
v-for="party in claim.parties"
:key="party.id"
class="cl-party-chip"
:class="{ 'cl-party-active': activePartyId === party.id }"
@click="activePartyId = activePartyId === party.id ? null : party.id"
>
<span class="cl-party-avatar">{{ party.initials }}</span>
<div class="cl-party-info">
<span class="cl-party-role">{{ party.role.replace('_', ' ') }}</span>
<span class="cl-party-name">{{ party.name }}</span>
</div>
<span v-if="party.unreadComms > 0" class="cl-party-dot" />
</button>
<button class="cl-party-chip cl-party-add">
<UIcon name="i-heroicons-plus" class="w-3.5 h-3.5" />
Add Party
</button>
</div>
<!-- QUICK STATS -->
<div class="cl-quick-strip">
<div class="cl-quick-item">
<UIcon name="i-heroicons-shield-exclamation" class="w-4 h-4 cl-quick-icon" />
<div class="cl-quick-data">
<span class="cl-quick-value">{{ fmtMoney(claim.reservedAmount) }}</span>
<span class="cl-quick-label">Reserved</span>
</div>
</div>
<div class="cl-quick-sep" />
<div class="cl-quick-item">
<UIcon name="i-heroicons-banknotes" class="w-4 h-4 cl-quick-icon" />
<div class="cl-quick-data">
<span class="cl-quick-value">{{ fmtMoney(claim.paidAmount) }}</span>
<span class="cl-quick-label">Paid</span>
</div>
</div>
<div class="cl-quick-sep" />
<div class="cl-quick-item">
<UIcon name="i-heroicons-folder-open" class="w-4 h-4 cl-quick-icon" />
<div class="cl-quick-data">
<span class="cl-quick-value">
{{ claim.documents.filter(d => d.required && d.received).length }}/{{ claim.documents.filter(d => d.required).length }}
</span>
<span class="cl-quick-label">Docs</span>
</div>
</div>
<div class="cl-quick-sep" />
<div class="cl-quick-item">
<UIcon name="i-heroicons-clock" class="w-4 h-4 cl-quick-icon" />
<div class="cl-quick-data">
<span class="cl-quick-value">{{ fmtDate(claim.communications[0]?.timestamp ?? '') }}</span>
<span class="cl-quick-label">Last Activity</span>
</div>
</div>
</div>
<!-- OPEN ACTIONS -->
<div v-if="openActions.length" class="cl-action-panel">
<div class="cl-action-header">
<UIcon name="i-heroicons-bell-alert" class="w-4 h-4" />
Open Actions
<span class="cl-action-count">{{ openActions.length }}</span>
</div>
<div
v-for="task in openActions"
:key="task.id"
class="cl-action-row"
>
<span class="cl-sla-dot" :class="`cl-sla-${slaColor(task.slaPercent)}`" />
<span class="cl-action-title">{{ task.title }}</span>
<span class="cl-action-assignee">{{ task.assignee }}</span>
<span class="cl-action-due" :class="{ 'cl-action-overdue': task.status === 'overdue' }">
{{ fmtDate(task.dueDate) }}
</span>
<div class="cl-action-btns">
<button v-if="task.isSystemSuggested" class="cl-btn-escalate">Escalate</button>
<button v-if="task.isSystemSuggested" class="cl-btn-dismiss">Dismiss</button>
<button v-if="task.type === 'communication'" class="cl-btn-draft">Draft IA</button>
</div>
</div>
</div>
<!-- AI RECAP -->
<div class="cl-ai-panel">
<div class="cl-ai-header" @click="aiRecapOpen = !aiRecapOpen">
<span class="cl-ai-badge">IA</span>
<span class="cl-ai-title">Claim Summary</span>
<span class="cl-ai-source">{{ claim.aiRecapSourceCount }} sources</span>
<UIcon :name="aiRecapOpen ? 'i-heroicons-chevron-up' : 'i-heroicons-chevron-down'" class="w-4 h-4 cl-ai-toggle" />
</div>
<div v-if="aiRecapOpen" class="cl-ai-body">
<p>{{ claim.aiRecap }}</p>
<button class="cl-ai-regen">
<UIcon name="i-heroicons-arrow-path" class="w-3.5 h-3.5" />
Regenerar
</button>
</div>
</div>
<!-- TAB NAVIGATION -->
<div class="cl-main-tabs">
<button
v-for="t in tabs"
:key="t.id"
type="button"
class="cl-main-tab"
:class="activeTab === t.id ? 'cl-main-tab-on' : 'cl-main-tab-off'"
@click="activeTab = t.id"
>
<UIcon :name="t.icon" class="w-3.5 h-3.5" />
{{ t.label }}
<span v-if="t.id === 'tasks'" class="cl-tab-count" :class="activeTab === 'tasks' ? 'cl-tab-count-on' : ''">{{ claim.tasks.length }}</span>
<span v-if="t.id === 'communications'" class="cl-tab-count" :class="activeTab === 'communications' ? 'cl-tab-count-on' : ''">{{ claim.communications.length }}</span>
<span v-if="t.id === 'documents'" class="cl-tab-count" :class="activeTab === 'documents' ? 'cl-tab-count-on' : ''">{{ claim.documents.length }}</span>
</button>
</div>
<!-- OVERVIEW TAB -->
<template v-if="activeTab === 'overview'">
<div class="cl-section-grid">
<!-- Key Dates Timeline -->
<div class="cl-card">
<h3 class="cl-card-title">Key Dates</h3>
<div class="cl-timeline">
<div
v-for="(kd, idx) in claim.keyDates"
:key="idx"
class="cl-timeline-entry"
:class="{ 'cl-timeline-done': kd.done }"
>
<div class="cl-timeline-dot" :class="kd.done ? 'cl-timeline-dot-done' : ''" />
<div v-if="idx < claim.keyDates.length - 1" class="cl-timeline-line" />
<div class="cl-timeline-content">
<span class="cl-timeline-label">{{ kd.label }}</span>
<span class="cl-timeline-date">{{ kd.date ? fmtDate(kd.date) : 'Pending' }}</span>
</div>
</div>
</div>
</div>
<!-- Parties Detail -->
<div class="cl-card">
<h3 class="cl-card-title">Parties</h3>
<div class="cl-party-cards">
<div v-for="party in claim.parties" :key="party.id" class="cl-party-card">
<div class="cl-party-card-header">
<span class="cl-party-card-avatar">{{ party.initials }}</span>
<div>
<div class="cl-party-card-name">{{ party.name }}</div>
<div class="cl-party-card-role">{{ party.role.replace('_', ' ') }}</div>
</div>
</div>
<div v-if="party.company" class="cl-party-card-detail">
<UIcon name="i-heroicons-building-office" class="w-3 h-3" />
{{ party.company }}
</div>
<div v-if="party.email" class="cl-party-card-detail">
<UIcon name="i-heroicons-envelope" class="w-3 h-3" />
{{ party.email }}
</div>
<div v-if="party.phone" class="cl-party-card-detail">
<UIcon name="i-heroicons-phone" class="w-3 h-3" />
{{ party.phone }}
</div>
</div>
</div>
</div>
</div>
<!-- Reserve History Chart -->
<div class="cl-card" style="margin-top: 16px;">
<h3 class="cl-card-title">Reserve History</h3>
<div class="cl-reserve-chart">
<div
v-for="(rh, idx) in claim.reserveHistory"
:key="idx"
class="cl-reserve-bar-wrap"
>
<div class="cl-reserve-bar" :style="{ width: (rh.amount / maxReserve * 100) + '%' }">
<span class="cl-reserve-amount">{{ fmtMoney(rh.amount) }}</span>
</div>
<div class="cl-reserve-meta">
<span>{{ fmtDate(rh.date) }}</span>
<span class="cl-reserve-note">{{ rh.annotation }}</span>
</div>
</div>
</div>
</div>
</template>
<!-- TASKS TAB -->
<template v-if="activeTab === 'tasks'">
<div class="cl-card">
<table class="cl-table">
<thead>
<tr>
<th style="width: 28px;"></th>
<th>Task</th>
<th>Status</th>
<th>Assignee</th>
<th>Due</th>
<th>SLA</th>
</tr>
</thead>
<tbody>
<tr v-for="task in sortedTasks" :key="task.id" :class="{ 'cl-row-overdue': task.status === 'overdue' }">
<td><span class="cl-sla-dot" :class="`cl-sla-${slaColor(task.slaPercent)}`" /></td>
<td>
<div class="cl-task-title">{{ task.title }}</div>
<div v-if="task.isSystemSuggested" class="cl-task-system">System suggested</div>
</td>
<td><span class="cl-task-status" :class="taskStatusClass(task.status)">{{ TASK_STATUS_LABELS[task.status] }}</span></td>
<td>{{ task.assignee }}</td>
<td :class="{ 'cl-action-overdue': task.status === 'overdue' }">{{ fmtDate(task.dueDate) }}</td>
<td>
<div class="cl-sla-bar-wrap">
<div class="cl-sla-bar" :class="`cl-sla-bar-${slaColor(task.slaPercent)}`" :style="{ width: Math.min(task.slaPercent, 100) + '%' }" />
</div>
<span class="cl-sla-pct">{{ task.slaPercent }}%</span>
</td>
</tr>
</tbody>
</table>
</div>
</template>
<!-- COMMUNICATIONS TAB -->
<template v-if="activeTab === 'communications'">
<!-- Type filter chips -->
<div class="cl-comm-filters">
<button
class="cl-comm-chip"
:class="{ 'cl-comm-chip-on': !commTypeFilter }"
@click="commTypeFilter = null"
>All</button>
<button
v-for="ct in (['email', 'call', 'note', 'system'] as CommType[])"
:key="ct"
class="cl-comm-chip"
:class="{ 'cl-comm-chip-on': commTypeFilter === ct }"
@click="commTypeFilter = commTypeFilter === ct ? null : ct"
>
<UIcon :name="commIcon(ct)" class="w-3 h-3" />
{{ commTypeLabel(ct) }}
</button>
</div>
<!-- Timeline -->
<div class="cl-comm-timeline">
<div v-for="comm in filteredComms" :key="comm.id" class="cl-comm-entry">
<div class="cl-comm-icon-wrap">
<UIcon :name="commIcon(comm.type)" class="w-4 h-4" />
</div>
<div class="cl-comm-body">
<div class="cl-comm-header">
<span class="cl-comm-from">{{ comm.from }}</span>
<span v-if="comm.to" class="cl-comm-to"> {{ comm.to }}</span>
<span class="cl-comm-type-badge">{{ commTypeLabel(comm.type) }}</span>
<span class="cl-comm-time">{{ fmtDateTime(comm.timestamp) }}</span>
</div>
<div v-if="comm.subject" class="cl-comm-subject">{{ comm.subject }}</div>
<div class="cl-comm-text">{{ comm.body }}</div>
<!-- AI Thread Digest -->
<div v-if="comm.aiDigest" class="cl-ai-panel cl-ai-inline">
<span class="cl-ai-badge">IA</span>
<span class="cl-ai-digest-text">{{ comm.aiDigest }}</span>
</div>
</div>
</div>
</div>
<!-- Quick Compose -->
<div class="cl-compose">
<div class="cl-compose-header">
<select v-model="composeType" class="cl-compose-type">
<option value="note">Note</option>
<option value="email">Email</option>
<option value="call">Call Log</option>
</select>
</div>
<textarea
v-model="composeText"
class="cl-compose-input"
placeholder="Write a note, log a call, or draft an email..."
rows="3"
/>
<div class="cl-compose-footer">
<button class="cl-btn-draft">
<UIcon name="i-heroicons-sparkles" class="w-3.5 h-3.5" />
Draft IA
</button>
<button class="cl-btn-send" :disabled="!composeText.trim()">
<UIcon name="i-heroicons-paper-airplane" class="w-3.5 h-3.5" />
Send
</button>
</div>
</div>
</template>
<!-- DOCUMENTS TAB -->
<template v-if="activeTab === 'documents'">
<!-- Section A: Required Documents by Category -->
<div class="cl-doc-section-label">Required Documents</div>
<div v-for="group in docsByCategory" :key="group.category" class="cl-doc-category">
<div class="cl-doc-cat-header">
<h4 class="cl-doc-cat-title">{{ group.label }}</h4>
<span v-if="group.requiredCount" class="cl-doc-cat-progress">
{{ group.receivedCount }}/{{ group.requiredCount }} received
</span>
<button class="cl-btn-upload">
<UIcon name="i-heroicons-arrow-up-tray" class="w-3 h-3" />
Upload
</button>
</div>
<div v-if="group.docs.length" class="cl-doc-list">
<div v-for="doc in group.docs" :key="doc.id" class="cl-doc-row">
<span class="cl-doc-check" :class="doc.received ? 'cl-doc-check-on' : ''">
<UIcon :name="doc.received ? 'i-heroicons-check-circle-solid' : 'i-heroicons-minus-circle'" class="w-4 h-4" />
</span>
<span class="cl-doc-name">{{ doc.name }}</span>
<span v-if="doc.required" class="cl-doc-req">Required</span>
<span class="cl-doc-meta">{{ doc.uploadedBy }} · {{ fmtDate(doc.uploadedAt) }} · {{ doc.size }}</span>
</div>
</div>
<div v-else class="cl-doc-empty">No documents in this category yet.</div>
</div>
<!-- Section B: Generated Forms -->
<div class="cl-doc-section-label" style="margin-top: 28px;">
Generated Carrier Forms
<span class="cl-doc-section-sub">Carrier-specific forms only government forms (FUD, police reports) are uploaded under Evidence</span>
</div>
<div v-if="claim.generatedForms.length" class="cl-card">
<div v-for="form in claim.generatedForms" :key="form.id" class="cl-form-row">
<div class="cl-form-info">
<span class="cl-form-name">{{ form.carrierFormName }}</span>
<span class="cl-form-carrier">{{ form.carrier }} · {{ form.lob }}</span>
</div>
<span class="cl-form-status" :class="formStatusClass(form.status)">{{ formStatusLabel(form.status) }}</span>
<span class="cl-form-date">{{ fmtDate(form.generatedAt) }}</span>
<button class="cl-btn-preview">
<UIcon name="i-heroicons-eye" class="w-3.5 h-3.5" />
Preview
</button>
<button v-if="form.status === 'ready_for_signature'" class="cl-btn-sign">
<UIcon name="i-heroicons-pencil" class="w-3.5 h-3.5" />
Send for Signature
</button>
</div>
</div>
<div v-else class="cl-card cl-doc-empty">
<template v-if="claim.intakeStatus === 'completed'">
<p>Intake complete forms ready to generate.</p>
<button class="cl-intake-btn" style="margin-top: 8px;" @click="handleGenerateForms">
<UIcon name="i-heroicons-document-duplicate" class="w-4 h-4" />
Generate Forms
</button>
</template>
<template v-else>
No generated forms yet. Complete the intake form first.
</template>
</div>
</template>
<!-- FINANCIAL TAB -->
<template v-if="activeTab === 'financial'">
<!-- Cost Summary Card -->
<div class="cl-fin-summary">
<div class="cl-fin-summary-item">
<span class="cl-fin-summary-label">Reserved</span>
<span class="cl-fin-summary-value">{{ fmtMoney(financialSummary.reserved) }}</span>
</div>
<div class="cl-fin-summary-sep" />
<div class="cl-fin-summary-item">
<span class="cl-fin-summary-label">Paid</span>
<span class="cl-fin-summary-value">{{ fmtMoney(financialSummary.paid) }}</span>
</div>
<div class="cl-fin-summary-sep" />
<div class="cl-fin-summary-item">
<span class="cl-fin-summary-label">Subrogated</span>
<span class="cl-fin-summary-value cl-fin-sub">{{ fmtMoney(financialSummary.subrogated) }}</span>
</div>
<div class="cl-fin-summary-sep" />
<div class="cl-fin-summary-item">
<span class="cl-fin-summary-label">Expenses</span>
<span class="cl-fin-summary-value">{{ fmtMoney(financialSummary.expenses) }}</span>
</div>
<div class="cl-fin-summary-sep" />
<div class="cl-fin-summary-item">
<span class="cl-fin-summary-label">Net Cost</span>
<span class="cl-fin-summary-value cl-fin-net">{{ fmtMoney(financialSummary.net) }}</span>
</div>
</div>
<!-- Reserve History -->
<div class="cl-card">
<h3 class="cl-card-title">Reserve History</h3>
<table class="cl-table">
<thead>
<tr><th>Date</th><th>Amount</th><th>Type</th><th>Description</th></tr>
</thead>
<tbody>
<tr v-for="e in reserveEntries" :key="e.id">
<td>{{ fmtDate(e.date) }}</td>
<td>{{ fmtMoney(e.amount) }}</td>
<td>Reserve Change</td>
<td>
{{ e.description }}
<span v-if="e.annotation" class="cl-fin-annotation">{{ e.annotation }}</span>
</td>
</tr>
<tr v-if="!reserveEntries.length"><td colspan="4" class="cl-table-empty">No reserve changes recorded.</td></tr>
</tbody>
</table>
</div>
<!-- Payments -->
<div class="cl-card">
<h3 class="cl-card-title">Payments</h3>
<table class="cl-table">
<thead>
<tr><th>Date</th><th>Amount</th><th>Description</th></tr>
</thead>
<tbody>
<tr v-for="e in paymentEntries" :key="e.id">
<td>{{ fmtDate(e.date) }}</td>
<td>{{ fmtMoney(e.amount) }}</td>
<td>{{ e.description }}</td>
</tr>
<tr v-if="!paymentEntries.length"><td colspan="3" class="cl-table-empty">No payments recorded.</td></tr>
</tbody>
</table>
</div>
<!-- Subrogation -->
<div class="cl-card">
<h3 class="cl-card-title">Subrogation</h3>
<table class="cl-table">
<thead>
<tr><th>Date</th><th>Amount</th><th>Description</th></tr>
</thead>
<tbody>
<tr v-for="e in subrogationEntries" :key="e.id">
<td>{{ fmtDate(e.date) }}</td>
<td class="cl-fin-sub">{{ fmtMoney(e.amount) }}</td>
<td>{{ e.description }}</td>
</tr>
<tr v-if="!subrogationEntries.length"><td colspan="3" class="cl-table-empty">No subrogation activity.</td></tr>
</tbody>
</table>
</div>
</template>
</template>
</div>
</template>
<style scoped>
/* =====================================================================
CLAIM DETAIL PAGE — scoped, cl- prefix
===================================================================== */
.cl-root {
--cl-brand: #01696f;
--cl-brand-soft: rgba(1, 105, 111, 0.06);
--cl-border: rgba(0, 0, 0, 0.06);
--cl-border-strong: rgba(0, 0, 0, 0.08);
--cl-muted: #8a8a86;
--cl-surface: #ffffff;
--cl-amber: #c27b1a;
--cl-red: #c13838;
--cl-purple: #9333ea;
}
/* ---- Not Found ---- */
.cl-not-found { text-align: center; padding: 80px 0; color: var(--cl-muted); }
.cl-not-found h2 { font-size: 18px; font-weight: 600; margin: 12px 0 4px; color: #1a1a1a; }
.cl-not-found p { margin-bottom: 16px; font-size: 13px; }
/* ---- Back link ---- */
.cl-back-link {
display: inline-flex; align-items: center; gap: 5px;
font-size: 12px; font-weight: 500; color: var(--cl-muted);
text-decoration: none; margin-bottom: 12px; transition: color 150ms ease;
}
.cl-back-link:hover { color: var(--cl-brand); }
/* ---- Detail header ---- */
.cl-detail-header {
display: flex; align-items: flex-start; justify-content: space-between;
gap: 16px; margin-bottom: 16px; flex-wrap: wrap;
}
.cl-header-left { display: flex; flex-direction: column; gap: 6px; }
.cl-header-right { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.cl-title-row { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.cl-title {
font-size: 22px; font-weight: 700; letter-spacing: -0.01em;
color: #1a1a1a; line-height: 1; font-family: 'SF Mono', 'Fira Code', monospace;
}
.cl-subtitle {
display: flex; align-items: center; gap: 6px; font-size: 13px;
color: #5c5650; flex-wrap: wrap;
}
.cl-subtitle-link { color: var(--cl-brand); text-decoration: none; font-weight: 500; }
.cl-subtitle-link:hover { text-decoration: underline; }
.cl-sep { color: var(--cl-muted); opacity: 0.5; }
.cl-meta-row { display: flex; align-items: center; gap: 6px; font-size: 12px; color: var(--cl-muted); flex-wrap: wrap; }
.cl-days-warn { color: var(--cl-red); font-weight: 600; }
/* ---- Carrier Status Pill (filled) ---- */
.cl-carrier-pill {
display: inline-flex; align-items: center; padding: 3px 10px;
border-radius: 10px; font-size: 11px; font-weight: 600;
white-space: nowrap; letter-spacing: 0.02em;
}
.cl-cs-fnol { background: rgba(59, 130, 246, 0.1); color: #2563eb; }
.cl-cs-ack { background: rgba(16, 185, 129, 0.1); color: #059669; }
.cl-cs-inv { background: rgba(245, 158, 11, 0.1); color: #d97706; }
.cl-cs-doc { background: rgba(147, 51, 234, 0.1); color: #9333ea; }
.cl-cs-rsv { background: rgba(1, 105, 111, 0.1); color: #01696f; }
.cl-cs-neg { background: rgba(194, 123, 26, 0.1); color: #c27b1a; }
.cl-cs-set { background: rgba(16, 185, 129, 0.1); color: #059669; }
.cl-cs-closed { background: rgba(138, 138, 134, 0.1); color: #8a8a86; }
/* ---- Workflow Pill (outline) ---- */
.cl-workflow-pill {
display: inline-flex; align-items: center; padding: 2px 9px;
border-radius: 10px; font-size: 11px; font-weight: 600;
border: 1.5px solid var(--cl-brand); color: var(--cl-brand);
white-space: nowrap; letter-spacing: 0.02em;
}
/* ---- Priority Badge ---- */
.cl-priority-badge {
display: inline-flex; align-items: center; padding: 2px 8px;
border-radius: 8px; font-size: 10px; font-weight: 700;
text-transform: uppercase; letter-spacing: 0.05em;
}
.cl-pri-critical { background: rgba(193, 56, 56, 0.1); color: var(--cl-red); }
.cl-pri-high { background: rgba(194, 123, 26, 0.1); color: var(--cl-amber); }
.cl-pri-medium { background: rgba(138, 138, 134, 0.08); color: #5c5650; }
.cl-pri-low { background: rgba(138, 138, 134, 0.06); color: var(--cl-muted); }
/* ---- Intake Button ---- */
.cl-intake-btn {
display: inline-flex; align-items: center; gap: 6px; padding: 8px 16px;
border-radius: 10px; font-size: 13px; font-weight: 600;
background: var(--cl-brand); color: white; border: none; cursor: pointer;
transition: opacity 150ms ease;
}
.cl-intake-btn:hover { opacity: 0.9; }
.cl-intake-btn-disabled { opacity: 0.5; cursor: not-allowed; background: var(--cl-muted); }
/* ---- Party Strip ---- */
.cl-party-strip {
display: flex; gap: 6px; overflow-x: auto; padding: 8px 0 12px;
scrollbar-width: thin; scrollbar-color: rgba(0,0,0,0.1) transparent;
}
.cl-party-chip {
display: flex; align-items: center; gap: 6px; padding: 6px 12px;
border-radius: 20px; border: 1px solid var(--cl-border-strong);
background: var(--cl-surface); font-size: 12px; cursor: pointer;
transition: all 150ms ease; white-space: nowrap; position: relative;
}
.cl-party-chip:hover { border-color: var(--cl-brand); }
.cl-party-active { border-color: var(--cl-brand); background: var(--cl-brand-soft); color: var(--cl-brand); font-weight: 600; }
.cl-party-avatar {
display: flex; align-items: center; justify-content: center;
width: 22px; height: 22px; border-radius: 50%;
background: var(--cl-brand-soft); color: var(--cl-brand);
font-size: 9px; font-weight: 700; text-transform: uppercase;
}
.cl-party-info { display: flex; flex-direction: column; }
.cl-party-role { font-size: 9px; text-transform: uppercase; letter-spacing: 0.05em; color: var(--cl-muted); font-weight: 600; }
.cl-party-name { font-size: 12px; }
.cl-party-dot {
position: absolute; top: 4px; right: 4px;
width: 7px; height: 7px; border-radius: 50%; background: var(--cl-red);
}
.cl-party-add { border-style: dashed; color: var(--cl-muted); }
.cl-party-add:hover { color: var(--cl-brand); border-color: var(--cl-brand); }
/* ---- Quick Stats Strip ---- */
.cl-quick-strip {
display: flex; align-items: center; gap: 16px; padding: 12px 16px;
background: var(--cl-surface); border: 1px solid var(--cl-border);
border-radius: 12px; margin-bottom: 16px;
}
.cl-quick-item { display: flex; align-items: center; gap: 8px; }
.cl-quick-icon { color: var(--cl-brand); }
.cl-quick-data { display: flex; flex-direction: column; }
.cl-quick-value { font-size: 15px; font-weight: 700; color: #1a1a1a; line-height: 1.2; }
.cl-quick-label { font-size: 10px; text-transform: uppercase; letter-spacing: 0.05em; color: var(--cl-muted); font-weight: 600; }
.cl-quick-sep { width: 1px; height: 28px; background: var(--cl-border); }
/* ---- Open Actions Panel ---- */
.cl-action-panel {
background: var(--cl-surface); border: 1px solid var(--cl-border);
border-radius: 12px; margin-bottom: 16px; overflow: hidden;
}
.cl-action-header {
display: flex; align-items: center; gap: 8px; padding: 12px 16px;
font-size: 13px; font-weight: 700; color: var(--cl-red);
border-bottom: 1px solid var(--cl-border);
}
.cl-action-count {
display: inline-flex; align-items: center; justify-content: center;
min-width: 20px; height: 20px; padding: 0 6px;
border-radius: 10px; background: rgba(193, 56, 56, 0.1);
font-size: 11px; font-weight: 700; color: var(--cl-red);
}
.cl-action-row {
display: flex; align-items: center; gap: 10px; padding: 10px 16px;
border-bottom: 1px solid var(--cl-border); font-size: 13px;
}
.cl-action-row:last-child { border-bottom: none; }
.cl-action-title { flex: 1; font-weight: 500; }
.cl-action-assignee { color: var(--cl-muted); font-size: 12px; }
.cl-action-due { font-size: 12px; color: var(--cl-muted); }
.cl-action-overdue { color: var(--cl-red); font-weight: 600; }
.cl-action-btns { display: flex; gap: 6px; }
/* ---- Action buttons ---- */
.cl-btn-escalate {
padding: 4px 10px; border-radius: 6px; font-size: 11px; font-weight: 600;
background: rgba(193, 56, 56, 0.08); color: var(--cl-red);
border: 1px solid rgba(193, 56, 56, 0.2); cursor: pointer;
}
.cl-btn-escalate:hover { background: rgba(193, 56, 56, 0.15); }
.cl-btn-dismiss {
padding: 4px 10px; border-radius: 6px; font-size: 11px; font-weight: 600;
background: rgba(138, 138, 134, 0.08); color: var(--cl-muted);
border: 1px solid var(--cl-border-strong); cursor: pointer;
}
.cl-btn-dismiss:hover { background: rgba(138, 138, 134, 0.15); }
.cl-btn-draft {
display: inline-flex; align-items: center; gap: 4px;
padding: 4px 10px; border-radius: 6px; font-size: 11px; font-weight: 600;
background: var(--cl-brand-soft); color: var(--cl-brand);
border: 1px solid rgba(1, 105, 111, 0.2); cursor: pointer;
}
.cl-btn-draft:hover { background: rgba(1, 105, 111, 0.12); }
.cl-btn-send {
display: inline-flex; align-items: center; gap: 4px;
padding: 6px 14px; border-radius: 8px; font-size: 12px; font-weight: 600;
background: var(--cl-brand); color: white; border: none; cursor: pointer;
}
.cl-btn-send:disabled { opacity: 0.4; cursor: not-allowed; }
.cl-btn-upload {
display: inline-flex; align-items: center; gap: 4px;
padding: 4px 10px; border-radius: 6px; font-size: 11px; font-weight: 600;
background: var(--cl-brand-soft); color: var(--cl-brand);
border: 1px solid rgba(1, 105, 111, 0.15); cursor: pointer; margin-left: auto;
}
.cl-btn-upload:hover { background: rgba(1, 105, 111, 0.12); }
.cl-btn-preview {
display: inline-flex; align-items: center; gap: 4px;
padding: 4px 10px; border-radius: 6px; font-size: 11px; font-weight: 600;
background: rgba(138, 138, 134, 0.06); color: #5c5650;
border: 1px solid var(--cl-border-strong); cursor: pointer;
}
.cl-btn-sign {
display: inline-flex; align-items: center; gap: 4px;
padding: 4px 10px; border-radius: 6px; font-size: 11px; font-weight: 600;
background: var(--cl-brand); color: white; border: none; cursor: pointer;
}
/* ---- AI Panel ---- */
.cl-ai-panel {
background: var(--cl-brand-soft); border-left: 3px solid var(--cl-brand);
border-radius: 8px; padding: 12px 16px; margin-bottom: 16px; position: relative;
}
.cl-ai-header {
display: flex; align-items: center; gap: 8px; cursor: pointer;
}
.cl-ai-badge {
display: inline-flex; align-items: center; justify-content: center;
padding: 2px 8px; border-radius: 6px; font-size: 10px; font-weight: 800;
background: var(--cl-brand); color: white; letter-spacing: 0.05em;
}
.cl-ai-title { font-size: 13px; font-weight: 600; color: #1a1a1a; }
.cl-ai-source { font-size: 11px; color: var(--cl-muted); margin-left: auto; }
.cl-ai-toggle { color: var(--cl-muted); }
.cl-ai-body { margin-top: 10px; font-size: 13px; line-height: 1.6; color: #3a3a3a; }
.cl-ai-regen {
display: inline-flex; align-items: center; gap: 4px; margin-top: 10px;
padding: 4px 10px; border-radius: 6px; font-size: 11px; font-weight: 600;
background: transparent; color: var(--cl-brand); border: 1px solid rgba(1, 105, 111, 0.2); cursor: pointer;
}
.cl-ai-regen:hover { background: rgba(1, 105, 111, 0.06); }
.cl-ai-inline { padding: 8px 12px; margin-top: 8px; display: flex; align-items: flex-start; gap: 8px; }
.cl-ai-digest-text { font-size: 12px; line-height: 1.5; color: #3a3a3a; flex: 1; }
/* ---- Main Tabs ---- */
.cl-main-tabs {
display: inline-flex; gap: 2px; padding: 3px;
border-radius: 8px; background: rgba(0,0,0,0.03); margin-bottom: 20px;
}
.cl-main-tab {
display: inline-flex; align-items: center; gap: 5px;
padding: 7px 14px; border-radius: 6px; font-size: 12px; font-weight: 600;
border: none; cursor: pointer; transition: all 150ms ease;
white-space: nowrap;
}
.cl-main-tab-on { background: var(--cl-surface); color: #1a1a1a; box-shadow: 0 1px 2px rgba(0,0,0,0.06); }
.cl-main-tab-off { background: transparent; color: var(--cl-muted); }
.cl-main-tab-off:hover { color: #1a1a1a; }
.cl-tab-count {
display: inline-flex; align-items: center; justify-content: center;
min-width: 18px; height: 18px; padding: 0 5px;
border-radius: 9px; background: rgba(0,0,0,0.06);
font-size: 10px; font-weight: 700;
}
.cl-tab-count-on { background: var(--cl-brand); color: white; }
/* ---- Cards ---- */
.cl-card {
background: var(--cl-surface); border: 1px solid var(--cl-border);
border-radius: 12px; padding: 16px; margin-bottom: 16px;
}
.cl-card-title { font-size: 14px; font-weight: 700; margin-bottom: 12px; color: #1a1a1a; }
.cl-section-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; }
/* ---- SLA Dot ---- */
.cl-sla-dot { width: 8px; height: 8px; border-radius: 50%; flex-shrink: 0; }
.cl-sla-green { background: #059669; }
.cl-sla-amber { background: var(--cl-amber); }
.cl-sla-red { background: var(--cl-red); }
/* ---- SLA Bar ---- */
.cl-sla-bar-wrap { width: 48px; height: 4px; background: rgba(0,0,0,0.06); border-radius: 2px; display: inline-block; vertical-align: middle; }
.cl-sla-bar { height: 100%; border-radius: 2px; transition: width 300ms ease; }
.cl-sla-bar-green { background: #059669; }
.cl-sla-bar-amber { background: var(--cl-amber); }
.cl-sla-bar-red { background: var(--cl-red); }
.cl-sla-pct { font-size: 10px; color: var(--cl-muted); margin-left: 4px; }
/* ---- Timeline (Overview) ---- */
.cl-timeline { display: flex; flex-direction: column; gap: 0; padding-left: 4px; }
.cl-timeline-entry { display: flex; align-items: flex-start; gap: 12px; position: relative; min-height: 40px; }
.cl-timeline-dot {
width: 10px; height: 10px; border-radius: 50%;
border: 2px solid var(--cl-border-strong); background: white;
margin-top: 4px; flex-shrink: 0; z-index: 1;
}
.cl-timeline-dot-done { border-color: var(--cl-brand); background: var(--cl-brand); }
.cl-timeline-line {
position: absolute; left: 4px; top: 16px; bottom: -8px;
width: 2px; background: var(--cl-border-strong);
}
.cl-timeline-done .cl-timeline-line { background: var(--cl-brand); }
.cl-timeline-content { padding-bottom: 12px; }
.cl-timeline-label { font-size: 13px; font-weight: 500; display: block; }
.cl-timeline-date { font-size: 11px; color: var(--cl-muted); }
/* ---- Party Cards (Overview) ---- */
.cl-party-cards { display: flex; flex-direction: column; gap: 10px; }
.cl-party-card {
padding: 10px 12px; border: 1px solid var(--cl-border);
border-radius: 8px; background: rgba(0,0,0,0.01);
}
.cl-party-card-header { display: flex; align-items: center; gap: 8px; margin-bottom: 6px; }
.cl-party-card-avatar {
display: flex; align-items: center; justify-content: center;
width: 28px; height: 28px; border-radius: 50%;
background: var(--cl-brand-soft); color: var(--cl-brand);
font-size: 10px; font-weight: 700;
}
.cl-party-card-name { font-size: 13px; font-weight: 600; }
.cl-party-card-role { font-size: 10px; text-transform: uppercase; letter-spacing: 0.04em; color: var(--cl-muted); }
.cl-party-card-detail { display: flex; align-items: center; gap: 6px; font-size: 12px; color: #5c5650; margin-top: 3px; }
/* ---- Reserve Chart (Overview) ---- */
.cl-reserve-chart { display: flex; flex-direction: column; gap: 10px; }
.cl-reserve-bar-wrap { display: flex; flex-direction: column; gap: 4px; }
.cl-reserve-bar {
height: 24px; background: var(--cl-brand); border-radius: 4px;
display: flex; align-items: center; padding: 0 8px; min-width: 60px;
}
.cl-reserve-amount { font-size: 11px; font-weight: 700; color: white; }
.cl-reserve-meta { display: flex; align-items: center; gap: 8px; font-size: 11px; color: var(--cl-muted); }
.cl-reserve-note { font-style: italic; }
/* ---- Table ---- */
.cl-table { width: 100%; border-collapse: collapse; font-size: 13px; }
.cl-table th {
text-align: left; padding: 8px 10px; font-size: 10px;
text-transform: uppercase; letter-spacing: 0.05em; color: var(--cl-muted);
font-weight: 600; border-bottom: 1px solid var(--cl-border);
}
.cl-table td { padding: 10px 10px; border-bottom: 1px solid var(--cl-border); vertical-align: top; }
.cl-table tbody tr:last-child td { border-bottom: none; }
.cl-table-empty { text-align: center; color: var(--cl-muted); padding: 24px 0 !important; font-style: italic; }
.cl-row-overdue { background: rgba(193, 56, 56, 0.03); }
.cl-task-title { font-weight: 500; }
.cl-task-system { font-size: 10px; color: var(--cl-amber); font-weight: 600; margin-top: 2px; }
/* ---- Task Status Badge ---- */
.cl-task-status {
display: inline-flex; padding: 2px 8px; border-radius: 8px;
font-size: 11px; font-weight: 600; white-space: nowrap;
}
.cl-ts-open { background: rgba(59, 130, 246, 0.08); color: #2563eb; }
.cl-ts-progress { background: rgba(1, 105, 111, 0.08); color: var(--cl-brand); }
.cl-ts-overdue { background: rgba(193, 56, 56, 0.08); color: var(--cl-red); }
.cl-ts-done { background: rgba(138, 138, 134, 0.08); color: var(--cl-muted); }
/* ---- Communications ---- */
.cl-comm-filters { display: flex; gap: 6px; margin-bottom: 16px; flex-wrap: wrap; }
.cl-comm-chip {
display: inline-flex; align-items: center; gap: 4px; padding: 5px 12px;
border-radius: 16px; font-size: 12px; font-weight: 500;
border: 1px solid var(--cl-border-strong); background: var(--cl-surface);
cursor: pointer; transition: all 150ms ease;
}
.cl-comm-chip:hover { border-color: var(--cl-brand); }
.cl-comm-chip-on { background: var(--cl-brand-soft); border-color: var(--cl-brand); color: var(--cl-brand); font-weight: 600; }
.cl-comm-timeline { display: flex; flex-direction: column; gap: 0; }
.cl-comm-entry {
display: flex; gap: 12px; padding: 14px 0;
border-bottom: 1px solid var(--cl-border);
}
.cl-comm-entry:last-child { border-bottom: none; }
.cl-comm-icon-wrap {
width: 32px; height: 32px; border-radius: 50%;
background: var(--cl-brand-soft); color: var(--cl-brand);
display: flex; align-items: center; justify-content: center;
flex-shrink: 0; margin-top: 2px;
}
.cl-comm-body { flex: 1; min-width: 0; }
.cl-comm-header { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; margin-bottom: 4px; }
.cl-comm-from { font-size: 13px; font-weight: 600; }
.cl-comm-to { font-size: 12px; color: var(--cl-muted); }
.cl-comm-type-badge {
font-size: 10px; font-weight: 600; text-transform: uppercase;
padding: 1px 6px; border-radius: 4px;
background: rgba(0,0,0,0.04); color: var(--cl-muted); letter-spacing: 0.03em;
}
.cl-comm-time { font-size: 11px; color: var(--cl-muted); margin-left: auto; }
.cl-comm-subject { font-size: 13px; font-weight: 500; margin-bottom: 4px; }
.cl-comm-text { font-size: 13px; line-height: 1.55; color: #3a3a3a; white-space: pre-wrap; }
/* ---- Compose ---- */
.cl-compose {
background: var(--cl-surface); border: 1px solid var(--cl-border);
border-radius: 12px; padding: 12px; margin-top: 16px;
}
.cl-compose-header { margin-bottom: 8px; }
.cl-compose-type {
padding: 4px 8px; border-radius: 6px; font-size: 12px;
border: 1px solid var(--cl-border-strong); background: white;
color: #1a1a1a; font-weight: 500;
}
.cl-compose-input {
width: 100%; border: 1px solid var(--cl-border); border-radius: 8px;
padding: 8px 10px; font-size: 13px; resize: vertical;
font-family: inherit; color: #1a1a1a;
}
.cl-compose-input:focus { outline: none; border-color: var(--cl-brand); }
.cl-compose-footer { display: flex; justify-content: flex-end; gap: 8px; margin-top: 8px; }
/* ---- Documents ---- */
.cl-doc-section-label {
font-size: 14px; font-weight: 700; margin-bottom: 12px; color: #1a1a1a;
}
.cl-doc-section-sub {
display: block; font-size: 11px; font-weight: 400; color: var(--cl-muted);
margin-top: 2px;
}
.cl-doc-category {
background: var(--cl-surface); border: 1px solid var(--cl-border);
border-radius: 12px; margin-bottom: 12px; overflow: hidden;
}
.cl-doc-cat-header {
display: flex; align-items: center; gap: 10px; padding: 10px 16px;
border-bottom: 1px solid var(--cl-border);
}
.cl-doc-cat-title { font-size: 13px; font-weight: 600; margin: 0; }
.cl-doc-cat-progress { font-size: 11px; color: var(--cl-muted); }
.cl-doc-list { padding: 0; }
.cl-doc-row {
display: flex; align-items: center; gap: 8px; padding: 8px 16px;
border-bottom: 1px solid var(--cl-border); font-size: 13px;
}
.cl-doc-row:last-child { border-bottom: none; }
.cl-doc-check { color: var(--cl-muted); }
.cl-doc-check-on { color: var(--cl-brand); }
.cl-doc-name { flex: 1; font-weight: 500; }
.cl-doc-req { font-size: 10px; font-weight: 600; text-transform: uppercase; color: var(--cl-amber); letter-spacing: 0.03em; }
.cl-doc-meta { font-size: 11px; color: var(--cl-muted); }
.cl-doc-empty { padding: 20px 16px; text-align: center; color: var(--cl-muted); font-size: 13px; }
/* ---- Generated Forms ---- */
.cl-form-row {
display: flex; align-items: center; gap: 12px; padding: 10px 0;
border-bottom: 1px solid var(--cl-border);
}
.cl-form-row:last-child { border-bottom: none; }
.cl-form-info { flex: 1; }
.cl-form-name { font-size: 13px; font-weight: 600; display: block; }
.cl-form-carrier { font-size: 11px; color: var(--cl-muted); }
.cl-form-status {
display: inline-flex; padding: 2px 8px; border-radius: 8px;
font-size: 11px; font-weight: 600;
}
.cl-fs-draft { background: rgba(138, 138, 134, 0.08); color: var(--cl-muted); }
.cl-fs-ready { background: rgba(194, 123, 26, 0.08); color: var(--cl-amber); }
.cl-fs-signed { background: rgba(1, 105, 111, 0.08); color: var(--cl-brand); }
.cl-fs-submitted { background: rgba(16, 185, 129, 0.08); color: #059669; }
.cl-form-date { font-size: 11px; color: var(--cl-muted); }
/* ---- Financial ---- */
.cl-fin-summary {
display: flex; align-items: center; gap: 16px; padding: 16px 20px;
background: var(--cl-surface); border: 1px solid var(--cl-border);
border-radius: 12px; margin-bottom: 16px; flex-wrap: wrap;
}
.cl-fin-summary-item { display: flex; flex-direction: column; gap: 2px; }
.cl-fin-summary-label { font-size: 10px; text-transform: uppercase; letter-spacing: 0.05em; color: var(--cl-muted); font-weight: 600; }
.cl-fin-summary-value { font-size: 16px; font-weight: 700; color: #1a1a1a; }
.cl-fin-summary-sep { width: 1px; height: 32px; background: var(--cl-border); }
.cl-fin-sub { color: #059669; }
.cl-fin-net { color: var(--cl-brand); }
.cl-fin-annotation { display: block; font-size: 11px; color: var(--cl-muted); font-style: italic; margin-top: 2px; }
/* ---- Responsive ---- */
@media (max-width: 768px) {
.cl-section-grid { grid-template-columns: 1fr; }
.cl-quick-strip { flex-wrap: wrap; }
.cl-fin-summary { flex-direction: column; align-items: flex-start; }
.cl-fin-summary-sep { width: 100%; height: 1px; }
.cl-detail-header { flex-direction: column; }
.cl-main-tabs { overflow-x: auto; width: 100%; }
.cl-action-row { flex-wrap: wrap; }
}
</style>