udpates on the ui changesand api integration
This commit is contained in:
@@ -5,15 +5,15 @@
|
||||
|
||||
import React from 'react';
|
||||
import {
|
||||
ShoppingBag,
|
||||
PackageCheck,
|
||||
Wallet,
|
||||
TrendingUp,
|
||||
Store,
|
||||
MapPin,
|
||||
Phone,
|
||||
Sparkles,
|
||||
AlertTriangle,
|
||||
Activity,
|
||||
Clock,
|
||||
ArrowUpRight,
|
||||
} from 'lucide-react';
|
||||
import { useOrderSummary, useTenantInfo, useTenantLocations, useInvoiceInsight } from '../services/queries';
|
||||
import { DEFAULT_TENANT_ID, DEFAULT_CONFIG_ID } from '../services/api';
|
||||
@@ -81,10 +81,42 @@ export default function DashboardView({ searchQuery }: DashboardViewProps) {
|
||||
const dashOffset = circumference - (circumference * activePct) / 100;
|
||||
|
||||
const kpis = [
|
||||
{ title: 'ACTIVE OUTLETS', display: `${activeStoresCount} / ${totalStoresCount}`, icon: Store, chip: 'bg-purple-50 text-[#581c87]', loading: locationsQ.isLoading },
|
||||
{ title: 'REGION FULFILLMENT', display: regionFulfillmentPct == null ? '—' : `${regionFulfillmentPct.toFixed(1)}%`, icon: Sparkles, chip: 'bg-emerald-50 text-emerald-600', loading: summaryQ.isLoading },
|
||||
{ title: 'MONTHLY REVENUE', display: money(monthlyRevenue), icon: Wallet, chip: 'bg-sky-50 text-sky-600', loading: insightQ.isLoading },
|
||||
{ title: 'MONTHLY PROFIT', display: money(monthlyProfit), icon: TrendingUp, chip: 'bg-emerald-50 text-emerald-600', loading: insightQ.isLoading },
|
||||
{
|
||||
title: 'ACTIVE OUTLETS',
|
||||
display: `${activeStoresCount} / ${totalStoresCount}`,
|
||||
sub: `${activePct}% of the network is live`,
|
||||
icon: Store,
|
||||
bar: 'from-purple-500 to-indigo-500',
|
||||
chip: 'bg-purple-50 text-purple-650 ring-purple-100',
|
||||
loading: locationsQ.isLoading,
|
||||
},
|
||||
{
|
||||
title: 'REGION FULFILLMENT',
|
||||
display: regionFulfillmentPct == null ? '—' : `${regionFulfillmentPct.toFixed(1)}%`,
|
||||
sub: `${ordersDelivered.toLocaleString('en-IN')} of ${ordersTotal.toLocaleString('en-IN')} orders delivered`,
|
||||
icon: Activity,
|
||||
bar: 'from-indigo-500 to-sky-500',
|
||||
chip: 'bg-indigo-50 text-indigo-600 ring-indigo-100',
|
||||
loading: summaryQ.isLoading,
|
||||
},
|
||||
{
|
||||
title: 'MONTHLY REVENUE',
|
||||
display: money(monthlyRevenue),
|
||||
sub: 'Gross billed · month-to-date',
|
||||
icon: Wallet,
|
||||
bar: 'from-sky-500 to-cyan-500',
|
||||
chip: 'bg-sky-50 text-sky-600 ring-sky-100',
|
||||
loading: insightQ.isLoading,
|
||||
},
|
||||
{
|
||||
title: 'MONTHLY PROFIT',
|
||||
display: money(monthlyProfit),
|
||||
sub: 'Net margin · month-to-date',
|
||||
icon: TrendingUp,
|
||||
bar: 'from-emerald-500 to-teal-500',
|
||||
chip: 'bg-emerald-50 text-emerald-600 ring-emerald-100',
|
||||
loading: insightQ.isLoading,
|
||||
},
|
||||
];
|
||||
|
||||
const statusRows = [
|
||||
@@ -97,35 +129,57 @@ export default function DashboardView({ searchQuery }: DashboardViewProps) {
|
||||
|
||||
return (
|
||||
<div className="space-y-lg animate-in fade-in duration-500 relative">
|
||||
{/* Scope banner */}
|
||||
<div className="bg-[#faf5ff] border border-purple-100 rounded-xl p-md flex items-center justify-between shadow-[0_1px_3px_rgba(0,0,0,0.05)]">
|
||||
<div className="flex items-center gap-sm">
|
||||
<Sparkles size={16} className="text-[#581c87]" />
|
||||
<span className="font-sans text-xs text-zinc-700 font-medium">
|
||||
Live operations data for <strong>{tenantName}</strong> · {fromdate} → {todate}
|
||||
</span>
|
||||
{/* ── Immersive Executive Banner (cover image + slate→purple gradient overlay) ── */}
|
||||
<div className="relative rounded-2xl p-6 md:p-8 text-white shadow-xl border border-purple-500/20 overflow-hidden animate-in fade-in duration-300">
|
||||
{/* Cover image background & decorative glow */}
|
||||
<div className="absolute inset-0 z-0 overflow-hidden rounded-2xl">
|
||||
<img
|
||||
src="https://images.unsplash.com/photo-1551288049-bebda4e38f71?auto=format&fit=crop&w=1400&q=80"
|
||||
alt="Executive operations dashboard"
|
||||
className="w-full h-full object-cover object-center opacity-40"
|
||||
/>
|
||||
<div className="absolute inset-0 bg-gradient-to-r from-slate-950 via-slate-900/90 to-purple-950/80" />
|
||||
<div className="absolute top-0 right-0 w-72 h-72 bg-purple-500/10 rounded-full blur-3xl -mr-20 -mt-20 pointer-events-none" />
|
||||
<div className="absolute bottom-0 left-0 w-56 h-56 bg-indigo-500/10 rounded-full blur-2xl -ml-20 -mb-20 pointer-events-none" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Header */}
|
||||
<div className="flex justify-between items-end">
|
||||
<div>
|
||||
<h1 className="font-sans font-bold text-3xl tracking-tight text-[#0f172a]">Executive Command Center</h1>
|
||||
<div className="flex items-center gap-2 mt-1">
|
||||
<p className="text-zinc-500 font-sans text-sm">Month-to-date order operations, pulled live from the API.</p>
|
||||
{loading ? (
|
||||
<span className="inline-flex items-center gap-1 text-[10px] font-semibold text-zinc-400 uppercase tracking-wide">
|
||||
<span className="w-1.5 h-1.5 rounded-full bg-zinc-300 animate-pulse" /> Loading…
|
||||
{/* Content row */}
|
||||
<div className="relative z-10 flex flex-col md:flex-row md:items-center justify-between gap-lg">
|
||||
<div>
|
||||
<h1 className="font-sans font-bold text-2xl md:text-3xl tracking-tight text-white flex items-center gap-2.5">
|
||||
Executive Command Center
|
||||
<span className="text-[10px] text-purple-200 font-bold bg-purple-900/60 border border-purple-500/30 px-2 py-0.5 rounded-full uppercase tracking-wider animate-pulse">
|
||||
Live Core
|
||||
</span>
|
||||
) : errored ? (
|
||||
<span className="inline-flex items-center gap-1 text-[10px] font-semibold text-rose-600 uppercase tracking-wide" title="Restart the dev server so the /hasura proxy is active.">
|
||||
<span className="w-1.5 h-1.5 rounded-full bg-rose-500" /> Live data unavailable
|
||||
</span>
|
||||
) : (
|
||||
<span className="inline-flex items-center gap-1 text-[10px] font-semibold text-emerald-600 uppercase tracking-wide">
|
||||
<span className="w-1.5 h-1.5 rounded-full bg-emerald-500" /> Live · {tenantName}
|
||||
</span>
|
||||
)}
|
||||
</h1>
|
||||
<p className="text-slate-300 font-sans text-sm mt-2 leading-relaxed whitespace-nowrap">
|
||||
Month-to-date order operations for <strong className="text-white font-semibold">{tenantName}</strong>, pulled live from the API.
|
||||
</p>
|
||||
|
||||
<div className="mt-4">
|
||||
{loading ? (
|
||||
<span className="inline-flex items-center gap-1.5 text-[11px] font-bold text-slate-300 uppercase tracking-wide">
|
||||
<span className="w-2 h-2 rounded-full bg-slate-400 animate-pulse" /> Syncing live data…
|
||||
</span>
|
||||
) : errored ? (
|
||||
<span className="inline-flex items-center gap-1.5 text-[11px] font-bold text-rose-300 uppercase tracking-wide" title="Restart the dev server so the /hasura proxy is active.">
|
||||
<span className="w-2 h-2 rounded-full bg-rose-400" /> Live data unavailable
|
||||
</span>
|
||||
) : (
|
||||
<span className="inline-flex items-center gap-1.5 text-[11px] font-bold text-emerald-300 uppercase tracking-wide">
|
||||
<span className="w-2 h-2 rounded-full bg-emerald-400 animate-pulse" /> Live · {tenantName}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Reporting scope panel */}
|
||||
<div className="flex flex-col items-start md:items-end gap-2 shrink-0">
|
||||
<div className="inline-flex items-center gap-2 bg-white/10 backdrop-blur-md border border-white/15 rounded-xl px-3.5 py-2.5 shadow-sm">
|
||||
<Clock size={14} className="text-purple-300" />
|
||||
<span className="text-xs font-bold font-mono text-white tracking-tight">{fromdate} → {todate}</span>
|
||||
</div>
|
||||
<span className="text-[10px] text-slate-400 uppercase tracking-widest font-bold">Month-to-date reporting scope</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -144,23 +198,31 @@ export default function DashboardView({ searchQuery }: DashboardViewProps) {
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* KPI cards — all live from getordersummary */}
|
||||
{/* KPI cards — all live from getordersummary / getinvoiceinsight */}
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-4 gap-gutter">
|
||||
{kpis.map((kpi) => {
|
||||
const Icon = kpi.icon;
|
||||
return (
|
||||
<div
|
||||
key={kpi.title}
|
||||
className="group relative flex flex-col bg-white border border-[#eceef2] rounded-xl p-3 shadow-[0_1px_2px_rgba(16,24,40,0.04)] transition-all duration-200 hover:-translate-y-0.5 hover:border-purple-300 hover:shadow-[0_8px_22px_rgba(16,24,40,0.08)]"
|
||||
className="group relative flex flex-col overflow-hidden bg-white border border-slate-200/70 rounded-2xl p-5 shadow-[0_1px_2px_rgba(16,24,40,0.04)] transition-all duration-300 hover:-translate-y-1 hover:border-purple-200 hover:shadow-[0_16px_36px_rgba(16,24,40,0.10)]"
|
||||
>
|
||||
<div className={`h-7 w-7 rounded-lg flex items-center justify-center ${kpi.chip}`}>
|
||||
<Icon size={14} />
|
||||
{/* Gradient accent bar */}
|
||||
<span className={`absolute inset-x-0 top-0 h-1 bg-gradient-to-r ${kpi.bar}`} />
|
||||
<div className="flex items-start justify-between">
|
||||
<div className={`h-11 w-11 rounded-xl flex items-center justify-center ring-1 group-hover:scale-110 transition-transform duration-300 ${kpi.chip}`}>
|
||||
<Icon size={19} />
|
||||
</div>
|
||||
<ArrowUpRight size={16} className="text-slate-300 group-hover:text-purple-400 transition-colors" />
|
||||
</div>
|
||||
<p className="text-[10px] font-semibold text-zinc-400 tracking-wider uppercase font-sans mt-3">
|
||||
<p className="text-[10px] font-bold text-slate-400 tracking-widest uppercase font-sans mt-4">
|
||||
{kpi.title}
|
||||
</p>
|
||||
<p className="font-sans font-bold text-2xl leading-tight text-[#0f172a] tracking-tight mt-0.5">
|
||||
{kpi.loading ? <span className="text-zinc-300">…</span> : kpi.display}
|
||||
<p className="font-sans font-extrabold text-[28px] leading-tight text-slate-900 tracking-tight mt-1">
|
||||
{kpi.loading ? <span className="text-slate-300">…</span> : kpi.display}
|
||||
</p>
|
||||
<p className="text-[11px] text-slate-400 font-medium mt-1.5 leading-snug">
|
||||
{kpi.sub}
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
@@ -170,115 +232,140 @@ export default function DashboardView({ searchQuery }: DashboardViewProps) {
|
||||
{/* Order status + store locations */}
|
||||
<div className="grid grid-cols-1 lg:grid-cols-3 gap-gutter">
|
||||
{/* Store Node Status donut (live) */}
|
||||
<div className="bg-white border border-[#e2e8f0] rounded-xl p-md flex flex-col shadow-[0_1px_3px_rgba(0,0,0,0.05)]">
|
||||
<div>
|
||||
<h3 className="font-sans font-bold text-base text-[#0f172a]">Store Outlet Status</h3>
|
||||
<p className="text-zinc-500 text-xs font-sans mt-0.5">Active share of all registered store nodes.</p>
|
||||
<div className="bg-white border border-slate-200/70 rounded-2xl p-lg flex flex-col shadow-[0_1px_3px_rgba(16,24,40,0.05)]">
|
||||
<div className="flex items-start gap-2.5">
|
||||
<div className="h-9 w-9 rounded-xl bg-emerald-50 text-emerald-600 ring-1 ring-emerald-100 flex items-center justify-center shrink-0">
|
||||
<Activity size={17} />
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="font-sans font-bold text-base text-slate-900 tracking-tight">Store Outlet Status</h3>
|
||||
<p className="text-slate-500 text-xs font-sans mt-0.5">Active share of all registered nodes.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="my-md flex justify-center items-center">
|
||||
<div className="relative w-40 h-40 flex items-center justify-center">
|
||||
<svg className="w-full h-full transform -rotate-90" viewBox="0 0 100 100">
|
||||
<circle cx="50" cy="50" r="40" fill="transparent" stroke="#eceef0" strokeWidth="8" />
|
||||
<div className="my-lg flex justify-center items-center">
|
||||
<div className="relative w-44 h-44 flex items-center justify-center">
|
||||
{/* soft glow behind the ring */}
|
||||
<div className="absolute w-32 h-32 rounded-full bg-emerald-400/10 blur-2xl" />
|
||||
<svg className="w-full h-full transform -rotate-90 relative" viewBox="0 0 100 100">
|
||||
<circle cx="50" cy="50" r="40" fill="transparent" stroke="#eef2f6" strokeWidth="9" />
|
||||
<circle
|
||||
cx="50"
|
||||
cy="50"
|
||||
r="40"
|
||||
fill="transparent"
|
||||
stroke="#10b981"
|
||||
strokeWidth="8"
|
||||
stroke="url(#dashGradient)"
|
||||
strokeWidth="9"
|
||||
strokeDasharray={circumference}
|
||||
strokeDashoffset={dashOffset}
|
||||
strokeLinecap="round"
|
||||
className="transition-all duration-700"
|
||||
/>
|
||||
<defs>
|
||||
<linearGradient id="dashGradient" x1="0" y1="0" x2="1" y2="1">
|
||||
<stop offset="0%" stopColor="#10b981" />
|
||||
<stop offset="100%" stopColor="#0d9488" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
<div className="absolute inset-0 flex flex-col items-center justify-center">
|
||||
<span className="font-sans font-bold text-3xl text-[#0f172a] tracking-tight">{activePct}%</span>
|
||||
<span className="text-[10px] text-emerald-600 uppercase tracking-widest font-semibold mt-1">Active</span>
|
||||
<span className="font-sans font-extrabold text-4xl text-slate-900 tracking-tight">{activePct}%</span>
|
||||
<span className="text-[10px] text-emerald-600 uppercase tracking-widest font-bold mt-1">Active</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="divide-y divide-[#f1f5f9] text-xs">
|
||||
<div className="divide-y divide-slate-100 text-xs">
|
||||
{statusRows.map((r) => (
|
||||
<div key={r.label} className="flex justify-between items-center py-2">
|
||||
<span className="flex items-center gap-1.5 text-zinc-500">
|
||||
<div key={r.label} className="flex justify-between items-center py-2.5">
|
||||
<span className="flex items-center gap-2 text-slate-500 font-medium">
|
||||
<span className={`w-2.5 h-2.5 rounded-full ${r.dot}`} />
|
||||
{r.label}
|
||||
</span>
|
||||
<span className="font-mono font-bold text-zinc-700">{r.value.toLocaleString('en-IN')}</span>
|
||||
<span className="font-mono font-bold text-slate-700">{r.value.toLocaleString('en-IN')}</span>
|
||||
</div>
|
||||
))}
|
||||
<div className="flex justify-between items-center py-2">
|
||||
<span className="text-zinc-500 font-semibold">Total Nodes</span>
|
||||
<span className="font-mono font-bold text-[#581c87]">{totalStoresCount.toLocaleString('en-IN')}</span>
|
||||
<div className="flex justify-between items-center pt-2.5">
|
||||
<span className="text-slate-600 font-bold">Total Nodes</span>
|
||||
<span className="font-mono font-extrabold text-purple-650">{totalStoresCount.toLocaleString('en-IN')}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Store locations (live) */}
|
||||
<div className="lg:col-span-2 bg-white border border-[#e2e8f0] rounded-xl p-md shadow-[0_1px_3px_rgba(0,0,0,0.05)]">
|
||||
<div className="flex justify-between items-center mb-md pb-xs border-b border-[#f1f5f9]">
|
||||
<h3 className="font-sans font-bold text-base text-[#0f172a] flex items-center gap-2">
|
||||
<Store size={16} className="text-[#581c87]" /> Store Locations
|
||||
<div className="lg:col-span-2 bg-white border border-slate-200/70 rounded-2xl p-lg shadow-[0_1px_3px_rgba(16,24,40,0.05)]">
|
||||
<div className="flex justify-between items-center mb-md pb-sm border-b border-slate-100">
|
||||
<h3 className="font-sans font-bold text-base text-slate-900 flex items-center gap-2.5 tracking-tight">
|
||||
<span className="h-9 w-9 rounded-xl bg-purple-50 text-purple-650 ring-1 ring-purple-100 flex items-center justify-center">
|
||||
<Store size={17} />
|
||||
</span>
|
||||
Store Locations
|
||||
</h3>
|
||||
<span className="text-[10px] text-[#581c87] uppercase font-bold bg-purple-50 px-2 py-0.5 rounded tracking-wide border border-purple-100">
|
||||
<span className="text-[10px] text-purple-650 uppercase font-bold bg-purple-50 px-2.5 py-1 rounded-lg tracking-wide border border-purple-100">
|
||||
{locationsQ.isLoading ? 'Loading…' : `${locations.length} Outlet${locations.length === 1 ? '' : 's'}`}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{locationsQ.isLoading ? (
|
||||
<div className="text-center py-xl text-zinc-400 text-xs">Loading store locations…</div>
|
||||
<div className="text-center py-xl text-slate-400 text-xs">Loading store locations…</div>
|
||||
) : locations.length === 0 ? (
|
||||
<div className="text-center py-xl text-zinc-400 text-xs">No store locations found for this tenant.</div>
|
||||
<div className="text-center py-xl text-slate-400 text-xs">No store locations found for this tenant.</div>
|
||||
) : (
|
||||
<div className="space-y-sm max-h-80 overflow-y-auto">
|
||||
<div className="space-y-sm max-h-80 overflow-y-auto pr-1">
|
||||
{locations.map((loc, i) => {
|
||||
const sum = summaries.find((s) => s.locationid === Number(loc.locationid));
|
||||
const deliveries = sum?.delivered ?? 0;
|
||||
const orders = Math.max(sum?.delivered ?? 0, sum?.total ?? 0);
|
||||
|
||||
const isActive = str(loc.status).toLowerCase() === 'active';
|
||||
const name = str(loc.locationname);
|
||||
|
||||
return (
|
||||
<div
|
||||
key={str(loc.locationid) || i}
|
||||
className="p-sm border border-[#e2e8f0] rounded-lg bg-[#f8fafc]/40 flex justify-between items-start gap-md hover:border-purple-200 transition-colors animate-in fade-in"
|
||||
className="group p-3.5 border border-slate-200/70 rounded-xl bg-slate-50/40 flex justify-between items-start gap-md hover:border-purple-200 hover:bg-white hover:shadow-[0_6px_18px_rgba(16,24,40,0.06)] transition-all animate-in fade-in"
|
||||
>
|
||||
<div className="min-w-0">
|
||||
<p className="font-sans font-semibold text-sm text-[#0f172a] truncate">{str(loc.locationname)}</p>
|
||||
<p className="text-[11px] text-zinc-500 mt-0.5 flex items-center gap-1">
|
||||
<MapPin size={11} className="shrink-0 text-zinc-400" />
|
||||
<span className="truncate">{str(loc.address) || `${str(loc.suburb)}, ${str(loc.city)}`}</span>
|
||||
</p>
|
||||
{str(loc.contactno) && (
|
||||
<p className="text-[11px] text-zinc-500 mt-0.5 flex items-center gap-1">
|
||||
<Phone size={11} className="shrink-0 text-zinc-400" />
|
||||
{str(loc.contactno)}
|
||||
<div className="flex items-start gap-3 min-w-0">
|
||||
{/* Outlet initial badge */}
|
||||
<div className="h-9 w-9 shrink-0 rounded-lg bg-gradient-to-br from-purple-500 to-indigo-500 text-white flex items-center justify-center font-bold text-xs uppercase shadow-sm">
|
||||
{name.slice(0, 2) || '—'}
|
||||
</div>
|
||||
<div className="min-w-0">
|
||||
<p className="font-sans font-semibold text-sm text-slate-900 truncate">{name}</p>
|
||||
<p className="text-[11px] text-slate-500 mt-0.5 flex items-center gap-1">
|
||||
<MapPin size={11} className="shrink-0 text-slate-400" />
|
||||
<span className="truncate">{str(loc.address) || `${str(loc.suburb)}, ${str(loc.city)}`}</span>
|
||||
</p>
|
||||
)}
|
||||
|
||||
{/* Node-specific Orders and Dispatches */}
|
||||
<div className="flex items-center gap-3 mt-2.5">
|
||||
<span className="text-[10px] bg-purple-50 text-[#581c87] font-semibold px-2 py-0.5 rounded border border-purple-100/50">
|
||||
{orders} Orders
|
||||
</span>
|
||||
<span className="text-[10px] bg-emerald-50 text-emerald-700 font-semibold px-2 py-0.5 rounded border border-emerald-100/50">
|
||||
{deliveries} Dispatched
|
||||
</span>
|
||||
{orders > 0 && (
|
||||
<span className="text-[10px] text-zinc-400 font-medium">
|
||||
{Math.round((deliveries / orders) * 100)}% Fulfilled
|
||||
</span>
|
||||
{str(loc.contactno) && (
|
||||
<p className="text-[11px] text-slate-500 mt-0.5 flex items-center gap-1">
|
||||
<Phone size={11} className="shrink-0 text-slate-400" />
|
||||
{str(loc.contactno)}
|
||||
</p>
|
||||
)}
|
||||
|
||||
{/* Node-specific Orders and Dispatches */}
|
||||
<div className="flex items-center gap-2 mt-2.5 flex-wrap">
|
||||
<span className="text-[10px] bg-purple-50 text-purple-650 font-bold px-2 py-0.5 rounded-md border border-purple-100/60">
|
||||
{orders} Orders
|
||||
</span>
|
||||
<span className="text-[10px] bg-emerald-50 text-emerald-700 font-bold px-2 py-0.5 rounded-md border border-emerald-100/60">
|
||||
{deliveries} Dispatched
|
||||
</span>
|
||||
{orders > 0 && (
|
||||
<span className="text-[10px] text-slate-400 font-semibold">
|
||||
{Math.round((deliveries / orders) * 100)}% Fulfilled
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<span
|
||||
className={`shrink-0 px-1.5 py-0.5 rounded text-[9px] font-bold uppercase ${
|
||||
str(loc.status).toLowerCase() === 'active'
|
||||
className={`shrink-0 inline-flex items-center gap-1 px-2 py-0.5 rounded-md text-[9px] font-bold uppercase tracking-wide ${
|
||||
isActive
|
||||
? 'text-emerald-600 bg-emerald-50 border border-emerald-100'
|
||||
: 'text-zinc-500 bg-zinc-100'
|
||||
: 'text-slate-500 bg-slate-100 border border-slate-200'
|
||||
}`}
|
||||
>
|
||||
<span className={`w-1.5 h-1.5 rounded-full ${isActive ? 'bg-emerald-500' : 'bg-slate-400'}`} />
|
||||
{str(loc.status) || '—'}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user