abbashkyt-creator 7d8ce0e322 V0.1
2026-03-14 04:02:22 +03:00

155 lines
5.9 KiB
TypeScript

'use client'
import { useEffect, useState } from 'react'
import { motion } from 'framer-motion'
import { useEngineStore } from '@/store/engineStore'
import { formatUptime } from '@/lib/utils'
import StatsGrid from '@/components/dashboard/StatsGrid'
import RecentListings from '@/components/dashboard/RecentListings'
import EngineConsole from '@/components/dashboard/EngineConsole'
import ActivityLog from '@/components/dashboard/ActivityLog'
const BASE = 'http://localhost:8000'
/* ── Live clock ──────────────────────────────────────────────── */
function LiveClock() {
const [tick, setTick] = useState('')
useEffect(() => {
const fmt = () =>
new Date().toLocaleTimeString([], {
hour: '2-digit',
minute: '2-digit',
second: '2-digit',
})
setTick(fmt())
const id = setInterval(() => setTick(fmt()), 1000)
return () => clearInterval(id)
}, [])
return (
<span className="text-[12px] font-mono text-g-faint/60 tabular-nums tracking-widest select-none">
{tick}
</span>
)
}
/* ── Connection badge ────────────────────────────────────────── */
function ConnectionBadge({ offline }: { offline: boolean }) {
return (
<div
className={`inline-flex items-center gap-1.5 px-2.5 py-1 rounded-full border text-[10px] font-bold tracking-wide transition-all duration-500 ${
offline
? 'bg-g-red/8 border-g-red/20 text-g-red'
: 'bg-g-green/8 border-g-green/15 text-g-green'
}`}
>
<span
className="w-1.5 h-1.5 rounded-full"
style={{
background: offline ? '#f43f5e' : '#00e87b',
boxShadow: offline ? '0 0 6px #f43f5e80' : '0 0 6px #00e87b80',
animation: offline ? undefined : 'pulse-ring 2s ease-out infinite',
}}
/>
{offline ? 'OFFLINE' : 'LIVE'}
</div>
)
}
/* ── Divider strip ───────────────────────────────────────────── */
function SectionLabel({ children }: { children: React.ReactNode }) {
return (
<div className="flex items-center gap-3">
<span className="text-[10px] font-bold uppercase tracking-[0.16em] text-g-faint/70 whitespace-nowrap">
{children}
</span>
<div className="flex-1 h-px bg-gradient-to-r from-g-border/60 to-transparent" />
</div>
)
}
/* ── Main ────────────────────────────────────────────────────── */
export default function DashboardPage() {
const { status, uptime_seconds, total_scanned, total_alerts, isOffline } = useEngineStore()
const [keywordCount, setKeywordCount] = useState(0)
useEffect(() => {
fetch(`${BASE}/api/keywords`)
.then(r => r.json())
.then(d => { if (Array.isArray(d)) setKeywordCount(d.length) })
.catch(() => {})
}, [])
return (
<div className="space-y-8">
{/* ── Page header ──────────────────────────────────────── */}
<motion.div
initial={{ opacity: 1, y: -10 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.45, ease: [0.22, 1, 0.36, 1] }}
className="flex items-end justify-between"
>
<div className="space-y-1">
<div className="flex items-center gap-3">
<h1 className="text-[22px] font-extrabold tracking-[-0.04em] leading-none bg-gradient-to-r from-g-text to-g-muted bg-clip-text text-transparent">
Mission Control
</h1>
<ConnectionBadge offline={isOffline} />
</div>
<p className="text-[12px] text-g-faint font-medium">
Auction intelligence engine · Ghost Node v2.7
</p>
</div>
<div className="flex items-center gap-4">
<LiveClock />
{/* Quick export shortcut */}
<a
href={`${BASE}/api/export/csv`}
target="_blank"
rel="noopener noreferrer"
className="g-btn h-8 px-3 text-[11px] gap-1.5"
title="Export all listings as CSV"
>
<svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round">
<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="7 10 12 15 17 10"/><line x1="12" y1="15" x2="12" y2="3"/>
</svg>
Export
</a>
</div>
</motion.div>
{/* ── Glow divider ─────────────────────────────────────── */}
<div className="glow-line" />
{/* ── Stats strip ──────────────────────────────────────── */}
<section>
<StatsGrid
scanned={total_scanned}
alerts={total_alerts}
keywords={keywordCount}
uptime={formatUptime(uptime_seconds)}
/>
</section>
{/* ── Main grid ────────────────────────────────────────── */}
<section className="space-y-3">
<SectionLabel>Live Feed</SectionLabel>
<div className="grid grid-cols-1 xl:grid-cols-[1fr_360px] gap-5">
<RecentListings />
<EngineConsole />
</div>
</section>
{/* ── Activity log ─────────────────────────────────────── */}
<section className="space-y-3">
<SectionLabel>Activity Log</SectionLabel>
<ActivityLog />
</section>
</div>
)
}