add quick leads
All checks were successful
Build and Publish / build-release (push) Successful in 4m11s
All checks were successful
Build and Publish / build-release (push) Successful in 4m11s
This commit is contained in:
@@ -9,62 +9,91 @@ interface QuickLead {
|
||||
name: string
|
||||
phone: string
|
||||
email: string
|
||||
product: string
|
||||
source: string
|
||||
priority: 'normal' | 'high' | 'urgent'
|
||||
note: string
|
||||
agent: string
|
||||
createdAt: string
|
||||
}
|
||||
|
||||
/* ── Storage ── */
|
||||
const STORAGE_KEY = 'policy-ui.quick-leads'
|
||||
|
||||
function loadLeads(): QuickLead[] {
|
||||
if (import.meta.client) {
|
||||
try {
|
||||
const raw = localStorage.getItem(STORAGE_KEY)
|
||||
if (raw) {
|
||||
return JSON.parse(raw) as QuickLead[]
|
||||
}
|
||||
} catch {
|
||||
return []
|
||||
}
|
||||
}
|
||||
return []
|
||||
}
|
||||
|
||||
function saveLeads(leads: QuickLead[]) {
|
||||
if (import.meta.client) {
|
||||
try {
|
||||
localStorage.setItem(STORAGE_KEY, JSON.stringify(leads))
|
||||
} catch { /* quota */ }
|
||||
}
|
||||
notes: string
|
||||
priority: 'low' | 'medium' | 'high'
|
||||
source: 'website' | 'referral' | 'social_media' | 'cold_call' | 'email_campaign' | 'other'
|
||||
status: 'new' | 'contacted' | 'qualified' | 'proposal' | 'negotiation' | 'converted' | 'lost'
|
||||
assigned_to?: string
|
||||
company_name?: string
|
||||
estimated_value?: string
|
||||
expected_close_date?: string
|
||||
inserted_at: string
|
||||
updated_at: string
|
||||
status_history?: any[]
|
||||
}
|
||||
|
||||
/* ── State ── */
|
||||
const leads = ref<QuickLead[]>(loadLeads())
|
||||
const page = ref(1)
|
||||
const pageSize = ref(100)
|
||||
|
||||
watch(leads, (v) => {
|
||||
saveLeads(v)
|
||||
}, { deep: true })
|
||||
/* ── List filtering ── */
|
||||
type ListFilter = 'all' | 'high' | 'medium' | 'low'
|
||||
const activeFilter = ref<ListFilter>('all')
|
||||
|
||||
function addLead(lead: Omit<QuickLead, 'id' | 'createdAt'>) {
|
||||
const newLead: QuickLead = {
|
||||
id: crypto.randomUUID?.() ?? String(Date.now() + Math.random()),
|
||||
createdAt: new Date().toISOString(),
|
||||
...lead,
|
||||
// Fetch leads from API
|
||||
const { data: leadsData, pending: leadsPending, refresh: refreshLeads } = useCustomer('/leads', {
|
||||
query: computed(() => {
|
||||
const filters: Record<string, string> = {}
|
||||
let i = 0
|
||||
|
||||
if (activeFilter.value !== 'all') {
|
||||
filters[`filters[${i}][field]`] = 'priority'
|
||||
filters[`filters[${i}][op]`] = '=='
|
||||
filters[`filters[${i}][value]`] = activeFilter.value
|
||||
i++
|
||||
}
|
||||
|
||||
return {
|
||||
page: page.value,
|
||||
page_size: pageSize.value,
|
||||
...filters
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
const leads = computed(() => leadsData.value?.data ?? [])
|
||||
const meta = computed(() => leadsData.value?.meta)
|
||||
|
||||
// Create lead function
|
||||
async function createLead(leadData: Partial<QuickLead>) {
|
||||
try {
|
||||
await $fetch('/api/v1/leads', {
|
||||
baseURL: 'https://dev.api.corredorconect.com/customer',
|
||||
method: 'POST',
|
||||
body: {
|
||||
name: leadData.name,
|
||||
phone: leadData.phone,
|
||||
email: leadData.email,
|
||||
notes: leadData.notes,
|
||||
priority: leadData.priority,
|
||||
source: leadData.source,
|
||||
status: 'new',
|
||||
assigned_to: leadData.assigned_to,
|
||||
company_name: leadData.company_name,
|
||||
estimated_value: leadData.estimated_value,
|
||||
expected_close_date: leadData.expected_close_date
|
||||
}
|
||||
})
|
||||
await refreshLeads()
|
||||
} catch (error) {
|
||||
console.error('Failed to create lead:', error)
|
||||
throw error
|
||||
}
|
||||
leads.value = [newLead, ...leads.value]
|
||||
}
|
||||
|
||||
function removeLead(id: string) {
|
||||
leads.value = leads.value.filter(l => l.id !== id)
|
||||
}
|
||||
|
||||
function recentLeads(days: number): QuickLead[] {
|
||||
const cutoff = Date.now() - days * 86400000
|
||||
return leads.value.filter(l => new Date(l.createdAt).getTime() > cutoff)
|
||||
// Update lead status function
|
||||
async function updateLeadStatus(id: string, status: QuickLead['status']) {
|
||||
try {
|
||||
await $fetch(`/api/v1/leads/${id}/status`, {
|
||||
baseURL: 'https://dev.api.corredorconect.com/customer',
|
||||
method: 'PUT',
|
||||
body: { status }
|
||||
})
|
||||
await refreshLeads()
|
||||
} catch (error) {
|
||||
console.error('Failed to update lead status:', error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
/* ── Form state ── */
|
||||
@@ -72,69 +101,109 @@ const formOpen = ref(false)
|
||||
const name = ref('')
|
||||
const phone = ref('')
|
||||
const email = ref('')
|
||||
const product = ref('')
|
||||
const notes = ref('')
|
||||
const priority = ref<'low' | 'medium' | 'high'>('low')
|
||||
const source = ref('')
|
||||
const priority = ref<'normal' | 'high' | 'urgent'>('normal')
|
||||
const note = ref('')
|
||||
|
||||
const productOptions = [
|
||||
{ label: 'Auto', value: 'Auto' },
|
||||
{ label: 'Health', value: 'Health' },
|
||||
{ label: 'Life', value: 'Life' },
|
||||
{ label: 'General Risk', value: 'General Risk' },
|
||||
{ label: 'Custom', value: 'Custom' },
|
||||
]
|
||||
const assigned_to = ref('')
|
||||
const company_name = ref('')
|
||||
const estimated_value = ref('')
|
||||
const expected_close_date = ref('')
|
||||
|
||||
const sourceOptions = [
|
||||
{ label: 'Walk-in', value: 'walk-in' },
|
||||
{ label: 'Referral', value: 'referral' },
|
||||
{ label: 'Phone call', value: 'phone' },
|
||||
{ label: 'Website', value: 'website' },
|
||||
{ label: 'Social media', value: 'social' },
|
||||
{ label: 'Referral', value: 'referral' },
|
||||
{ label: 'Social media', value: 'social_media' },
|
||||
{ label: 'Cold call', value: 'cold_call' },
|
||||
{ label: 'Email campaign', value: 'email_campaign' },
|
||||
{ label: 'Other', value: 'other' },
|
||||
]
|
||||
|
||||
const priorityOptions = [
|
||||
{ label: 'Normal', value: 'normal' as const },
|
||||
{ label: 'High — follow up today', value: 'high' as const },
|
||||
{ label: 'Urgent — client waiting', value: 'urgent' as const },
|
||||
{ label: 'Low', value: 'low' as const },
|
||||
{ label: 'Medium', value: 'medium' as const },
|
||||
{ label: 'High', value: 'high' as const },
|
||||
]
|
||||
|
||||
function submit() {
|
||||
if (!name.value.trim() || !product.value) return
|
||||
addLead({
|
||||
name: name.value.trim(),
|
||||
phone: phone.value.trim(),
|
||||
email: email.value.trim(),
|
||||
product: product.value,
|
||||
source: source.value || 'other',
|
||||
priority: priority.value,
|
||||
note: note.value.trim(),
|
||||
agent: 'Me', // placeholder
|
||||
})
|
||||
toast.add({ title: 'Lead captured', description: `${name.value} added to quick leads`, color: 'success' })
|
||||
resetForm()
|
||||
const statusOptions = [
|
||||
{ label: 'New', value: 'new' },
|
||||
{ label: 'Contacted', value: 'contacted' },
|
||||
{ label: 'Qualified', value: 'qualified' },
|
||||
{ label: 'Proposal', value: 'proposal' },
|
||||
{ label: 'Negotiation', value: 'negotiation' },
|
||||
{ label: 'Converted', value: 'converted' },
|
||||
{ label: 'Lost', value: 'lost' },
|
||||
]
|
||||
|
||||
async function submit() {
|
||||
if (!name.value.trim()) return
|
||||
|
||||
try {
|
||||
await createLead({
|
||||
name: name.value.trim(),
|
||||
phone: phone.value.trim(),
|
||||
email: email.value.trim(),
|
||||
notes: notes.value.trim(),
|
||||
priority: priority.value,
|
||||
source: source.value || 'other',
|
||||
assigned_to: assigned_to.value.trim() || undefined,
|
||||
company_name: company_name.value.trim() || undefined,
|
||||
estimated_value: estimated_value.value.trim() || undefined,
|
||||
expected_close_date: expected_close_date.value || undefined,
|
||||
})
|
||||
|
||||
toast.add({ title: 'Lead captured', description: `${name.value} added to quick leads`, color: 'success' })
|
||||
resetForm()
|
||||
} catch (error) {
|
||||
toast.add({ title: 'Failed to create lead', description: 'Please try again', color: 'error' })
|
||||
}
|
||||
}
|
||||
|
||||
function resetForm() {
|
||||
name.value = ''
|
||||
phone.value = ''
|
||||
email.value = ''
|
||||
product.value = ''
|
||||
notes.value = ''
|
||||
priority.value = 'low'
|
||||
source.value = ''
|
||||
priority.value = 'normal'
|
||||
note.value = ''
|
||||
assigned_to.value = ''
|
||||
company_name.value = ''
|
||||
estimated_value.value = ''
|
||||
expected_close_date.value = ''
|
||||
formOpen.value = false
|
||||
}
|
||||
|
||||
function confirmRemove(id: string) {
|
||||
removeLead(id)
|
||||
toast.add({ title: 'Lead removed', color: 'neutral' })
|
||||
async function handleStatusChange(leadId: string, newStatus: QuickLead['status']) {
|
||||
try {
|
||||
await updateLeadStatus(leadId, newStatus)
|
||||
toast.add({ title: 'Status updated', description: `Lead status changed to ${newStatus}`, color: 'success' })
|
||||
} catch (error) {
|
||||
toast.add({ title: 'Failed to update status', description: 'Please try again', color: 'error' })
|
||||
}
|
||||
}
|
||||
|
||||
/* ── List filtering ── */
|
||||
type ListFilter = 'all' | 'urgent' | 'high' | 'normal'
|
||||
const activeFilter = ref<ListFilter>('all')
|
||||
/* ── Drag and drop for status ── */
|
||||
const draggedLead = ref<string | null>(null)
|
||||
const draggedOverStatus = ref<QuickLead['status'] | null>(null)
|
||||
|
||||
function handleDragStart(leadId: string) {
|
||||
draggedLead.value = leadId
|
||||
}
|
||||
|
||||
function handleDragOver(status: QuickLead['status']) {
|
||||
draggedOverStatus.value = status
|
||||
}
|
||||
|
||||
function handleDragEnd() {
|
||||
if (draggedLead.value && draggedOverStatus.value) {
|
||||
handleStatusChange(draggedLead.value, draggedOverStatus.value)
|
||||
}
|
||||
draggedLead.value = null
|
||||
draggedOverStatus.value = null
|
||||
}
|
||||
|
||||
function handleDrop(leadId: string, newStatus: QuickLead['status']) {
|
||||
handleStatusChange(leadId, newStatus)
|
||||
}
|
||||
|
||||
const filteredLeads = computed(() => {
|
||||
if (activeFilter.value === 'all') return leads.value
|
||||
@@ -143,16 +212,34 @@ const filteredLeads = computed(() => {
|
||||
|
||||
const filterCounts = computed(() => ({
|
||||
all: leads.value.length,
|
||||
urgent: leads.value.filter(l => l.priority === 'urgent').length,
|
||||
high: leads.value.filter(l => l.priority === 'high').length,
|
||||
normal: leads.value.filter(l => l.priority === 'normal').length,
|
||||
medium: leads.value.filter(l => l.priority === 'medium').length,
|
||||
low: leads.value.filter(l => l.priority === 'low').length,
|
||||
}))
|
||||
|
||||
const recentLeads = computed(() => {
|
||||
const cutoff = Date.now() - 10 * 86400000
|
||||
return leads.value.filter(l => new Date(l.inserted_at).getTime() > cutoff)
|
||||
})
|
||||
|
||||
/* ── Helpers ── */
|
||||
function priorityMeta(p: string) {
|
||||
if (p === 'urgent') return { label: 'Urgent', class: 'ql-pri-urgent' }
|
||||
if (p === 'high') return { label: 'High', class: 'ql-pri-high' }
|
||||
return { label: 'Normal', class: 'ql-pri-normal' }
|
||||
if (p === 'high') return { label: 'High', class: 'ql-pri-urgent' }
|
||||
if (p === 'medium') return { label: 'Medium', class: 'ql-pri-high' }
|
||||
return { label: 'Low', class: 'ql-pri-normal' }
|
||||
}
|
||||
|
||||
function statusMeta(s: string) {
|
||||
const statusMap: Record<string, { label: string; class: string }> = {
|
||||
new: { label: 'New', class: 'ql-status-new' },
|
||||
contacted: { label: 'Contacted', class: 'ql-status-contacted' },
|
||||
qualified: { label: 'Qualified', class: 'ql-status-qualified' },
|
||||
proposal: { label: 'Proposal', class: 'ql-status-proposal' },
|
||||
negotiation: { label: 'Negotiation', class: 'ql-status-negotiation' },
|
||||
converted: { label: 'Converted', class: 'ql-status-converted' },
|
||||
lost: { label: 'Lost', class: 'ql-status-lost' },
|
||||
}
|
||||
return statusMap[s] || { label: s, class: 'ql-status-new' }
|
||||
}
|
||||
|
||||
function formatDate(iso: string) {
|
||||
@@ -228,21 +315,33 @@ function formatDate(iso: string) {
|
||||
<div class="ql-section">
|
||||
<p class="ql-section-title">Lead info</p>
|
||||
<div class="ql-fields">
|
||||
<div class="ql-field">
|
||||
<label class="ql-label">Product line <span class="ql-required">*</span></label>
|
||||
<USelect v-model="product" :items="productOptions" placeholder="Select line..." size="sm" />
|
||||
</div>
|
||||
<div class="ql-field">
|
||||
<label class="ql-label">Source</label>
|
||||
<USelect v-model="source" :items="sourceOptions" placeholder="How did they find us?" size="sm" />
|
||||
</div>
|
||||
<div class="ql-field ql-field-full">
|
||||
<div class="ql-field">
|
||||
<label class="ql-label">Priority</label>
|
||||
<USelect v-model="priority" :items="priorityOptions" placeholder="Normal" size="sm" />
|
||||
<USelect v-model="priority" :items="priorityOptions" placeholder="Low" size="sm" />
|
||||
</div>
|
||||
<div class="ql-field">
|
||||
<label class="ql-label">Assigned to</label>
|
||||
<UInput v-model="assigned_to" placeholder="Agent name" size="sm" />
|
||||
</div>
|
||||
<div class="ql-field">
|
||||
<label class="ql-label">Company name</label>
|
||||
<UInput v-model="company_name" placeholder="Company (optional)" size="sm" />
|
||||
</div>
|
||||
<div class="ql-field">
|
||||
<label class="ql-label">Estimated value</label>
|
||||
<UInput v-model="estimated_value" placeholder="$0.00" size="sm" />
|
||||
</div>
|
||||
<div class="ql-field">
|
||||
<label class="ql-label">Expected close date</label>
|
||||
<UInput v-model="expected_close_date" type="date" size="sm" />
|
||||
</div>
|
||||
<div class="ql-field ql-field-full">
|
||||
<label class="ql-label">Notes</label>
|
||||
<UTextarea v-model="note" placeholder="Brief context, referral source, or anything useful for follow-up..." size="sm" :rows="2" />
|
||||
<UTextarea v-model="notes" placeholder="Brief context, referral source, or anything useful for follow-up..." size="sm" :rows="2" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -255,7 +354,7 @@ function formatDate(iso: string) {
|
||||
</div>
|
||||
<div class="flex gap-2">
|
||||
<button type="button" class="ql-cancel-btn" @click="resetForm">Cancel</button>
|
||||
<button type="button" class="ql-submit-btn" :class="!name.trim() || !product ? 'ql-btn-disabled' : ''" @click="submit">
|
||||
<button type="button" class="ql-submit-btn" :class="!name.trim() ? 'ql-btn-disabled' : ''" @click="submit">
|
||||
<UIcon name="i-heroicons-paper-airplane" style="width: 14px; height: 14px;" />
|
||||
Add Lead
|
||||
</button>
|
||||
@@ -272,15 +371,15 @@ function formatDate(iso: string) {
|
||||
</div>
|
||||
<div class="ql-kpi">
|
||||
<p class="ql-kpi-label">Last 10 days</p>
|
||||
<p class="ql-kpi-value">{{ recentLeads(10).length }}</p>
|
||||
</div>
|
||||
<div class="ql-kpi">
|
||||
<p class="ql-kpi-label">Urgent</p>
|
||||
<p class="ql-kpi-value" style="color: #c13838;">{{ filterCounts.urgent }}</p>
|
||||
<p class="ql-kpi-value">{{ recentLeads.length }}</p>
|
||||
</div>
|
||||
<div class="ql-kpi">
|
||||
<p class="ql-kpi-label">High priority</p>
|
||||
<p class="ql-kpi-value" style="color: #c27b1a;">{{ filterCounts.high }}</p>
|
||||
<p class="ql-kpi-value" style="color: #c13838;">{{ filterCounts.high }}</p>
|
||||
</div>
|
||||
<div class="ql-kpi">
|
||||
<p class="ql-kpi-label">Medium priority</p>
|
||||
<p class="ql-kpi-value" style="color: #c27b1a;">{{ filterCounts.medium }}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -290,9 +389,9 @@ function formatDate(iso: string) {
|
||||
<button
|
||||
v-for="f in ([
|
||||
{ id: 'all', label: 'All' },
|
||||
{ id: 'urgent', label: 'Urgent' },
|
||||
{ id: 'high', label: 'High' },
|
||||
{ id: 'normal', label: 'Normal' },
|
||||
{ id: 'medium', label: 'Medium' },
|
||||
{ id: 'low', label: 'Low' },
|
||||
] as { id: ListFilter; label: string }[])"
|
||||
:key="f.id"
|
||||
type="button"
|
||||
@@ -308,7 +407,12 @@ function formatDate(iso: string) {
|
||||
</div>
|
||||
|
||||
<!-- ═══ Leads list ═══ -->
|
||||
<div v-if="filteredLeads.length === 0" class="ql-empty">
|
||||
<div v-if="leadsPending" class="ql-empty">
|
||||
<UIcon name="i-heroicons-arrow-path" class="w-8 h-8 animate-spin" style="color: #c0c0bc;" />
|
||||
<p class="text-[13px] text-[var(--text-muted)] mt-2">Loading leads...</p>
|
||||
</div>
|
||||
|
||||
<div v-else-if="filteredLeads.length === 0" class="ql-empty">
|
||||
<UIcon name="i-heroicons-bolt" style="width: 32px; height: 32px; color: #c0c0bc;" />
|
||||
<p class="text-[13px] text-[var(--text-muted)] mt-2">No quick leads yet.</p>
|
||||
<button type="button" class="ql-add-btn mt-3" @click="formOpen = true">
|
||||
@@ -333,32 +437,47 @@ function formatDate(iso: string) {
|
||||
<div class="flex items-center gap-2 flex-wrap">
|
||||
<p class="text-[14px] font-semibold text-[var(--text-primary)] truncate">{{ lead.name }}</p>
|
||||
<span :class="priorityMeta(lead.priority).class">{{ priorityMeta(lead.priority).label }}</span>
|
||||
<span class="ql-product-tag">{{ lead.product }}</span>
|
||||
<div
|
||||
:class="statusMeta(lead.status).class"
|
||||
class="cursor-pointer hover:opacity-80"
|
||||
draggable="true"
|
||||
@dragstart="handleDragStart(lead.id)"
|
||||
@dragend="handleDragEnd"
|
||||
@dragover.prevent="handleDragOver(lead.status)"
|
||||
@drop="handleDrop(lead.id, lead.status)"
|
||||
title="Drag to change status"
|
||||
>
|
||||
{{ statusMeta(lead.status).label }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center gap-3 mt-0.5 text-[11px] text-[var(--text-muted)]">
|
||||
<span v-if="lead.phone">{{ lead.phone }}</span>
|
||||
<span v-if="lead.email">{{ lead.email }}</span>
|
||||
<span>{{ formatDate(lead.createdAt) }}</span>
|
||||
<span>{{ formatDate(lead.inserted_at) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ql-lead-actions">
|
||||
<UDropdown :items="statusOptions.map(s => ({ label: s.label, click: () => handleStatusChange(lead.id, s.value as QuickLead['status']) }))">
|
||||
<button type="button" class="ql-action-btn" title="Change status">
|
||||
<UIcon name="i-heroicons-arrows-pointing-out" style="width: 14px; height: 14px;" />
|
||||
</button>
|
||||
</UDropdown>
|
||||
<NuxtLink :to="`/quotes/new`" title="Start quote">
|
||||
<button type="button" class="ql-action-btn ql-action-quote">
|
||||
<UIcon name="i-heroicons-calculator" style="width: 14px; height: 14px;" />
|
||||
</button>
|
||||
</NuxtLink>
|
||||
<button type="button" class="ql-action-btn ql-action-delete" title="Remove" @click="confirmRemove(lead.id)">
|
||||
<UIcon name="i-heroicons-trash" style="width: 14px; height: 14px;" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="lead.note" class="ql-lead-note">
|
||||
<div v-if="lead.notes" class="ql-lead-note">
|
||||
<UIcon name="i-heroicons-chat-bubble-left-ellipsis" style="width: 11px; height: 11px; color: #8a8a86; flex-shrink: 0;" />
|
||||
<span>{{ lead.note }}</span>
|
||||
<span>{{ lead.notes }}</span>
|
||||
</div>
|
||||
<div class="ql-lead-meta">
|
||||
<span v-if="lead.source" class="ql-meta-tag">{{ lead.source }}</span>
|
||||
<span class="ql-meta-tag">{{ lead.agent }}</span>
|
||||
<span v-if="lead.company_name" class="ql-meta-tag">{{ lead.company_name }}</span>
|
||||
<span v-if="lead.assigned_to" class="ql-meta-tag">{{ lead.assigned_to }}</span>
|
||||
<span v-if="lead.estimated_value" class="ql-meta-tag">{{ lead.estimated_value }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</TransitionGroup>
|
||||
@@ -546,9 +665,35 @@ function formatDate(iso: string) {
|
||||
font-size: 10px; font-weight: 600; padding: 1px 7px; border-radius: 9999px;
|
||||
background: rgba(0,0,0,0.04); color: #8a8a86; white-space: nowrap;
|
||||
}
|
||||
.ql-product-tag {
|
||||
|
||||
/* ── Status badges ── */
|
||||
.ql-status-new {
|
||||
font-size: 10px; font-weight: 600; padding: 1px 7px; border-radius: 9999px;
|
||||
background: rgba(1,105,111,0.07); color: #01696f; white-space: nowrap;
|
||||
background: rgba(1,105,111,0.08); color: #01696f; white-space: nowrap;
|
||||
}
|
||||
.ql-status-contacted {
|
||||
font-size: 10px; font-weight: 600; padding: 1px 7px; border-radius: 9999px;
|
||||
background: rgba(59,130,246,0.08); color: #3b82f6; white-space: nowrap;
|
||||
}
|
||||
.ql-status-qualified {
|
||||
font-size: 10px; font-weight: 600; padding: 1px 7px; border-radius: 9999px;
|
||||
background: rgba(16,185,129,0.08); color: #10b981; white-space: nowrap;
|
||||
}
|
||||
.ql-status-proposal {
|
||||
font-size: 10px; font-weight: 600; padding: 1px 7px; border-radius: 9999px;
|
||||
background: rgba(245,158,11,0.08); color: #f59e0b; white-space: nowrap;
|
||||
}
|
||||
.ql-status-negotiation {
|
||||
font-size: 10px; font-weight: 600; padding: 1px 7px; border-radius: 9999px;
|
||||
background: rgba(139,92,246,0.08); color: #8b5cf6; white-space: nowrap;
|
||||
}
|
||||
.ql-status-converted {
|
||||
font-size: 10px; font-weight: 600; padding: 1px 7px; border-radius: 9999px;
|
||||
background: rgba(34,197,94,0.08); color: #22c55e; white-space: nowrap;
|
||||
}
|
||||
.ql-status-lost {
|
||||
font-size: 10px; font-weight: 600; padding: 1px 7px; border-radius: 9999px;
|
||||
background: rgba(107,114,128,0.08); color: #6b7280; white-space: nowrap;
|
||||
}
|
||||
|
||||
/* ── Empty ── */
|
||||
|
||||
Reference in New Issue
Block a user