507 lines
24 KiB
Vue
507 lines
24 KiB
Vue
<script setup lang="ts">
|
||
import { slaColor, CARRIER_STATUS_LABELS, WORKFLOW_STATUS_LABELS } from '~/data/mock-claims'
|
||
import type { CarrierStatus, BrokerWorkflowStatus } from '~/data/mock-claims'
|
||
|
||
usePageTitle('Claims')
|
||
|
||
interface Claim {
|
||
id: string
|
||
customer: string
|
||
agent: string
|
||
line: string
|
||
type: string
|
||
carrier: string
|
||
reserved: string
|
||
paid: string
|
||
daysOpen: number
|
||
priority: 'critical' | 'high' | 'medium' | 'low'
|
||
status: 'open' | 'under_review' | 'awaiting_docs' | 'approved' | 'denied' | 'closed'
|
||
docsPending: number
|
||
opened: string
|
||
carrierStatus: CarrierStatus
|
||
workflowStatus: BrokerWorkflowStatus
|
||
slaPercent: number
|
||
handler: string
|
||
}
|
||
|
||
const claims = ref<Claim[]>([
|
||
{ id: 'CLM-0048', customer: 'Hotel Pacífico', agent: 'Marco V.', line: 'General Risk', type: 'Fire damage', carrier: 'ASSA', reserved: '$128,000', paid: '$0', daysOpen: 3, priority: 'critical', status: 'open', docsPending: 4, opened: 'Apr 2, 2026', carrierStatus: 'investigation', workflowStatus: 'waiting_carrier', slaPercent: 110, handler: 'Ana R.' },
|
||
{ id: 'CLM-0047', customer: 'Empresa ABC S.A.', agent: 'Ana R.', line: 'Auto', type: 'Collision — fleet unit #7', carrier: 'Qualitas', reserved: '$14,200', paid: '$0', daysOpen: 5, priority: 'high', status: 'under_review', docsPending: 2, opened: 'Mar 31, 2026', carrierStatus: 'documentation_pending', workflowStatus: 'waiting_insured_docs', slaPercent: 60, handler: 'Ana R.' },
|
||
{ id: 'CLM-0046', customer: 'Jorge Herrera', agent: 'Marco V.', line: 'Auto', type: 'Windshield replacement', carrier: 'Qualitas', reserved: '$1,100', paid: '$0', daysOpen: 8, priority: 'low', status: 'awaiting_docs', docsPending: 1, opened: 'Mar 28, 2026', carrierStatus: 'documentation_pending', workflowStatus: 'waiting_insured_docs', slaPercent: 40, handler: 'Marco V.' },
|
||
{ id: 'CLM-0045', customer: 'Clínica San José', agent: 'Ana R.', line: 'Life', type: 'Surgery pre-auth', carrier: 'Pan-American Life', reserved: '$23,500', paid: '$0', daysOpen: 12, priority: 'high', status: 'under_review', docsPending: 0, opened: 'Mar 24, 2026', carrierStatus: 'reserved', workflowStatus: 'waiting_carrier', slaPercent: 85, handler: 'Ana R.' },
|
||
{ id: 'CLM-0044', customer: 'Carmen Ruiz', agent: 'Ana R.', line: 'Life', type: 'Outpatient claim', carrier: 'Pan-American Life', reserved: '$3,800', paid: '$3,200', daysOpen: 18, priority: 'medium', status: 'approved', docsPending: 0, opened: 'Mar 18, 2026', carrierStatus: 'settlement_offered', workflowStatus: 'ready_to_close', slaPercent: 50, handler: 'Ana R.' },
|
||
{ id: 'CLM-0043', customer: 'Supermercado Tico', agent: 'Marco V.', line: 'General Risk', type: 'Customer injury — store premises', carrier: 'Mapfre', reserved: '$45,000', paid: '$0', daysOpen: 22, priority: 'high', status: 'under_review', docsPending: 3, opened: 'Mar 14, 2026', carrierStatus: 'negotiation', workflowStatus: 'client_update_overdue', slaPercent: 100, handler: 'Marco V.' },
|
||
{ id: 'CLM-0042', customer: 'Isabel Mora', agent: 'Ana R.', line: 'Auto', type: 'Theft — total loss', carrier: 'ASSA', reserved: '$18,500', paid: '$18,500', daysOpen: 35, priority: 'medium', status: 'closed', docsPending: 0, opened: 'Mar 1, 2026', carrierStatus: 'closed', workflowStatus: 'ready_to_close', slaPercent: 95, handler: 'Ana R.' },
|
||
{ id: 'CLM-0041', customer: 'Manuel Torres', agent: 'Marco V.', line: 'Life', type: 'Disability benefit', carrier: 'Pan-American Life', reserved: '$52,000', paid: '$12,000', daysOpen: 41, priority: 'medium', status: 'approved', docsPending: 0, opened: 'Feb 23, 2026', carrierStatus: 'reserved', workflowStatus: 'waiting_carrier', slaPercent: 70, handler: 'Marco V.' },
|
||
])
|
||
|
||
// ── View toggle ─────────────────────────────────────────────────────────────
|
||
const viewMode = ref<'my' | 'all'>('all')
|
||
|
||
type ClaimFilter = 'all' | 'active' | 'resolved'
|
||
const activeFilter = ref<ClaimFilter>('all')
|
||
|
||
// ── Filter dropdowns ────────────────────────────────────────────────────────
|
||
const statusFilter = ref('')
|
||
const carrierFilter = ref('')
|
||
const lobFilter = ref('')
|
||
const handlerFilter = ref('')
|
||
const agingFilter = ref('')
|
||
const priorityFilter = ref('')
|
||
|
||
const uniqueCarriers = computed(() => [...new Set(claims.value.map(c => c.carrier))].sort())
|
||
const uniqueLobs = computed(() => [...new Set(claims.value.map(c => c.line))].sort())
|
||
const uniqueHandlers = computed(() => [...new Set(claims.value.map(c => c.handler))].sort())
|
||
|
||
const filteredClaims = computed(() => {
|
||
let result = [...claims.value]
|
||
|
||
if (activeFilter.value === 'active') result = result.filter(c => !['closed', 'denied'].includes(c.status))
|
||
if (activeFilter.value === 'resolved') result = result.filter(c => ['closed', 'denied'].includes(c.status))
|
||
|
||
if (statusFilter.value) result = result.filter(c => c.status === statusFilter.value)
|
||
if (carrierFilter.value) result = result.filter(c => c.carrier === carrierFilter.value)
|
||
if (lobFilter.value) result = result.filter(c => c.line === lobFilter.value)
|
||
if (handlerFilter.value) result = result.filter(c => c.handler === handlerFilter.value)
|
||
if (priorityFilter.value) result = result.filter(c => c.priority === priorityFilter.value)
|
||
|
||
if (agingFilter.value) {
|
||
const ranges: Record<string, [number, number]> = { '0-7': [0, 7], '8-14': [8, 14], '15-30': [15, 30], '30+': [30, 9999] }
|
||
const [min, max] = ranges[agingFilter.value] ?? [0, 9999]
|
||
result = result.filter(c => c.daysOpen >= min && c.daysOpen <= max)
|
||
}
|
||
|
||
// Sort: breached first
|
||
result.sort((a, b) => {
|
||
const aBreached = a.slaPercent >= 100 ? 0 : 1
|
||
const bBreached = b.slaPercent >= 100 ? 0 : 1
|
||
if (aBreached !== bBreached) return aBreached - bBreached
|
||
return b.slaPercent - a.slaPercent
|
||
})
|
||
|
||
return result
|
||
})
|
||
|
||
const filterCounts = computed(() => ({
|
||
all: claims.value.length,
|
||
active: claims.value.filter(c => !['closed', 'denied'].includes(c.status)).length,
|
||
resolved: claims.value.filter(c => ['closed', 'denied'].includes(c.status)).length,
|
||
}))
|
||
|
||
const kpis = computed(() => {
|
||
const active = claims.value.filter(c => !['closed', 'denied'].includes(c.status))
|
||
const underReview = claims.value.filter(c => c.status === 'under_review').length
|
||
const avgDays = active.length ? Math.round(active.reduce((s, c) => s + c.daysOpen, 0) / active.length) : 0
|
||
const totalReserved = claims.value.filter(c => c.status !== 'closed').reduce((s, c) => s + parseFloat(c.reserved.replace(/[$,]/g, '')), 0)
|
||
const breached = claims.value.filter(c => c.slaPercent >= 100 && !['closed', 'denied'].includes(c.status)).length
|
||
return { openClaims: active.length, underReview, avgDays, totalReserved, breached }
|
||
})
|
||
|
||
const statusMeta: Record<string, { label: string; class: string }> = {
|
||
open: { label: 'Open', class: 'cl-st-open' },
|
||
under_review: { label: 'Under Review', class: 'cl-st-review' },
|
||
awaiting_docs: { label: 'Awaiting Docs', class: 'cl-st-docs' },
|
||
approved: { label: 'Approved', class: 'cl-st-approved' },
|
||
denied: { label: 'Denied', class: 'cl-st-denied' },
|
||
closed: { label: 'Closed', class: 'cl-st-closed' },
|
||
}
|
||
|
||
const priorityMeta: Record<string, { label: string; class: string }> = {
|
||
critical: { label: 'Critical', class: 'cl-pri-critical' },
|
||
high: { label: 'High', class: 'cl-pri-high' },
|
||
medium: { label: 'Med', class: 'cl-pri-medium' },
|
||
low: { label: 'Low', class: 'cl-pri-low' },
|
||
}
|
||
|
||
const carrierPillClass = (s: CarrierStatus) => {
|
||
const map: Record<string, string> = {
|
||
fnol_submitted: 'cl-csp-fnol', acknowledged: 'cl-csp-ack', investigation: 'cl-csp-inv',
|
||
documentation_pending: 'cl-csp-doc', reserved: 'cl-csp-rsv', negotiation: 'cl-csp-neg',
|
||
settlement_offered: 'cl-csp-set', closed: 'cl-csp-closed',
|
||
}
|
||
return map[s] ?? ''
|
||
}
|
||
|
||
function formatCurrency(n: number) {
|
||
return '$' + n.toLocaleString('en-US', { minimumFractionDigits: 0 })
|
||
}
|
||
|
||
function clearFilters() {
|
||
statusFilter.value = ''
|
||
carrierFilter.value = ''
|
||
lobFilter.value = ''
|
||
handlerFilter.value = ''
|
||
agingFilter.value = ''
|
||
priorityFilter.value = ''
|
||
}
|
||
|
||
const hasActiveFilters = computed(() => !!(statusFilter.value || carrierFilter.value || lobFilter.value || handlerFilter.value || agingFilter.value || priorityFilter.value))
|
||
|
||
const toast = useToast()
|
||
function handleNewClaim() {
|
||
toast.add({ title: 'New claim flow coming soon', description: 'This will open the FNOL intake wizard.', color: 'neutral' })
|
||
}
|
||
</script>
|
||
|
||
<template>
|
||
<div class="cl-page">
|
||
<!-- Header -->
|
||
<div class="flex flex-wrap items-end justify-between gap-3">
|
||
<div class="max-w-xl">
|
||
<h1 class="mt-1 text-2xl font-semibold tracking-tight text-[var(--text-primary)]">Claims</h1>
|
||
<p class="mt-1 text-[13px] text-[var(--text-muted)]">
|
||
Track claims lifecycle from first notice of loss through resolution and payment.
|
||
</p>
|
||
</div>
|
||
<button type="button" class="cl-action-btn-primary" @click="handleNewClaim">
|
||
<UIcon name="i-heroicons-plus" style="width: 14px; height: 14px;" />
|
||
New Claim
|
||
</button>
|
||
</div>
|
||
|
||
<!-- KPI strip -->
|
||
<div class="cl-kpi-strip">
|
||
<div class="cl-kpi">
|
||
<p class="cl-kpi-label">Open claims</p>
|
||
<p class="cl-kpi-value">{{ kpis.openClaims }}</p>
|
||
</div>
|
||
<div class="cl-kpi">
|
||
<p class="cl-kpi-label">Under review</p>
|
||
<p class="cl-kpi-value" style="color: #c27b1a;">{{ kpis.underReview }}</p>
|
||
</div>
|
||
<div class="cl-kpi">
|
||
<p class="cl-kpi-label">SLA breached</p>
|
||
<p class="cl-kpi-value" :style="kpis.breached > 0 ? 'color: #c13838;' : ''">{{ kpis.breached }}</p>
|
||
</div>
|
||
<div class="cl-kpi">
|
||
<p class="cl-kpi-label">Avg days open</p>
|
||
<p class="cl-kpi-value">{{ kpis.avgDays }}d</p>
|
||
</div>
|
||
<div class="cl-kpi">
|
||
<p class="cl-kpi-label">Total reserved</p>
|
||
<p class="cl-kpi-value">{{ formatCurrency(kpis.totalReserved) }}</p>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- View toggle + Filter tabs -->
|
||
<div class="cl-controls-row">
|
||
<div class="cl-view-toggle">
|
||
<button
|
||
type="button"
|
||
class="cl-view-btn"
|
||
:class="viewMode === 'my' ? 'cl-view-on' : 'cl-view-off'"
|
||
@click="viewMode = 'my'"
|
||
>My Claims</button>
|
||
<button
|
||
type="button"
|
||
class="cl-view-btn"
|
||
:class="viewMode === 'all' ? 'cl-view-on' : 'cl-view-off'"
|
||
@click="viewMode = 'all'"
|
||
>All Claims</button>
|
||
</div>
|
||
|
||
<div class="cl-filter-tabs">
|
||
<button
|
||
v-for="f in ([
|
||
{ id: 'all', label: 'All' },
|
||
{ id: 'active', label: 'Active' },
|
||
{ id: 'resolved', label: 'Resolved' },
|
||
] as { id: ClaimFilter; label: string }[])"
|
||
:key="f.id"
|
||
type="button"
|
||
class="cl-filter-tab"
|
||
:class="activeFilter === f.id ? 'cl-filter-on' : 'cl-filter-off'"
|
||
@click="activeFilter = f.id"
|
||
>
|
||
{{ f.label }}
|
||
<span class="cl-filter-count" :class="activeFilter === f.id ? 'cl-filter-count-on' : ''">{{ filterCounts[f.id] }}</span>
|
||
</button>
|
||
</div>
|
||
|
||
<span class="text-[11px] text-[var(--text-muted)] ml-auto">{{ filteredClaims.length }} results</span>
|
||
</div>
|
||
|
||
<!-- Filter dropdowns row -->
|
||
<div class="cl-dropdown-row">
|
||
<select v-model="statusFilter" class="cl-dropdown">
|
||
<option value="">Status</option>
|
||
<option value="open">Open</option>
|
||
<option value="under_review">Under Review</option>
|
||
<option value="awaiting_docs">Awaiting Docs</option>
|
||
<option value="approved">Approved</option>
|
||
<option value="denied">Denied</option>
|
||
<option value="closed">Closed</option>
|
||
</select>
|
||
<select v-model="carrierFilter" class="cl-dropdown">
|
||
<option value="">Carrier</option>
|
||
<option v-for="c in uniqueCarriers" :key="c" :value="c">{{ c }}</option>
|
||
</select>
|
||
<select v-model="lobFilter" class="cl-dropdown">
|
||
<option value="">LOB</option>
|
||
<option v-for="l in uniqueLobs" :key="l" :value="l">{{ l }}</option>
|
||
</select>
|
||
<select v-model="handlerFilter" class="cl-dropdown">
|
||
<option value="">Handler</option>
|
||
<option v-for="h in uniqueHandlers" :key="h" :value="h">{{ h }}</option>
|
||
</select>
|
||
<select v-model="agingFilter" class="cl-dropdown">
|
||
<option value="">Aging</option>
|
||
<option value="0-7">0–7 days</option>
|
||
<option value="8-14">8–14 days</option>
|
||
<option value="15-30">15–30 days</option>
|
||
<option value="30+">30+ days</option>
|
||
</select>
|
||
<select v-model="priorityFilter" class="cl-dropdown">
|
||
<option value="">Priority</option>
|
||
<option value="critical">Critical</option>
|
||
<option value="high">High</option>
|
||
<option value="medium">Medium</option>
|
||
<option value="low">Low</option>
|
||
</select>
|
||
<button v-if="hasActiveFilters" class="cl-clear-btn" @click="clearFilters">
|
||
<UIcon name="i-heroicons-x-mark" class="w-3 h-3" />
|
||
Clear
|
||
</button>
|
||
</div>
|
||
|
||
<!-- Claims table -->
|
||
<div class="cl-table-wrap">
|
||
<table class="cl-table">
|
||
<thead>
|
||
<tr>
|
||
<th style="width: 28px;"></th>
|
||
<th>Claim</th>
|
||
<th>Customer / Agent</th>
|
||
<th>Line / Type</th>
|
||
<th>Carrier</th>
|
||
<th>Status</th>
|
||
<th class="text-right">Reserved</th>
|
||
<th class="text-right">Paid</th>
|
||
<th class="text-right">Days</th>
|
||
<th>Priority</th>
|
||
<th class="text-center">Docs</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
<tr
|
||
v-for="c in filteredClaims"
|
||
:key="c.id"
|
||
class="cl-row"
|
||
:class="{ 'cl-breach-row': c.slaPercent >= 100 && !['closed', 'denied'].includes(c.status) }"
|
||
style="cursor: pointer;"
|
||
@click="navigateTo(`/claims/${c.id}`)"
|
||
>
|
||
<td><span class="cl-sla-dot" :class="`cl-sla-${slaColor(c.slaPercent)}`" /></td>
|
||
<td>
|
||
<NuxtLink :to="`/claims/${c.id}`" class="cl-claim-link" @click.stop>{{ c.id }}</NuxtLink>
|
||
</td>
|
||
<td>
|
||
<p class="text-[13px] font-medium text-[var(--text-primary)]">{{ c.customer || 'Unnamed customer' }}</p>
|
||
<p class="text-[11px] text-[var(--text-muted)]">{{ c.agent || '—' }}</p>
|
||
</td>
|
||
<td>
|
||
<p class="text-[13px] text-[var(--text-primary)]">{{ c.line }}</p>
|
||
<p class="text-[11px] text-[var(--text-muted)]">{{ c.type }}</p>
|
||
</td>
|
||
<td class="text-[13px] text-[var(--text-muted)]">{{ c.carrier }}</td>
|
||
<td>
|
||
<div class="cl-dual-status">
|
||
<span class="cl-carrier-status-pill" :class="carrierPillClass(c.carrierStatus)">{{ CARRIER_STATUS_LABELS[c.carrierStatus] }}</span>
|
||
<span class="cl-workflow-status-pill">{{ WORKFLOW_STATUS_LABELS[c.workflowStatus] }}</span>
|
||
</div>
|
||
</td>
|
||
<td class="text-right font-semibold text-[13px] text-[var(--text-primary)]">{{ c.reserved }}</td>
|
||
<td class="text-right text-[13px]" :class="c.paid !== '$0' ? 'text-[var(--text-primary)] font-medium' : 'text-[var(--text-muted)] opacity-50'">{{ c.paid }}</td>
|
||
<td class="text-right">
|
||
<span class="text-[13px] font-bold" :class="c.daysOpen > 30 ? 'text-rose-600' : c.daysOpen > 14 ? 'text-amber-600' : 'text-[var(--text-primary)]'">{{ c.daysOpen }}d</span>
|
||
</td>
|
||
<td>
|
||
<span :class="priorityMeta[c.priority].class">{{ priorityMeta[c.priority].label }}</span>
|
||
</td>
|
||
<td class="text-center">
|
||
<span v-if="c.docsPending > 0" class="cl-docs-badge">{{ c.docsPending }}</span>
|
||
<span v-else class="text-[11px] text-[var(--text-muted)] opacity-40">—</span>
|
||
</td>
|
||
</tr>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
|
||
<style scoped>
|
||
.cl-page {
|
||
max-width: 76rem;
|
||
margin: 0 auto;
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 20px;
|
||
padding-bottom: 3rem;
|
||
}
|
||
|
||
.cl-action-btn-primary {
|
||
display: inline-flex; align-items: center; gap: 6px;
|
||
padding: 8px 16px; border-radius: 8px;
|
||
background: #01696f; color: #fff;
|
||
font-size: 13px; font-weight: 500; border: none;
|
||
cursor: pointer; transition: all 150ms ease; white-space: nowrap;
|
||
}
|
||
.cl-action-btn-primary:hover { background: #015458; }
|
||
|
||
/* ── KPI strip ── */
|
||
.cl-kpi-strip {
|
||
display: grid; grid-template-columns: repeat(5, 1fr); gap: 1px;
|
||
border-radius: 12px; border: 1px solid rgba(0,0,0,0.06);
|
||
background: rgba(0,0,0,0.06); box-shadow: 0 1px 3px rgba(0,0,0,0.03);
|
||
overflow: hidden;
|
||
}
|
||
.cl-kpi { padding: 14px 18px; background: #fff; }
|
||
.cl-kpi:first-child { border-radius: 12px 0 0 12px; }
|
||
.cl-kpi:last-child { border-radius: 0 12px 12px 0; }
|
||
.cl-kpi-label {
|
||
font-size: 11px; font-weight: 600; text-transform: uppercase;
|
||
letter-spacing: 0.04em; color: #8a8a86;
|
||
}
|
||
.cl-kpi-value {
|
||
margin-top: 4px; font-size: 22px; font-weight: 600;
|
||
color: var(--text-primary); font-variant-numeric: tabular-nums;
|
||
}
|
||
@media (max-width: 640px) { .cl-kpi-strip { grid-template-columns: repeat(2, 1fr); } }
|
||
|
||
/* ── Controls row ── */
|
||
.cl-controls-row {
|
||
display: flex; align-items: center; gap: 12px; flex-wrap: wrap;
|
||
}
|
||
|
||
/* ── View toggle ── */
|
||
.cl-view-toggle {
|
||
display: inline-flex; gap: 1px; padding: 2px;
|
||
border-radius: 8px; background: rgba(0,0,0,0.04);
|
||
}
|
||
.cl-view-btn {
|
||
padding: 5px 12px; border-radius: 6px; font-size: 12px; font-weight: 600;
|
||
border: none; cursor: pointer; transition: all 150ms ease; white-space: nowrap;
|
||
}
|
||
.cl-view-on { background: #01696f; color: white; }
|
||
.cl-view-off { background: transparent; color: #8a8a86; }
|
||
.cl-view-off:hover { color: var(--text-primary); }
|
||
|
||
/* ── Filter tabs ── */
|
||
.cl-filter-tabs {
|
||
display: inline-flex; gap: 2px; padding: 3px;
|
||
border-radius: 10px; background: rgba(0,0,0,0.04);
|
||
}
|
||
.cl-filter-tab {
|
||
display: inline-flex; align-items: center; gap: 5px;
|
||
padding: 6px 12px; border-radius: 8px;
|
||
font-size: 12px; font-weight: 500; border: none;
|
||
cursor: pointer; transition: all 150ms ease; white-space: nowrap;
|
||
}
|
||
.cl-filter-on { background: #fff; color: var(--text-primary); box-shadow: 0 1px 3px rgba(0,0,0,0.08); }
|
||
.cl-filter-off { background: transparent; color: var(--text-muted); }
|
||
.cl-filter-off:hover { color: var(--text-primary); }
|
||
.cl-filter-count {
|
||
font-size: 10px; font-weight: 600; padding: 1px 5px;
|
||
border-radius: 9999px; background: rgba(0,0,0,0.06); color: var(--text-muted);
|
||
}
|
||
.cl-filter-count-on { background: rgba(1,105,111,0.1); color: #01696f; }
|
||
|
||
/* ── Dropdown filters ── */
|
||
.cl-dropdown-row { display: flex; gap: 8px; flex-wrap: wrap; align-items: center; }
|
||
.cl-dropdown {
|
||
padding: 5px 10px; border-radius: 8px; font-size: 12px; font-weight: 500;
|
||
border: 1px solid rgba(0,0,0,0.08); background: #fff; color: var(--text-primary);
|
||
cursor: pointer; min-width: 100px;
|
||
}
|
||
.cl-dropdown:focus { outline: none; border-color: #01696f; }
|
||
.cl-clear-btn {
|
||
display: inline-flex; align-items: center; gap: 4px; padding: 5px 10px;
|
||
border-radius: 8px; font-size: 11px; font-weight: 600;
|
||
background: rgba(193, 56, 56, 0.06); color: #c13838;
|
||
border: 1px solid rgba(193, 56, 56, 0.15); cursor: pointer;
|
||
}
|
||
.cl-clear-btn:hover { background: rgba(193, 56, 56, 0.12); }
|
||
|
||
/* ── Table ── */
|
||
.cl-table-wrap {
|
||
border-radius: 12px; border: 1px solid rgba(0,0,0,0.06);
|
||
background: #fff; box-shadow: 0 1px 3px rgba(0,0,0,0.03);
|
||
overflow-x: auto;
|
||
}
|
||
.cl-table { width: 100%; border-collapse: collapse; font-size: 13px; }
|
||
.cl-table thead th {
|
||
padding: 10px 14px;
|
||
font-size: 11px; font-weight: 600; text-transform: uppercase;
|
||
letter-spacing: 0.04em; color: #8a8a86;
|
||
border-bottom: 1px solid rgba(0,0,0,0.06);
|
||
white-space: nowrap; text-align: left;
|
||
}
|
||
.cl-table tbody td {
|
||
padding: 12px 14px; border-bottom: 1px solid rgba(0,0,0,0.04);
|
||
vertical-align: top;
|
||
}
|
||
.cl-row { transition: background 100ms ease; }
|
||
.cl-row:hover { background: rgba(0,0,0,0.015); }
|
||
.cl-row:last-child td { border-bottom: none; }
|
||
|
||
/* ── Breach row ── */
|
||
.cl-breach-row { box-shadow: inset 3px 0 0 #c13838; }
|
||
|
||
/* ── SLA dot ── */
|
||
.cl-sla-dot { width: 8px; height: 8px; border-radius: 50%; display: inline-block; }
|
||
.cl-sla-green { background: #059669; }
|
||
.cl-sla-amber { background: #c27b1a; }
|
||
.cl-sla-red { background: #c13838; }
|
||
|
||
/* ── Claim link ── */
|
||
.cl-claim-link {
|
||
font-family: 'SF Mono', 'Fira Code', monospace;
|
||
font-size: 12px; font-weight: 600; color: #01696f;
|
||
text-decoration: none;
|
||
}
|
||
.cl-claim-link:hover { text-decoration: underline; }
|
||
|
||
/* ── Dual status pills ── */
|
||
.cl-dual-status { display: flex; flex-direction: column; gap: 3px; }
|
||
.cl-carrier-status-pill {
|
||
display: inline-flex; padding: 2px 7px; border-radius: 8px;
|
||
font-size: 10px; font-weight: 600; white-space: nowrap;
|
||
}
|
||
.cl-csp-fnol { background: rgba(59, 130, 246, 0.08); color: #2563eb; }
|
||
.cl-csp-ack { background: rgba(16, 185, 129, 0.08); color: #059669; }
|
||
.cl-csp-inv { background: rgba(245, 158, 11, 0.08); color: #d97706; }
|
||
.cl-csp-doc { background: rgba(147, 51, 234, 0.08); color: #9333ea; }
|
||
.cl-csp-rsv { background: rgba(1, 105, 111, 0.08); color: #01696f; }
|
||
.cl-csp-neg { background: rgba(194, 123, 26, 0.08); color: #c27b1a; }
|
||
.cl-csp-set { background: rgba(16, 185, 129, 0.08); color: #059669; }
|
||
.cl-csp-closed { background: rgba(138, 138, 134, 0.08); color: #8a8a86; }
|
||
|
||
.cl-workflow-status-pill {
|
||
display: inline-flex; padding: 0; border-radius: 0;
|
||
font-size: 10px; font-weight: 500; white-space: nowrap;
|
||
border: none; color: var(--text-muted);
|
||
}
|
||
|
||
/* ── Status badges ── */
|
||
.cl-st-open { font-size: 11px; font-weight: 600; padding: 2px 8px; border-radius: 9999px; background: rgba(193,56,56,0.08); color: #c13838; white-space: nowrap; }
|
||
.cl-st-review { font-size: 11px; font-weight: 600; padding: 2px 8px; border-radius: 9999px; background: rgba(194,123,26,0.08); color: #c27b1a; white-space: nowrap; }
|
||
.cl-st-docs { font-size: 11px; font-weight: 600; padding: 2px 8px; border-radius: 9999px; background: rgba(147,51,234,0.08); color: #9333ea; white-space: nowrap; }
|
||
.cl-st-approved { font-size: 11px; font-weight: 600; padding: 2px 8px; border-radius: 9999px; background: rgba(1,105,111,0.08); color: #01696f; white-space: nowrap; }
|
||
.cl-st-denied { font-size: 11px; font-weight: 600; padding: 2px 8px; border-radius: 9999px; background: rgba(0,0,0,0.06); color: #6b6b68; white-space: nowrap; }
|
||
.cl-st-closed { font-size: 11px; font-weight: 600; padding: 2px 8px; border-radius: 9999px; background: rgba(0,0,0,0.04); color: #8a8a86; white-space: nowrap; }
|
||
|
||
/* ── Priority badges ── */
|
||
.cl-pri-critical { font-size: 10px; font-weight: 700; padding: 1px 7px; border-radius: 9999px; background: rgba(193,56,56,0.12); color: #c13838; white-space: nowrap; }
|
||
.cl-pri-high { font-size: 10px; font-weight: 600; padding: 1px 7px; border-radius: 9999px; background: rgba(194,123,26,0.08); color: #c27b1a; white-space: nowrap; }
|
||
.cl-pri-medium { font-size: 10px; font-weight: 600; padding: 1px 7px; border-radius: 9999px; background: rgba(0,0,0,0.05); color: #6b6b68; white-space: nowrap; }
|
||
.cl-pri-low { font-size: 10px; font-weight: 600; padding: 1px 7px; border-radius: 9999px; background: rgba(0,0,0,0.03); color: #8a8a86; white-space: nowrap; }
|
||
|
||
/* ── Docs pending badge ── */
|
||
.cl-docs-badge {
|
||
display: inline-flex; align-items: center; justify-content: center;
|
||
min-width: 20px; height: 20px; padding: 0 5px;
|
||
border-radius: 9999px; background: rgba(147,51,234,0.08); color: #9333ea;
|
||
font-size: 11px; font-weight: 700;
|
||
}
|
||
</style>
|