All tables: Unified sortable headers for Radar, Market, Watchlist, Intel
Some checks failed
CI / Frontend Lint & Type Check (push) Has been cancelled
CI / Frontend Build (push) Has been cancelled
CI / Backend Lint (push) Has been cancelled
CI / Backend Tests (push) Has been cancelled
CI / Docker Build (push) Has been cancelled
CI / Security Scan (push) Has been cancelled
Deploy / Build & Push Images (push) Has been cancelled
Deploy / Deploy to Server (push) Has been cancelled
Deploy / Notify (push) Has been cancelled
Some checks failed
CI / Frontend Lint & Type Check (push) Has been cancelled
CI / Frontend Build (push) Has been cancelled
CI / Backend Lint (push) Has been cancelled
CI / Backend Tests (push) Has been cancelled
CI / Docker Build (push) Has been cancelled
CI / Security Scan (push) Has been cancelled
Deploy / Build & Push Images (push) Has been cancelled
Deploy / Deploy to Server (push) Has been cancelled
Deploy / Notify (push) Has been cancelled
This commit is contained in:
@ -12,6 +12,8 @@ import {
|
|||||||
Zap,
|
Zap,
|
||||||
ChevronLeft,
|
ChevronLeft,
|
||||||
ChevronRight,
|
ChevronRight,
|
||||||
|
ChevronUp,
|
||||||
|
ChevronDown,
|
||||||
TrendingUp,
|
TrendingUp,
|
||||||
RefreshCw,
|
RefreshCw,
|
||||||
Clock,
|
Clock,
|
||||||
@ -138,6 +140,10 @@ export default function MarketPage() {
|
|||||||
// Mobile Menu & Filters
|
// Mobile Menu & Filters
|
||||||
const [menuOpen, setMenuOpen] = useState(false)
|
const [menuOpen, setMenuOpen] = useState(false)
|
||||||
const [filtersOpen, setFiltersOpen] = useState(false)
|
const [filtersOpen, setFiltersOpen] = useState(false)
|
||||||
|
|
||||||
|
// Sorting
|
||||||
|
const [sortField, setSortField] = useState<'domain' | 'score' | 'price' | 'time'>('time')
|
||||||
|
const [sortDirection, setSortDirection] = useState<'asc' | 'desc'>('asc')
|
||||||
|
|
||||||
// Check auth on mount
|
// Check auth on mount
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -254,8 +260,32 @@ export default function MarketPage() {
|
|||||||
filtered = filtered.filter(item => !isSpam(item.domain))
|
filtered = filtered.filter(item => !isSpam(item.domain))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Sort
|
||||||
|
const mult = sortDirection === 'asc' ? 1 : -1
|
||||||
|
filtered.sort((a, b) => {
|
||||||
|
switch (sortField) {
|
||||||
|
case 'domain': return mult * a.domain.localeCompare(b.domain)
|
||||||
|
case 'score': return mult * ((a.pounce_score || 0) - (b.pounce_score || 0))
|
||||||
|
case 'price': return mult * (a.price - b.price)
|
||||||
|
case 'time':
|
||||||
|
const aTime = a.end_time ? new Date(a.end_time).getTime() : Infinity
|
||||||
|
const bTime = b.end_time ? new Date(b.end_time).getTime() : Infinity
|
||||||
|
return mult * (aTime - bTime)
|
||||||
|
default: return 0
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
return filtered
|
return filtered
|
||||||
}, [items, searchQuery, loading, hideSpam])
|
}, [items, searchQuery, loading, hideSpam, sortField, sortDirection])
|
||||||
|
|
||||||
|
const handleSort = useCallback((field: typeof sortField) => {
|
||||||
|
if (sortField === field) {
|
||||||
|
setSortDirection(d => d === 'asc' ? 'desc' : 'asc')
|
||||||
|
} else {
|
||||||
|
setSortField(field)
|
||||||
|
setSortDirection(field === 'time' || field === 'price' ? 'asc' : 'desc')
|
||||||
|
}
|
||||||
|
}, [sortField])
|
||||||
|
|
||||||
// Active filters count
|
// Active filters count
|
||||||
const activeFiltersCount = [
|
const activeFiltersCount = [
|
||||||
@ -663,6 +693,27 @@ export default function MarketPage() {
|
|||||||
<>
|
<>
|
||||||
{/* Results List */}
|
{/* Results List */}
|
||||||
<div className="space-y-px bg-white/[0.04] border border-white/[0.08]">
|
<div className="space-y-px bg-white/[0.04] border border-white/[0.08]">
|
||||||
|
{/* Desktop Table Header */}
|
||||||
|
<div className="hidden lg:grid grid-cols-[1fr_80px_100px_80px_120px] gap-4 px-3 py-2 text-[10px] font-mono text-white/40 uppercase tracking-wider border-b border-white/[0.08]">
|
||||||
|
<button onClick={() => handleSort('domain')} className="flex items-center gap-1 hover:text-white/60 text-left">
|
||||||
|
Domain
|
||||||
|
{sortField === 'domain' && (sortDirection === 'asc' ? <ChevronUp className="w-3 h-3" /> : <ChevronDown className="w-3 h-3" />)}
|
||||||
|
</button>
|
||||||
|
<button onClick={() => handleSort('score')} className="flex items-center gap-1 justify-center hover:text-white/60">
|
||||||
|
Score
|
||||||
|
{sortField === 'score' && (sortDirection === 'asc' ? <ChevronUp className="w-3 h-3" /> : <ChevronDown className="w-3 h-3" />)}
|
||||||
|
</button>
|
||||||
|
<button onClick={() => handleSort('price')} className="flex items-center gap-1 justify-end hover:text-white/60">
|
||||||
|
Price
|
||||||
|
{sortField === 'price' && (sortDirection === 'asc' ? <ChevronUp className="w-3 h-3" /> : <ChevronDown className="w-3 h-3" />)}
|
||||||
|
</button>
|
||||||
|
<button onClick={() => handleSort('time')} className="flex items-center gap-1 justify-center hover:text-white/60">
|
||||||
|
Time
|
||||||
|
{sortField === 'time' && (sortDirection === 'asc' ? <ChevronUp className="w-3 h-3" /> : <ChevronDown className="w-3 h-3" />)}
|
||||||
|
</button>
|
||||||
|
<div className="text-right">Actions</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
{filteredItems.map((item) => {
|
{filteredItems.map((item) => {
|
||||||
const timeLeftSec = getSecondsUntilEnd(item.end_time)
|
const timeLeftSec = getSecondsUntilEnd(item.end_time)
|
||||||
const isUrgent = timeLeftSec > 0 && timeLeftSec < 3600
|
const isUrgent = timeLeftSec > 0 && timeLeftSec < 3600
|
||||||
@ -763,7 +814,7 @@ export default function MarketPage() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Desktop Row */}
|
{/* Desktop Row */}
|
||||||
<div className="hidden lg:flex items-center justify-between p-3 gap-4 group">
|
<div className="hidden lg:grid grid-cols-[1fr_80px_100px_80px_120px] gap-4 items-center p-3 group">
|
||||||
<div className="flex items-center gap-3 min-w-0 flex-1">
|
<div className="flex items-center gap-3 min-w-0 flex-1">
|
||||||
<div className={clsx(
|
<div className={clsx(
|
||||||
"w-8 h-8 flex items-center justify-center border shrink-0",
|
"w-8 h-8 flex items-center justify-center border shrink-0",
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
'use client'
|
'use client'
|
||||||
|
|
||||||
import { useEffect, useState, useCallback, useRef } from 'react'
|
import { useEffect, useState, useCallback, useRef, useMemo } from 'react'
|
||||||
import { useStore } from '@/lib/store'
|
import { useStore } from '@/lib/store'
|
||||||
import { api } from '@/lib/api'
|
import { api } from '@/lib/api'
|
||||||
import { Sidebar } from '@/components/Sidebar'
|
import { Sidebar } from '@/components/Sidebar'
|
||||||
@ -22,6 +22,8 @@ import {
|
|||||||
Settings,
|
Settings,
|
||||||
Clock,
|
Clock,
|
||||||
ChevronRight,
|
ChevronRight,
|
||||||
|
ChevronUp,
|
||||||
|
ChevronDown,
|
||||||
Sparkles,
|
Sparkles,
|
||||||
Radio,
|
Radio,
|
||||||
Activity,
|
Activity,
|
||||||
@ -81,6 +83,10 @@ export default function RadarPage() {
|
|||||||
// Mobile Menu State
|
// Mobile Menu State
|
||||||
const [menuOpen, setMenuOpen] = useState(false)
|
const [menuOpen, setMenuOpen] = useState(false)
|
||||||
|
|
||||||
|
// Sorting for Auctions
|
||||||
|
const [auctionSort, setAuctionSort] = useState<'domain' | 'time' | 'bid'>('time')
|
||||||
|
const [auctionSortDir, setAuctionSortDir] = useState<'asc' | 'desc'>('asc')
|
||||||
|
|
||||||
// Check auth on mount
|
// Check auth on mount
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
checkAuth()
|
checkAuth()
|
||||||
@ -112,6 +118,36 @@ export default function RadarPage() {
|
|||||||
}
|
}
|
||||||
}, [authLoading, isAuthenticated, loadDashboardData])
|
}, [authLoading, isAuthenticated, loadDashboardData])
|
||||||
|
|
||||||
|
// Sorted auctions
|
||||||
|
const sortedAuctions = useMemo(() => {
|
||||||
|
const mult = auctionSortDir === 'asc' ? 1 : -1
|
||||||
|
return [...hotAuctions].sort((a, b) => {
|
||||||
|
switch (auctionSort) {
|
||||||
|
case 'domain': return mult * a.domain.localeCompare(b.domain)
|
||||||
|
case 'bid': return mult * (a.current_bid - b.current_bid)
|
||||||
|
case 'time':
|
||||||
|
// Parse time_remaining like "2h 30m" or "5d 12h"
|
||||||
|
const parseTime = (t: string) => {
|
||||||
|
const d = t.match(/(\d+)d/)?.[1] || 0
|
||||||
|
const h = t.match(/(\d+)h/)?.[1] || 0
|
||||||
|
const m = t.match(/(\d+)m/)?.[1] || 0
|
||||||
|
return Number(d) * 86400 + Number(h) * 3600 + Number(m) * 60
|
||||||
|
}
|
||||||
|
return mult * (parseTime(a.time_remaining) - parseTime(b.time_remaining))
|
||||||
|
default: return 0
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}, [hotAuctions, auctionSort, auctionSortDir])
|
||||||
|
|
||||||
|
const handleAuctionSort = useCallback((field: typeof auctionSort) => {
|
||||||
|
if (auctionSort === field) {
|
||||||
|
setAuctionSortDir(d => d === 'asc' ? 'desc' : 'asc')
|
||||||
|
} else {
|
||||||
|
setAuctionSort(field)
|
||||||
|
setAuctionSortDir(field === 'bid' ? 'desc' : 'asc')
|
||||||
|
}
|
||||||
|
}, [auctionSort])
|
||||||
|
|
||||||
// Search
|
// Search
|
||||||
const handleSearch = useCallback(async (domainInput: string) => {
|
const handleSearch = useCallback(async (domainInput: string) => {
|
||||||
if (!domainInput.trim()) { setSearchResult(null); return }
|
if (!domainInput.trim()) { setSearchResult(null); return }
|
||||||
@ -498,7 +534,7 @@ export default function RadarPage() {
|
|||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<Activity className="w-4 h-4 text-accent" />
|
<Activity className="w-4 h-4 text-accent" />
|
||||||
<span className="text-xs font-bold text-white uppercase tracking-wider">Live Auctions</span>
|
<span className="text-xs font-bold text-white uppercase tracking-wider">Live Auctions</span>
|
||||||
<span className="text-[10px] font-mono text-white/30">({hotAuctions.length})</span>
|
<span className="text-[10px] font-mono text-white/30">({sortedAuctions.length})</span>
|
||||||
</div>
|
</div>
|
||||||
<Link href="/terminal/market" className="text-[10px] font-mono text-accent hover:text-white transition-colors flex items-center gap-1">
|
<Link href="/terminal/market" className="text-[10px] font-mono text-accent hover:text-white transition-colors flex items-center gap-1">
|
||||||
View all
|
View all
|
||||||
@ -510,41 +546,68 @@ export default function RadarPage() {
|
|||||||
<div className="flex items-center justify-center py-12">
|
<div className="flex items-center justify-center py-12">
|
||||||
<Loader2 className="w-6 h-6 text-accent animate-spin" />
|
<Loader2 className="w-6 h-6 text-accent animate-spin" />
|
||||||
</div>
|
</div>
|
||||||
) : hotAuctions.length > 0 ? (
|
) : sortedAuctions.length > 0 ? (
|
||||||
<div className="space-y-px bg-white/[0.04] border border-white/[0.08]">
|
<div className="space-y-px bg-white/[0.04] border border-white/[0.08]">
|
||||||
{hotAuctions.map((auction, i) => (
|
{/* Desktop Table Header */}
|
||||||
|
<div className="hidden lg:grid grid-cols-[1fr_100px_80px_100px_40px] gap-4 px-3 py-2 text-[10px] font-mono text-white/40 uppercase tracking-wider border-b border-white/[0.08]">
|
||||||
|
<button onClick={() => handleAuctionSort('domain')} className="flex items-center gap-1 hover:text-white/60 text-left">
|
||||||
|
Domain
|
||||||
|
{auctionSort === 'domain' && (auctionSortDir === 'asc' ? <ChevronUp className="w-3 h-3" /> : <ChevronDown className="w-3 h-3" />)}
|
||||||
|
</button>
|
||||||
|
<div className="text-center">Platform</div>
|
||||||
|
<button onClick={() => handleAuctionSort('time')} className="flex items-center gap-1 justify-center hover:text-white/60">
|
||||||
|
Time
|
||||||
|
{auctionSort === 'time' && (auctionSortDir === 'asc' ? <ChevronUp className="w-3 h-3" /> : <ChevronDown className="w-3 h-3" />)}
|
||||||
|
</button>
|
||||||
|
<button onClick={() => handleAuctionSort('bid')} className="flex items-center gap-1 justify-end hover:text-white/60">
|
||||||
|
Bid
|
||||||
|
{auctionSort === 'bid' && (auctionSortDir === 'asc' ? <ChevronUp className="w-3 h-3" /> : <ChevronDown className="w-3 h-3" />)}
|
||||||
|
</button>
|
||||||
|
<div></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{sortedAuctions.map((auction, i) => (
|
||||||
<a
|
<a
|
||||||
key={i}
|
key={i}
|
||||||
href={auction.affiliate_url || '#'}
|
href={auction.affiliate_url || '#'}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
className="flex items-center justify-between p-3 bg-[#020202] hover:bg-white/[0.02] active:bg-white/[0.03] transition-all group"
|
className="grid grid-cols-[1fr_100px_80px_100px_40px] gap-4 items-center p-3 bg-[#020202] hover:bg-white/[0.02] active:bg-white/[0.03] transition-all group"
|
||||||
>
|
>
|
||||||
<div className="flex items-center gap-3 min-w-0 flex-1">
|
{/* Domain */}
|
||||||
|
<div className="flex items-center gap-3 min-w-0">
|
||||||
<div className="w-8 h-8 bg-white/[0.02] border border-white/[0.06] flex items-center justify-center shrink-0">
|
<div className="w-8 h-8 bg-white/[0.02] border border-white/[0.06] flex items-center justify-center shrink-0">
|
||||||
<Gavel className="w-4 h-4 text-white/40 group-hover:text-accent transition-colors" />
|
<Gavel className="w-4 h-4 text-white/40 group-hover:text-accent transition-colors" />
|
||||||
</div>
|
</div>
|
||||||
<div className="min-w-0 flex-1">
|
<div className="text-sm font-bold text-white font-mono truncate group-hover:text-accent transition-colors">
|
||||||
<div className="text-sm font-bold text-white font-mono truncate group-hover:text-accent transition-colors">
|
{auction.domain}
|
||||||
{auction.domain}
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center gap-2 text-[10px] font-mono text-white/30">
|
|
||||||
<span className="uppercase">{auction.platform}</span>
|
|
||||||
<span className="text-white/10">|</span>
|
|
||||||
<span className="flex items-center gap-1">
|
|
||||||
<Clock className="w-3 h-3" />
|
|
||||||
{auction.time_remaining}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="text-right shrink-0 ml-3">
|
|
||||||
<div className="text-base font-bold text-accent font-mono">
|
{/* Platform */}
|
||||||
|
<div className="text-center">
|
||||||
|
<span className="text-[10px] font-mono text-white/40 uppercase">{auction.platform}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Time */}
|
||||||
|
<div className="text-center">
|
||||||
|
<span className="text-xs font-mono text-white/50 flex items-center justify-center gap-1">
|
||||||
|
<Clock className="w-3 h-3" />
|
||||||
|
{auction.time_remaining}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Bid */}
|
||||||
|
<div className="text-right">
|
||||||
|
<div className="text-sm font-bold text-accent font-mono">
|
||||||
${auction.current_bid.toLocaleString()}
|
${auction.current_bid.toLocaleString()}
|
||||||
</div>
|
</div>
|
||||||
<div className="text-[9px] font-mono text-white/20 uppercase">Current Bid</div>
|
|
||||||
</div>
|
</div>
|
||||||
<ExternalLink className="w-4 h-4 text-white/10 group-hover:text-accent ml-3 shrink-0" />
|
|
||||||
|
{/* Link */}
|
||||||
|
<div className="flex justify-end">
|
||||||
|
<ExternalLink className="w-4 h-4 text-white/20 group-hover:text-accent transition-colors" />
|
||||||
|
</div>
|
||||||
</a>
|
</a>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -31,7 +31,9 @@ import {
|
|||||||
Coins,
|
Coins,
|
||||||
Tag,
|
Tag,
|
||||||
Zap,
|
Zap,
|
||||||
Search
|
Search,
|
||||||
|
ChevronUp,
|
||||||
|
ChevronDown
|
||||||
} from 'lucide-react'
|
} from 'lucide-react'
|
||||||
import clsx from 'clsx'
|
import clsx from 'clsx'
|
||||||
import Link from 'next/link'
|
import Link from 'next/link'
|
||||||
@ -81,6 +83,10 @@ export default function WatchlistPage() {
|
|||||||
const [selectedDomain, setSelectedDomain] = useState<number | null>(null)
|
const [selectedDomain, setSelectedDomain] = useState<number | null>(null)
|
||||||
const [filter, setFilter] = useState<'all' | 'available' | 'expiring'>('all')
|
const [filter, setFilter] = useState<'all' | 'available' | 'expiring'>('all')
|
||||||
|
|
||||||
|
// Sorting
|
||||||
|
const [sortField, setSortField] = useState<'domain' | 'status' | 'health' | 'expiry'>('domain')
|
||||||
|
const [sortDirection, setSortDirection] = useState<'asc' | 'desc'>('asc')
|
||||||
|
|
||||||
// Mobile Menu
|
// Mobile Menu
|
||||||
const [menuOpen, setMenuOpen] = useState(false)
|
const [menuOpen, setMenuOpen] = useState(false)
|
||||||
|
|
||||||
@ -103,19 +109,47 @@ export default function WatchlistPage() {
|
|||||||
// Filtered
|
// Filtered
|
||||||
const filteredDomains = useMemo(() => {
|
const filteredDomains = useMemo(() => {
|
||||||
if (!domains) return []
|
if (!domains) return []
|
||||||
return domains.filter(d => {
|
let filtered = domains.filter(d => {
|
||||||
if (filter === 'available') return d.is_available
|
if (filter === 'available') return d.is_available
|
||||||
if (filter === 'expiring') {
|
if (filter === 'expiring') {
|
||||||
const days = getDaysUntilExpiry(d.expiration_date)
|
const days = getDaysUntilExpiry(d.expiration_date)
|
||||||
return days !== null && days <= 30 && days > 0
|
return days !== null && days <= 30 && days > 0
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}).sort((a, b) => {
|
|
||||||
if (a.is_available && !b.is_available) return -1
|
|
||||||
if (!a.is_available && b.is_available) return 1
|
|
||||||
return a.name.localeCompare(b.name)
|
|
||||||
})
|
})
|
||||||
}, [domains, filter])
|
|
||||||
|
// Sort
|
||||||
|
const mult = sortDirection === 'asc' ? 1 : -1
|
||||||
|
filtered.sort((a, b) => {
|
||||||
|
switch (sortField) {
|
||||||
|
case 'domain': return mult * a.name.localeCompare(b.name)
|
||||||
|
case 'status':
|
||||||
|
if (a.is_available && !b.is_available) return mult * -1
|
||||||
|
if (!a.is_available && b.is_available) return mult * 1
|
||||||
|
return 0
|
||||||
|
case 'health':
|
||||||
|
const aHealth = healthReports[a.id]?.score || 0
|
||||||
|
const bHealth = healthReports[b.id]?.score || 0
|
||||||
|
return mult * (aHealth - bHealth)
|
||||||
|
case 'expiry':
|
||||||
|
const aExp = a.expiration_date ? new Date(a.expiration_date).getTime() : Infinity
|
||||||
|
const bExp = b.expiration_date ? new Date(b.expiration_date).getTime() : Infinity
|
||||||
|
return mult * (aExp - bExp)
|
||||||
|
default: return 0
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return filtered
|
||||||
|
}, [domains, filter, sortField, sortDirection, healthReports])
|
||||||
|
|
||||||
|
const handleSortWatch = useCallback((field: typeof sortField) => {
|
||||||
|
if (sortField === field) {
|
||||||
|
setSortDirection(d => d === 'asc' ? 'desc' : 'asc')
|
||||||
|
} else {
|
||||||
|
setSortField(field)
|
||||||
|
setSortDirection('asc')
|
||||||
|
}
|
||||||
|
}, [sortField])
|
||||||
|
|
||||||
// Handlers
|
// Handlers
|
||||||
const handleAdd = useCallback(async (e: React.FormEvent) => {
|
const handleAdd = useCallback(async (e: React.FormEvent) => {
|
||||||
@ -395,6 +429,28 @@ export default function WatchlistPage() {
|
|||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="space-y-px bg-white/[0.04] border border-white/[0.08]">
|
<div className="space-y-px bg-white/[0.04] border border-white/[0.08]">
|
||||||
|
{/* Desktop Table Header */}
|
||||||
|
<div className="hidden lg:grid grid-cols-[1fr_80px_90px_90px_60px_100px] gap-4 px-3 py-2 text-[10px] font-mono text-white/40 uppercase tracking-wider border-b border-white/[0.08]">
|
||||||
|
<button onClick={() => handleSortWatch('domain')} className="flex items-center gap-1 hover:text-white/60 text-left">
|
||||||
|
Domain
|
||||||
|
{sortField === 'domain' && (sortDirection === 'asc' ? <ChevronUp className="w-3 h-3" /> : <ChevronDown className="w-3 h-3" />)}
|
||||||
|
</button>
|
||||||
|
<button onClick={() => handleSortWatch('status')} className="flex items-center gap-1 justify-center hover:text-white/60">
|
||||||
|
Status
|
||||||
|
{sortField === 'status' && (sortDirection === 'asc' ? <ChevronUp className="w-3 h-3" /> : <ChevronDown className="w-3 h-3" />)}
|
||||||
|
</button>
|
||||||
|
<button onClick={() => handleSortWatch('health')} className="flex items-center gap-1 justify-center hover:text-white/60">
|
||||||
|
Health
|
||||||
|
{sortField === 'health' && (sortDirection === 'asc' ? <ChevronUp className="w-3 h-3" /> : <ChevronDown className="w-3 h-3" />)}
|
||||||
|
</button>
|
||||||
|
<button onClick={() => handleSortWatch('expiry')} className="flex items-center gap-1 justify-center hover:text-white/60">
|
||||||
|
Expiry
|
||||||
|
{sortField === 'expiry' && (sortDirection === 'asc' ? <ChevronUp className="w-3 h-3" /> : <ChevronDown className="w-3 h-3" />)}
|
||||||
|
</button>
|
||||||
|
<div className="text-center">Alert</div>
|
||||||
|
<div className="text-right">Actions</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
{filteredDomains.map((domain) => {
|
{filteredDomains.map((domain) => {
|
||||||
const health = healthReports[domain.id]
|
const health = healthReports[domain.id]
|
||||||
const healthStatus = health?.status || 'unknown'
|
const healthStatus = health?.status || 'unknown'
|
||||||
@ -510,7 +566,7 @@ export default function WatchlistPage() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Desktop Row */}
|
{/* Desktop Row */}
|
||||||
<div className="hidden lg:flex items-center justify-between p-3 gap-4 group">
|
<div className="hidden lg:grid grid-cols-[1fr_80px_90px_90px_60px_100px] gap-4 items-center p-3 group">
|
||||||
<div className="flex items-center gap-3 min-w-0 flex-1">
|
<div className="flex items-center gap-3 min-w-0 flex-1">
|
||||||
<div className={clsx(
|
<div className={clsx(
|
||||||
"w-8 h-8 flex items-center justify-center border shrink-0",
|
"w-8 h-8 flex items-center justify-center border shrink-0",
|
||||||
|
|||||||
Reference in New Issue
Block a user