import { useCallback, useEffect, useMemo, useState } from 'react'
import {
Users,
UserCog,
Package,
ShoppingCart,
DollarSign,
Activity,
Boxes,
RefreshCw,
Search,
Loader2,
TrendingUp,
ExternalLink,
} from 'lucide-react'
import { cn } from '../../lib/utils'
type Bucket = { key: string; count: number; value?: number }
type Business = {
ok: boolean
generated_at: string
filters: { q: string; region: string }
kpis: {
customers: number
employees: number
products: number
orders_indexed: number
revenue_indexed: number
avg_order: number
telemetry_indexed: number
supply_indexed: number
source_totals: { orders?: number; hr_events?: number; supply_events?: number }
}
orders: {
by_region: Bucket[]
by_channel: Bucket[]
by_status: Bucket[]
top_customers: Bucket[]
top_products: Bucket[]
over_time: Bucket[]
}
hr: { by_department: Bucket[]; by_role: Bucket[]; by_event: Bucket[] }
supply: { by_type: Bucket[]; by_region: Bucket[] }
telemetry: { by_metric: Bucket[] }
regions: string[]
}
const REGION_COLORS = ['#34d399', '#60a5fa', '#f472b6', '#fbbf24', '#a78bfa', '#22d3ee', '#fb7185']
function fmtNum(n?: number | null) {
if (n == null) return '—'
if (Math.abs(n) >= 1_000_000_000) return `${(n / 1_000_000_000).toFixed(2)}B`
if (Math.abs(n) >= 1_000_000) return `${(n / 1_000_000).toFixed(1)}M`
if (Math.abs(n) >= 1_000) return `${(n / 1_000).toFixed(1)}K`
return String(n)
}
function fmtMoney(n?: number | null) {
if (n == null) return '—'
return `€${fmtNum(n)}`
}
function monthLabel(key: string) {
const m = /^(\d{4})-(\d{2})/.exec(key)
if (!m) return key
const months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']
return `${months[Number(m[2]) - 1]} '${m[1].slice(2)}`
}
function Kpi({ icon: Icon, label, value, sub, accent }: { icon: typeof Users; label: string; value: string; sub?: string; accent: string }) {
return (
{label}
{value}
{sub &&
{sub}
}
)
}
function Panel({ title, subtitle, children, icon: Icon }: { title: string; subtitle?: string; children: React.ReactNode; icon?: typeof Users }) {
return (
{Icon && }
{title}
{subtitle && {subtitle} }
{children}
)
}
function BarsH({ data, valueKind, colorByIndex }: { data: Bucket[]; valueKind?: 'money' | 'num'; colorByIndex?: boolean }) {
const useVal = valueKind != null
const max = Math.max(1, ...data.map((d) => (useVal && d.value != null ? d.value : d.count)))
if (!data.length) return No data
return (
{data.map((d, i) => {
const metric = useVal && d.value != null ? d.value : d.count
const pct = Math.max(2, (metric / max) * 100)
const color = colorByIndex ? REGION_COLORS[i % REGION_COLORS.length] : '#38bdf8'
const label = useVal && d.value != null ? (valueKind === 'money' ? fmtMoney(d.value) : fmtNum(d.value)) : fmtNum(d.count)
return (
)
})}
)
}
function AreaTrend({ data }: { data: Bucket[] }) {
const pts = data.filter((d) => d.value != null)
if (pts.length < 2) return Not enough data
const w = 560
const h = 120
const max = Math.max(...pts.map((p) => p.value || 0))
const min = Math.min(...pts.map((p) => p.value || 0))
const range = max - min || 1
const step = w / (pts.length - 1)
const coords = pts.map((p, i) => [i * step, h - ((((p.value || 0) - min) / range) * (h - 16) + 8)])
const line = coords.map((c, i) => `${i === 0 ? 'M' : 'L'}${c[0].toFixed(1)},${c[1].toFixed(1)}`).join(' ')
const area = `${line} L${w},${h} L0,${h} Z`
return (
{coords.map((c, i) => )}
{monthLabel(pts[0].key)}
{monthLabel(pts[Math.floor(pts.length / 2)].key)}
{monthLabel(pts[pts.length - 1].key)}
)
}
function Donut({ data }: { data: Bucket[] }) {
const total = data.reduce((s, d) => s + d.count, 0) || 1
let acc = 0
const r = 42
const c = 2 * Math.PI * r
if (!data.length) return No data
return (
{data.map((d, i) => {
const frac = d.count / total
const dash = frac * c
const seg = (
)
acc += dash
return seg
})}
{data.slice(0, 7).map((d, i) => (
{d.key ?? '—'}
{((d.count / total) * 100).toFixed(0)}%
))}
)
}
export function DataExplorerView() {
const [data, setData] = useState(null)
const [loading, setLoading] = useState(true)
const [region, setRegion] = useState('')
const [qInput, setQInput] = useState('')
const [q, setQ] = useState('')
const load = useCallback(async () => {
setLoading(true)
try {
const params = new URLSearchParams()
if (q) params.set('q', q)
if (region) params.set('region', region)
const r = await fetch(`/api/search/business?${params.toString()}`)
if (r.ok) setData(await r.json())
} catch { /* */ } finally {
setLoading(false)
}
}, [q, region])
useEffect(() => { load() }, [load])
const k = data?.kpis
const regions = useMemo(() => data?.regions || [], [data])
return (
{/* header */}
Data Explorer
Your actual business data — customers, orders, employees, supply chain & telemetry across all sources
{loading ? : } Refresh
{/* region filters */}
Region
{['', ...regions].map((rg) => (
setRegion(rg)}
className={cn('rounded-full border px-2.5 py-1 font-medium transition-colors', region === rg ? 'border-docker bg-docker/15 text-docker' : 'border-border text-foreground-muted hover:bg-surface-overlay')}
>
{rg || 'All regions'}
))}
{(q || region) && (
{ setQ(''); setQInput(''); setRegion('') }} className="ml-1 rounded-full border border-amber-500/40 px-2.5 py-1 font-medium text-amber-400 hover:bg-amber-500/10">
Clear filters
)}
{data && updated {new Date(data.generated_at).toLocaleTimeString()} }
{/* KPIs */}
{/* Sales */}
Sales & Customers — PostgreSQL
{/* HR */}
Workforce — MySQL HR
{/* Supply + telemetry */}
Supply Chain (MongoDB) & Telemetry (Cassandra)
)
}