Add Dashboard and Universal Search plugins with modern UI
- Dashboard: Stats cards, quick actions, recent activity - Universal Search: Nexus API integration, category filters - Plugin components with proper styling - Inline CSS for consistent rendering
This commit is contained in:
parent
3c5c7f2ed1
commit
b1ca77c749
|
|
@ -1,158 +1,154 @@
|
|||
import { useEffect, useState } from 'react'
|
||||
import { invoke } from '@tauri-apps/api/tauri'
|
||||
import { useState } from 'react'
|
||||
import {
|
||||
Zap,
|
||||
Layers,
|
||||
Search,
|
||||
LayoutDashboard,
|
||||
Puzzle,
|
||||
Settings,
|
||||
Database,
|
||||
Search,
|
||||
Target,
|
||||
TrendingUp,
|
||||
Clock,
|
||||
Activity
|
||||
Calculator,
|
||||
Zap
|
||||
} from 'lucide-react'
|
||||
import { Link, useLocation } from 'react-router-dom'
|
||||
|
||||
export default function Dashboard() {
|
||||
const [stats, setStats] = useState({
|
||||
activePlugins: 0,
|
||||
totalPlugins: 0,
|
||||
overlayVisible: false,
|
||||
const [stats] = useState({
|
||||
pedPerHour: 0,
|
||||
totalLoot: 0,
|
||||
sessionTime: '00:00',
|
||||
activePlugins: 3
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
loadStats()
|
||||
}, [])
|
||||
|
||||
const loadStats = async () => {
|
||||
const plugins = await invoke<Array<{ active: boolean }>>('get_plugins')
|
||||
setStats({
|
||||
activePlugins: plugins.filter(p => p.active).length,
|
||||
totalPlugins: plugins.length,
|
||||
overlayVisible: false,
|
||||
})
|
||||
}
|
||||
|
||||
const quickActions = [
|
||||
{
|
||||
icon: Search,
|
||||
label: 'Universal Search',
|
||||
shortcut: 'Ctrl+Shift+F',
|
||||
onClick: () => {}
|
||||
},
|
||||
{
|
||||
icon: Layers,
|
||||
label: 'Toggle Overlay',
|
||||
shortcut: 'Ctrl+Shift+U',
|
||||
onClick: () => invoke('toggle_overlay')
|
||||
},
|
||||
{
|
||||
icon: Target,
|
||||
label: 'Loot Tracker',
|
||||
shortcut: 'Ctrl+Shift+L',
|
||||
onClick: () => {}
|
||||
},
|
||||
{
|
||||
icon: TrendingUp,
|
||||
label: 'Price Check',
|
||||
shortcut: 'Ctrl+Shift+C',
|
||||
onClick: () => {}
|
||||
},
|
||||
{ icon: <Calculator className="w-5 h-5" />, label: 'Calculator', color: '#fbbf24' },
|
||||
{ icon: <Target className="w-5 h-5" />, label: 'Loot', color: '#10b981' },
|
||||
{ icon: <TrendingUp className="w-5 h-5" />, label: 'Skills', color: '#3b82f6' },
|
||||
{ icon: <Search className="w-5 h-5" />, label: 'Search', color: '#6366f1' },
|
||||
{ icon: <Zap className="w-5 h-5" />, label: 'DPP', color: '#8b5cf6' },
|
||||
{ icon: <Settings className="w-5 h-5" />, label: 'Settings', color: '#64748b' },
|
||||
]
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<h2 className="text-2xl font-bold text-white">Dashboard</h2>
|
||||
<p className="text-text-muted mt-1">Welcome to EU-Utility V3</p>
|
||||
</div>
|
||||
<div className="p-8 animate-fade-in">
|
||||
<!-- Header -->
|
||||
<div className="mb-8">
|
||||
<h1 className="text-4xl font-bold text-white mb-2">Dashboard</h1>
|
||||
<p style={{ color: 'rgba(255, 255, 255, 0.5)' }}>
|
||||
Welcome to EU-Utility V3. Your session is active.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Stats */}
|
||||
<div className="grid grid-cols-4 gap-4">
|
||||
<div className="bg-surface rounded-xl p-6 border border-border">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<p className="text-text-muted text-sm">Active Plugins</p>
|
||||
<p className="text-3xl font-bold text-white mt-1">{stats.activePlugins}</p>
|
||||
</div>
|
||||
<div className="w-12 h-12 rounded-xl bg-primary/10 flex items-center justify-center">
|
||||
<Zap className="w-6 h-6 text-primary" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="bg-surface rounded-xl p-6 border border-border">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<p className="text-text-muted text-sm">Total Plugins</p>
|
||||
<p className="text-3xl font-bold text-white mt-1">{stats.totalPlugins}</p>
|
||||
</div>
|
||||
<div className="w-12 h-12 rounded-xl bg-secondary/10 flex items-center justify-center">
|
||||
<Layers className="w-6 h-6 text-secondary" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="bg-surface rounded-xl p-6 border border-border">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<p className="text-text-muted text-sm">Session Time</p>
|
||||
<p className="text-3xl font-bold text-white mt-1">00:42:15</p>
|
||||
</div>
|
||||
<div className="w-12 h-12 rounded-xl bg-accent/10 flex items-center justify-center">
|
||||
<Clock className="w-6 h-6 text-accent" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="bg-surface rounded-xl p-6 border border-border">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<p className="text-text-muted text-sm">System Status</p>
|
||||
<p className="text-3xl font-bold text-accent mt-1">Online</p>
|
||||
</div>
|
||||
<div className="w-12 h-12 rounded-xl bg-accent/10 flex items-center justify-center">
|
||||
<Activity className="w-6 h-6 text-accent" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Quick Actions */}
|
||||
<div className="bg-surface rounded-xl p-6 border border-border">
|
||||
<h3 className="text-lg font-semibold text-white mb-4">Quick Actions</h3>
|
||||
<div className="grid grid-cols-4 gap-4">
|
||||
{quickActions.map((action) => {
|
||||
const Icon = action.icon
|
||||
return (
|
||||
<button
|
||||
key={action.label}
|
||||
onClick={action.onClick}
|
||||
className="p-4 bg-surface-light hover:bg-surface-light/80 rounded-lg border border-border transition-colors text-left group"
|
||||
<!-- Stats Grid -->
|
||||
<div className="grid grid-cols-4 gap-6 mb-8">
|
||||
{[
|
||||
{ label: 'PED/hour', value: stats.pedPerHour.toFixed(2), icon: <Zap />, color: '#fbbf24' },
|
||||
{ label: 'Total Loot', value: `${stats.totalLoot} PED`, icon: <Target />, color: '#10b981' },
|
||||
{ label: 'Session', value: stats.sessionTime, icon: <LayoutDashboard />, color: '#3b82f6' },
|
||||
{ label: 'Active', value: stats.activePlugins.toString(), icon: <Puzzle />, color: '#8b5cf6' },
|
||||
].map((stat, i) => (
|
||||
<div
|
||||
key={i}
|
||||
className="p-6 rounded-2xl"
|
||||
style={{
|
||||
background: 'rgba(255, 255, 255, 0.03)',
|
||||
border: '1px solid rgba(255, 255, 255, 0.06)'
|
||||
}}
|
||||
>
|
||||
<div className="flex items-start justify-between mb-4">
|
||||
<div
|
||||
className="w-12 h-12 rounded-xl flex items-center justify-center"
|
||||
style={{ background: `${stat.color}20`, color: stat.color }}
|
||||
>
|
||||
<div className="w-10 h-10 rounded-lg bg-primary/10 flex items-center justify-center mb-3 group-hover:bg-primary/20 transition-colors">
|
||||
<Icon className="w-5 h-5 text-primary" />
|
||||
</div>
|
||||
<p className="font-medium text-white">{action.label}</p>
|
||||
<p className="text-xs text-text-muted mt-1">{action.shortcut}</p>
|
||||
</button>
|
||||
)
|
||||
})}
|
||||
{stat.icon}
|
||||
</div>
|
||||
</div>
|
||||
<p style={{ color: 'rgba(255, 255, 255, 0.5)' }} className="text-sm mb-1">
|
||||
{stat.label}
|
||||
</p>
|
||||
<p className="text-3xl font-bold text-white">{stat.value}</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<!-- Quick Actions -->
|
||||
<div className="mb-8">
|
||||
<h2
|
||||
className="text-sm font-semibold uppercase tracking-wider mb-4"
|
||||
style={{ color: 'rgba(255, 255, 255, 0.4)' }}
|
||||
>
|
||||
Quick Actions
|
||||
</h2>
|
||||
|
||||
<div className="grid grid-cols-6 gap-4">
|
||||
{quickActions.map((action, i) => (
|
||||
<button
|
||||
key={i}
|
||||
className="flex flex-col items-center gap-3 p-6 rounded-2xl transition-all hover:scale-105"
|
||||
style={{
|
||||
background: 'rgba(255, 255, 255, 0.03)',
|
||||
border: '1px solid rgba(255, 255, 255, 0.06)'
|
||||
}}
|
||||
>
|
||||
<div
|
||||
className="w-14 h-14 rounded-xl flex items-center justify-center"
|
||||
style={{ background: `${action.color}15`, color: action.color }}
|
||||
>
|
||||
{action.icon}
|
||||
</div>
|
||||
<span style={{ color: 'rgba(255, 255, 255, 0.7)' }} className="text-sm">
|
||||
{action.label}
|
||||
</span>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Recent Activity */}
|
||||
<div className="bg-surface rounded-xl p-6 border border-border">
|
||||
<h3 className="text-lg font-semibold text-white mb-4">Recent Activity</h3>
|
||||
<!-- Recent Activity -->
|
||||
<div
|
||||
className="rounded-2xl p-6"
|
||||
style={{
|
||||
background: 'rgba(255, 255, 255, 0.02)',
|
||||
border: '1px solid rgba(255, 255, 255, 0.05)'
|
||||
}}
|
||||
>
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<h2 className="text-lg font-semibold text-white">Recent Activity</h2>
|
||||
<button
|
||||
className="text-sm px-4 py-2 rounded-lg"
|
||||
style={{
|
||||
background: 'rgba(99, 102, 241, 0.2)',
|
||||
color: '#818cf8'
|
||||
}}
|
||||
>
|
||||
View All
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="space-y-3">
|
||||
<div className="flex items-center gap-4 p-3 bg-surface-light rounded-lg">
|
||||
<div className="w-8 h-8 rounded-full bg-primary/10 flex items-center justify-center">
|
||||
<Zap className="w-4 h-4 text-primary" />
|
||||
{[
|
||||
{ time: '2m ago', event: 'EU-Utility V3 started', type: 'system' },
|
||||
{ time: 'Just now', event: 'Ready for gameplay', type: 'ready' },
|
||||
].map((item, i) => (
|
||||
<div
|
||||
key={i}
|
||||
className="flex items-center gap-4 p-4 rounded-xl"
|
||||
style={{ background: 'rgba(255, 255, 255, 0.03)' }}
|
||||
>
|
||||
<div
|
||||
className="w-2 h-2 rounded-full"
|
||||
style={{
|
||||
background: item.type === 'system' ? '#3b82f6' : '#10b981'
|
||||
}}
|
||||
/>
|
||||
<div className="flex-1">
|
||||
<p className="text-white text-sm">{item.event}</p>
|
||||
</div>
|
||||
<span style={{ color: 'rgba(255, 255, 255, 0.4)' }} className="text-xs">
|
||||
{item.time}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex-1">
|
||||
<p className="text-sm text-white">System initialized</p>
|
||||
<p className="text-xs text-text-muted">Just now</p>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,251 @@
|
|||
import { useState, useEffect } from 'react'
|
||||
import { invoke } from '@tauri-apps/api/tauri'
|
||||
import {
|
||||
LayoutDashboard,
|
||||
Clock,
|
||||
Target,
|
||||
TrendingUp,
|
||||
Zap,
|
||||
BarChart3,
|
||||
Calculator,
|
||||
Settings,
|
||||
Search,
|
||||
Layers
|
||||
} from 'lucide-react'
|
||||
|
||||
interface StatCardProps {
|
||||
title: string
|
||||
value: string
|
||||
subtitle?: string
|
||||
icon: React.ReactNode
|
||||
color: string
|
||||
}
|
||||
|
||||
function StatCard({ title, value, subtitle, icon, color }: StatCardProps) {
|
||||
return (
|
||||
<div
|
||||
className="p-4 rounded-xl transition-all hover:scale-[1.02]"
|
||||
style={{
|
||||
background: 'rgba(255, 255, 255, 0.03)',
|
||||
border: '1px solid rgba(255, 255, 255, 0.06)'
|
||||
}}
|
||||
>
|
||||
<div className="flex items-start justify-between">
|
||||
<div>
|
||||
<p style={{ color: 'rgba(255, 255, 255, 0.5)' }} className="text-xs mb-1">{title}</p>
|
||||
<p className="text-2xl font-bold text-white">{value}</p>
|
||||
{subtitle && (
|
||||
<p style={{ color: 'rgba(255, 255, 255, 0.4)' }} className="text-xs mt-1">{subtitle}</p>
|
||||
)}
|
||||
</div>
|
||||
<div
|
||||
className="w-10 h-10 rounded-lg flex items-center justify-center"
|
||||
style={{ background: `${color}20`, color }}
|
||||
>
|
||||
{icon}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
interface QuickActionProps {
|
||||
icon: React.ReactNode
|
||||
label: string
|
||||
onClick: () => void
|
||||
color?: string
|
||||
}
|
||||
|
||||
function QuickAction({ icon, label, onClick, color = '#6366f1' }: QuickActionProps) {
|
||||
return (
|
||||
<button
|
||||
onClick={onClick}
|
||||
className="flex flex-col items-center gap-2 p-4 rounded-xl transition-all hover:scale-105 active:scale-95"
|
||||
style={{
|
||||
background: 'rgba(255, 255, 255, 0.03)',
|
||||
border: '1px solid rgba(255, 255, 255, 0.06)'
|
||||
}}
|
||||
>
|
||||
<div
|
||||
className="w-12 h-12 rounded-xl flex items-center justify-center"
|
||||
style={{ background: `${color}15`, color }}
|
||||
>
|
||||
{icon}
|
||||
</div>
|
||||
<span style={{ color: 'rgba(255, 255, 255, 0.7)' }} className="text-xs">{label}</span>
|
||||
</button>
|
||||
)
|
||||
}
|
||||
|
||||
export default function Dashboard() {
|
||||
const [sessionTime, setSessionTime] = useState(0)
|
||||
const [stats, setStats] = useState({
|
||||
pedPerHour: 0,
|
||||
totalLoot: 0,
|
||||
skillsGained: 0,
|
||||
globals: 0
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
const timer = setInterval(() => {
|
||||
setSessionTime(prev => prev + 1)
|
||||
}, 1000)
|
||||
|
||||
return () => clearInterval(timer)
|
||||
}, [])
|
||||
|
||||
const formatTime = (seconds: number) => {
|
||||
const h = Math.floor(seconds / 3600)
|
||||
const m = Math.floor((seconds % 3600) / 60)
|
||||
return `${h}h ${m}m`
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="p-6 animate-fade-in">
|
||||
{/* Header */}
|
||||
<div className="mb-8">
|
||||
<h1 className="text-3xl font-bold text-white mb-2">Dashboard</h1>
|
||||
<p style={{ color: 'rgba(255, 255, 255, 0.5)' }}>
|
||||
Welcome back to EU-Utility. Session time: {formatTime(sessionTime)}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Stats Grid */}
|
||||
<div className="grid grid-cols-4 gap-4 mb-8">
|
||||
<StatCard
|
||||
title="PED/hour"
|
||||
value={stats.pedPerHour.toFixed(2)}
|
||||
subtitle="Current rate"
|
||||
icon={<Zap className="w-5 h-5" />}
|
||||
color="#fbbf24"
|
||||
/>
|
||||
<StatCard
|
||||
title="Total Loot"
|
||||
value={`${stats.totalLoot.toFixed(2)} PED`}
|
||||
subtitle="This session"
|
||||
icon={<Target className="w-5 h-5" />}
|
||||
color="#10b981"
|
||||
/>
|
||||
<StatCard
|
||||
title="Skills"
|
||||
value={`+${stats.skillsGained}`}
|
||||
subtitle="Points gained"
|
||||
icon={<TrendingUp className="w-5 h-5" />}
|
||||
color="#3b82f6"
|
||||
/>
|
||||
<StatCard
|
||||
title="Globals"
|
||||
value={stats.globals.toString()}
|
||||
subtitle="This session"
|
||||
icon={<BarChart3 className="w-5 h-5" />}
|
||||
color="#8b5cf6"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Quick Actions */}
|
||||
<div className="mb-8">
|
||||
<h2
|
||||
className="text-sm font-semibold uppercase tracking-wider mb-4"
|
||||
style={{ color: 'rgba(255, 255, 255, 0.4)' }}
|
||||
>
|
||||
Quick Actions
|
||||
</h2>
|
||||
<div className="grid grid-cols-6 gap-3">
|
||||
<QuickAction
|
||||
icon={<Calculator className="w-6 h-6" />}
|
||||
label="Calculator"
|
||||
onClick={() => invoke('open_calculator')}
|
||||
color="#fbbf24"
|
||||
/>
|
||||
<QuickAction
|
||||
icon={<Target className="w-6 h-6" />}
|
||||
label="Loot"
|
||||
onClick={() => invoke('open_loot_tracker')}
|
||||
color="#10b981"
|
||||
/>
|
||||
<QuickAction
|
||||
icon={<TrendingUp className="w-6 h-6" />}
|
||||
label="Skills"
|
||||
onClick={() => invoke('open_skill_tracker')}
|
||||
color="#3b82f6"
|
||||
/>
|
||||
<QuickAction
|
||||
icon={<Search className="w-6 h-6" />}
|
||||
label="Search"
|
||||
onClick={() => invoke('open_universal_search')}
|
||||
color="#6366f1"
|
||||
/>
|
||||
<QuickAction
|
||||
icon={<BarChart3 className="w-6 h-6" />}
|
||||
label="DPP Calc"
|
||||
onClick={() => invoke('open_dpp_calculator')}
|
||||
color="#8b5cf6"
|
||||
/>
|
||||
<QuickAction
|
||||
icon={<Settings className="w-6 h-6" />}
|
||||
label="Settings"
|
||||
onClick={() => invoke('show_settings_window')}
|
||||
color="#64748b"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Recent Activity */}
|
||||
<div
|
||||
className="rounded-xl p-6"
|
||||
style={{
|
||||
background: 'rgba(255, 255, 255, 0.02)',
|
||||
border: '1px solid rgba(255, 255, 255, 0.05)'
|
||||
}}
|
||||
>
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<h2
|
||||
className="text-lg font-semibold"
|
||||
style={{ color: 'rgba(255, 255, 255, 0.9)' }}
|
||||
>
|
||||
Recent Activity
|
||||
</h2>
|
||||
<button
|
||||
className="text-sm px-3 py-1.5 rounded-lg transition-colors"
|
||||
style={{
|
||||
background: 'rgba(99, 102, 241, 0.2)',
|
||||
color: '#818cf8'
|
||||
}}
|
||||
>
|
||||
View All
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="space-y-3">
|
||||
{[
|
||||
{ time: '2m ago', event: 'Looted 15.23 PED from Atrox', type: 'loot' },
|
||||
{ time: '5m ago', event: 'Skill increase: Ranged Combat', type: 'skill' },
|
||||
{ time: '12m ago', event: 'Global! 123 PED from Proteron', type: 'global' },
|
||||
].map((item, i) => (
|
||||
<div
|
||||
key={i}
|
||||
className="flex items-center gap-4 p-3 rounded-lg"
|
||||
style={{ background: 'rgba(255, 255, 255, 0.03)' }}
|
||||
>
|
||||
<div
|
||||
className="w-2 h-2 rounded-full"
|
||||
style={{
|
||||
background: item.type === 'loot' ? '#10b981' :
|
||||
item.type === 'skill' ? '#3b82f6' : '#fbbf24'
|
||||
}}
|
||||
/>
|
||||
<div className="flex-1">
|
||||
<p style={{ color: 'rgba(255, 255, 255, 0.8)' }} className="text-sm">
|
||||
{item.event}
|
||||
</p>
|
||||
</div>
|
||||
<span style={{ color: 'rgba(255, 255, 255, 0.4)' }} className="text-xs">
|
||||
{item.time}
|
||||
</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,226 @@
|
|||
import { useState, useEffect, useCallback } from 'react'
|
||||
import { invoke } from '@tauri-apps/api/tauri'
|
||||
import { Search, Database, TrendingUp, Package, ExternalLink } from 'lucide-react'
|
||||
|
||||
interface SearchResult {
|
||||
id: string
|
||||
name: string
|
||||
type: string
|
||||
category: string
|
||||
value?: number
|
||||
markup?: number
|
||||
icon?: string
|
||||
}
|
||||
|
||||
export default function UniversalSearch() {
|
||||
const [query, setQuery] = useState('')
|
||||
const [results, setResults] = useState<SearchResult[]>([])
|
||||
const [loading, setLoading] = useState(false)
|
||||
const [selectedCategory, setSelectedCategory] = useState('all')
|
||||
|
||||
const categories = [
|
||||
{ id: 'all', label: 'All', icon: <Database className="w-4 h-4" /> },
|
||||
{ id: 'items', label: 'Items', icon: <Package className="w-4 h-4" /> },
|
||||
{ id: 'mobs', label: 'Creatures', icon: <TrendingUp className="w-4 h-4" /> },
|
||||
{ id: 'locations', label: 'Locations', icon: <ExternalLink className="w-4 h-4" /> },
|
||||
]
|
||||
|
||||
const performSearch = useCallback(async (searchQuery: string) => {
|
||||
if (!searchQuery.trim()) {
|
||||
setResults([])
|
||||
return
|
||||
}
|
||||
|
||||
setLoading(true)
|
||||
try {
|
||||
const searchResults = await invoke<SearchResult[]>('search_nexus', {
|
||||
query: searchQuery,
|
||||
entityType: selectedCategory === 'all' ? undefined : selectedCategory,
|
||||
limit: 20
|
||||
})
|
||||
setResults(searchResults)
|
||||
} catch (error) {
|
||||
console.error('Search failed:', error)
|
||||
setResults([])
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
}, [selectedCategory])
|
||||
|
||||
useEffect(() => {
|
||||
const timeoutId = setTimeout(() => {
|
||||
performSearch(query)
|
||||
}, 300)
|
||||
|
||||
return () => clearTimeout(timeoutId)
|
||||
}, [query, performSearch])
|
||||
|
||||
const getTypeColor = (type: string) => {
|
||||
const colors: Record<string, string> = {
|
||||
weapon: '#ef4444',
|
||||
armor: '#3b82f6',
|
||||
tool: '#10b981',
|
||||
material: '#f59e0b',
|
||||
creature: '#8b5cf6',
|
||||
location: '#06b6d4'
|
||||
}
|
||||
return colors[type] || '#64748b'
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="p-6 animate-fade-in h-full flex flex-col">
|
||||
{/* Header */}
|
||||
<div className="mb-6">
|
||||
<h1 className="text-3xl font-bold text-white mb-2">Universal Search</h1>
|
||||
<p style={{ color: 'rgba(255, 255, 255, 0.5)' }}>
|
||||
Search items, creatures, locations and more
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Search Bar */}
|
||||
<div className="mb-6">
|
||||
<div
|
||||
className="relative"
|
||||
style={{
|
||||
background: 'rgba(255, 255, 255, 0.05)',
|
||||
border: '1px solid rgba(255, 255, 255, 0.1)',
|
||||
borderRadius: '12px'
|
||||
}}
|
||||
>
|
||||
<Search
|
||||
className="absolute left-4 top-1/2 -translate-y-1/2 w-5 h-5"
|
||||
style={{ color: 'rgba(255, 255, 255, 0.4)' }}
|
||||
/>
|
||||
<input
|
||||
type="text"
|
||||
value={query}
|
||||
onChange={(e) => setQuery(e.target.value)}
|
||||
placeholder="Search for weapons, armor, creatures..."
|
||||
className="w-full pl-12 pr-4 py-4 bg-transparent border-none text-white text-lg focus:outline-none"
|
||||
style={{ fontSize: '16px' }}
|
||||
/>
|
||||
|
||||
{loading && (
|
||||
<div className="absolute right-4 top-1/2 -translate-y-1/2">
|
||||
<div
|
||||
className="w-5 h-5 border-2 rounded-full animate-spin"
|
||||
style={{
|
||||
borderColor: 'rgba(99, 102, 241, 0.3)',
|
||||
borderTopColor: '#6366f1'
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Category Filters */}
|
||||
<div className="flex gap-2 mb-6">
|
||||
{categories.map(cat => (
|
||||
<button
|
||||
key={cat.id}
|
||||
onClick={() => setSelectedCategory(cat.id)}
|
||||
className="flex items-center gap-2 px-4 py-2 rounded-lg transition-all"
|
||||
style={{
|
||||
background: selectedCategory === cat.id
|
||||
? 'rgba(99, 102, 241, 0.2)'
|
||||
: 'rgba(255, 255, 255, 0.05)',
|
||||
border: `1px solid ${selectedCategory === cat.id ? 'rgba(99, 102, 241, 0.3)' : 'rgba(255, 255, 255, 0.1)'}`,
|
||||
color: selectedCategory === cat.id ? '#818cf8' : 'rgba(255, 255, 255, 0.6)'
|
||||
}}
|
||||
>
|
||||
{cat.icon}
|
||||
<span className="text-sm font-medium">{cat.label}</span>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Results */}
|
||||
<div className="flex-1 overflow-y-auto">
|
||||
{results.length > 0 ? (
|
||||
<div className="space-y-2">
|
||||
{results.map((result) => (
|
||||
<div
|
||||
key={result.id}
|
||||
className="flex items-center gap-4 p-4 rounded-xl transition-all hover:bg-white/5 cursor-pointer group"
|
||||
style={{ background: 'rgba(255, 255, 255, 0.03)' }}
|
||||
>
|
||||
{/* Icon/Type Badge */}
|
||||
<div
|
||||
className="w-12 h-12 rounded-lg flex items-center justify-center text-lg font-bold"
|
||||
style={{
|
||||
background: `${getTypeColor(result.type)}20`,
|
||||
color: getTypeColor(result.type)
|
||||
}}
|
||||
>
|
||||
{result.name.charAt(0).toUpperCase()}
|
||||
</div>
|
||||
|
||||
{/* Content */}
|
||||
<div className="flex-1">
|
||||
<h3 className="text-white font-medium mb-1">{result.name}</h3>
|
||||
<div className="flex items-center gap-2">
|
||||
<span
|
||||
className="text-xs px-2 py-0.5 rounded"
|
||||
style={{
|
||||
background: `${getTypeColor(result.type)}20`,
|
||||
color: getTypeColor(result.type)
|
||||
}}
|
||||
>
|
||||
{result.type}
|
||||
</span>
|
||||
<span style={{ color: 'rgba(255, 255, 255, 0.4)' }} className="text-xs">
|
||||
{result.category}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Value/Markup */}
|
||||
{(result.value || result.markup) && (
|
||||
<div className="text-right">
|
||||
{result.value && (
|
||||
<p className="text-white font-medium">
|
||||
{result.value.toFixed(2)} PED
|
||||
</p>
|
||||
)}
|
||||
{result.markup && (
|
||||
<p
|
||||
className="text-sm"
|
||||
style={{ color: result.markup > 100 ? '#10b981' : '#ef4444' }}
|
||||
>
|
||||
{result.markup.toFixed(1)}% MU
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Arrow */}
|
||||
<ExternalLink
|
||||
className="w-5 h-5 opacity-0 group-hover:opacity-100 transition-opacity"
|
||||
style={{ color: 'rgba(255, 255, 255, 0.4)' }}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
) : query ? (
|
||||
<div className="text-center py-12">
|
||||
<Search className="w-16 h-16 mx-auto mb-4" style={{ color: 'rgba(255, 255, 255, 0.1)' }} />
|
||||
<p style={{ color: 'rgba(255, 255, 255, 0.4)' }} className="text-lg mb-2">
|
||||
No results found
|
||||
</p>
|
||||
<p style={{ color: 'rgba(255, 255, 255, 0.3)' }} className="text-sm">
|
||||
Try searching for "weapons", "armor", or "creatures"
|
||||
</p>
|
||||
</div>
|
||||
) : (
|
||||
<div className="text-center py-12">
|
||||
<Database className="w-16 h-16 mx-auto mb-4" style={{ color: 'rgba(255, 255, 255, 0.1)' }} />
|
||||
<p style={{ color: 'rgba(255, 255, 255, 0.4)' }} className="text-lg">
|
||||
Start typing to search the Nexus database
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Loading…
Reference in New Issue