WIP jordan
This commit is contained in:
662
app/pages/quotes/mission-control.vue
Normal file
662
app/pages/quotes/mission-control.vue
Normal file
@@ -0,0 +1,662 @@
|
||||
<script setup lang="ts">
|
||||
import { PIPELINE_STAGES, type PipelineStage } from '~/composables/useSalesPipeline'
|
||||
|
||||
definePageMeta({ ssr: false })
|
||||
usePageTitle('Mission Control \u00b7 Quotes')
|
||||
|
||||
/* ── Mock agents ── */
|
||||
interface AgentStats {
|
||||
name: string
|
||||
activeDeals: number
|
||||
quotesSent: number
|
||||
conversionRate: number
|
||||
avgResponseTime: string
|
||||
pipelineValue: number
|
||||
}
|
||||
|
||||
const agents: AgentStats[] = [
|
||||
{ name: 'Ana Ram\u00edrez', activeDeals: 8, quotesSent: 14, conversionRate: 71, avgResponseTime: '1.2d', pipelineValue: 18_400 },
|
||||
{ name: 'Marco Villanueva', activeDeals: 4, quotesSent: 6, conversionRate: 83, avgResponseTime: '0.8d', pipelineValue: 22_100 },
|
||||
{ name: 'Luc\u00eda Fern\u00e1ndez', activeDeals: 6, quotesSent: 4, conversionRate: 42, avgResponseTime: '2.4d', pipelineValue: 4_700 },
|
||||
]
|
||||
|
||||
/* ── Mock deals spread across pipeline ── */
|
||||
interface MockDeal {
|
||||
id: string
|
||||
customerName: string
|
||||
productLine: string
|
||||
currentStage: PipelineStage
|
||||
agent: string
|
||||
daysAtStage: number
|
||||
premium: number
|
||||
formCompletion: number
|
||||
createdDaysAgo: number
|
||||
}
|
||||
|
||||
const mockDeals: MockDeal[] = [
|
||||
{ id: 'd01', customerName: 'Mar\u00eda Elena P\u00e9rez', productLine: 'Auto', currentStage: 'customer', agent: 'Ana Ram\u00edrez', daysAtStage: 1, premium: 1200, formCompletion: 45, createdDaysAgo: 1 },
|
||||
{ id: 'd02', customerName: 'Carlos Mendoza', productLine: 'Life', currentStage: 'customer', agent: 'Luc\u00eda Fern\u00e1ndez', daysAtStage: 3, premium: 3200, formCompletion: 20, createdDaysAgo: 3 },
|
||||
{ id: 'd03', customerName: 'Laura Castillo', productLine: 'Health', currentStage: 'get_quotes', agent: 'Ana Ram\u00edrez', daysAtStage: 2, premium: 2800, formCompletion: 60, createdDaysAgo: 5 },
|
||||
{ id: 'd04', customerName: 'Sof\u00eda Rojas Delgado', productLine: 'Auto', currentStage: 'get_quotes', agent: 'Luc\u00eda Fern\u00e1ndez', daysAtStage: 4, premium: 1500, formCompletion: 50, createdDaysAgo: 7 },
|
||||
{ id: 'd05', customerName: 'Andr\u00e9s Vargas', productLine: 'General Risk', currentStage: 'get_quotes', agent: 'Marco Villanueva', daysAtStage: 1, premium: 8500, formCompletion: 75, createdDaysAgo: 4 },
|
||||
{ id: 'd06', customerName: 'Patricia Herrera', productLine: 'Auto', currentStage: 'waiting_carriers', agent: 'Ana Ram\u00edrez', daysAtStage: 6, premium: 1100, formCompletion: 100, createdDaysAgo: 10 },
|
||||
{ id: 'd07', customerName: 'Fernando L\u00f3pez', productLine: 'Life', currentStage: 'waiting_carriers', agent: 'Luc\u00eda Fern\u00e1ndez', daysAtStage: 8, premium: 4200, formCompletion: 100, createdDaysAgo: 12 },
|
||||
{ id: 'd08', customerName: 'Roberto Jim\u00e9nez Mora', productLine: 'Health', currentStage: 'waiting_carriers', agent: 'Marco Villanueva', daysAtStage: 3, premium: 3100, formCompletion: 100, createdDaysAgo: 8 },
|
||||
{ id: 'd09', customerName: 'Gabriela Torres', productLine: 'Auto', currentStage: 'present_quotes', agent: 'Ana Ram\u00edrez', daysAtStage: 2, premium: 1400, formCompletion: 100, createdDaysAgo: 14 },
|
||||
{ id: 'd10', customerName: 'Diego Salazar', productLine: 'Life', currentStage: 'present_quotes', agent: 'Marco Villanueva', daysAtStage: 1, premium: 5600, formCompletion: 100, createdDaysAgo: 9 },
|
||||
{ id: 'd11', customerName: 'Isabel Moreno', productLine: 'General Risk', currentStage: 'waiting_client', agent: 'Ana Ram\u00edrez', daysAtStage: 7, premium: 2200, formCompletion: 100, createdDaysAgo: 16 },
|
||||
{ id: 'd12', customerName: 'Alejandro Rios', productLine: 'Auto', currentStage: 'waiting_client', agent: 'Luc\u00eda Fern\u00e1ndez', daysAtStage: 12, premium: 900, formCompletion: 100, createdDaysAgo: 20 },
|
||||
{ id: 'd13', customerName: 'Valentina Cruz', productLine: 'Health', currentStage: 'solicitud', agent: 'Ana Ram\u00edrez', daysAtStage: 3, premium: 2700, formCompletion: 68, createdDaysAgo: 18 },
|
||||
{ id: 'd14', customerName: 'Jorge Navarro', productLine: 'Life', currentStage: 'solicitud', agent: 'Marco Villanueva', daysAtStage: 2, premium: 6800, formCompletion: 85, createdDaysAgo: 15 },
|
||||
{ id: 'd15', customerName: 'Camila Fuentes', productLine: 'Auto', currentStage: 'emission', agent: 'Ana Ram\u00edrez', daysAtStage: 1, premium: 1600, formCompletion: 92, createdDaysAgo: 22 },
|
||||
{ id: 'd16', customerName: 'Ra\u00fal Espinoza', productLine: 'General Risk', currentStage: 'emission', agent: 'Marco Villanueva', daysAtStage: 2, premium: 7200, formCompletion: 100, createdDaysAgo: 25 },
|
||||
{ id: 'd17', customerName: 'M\u00f3nica Delgado', productLine: 'Life', currentStage: 'waiting_carriers', agent: 'Ana Ram\u00edrez', daysAtStage: 11, premium: 3800, formCompletion: 100, createdDaysAgo: 15 },
|
||||
{ id: 'd18', customerName: 'Enrique Paredes', productLine: 'Health', currentStage: 'customer', agent: 'Luc\u00eda Fern\u00e1ndez', daysAtStage: 6, premium: 2100, formCompletion: 30, createdDaysAgo: 6 },
|
||||
]
|
||||
|
||||
/* ── KPI computations ── */
|
||||
const totalActiveDeals = computed(() => mockDeals.length)
|
||||
|
||||
const totalPipelinePremium = computed(() =>
|
||||
mockDeals.reduce((sum, d) => sum + d.premium, 0),
|
||||
)
|
||||
|
||||
const stageCounts = computed(() => {
|
||||
const counts: Record<PipelineStage, number> = {} as any
|
||||
for (const s of PIPELINE_STAGES) counts[s.id] = 0
|
||||
for (const d of mockDeals) counts[d.currentStage]++
|
||||
return counts
|
||||
})
|
||||
|
||||
const bottleneckStage = computed(() => {
|
||||
let max = 0
|
||||
let stage = PIPELINE_STAGES[0]
|
||||
for (const s of PIPELINE_STAGES) {
|
||||
if (stageCounts.value[s.id] > max) {
|
||||
max = stageCounts.value[s.id]
|
||||
stage = s
|
||||
}
|
||||
}
|
||||
return stage.label
|
||||
})
|
||||
|
||||
const maxStageCount = computed(() =>
|
||||
Math.max(...PIPELINE_STAGES.map(s => stageCounts.value[s.id]), 1),
|
||||
)
|
||||
|
||||
/* ── Funnel colors ── */
|
||||
const stageColors: Record<PipelineStage, string> = {
|
||||
customer: '#01696f',
|
||||
get_quotes: '#0d8a8f',
|
||||
waiting_carriers: '#f59e0b',
|
||||
present_quotes: '#3b82f6',
|
||||
waiting_client: '#f59e0b',
|
||||
solicitud: '#8b5cf6',
|
||||
emission: '#10b981',
|
||||
}
|
||||
|
||||
/* ── Bottleneck deals (stuck > 5 days) ── */
|
||||
const stuckDeals = computed(() =>
|
||||
mockDeals
|
||||
.filter(d => d.daysAtStage > 5)
|
||||
.sort((a, b) => b.daysAtStage - a.daysAtStage),
|
||||
)
|
||||
|
||||
function severityColor(days: number) {
|
||||
if (days > 10) return '#ef4444'
|
||||
if (days > 5) return '#f59e0b'
|
||||
return '#6b7280'
|
||||
}
|
||||
|
||||
function severityBg(days: number) {
|
||||
if (days > 10) return 'rgba(239,68,68,0.08)'
|
||||
if (days > 5) return 'rgba(245,158,11,0.08)'
|
||||
return 'transparent'
|
||||
}
|
||||
|
||||
/* ── Stage breakdown tab ── */
|
||||
const activeStageTab = ref<PipelineStage>('customer')
|
||||
|
||||
const stageDeals = computed(() =>
|
||||
mockDeals.filter(d => d.currentStage === activeStageTab.value),
|
||||
)
|
||||
|
||||
function stageLabelFor(id: PipelineStage) {
|
||||
return PIPELINE_STAGES.find(s => s.id === id)?.label ?? id
|
||||
}
|
||||
|
||||
function formatCurrency(v: number) {
|
||||
return '$' + v.toLocaleString('en-US')
|
||||
}
|
||||
|
||||
const toast = useToast()
|
||||
const nudgeConfirmDeal = ref<MockDeal | null>(null)
|
||||
|
||||
function onNudge(deal: MockDeal) {
|
||||
nudgeConfirmDeal.value = deal
|
||||
}
|
||||
|
||||
function confirmNudge() {
|
||||
if (nudgeConfirmDeal.value) {
|
||||
toast.add({ title: `Nudge sent to ${nudgeConfirmDeal.value.customerName}`, color: 'success' })
|
||||
}
|
||||
nudgeConfirmDeal.value = null
|
||||
}
|
||||
|
||||
function cancelNudge() {
|
||||
nudgeConfirmDeal.value = null
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="mc-page">
|
||||
<!-- Header -->
|
||||
<div class="mc-header">
|
||||
<h1 class="mc-title">Mission Control</h1>
|
||||
<p class="mc-subtitle">Pipeline analytics & agent performance</p>
|
||||
</div>
|
||||
|
||||
<!-- KPI Strip -->
|
||||
<div class="mc-kpi-strip">
|
||||
<div class="mc-card mc-kpi-card">
|
||||
<span class="mc-kpi-label">Total Active Deals</span>
|
||||
<span class="mc-kpi-value">{{ totalActiveDeals }}</span>
|
||||
</div>
|
||||
<div class="mc-card mc-kpi-card">
|
||||
<span class="mc-kpi-label">Quotes This Month</span>
|
||||
<span class="mc-kpi-value">24</span>
|
||||
</div>
|
||||
<div class="mc-card mc-kpi-card">
|
||||
<span class="mc-kpi-label">Avg Days in Pipeline</span>
|
||||
<span class="mc-kpi-value">8.3</span>
|
||||
</div>
|
||||
<div class="mc-card mc-kpi-card">
|
||||
<span class="mc-kpi-label">Conversion Rate</span>
|
||||
<span class="mc-kpi-value">62%</span>
|
||||
</div>
|
||||
<div class="mc-card mc-kpi-card">
|
||||
<span class="mc-kpi-label">Pipeline Premium</span>
|
||||
<span class="mc-kpi-value">{{ formatCurrency(totalPipelinePremium) }}</span>
|
||||
</div>
|
||||
<div class="mc-card mc-kpi-card">
|
||||
<span class="mc-kpi-label">Bottleneck Stage</span>
|
||||
<span class="mc-kpi-value mc-kpi-value--bottleneck">{{ bottleneckStage }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Pipeline Funnel -->
|
||||
<div class="mc-card mc-section">
|
||||
<h2 class="mc-section-title">Pipeline Funnel</h2>
|
||||
<div class="mc-funnel">
|
||||
<div
|
||||
v-for="stage in PIPELINE_STAGES"
|
||||
:key="stage.id"
|
||||
class="mc-funnel-row"
|
||||
>
|
||||
<span class="mc-funnel-label">{{ stage.label }}</span>
|
||||
<div class="mc-funnel-bar-track">
|
||||
<div
|
||||
class="mc-funnel-bar"
|
||||
:style="{
|
||||
width: Math.max((stageCounts[stage.id] / maxStageCount) * 100, 4) + '%',
|
||||
background: stageColors[stage.id],
|
||||
}"
|
||||
/>
|
||||
</div>
|
||||
<span class="mc-funnel-count">{{ stageCounts[stage.id] }}</span>
|
||||
<span class="mc-funnel-pct">
|
||||
{{ totalActiveDeals ? Math.round((stageCounts[stage.id] / totalActiveDeals) * 100) : 0 }}%
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Agent Performance -->
|
||||
<div class="mc-card mc-section">
|
||||
<h2 class="mc-section-title">Agent Performance</h2>
|
||||
<div class="mc-table-wrap">
|
||||
<table class="mc-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Agent</th>
|
||||
<th>Active Deals</th>
|
||||
<th>Quotes Sent</th>
|
||||
<th>Conversion</th>
|
||||
<th>Avg Response</th>
|
||||
<th>Pipeline Value</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="a in agents" :key="a.name">
|
||||
<td class="mc-agent-name">{{ a.name }}</td>
|
||||
<td>{{ a.activeDeals }}</td>
|
||||
<td>{{ a.quotesSent }}</td>
|
||||
<td>
|
||||
<span
|
||||
class="mc-badge"
|
||||
:style="{ background: a.conversionRate >= 70 ? 'rgba(16,185,129,0.1)' : a.conversionRate >= 50 ? 'rgba(245,158,11,0.1)' : 'rgba(239,68,68,0.1)', color: a.conversionRate >= 70 ? '#059669' : a.conversionRate >= 50 ? '#d97706' : '#dc2626' }"
|
||||
>
|
||||
{{ a.conversionRate }}%
|
||||
</span>
|
||||
</td>
|
||||
<td>{{ a.avgResponseTime }}</td>
|
||||
<td>{{ formatCurrency(a.pipelineValue) }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Bottleneck Analysis -->
|
||||
<div class="mc-card mc-section">
|
||||
<h2 class="mc-section-title">Bottleneck Analysis</h2>
|
||||
<p class="mc-section-desc">Deals stuck at a stage for more than 5 days</p>
|
||||
<div v-if="stuckDeals.length === 0" class="mc-empty">No bottlenecks detected.</div>
|
||||
<div v-else class="mc-stuck-list">
|
||||
<div
|
||||
v-for="deal in stuckDeals"
|
||||
:key="deal.id"
|
||||
class="mc-stuck-row"
|
||||
:style="{ background: severityBg(deal.daysAtStage) }"
|
||||
>
|
||||
<div class="mc-stuck-info">
|
||||
<span class="mc-stuck-name">{{ deal.customerName }}</span>
|
||||
<span class="mc-stuck-meta">
|
||||
<span
|
||||
class="mc-stuck-days"
|
||||
:style="{ color: severityColor(deal.daysAtStage) }"
|
||||
>
|
||||
{{ deal.daysAtStage }}d
|
||||
</span>
|
||||
at {{ stageLabelFor(deal.currentStage) }}
|
||||
</span>
|
||||
</div>
|
||||
<span class="mc-stuck-agent">{{ deal.agent }}</span>
|
||||
<button class="mc-btn-nudge" @click="onNudge(deal)">Nudge</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Stage Breakdown -->
|
||||
<div class="mc-card mc-section">
|
||||
<h2 class="mc-section-title">Stage Breakdown</h2>
|
||||
<div class="mc-tab-bar">
|
||||
<button
|
||||
v-for="stage in PIPELINE_STAGES"
|
||||
:key="stage.id"
|
||||
class="mc-tab"
|
||||
:class="{ 'mc-tab--active': activeStageTab === stage.id }"
|
||||
@click="activeStageTab = stage.id"
|
||||
>
|
||||
{{ stage.label }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div v-if="stageDeals.length === 0" class="mc-empty">
|
||||
<UIcon name="i-heroicons-inbox" style="width: 20px; height: 20px; color: #c0c0bc;" />
|
||||
<p>No deals at this stage</p>
|
||||
</div>
|
||||
<div v-else class="mc-table-wrap">
|
||||
<table class="mc-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Customer</th>
|
||||
<th>Product Line</th>
|
||||
<th>Days at Stage</th>
|
||||
<th>Agent</th>
|
||||
<th>Premium</th>
|
||||
<th>Form Completion</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="deal in stageDeals" :key="deal.id">
|
||||
<td class="mc-agent-name">{{ deal.customerName }}</td>
|
||||
<td>{{ deal.productLine }}</td>
|
||||
<td>
|
||||
<span :style="{ color: deal.daysAtStage > 5 ? severityColor(deal.daysAtStage) : 'inherit', fontWeight: deal.daysAtStage > 5 ? 600 : 400 }">
|
||||
{{ deal.daysAtStage }}d
|
||||
</span>
|
||||
</td>
|
||||
<td>{{ deal.agent }}</td>
|
||||
<td>{{ formatCurrency(deal.premium) }}</td>
|
||||
<td>
|
||||
<div class="mc-progress-wrap">
|
||||
<div class="mc-progress-track">
|
||||
<div
|
||||
class="mc-progress-fill"
|
||||
:style="{ width: deal.formCompletion + '%' }"
|
||||
/>
|
||||
</div>
|
||||
<span class="mc-progress-label">{{ deal.formCompletion }}%</span>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Nudge confirmation modal -->
|
||||
<Teleport to="body">
|
||||
<Transition
|
||||
enter-active-class="transition duration-150 ease-out"
|
||||
enter-from-class="opacity-0"
|
||||
enter-to-class="opacity-100"
|
||||
leave-active-class="transition duration-100 ease-in"
|
||||
leave-from-class="opacity-100"
|
||||
leave-to-class="opacity-0"
|
||||
>
|
||||
<div v-if="nudgeConfirmDeal" class="fixed inset-0 z-50 flex items-center justify-center bg-black/30" @click.self="cancelNudge">
|
||||
<div class="w-full max-w-sm rounded-xl border border-[var(--card-border)] bg-white p-6 shadow-xl">
|
||||
<div class="flex items-start gap-3">
|
||||
<div class="flex h-9 w-9 shrink-0 items-center justify-center rounded-lg" style="background: rgba(1,105,111,0.08); color: #01696f;">
|
||||
<UIcon name="i-heroicons-bell-alert" style="width: 18px; height: 18px;" />
|
||||
</div>
|
||||
<div class="min-w-0">
|
||||
<p class="text-[14px] font-semibold text-[var(--text-primary)]">Send nudge?</p>
|
||||
<p class="mt-1 text-[13px] text-[var(--text-muted)]">
|
||||
This will send a follow-up reminder to <span class="font-semibold text-[var(--text-primary)]">{{ nudgeConfirmDeal.customerName }}</span> about their pending deal.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-5 flex justify-end gap-2">
|
||||
<UButton color="neutral" variant="soft" size="sm" @click="cancelNudge">Cancel</UButton>
|
||||
<UButton color="primary" size="sm" @click="confirmNudge">Send nudge</UButton>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Transition>
|
||||
</Teleport>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
/* ── Page ── */
|
||||
.mc-page {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 20px;
|
||||
max-width: 1120px;
|
||||
}
|
||||
|
||||
/* ── Header ── */
|
||||
.mc-header {
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
.mc-title {
|
||||
font-size: 24px;
|
||||
font-weight: 650;
|
||||
letter-spacing: -0.015em;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
.mc-subtitle {
|
||||
font-size: 14px;
|
||||
color: #8a8a86;
|
||||
margin-top: 2px;
|
||||
}
|
||||
|
||||
/* ── Card ── */
|
||||
.mc-card {
|
||||
background: #fff;
|
||||
border: 1px solid rgba(0, 0, 0, 0.06);
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.03);
|
||||
}
|
||||
|
||||
/* ── KPI strip ── */
|
||||
.mc-kpi-strip {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(6, 1fr);
|
||||
gap: 12px;
|
||||
}
|
||||
.mc-kpi-card {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: 16px 12px;
|
||||
gap: 6px;
|
||||
}
|
||||
.mc-kpi-label {
|
||||
font-size: 11px;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.04em;
|
||||
color: #8a8a86;
|
||||
text-align: center;
|
||||
}
|
||||
.mc-kpi-value {
|
||||
font-size: 22px;
|
||||
font-weight: 650;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
.mc-kpi-value--bottleneck {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: #01696f;
|
||||
}
|
||||
|
||||
/* ── Section ── */
|
||||
.mc-section {
|
||||
padding: 20px 24px;
|
||||
}
|
||||
.mc-section-title {
|
||||
font-size: 14px;
|
||||
font-weight: 700;
|
||||
color: var(--text-primary);
|
||||
margin-bottom: 14px;
|
||||
}
|
||||
.mc-section-desc {
|
||||
font-size: 13px;
|
||||
color: #8a8a86;
|
||||
margin-top: -8px;
|
||||
margin-bottom: 14px;
|
||||
}
|
||||
|
||||
/* ── Funnel ── */
|
||||
.mc-funnel {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
.mc-funnel-row {
|
||||
display: grid;
|
||||
grid-template-columns: 140px 1fr 36px 44px;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
.mc-funnel-label {
|
||||
font-size: 13px;
|
||||
color: var(--text-primary);
|
||||
white-space: nowrap;
|
||||
}
|
||||
.mc-funnel-bar-track {
|
||||
height: 22px;
|
||||
background: rgba(0, 0, 0, 0.03);
|
||||
border-radius: 6px;
|
||||
overflow: hidden;
|
||||
}
|
||||
.mc-funnel-bar {
|
||||
height: 100%;
|
||||
border-radius: 6px;
|
||||
transition: width 0.4s ease;
|
||||
min-width: 4px;
|
||||
}
|
||||
.mc-funnel-count {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
text-align: right;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
.mc-funnel-pct {
|
||||
font-size: 12px;
|
||||
color: #8a8a86;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
/* ── Table ── */
|
||||
.mc-table-wrap {
|
||||
overflow-x: auto;
|
||||
}
|
||||
.mc-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
font-size: 13px;
|
||||
}
|
||||
.mc-table th {
|
||||
font-size: 11px;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.04em;
|
||||
color: #8a8a86;
|
||||
font-weight: 500;
|
||||
text-align: left;
|
||||
padding: 0 12px 10px;
|
||||
border-bottom: 1px solid rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
.mc-table td {
|
||||
padding: 10px 12px;
|
||||
color: var(--text-primary);
|
||||
border-bottom: 1px solid rgba(0, 0, 0, 0.04);
|
||||
}
|
||||
.mc-table tbody tr:last-child td {
|
||||
border-bottom: none;
|
||||
}
|
||||
.mc-agent-name {
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* ── Badge ── */
|
||||
.mc-badge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
padding: 2px 8px;
|
||||
border-radius: 6px;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
/* ── Stuck list ── */
|
||||
.mc-stuck-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
}
|
||||
.mc-stuck-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
padding: 10px 14px;
|
||||
border-radius: 8px;
|
||||
border: 1px solid rgba(0, 0, 0, 0.04);
|
||||
}
|
||||
.mc-stuck-info {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
}
|
||||
.mc-stuck-name {
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
.mc-stuck-meta {
|
||||
font-size: 12px;
|
||||
color: #8a8a86;
|
||||
}
|
||||
.mc-stuck-days {
|
||||
font-weight: 600;
|
||||
}
|
||||
.mc-stuck-agent {
|
||||
font-size: 12px;
|
||||
color: #8a8a86;
|
||||
min-width: 120px;
|
||||
}
|
||||
.mc-btn-nudge {
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
padding: 4px 14px;
|
||||
border-radius: 6px;
|
||||
border: 1px solid rgba(0, 0, 0, 0.1);
|
||||
background: #fff;
|
||||
color: #01696f;
|
||||
cursor: pointer;
|
||||
white-space: nowrap;
|
||||
transition: background 0.15s;
|
||||
}
|
||||
.mc-btn-nudge:hover {
|
||||
background: rgba(1, 105, 111, 0.06);
|
||||
}
|
||||
|
||||
/* ── Tab bar ── */
|
||||
.mc-tab-bar {
|
||||
display: inline-flex;
|
||||
background: rgba(0, 0, 0, 0.04);
|
||||
padding: 3px;
|
||||
border-radius: 10px;
|
||||
gap: 2px;
|
||||
margin-bottom: 16px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.mc-tab {
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
padding: 5px 12px;
|
||||
border-radius: 8px;
|
||||
border: none;
|
||||
background: transparent;
|
||||
color: #6b6b68;
|
||||
cursor: pointer;
|
||||
white-space: nowrap;
|
||||
transition: all 0.15s;
|
||||
}
|
||||
.mc-tab--active {
|
||||
background: #fff;
|
||||
color: var(--text-primary);
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
/* ── Progress bar ── */
|
||||
.mc-progress-wrap {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
.mc-progress-track {
|
||||
flex: 1;
|
||||
height: 4px;
|
||||
background: rgba(0, 0, 0, 0.06);
|
||||
border-radius: 2px;
|
||||
overflow: hidden;
|
||||
min-width: 60px;
|
||||
}
|
||||
.mc-progress-fill {
|
||||
height: 100%;
|
||||
background: #01696f;
|
||||
border-radius: 2px;
|
||||
transition: width 0.3s ease;
|
||||
}
|
||||
.mc-progress-label {
|
||||
font-size: 12px;
|
||||
color: #8a8a86;
|
||||
min-width: 32px;
|
||||
}
|
||||
|
||||
/* ── Empty state ── */
|
||||
.mc-empty {
|
||||
font-size: 13px;
|
||||
color: #8a8a86;
|
||||
padding: 20px 0;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* ── Responsive ── */
|
||||
@media (max-width: 900px) {
|
||||
.mc-kpi-strip {
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
}
|
||||
}
|
||||
@media (max-width: 600px) {
|
||||
.mc-kpi-strip {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user