EU-Utility-V3/src/pages/Plugins.tsx

213 lines
7.7 KiB
TypeScript

import { useState } from 'react'
import {
Puzzle,
Settings2,
Search,
Grid,
List
} from 'lucide-react'
import { useAppStore } from '../store/appStore'
import type { Plugin } from '../store/appStore'
export default function Plugins() {
const { plugins, activatePlugin, deactivatePlugin } = useAppStore()
const [searchQuery, setSearchQuery] = useState('')
const [viewMode, setViewMode] = useState<'grid' | 'list'>('grid')
const [_selectedPlugin, __setSelectedPlugin] = useState<Plugin | null>(null)
const filteredPlugins = plugins.filter(p =>
p.name.toLowerCase().includes(searchQuery.toLowerCase()) ||
p.description.toLowerCase().includes(searchQuery.toLowerCase())
)
const activePlugins = filteredPlugins.filter(p => p.active)
const inactivePlugins = filteredPlugins.filter(p => !p.active)
return (
<div className="space-y-6">
<div className="flex items-center justify-between">
<div>
<h2 className="text-2xl font-bold text-white">Plugins</h2>
<p className="text-text-muted mt-1">Manage your EU-Utility plugins</p>
</div>
<div className="flex items-center gap-3">
<div className="relative">
<Search className="w-5 h-5 text-text-muted absolute left-3 top-1/2 -translate-y-1/2" />
<input
type="text"
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
placeholder="Search plugins..."
className="pl-10 pr-4 py-2 bg-surface border border-border rounded-lg text-sm focus:outline-none focus:border-primary w-64"
/>
</div>
<div className="flex items-center gap-1 bg-surface rounded-lg p-1 border border-border">
<button
onClick={() => setViewMode('grid')}
className={`p-2 rounded ${viewMode === 'grid' ? 'bg-surface-light text-white' : 'text-text-muted'}`}
>
<Grid className="w-4 h-4" />
</button>
<button
onClick={() => setViewMode('list')}
className={`p-2 rounded ${viewMode === 'list' ? 'bg-surface-light text-white' : 'text-text-muted'}`}
>
<List className="w-4 h-4" />
</button>
</div>
</div>
</div>
{/* Active Plugins */}
{activePlugins.length > 0 && (
<div>
<h3 className="text-sm font-semibold text-text-muted uppercase tracking-wider mb-3">Active Plugins ({activePlugins.length})</h3>
<div className={viewMode === 'grid' ? 'grid grid-cols-3 gap-4' : 'space-y-2'}>
{activePlugins.map((plugin) => (
<PluginCard
key={plugin.id}
plugin={plugin}
onActivate={() => activatePlugin(plugin.id)}
onDeactivate={() => deactivatePlugin(plugin.id)}
onClick={() => _setSelectedPlugin(plugin)}
viewMode={viewMode}
/>
))}
</div>
</div>
)}
{/* Inactive Plugins */}
{inactivePlugins.length > 0 && (
<div>
<h3 className="text-sm font-semibold text-text-muted uppercase tracking-wider mb-3">Inactive Plugins ({inactivePlugins.length})</h3>
<div className={viewMode === 'grid' ? 'grid grid-cols-3 gap-4' : 'space-y-2'}>
{inactivePlugins.map((plugin) => (
<PluginCard
key={plugin.id}
plugin={plugin}
onActivate={() => activatePlugin(plugin.id)}
onDeactivate={() => deactivatePlugin(plugin.id)}
onClick={() => _setSelectedPlugin(plugin)}
viewMode={viewMode}
/>
))}
</div>
</div>
)}
{/* Empty State */}
{filteredPlugins.length === 0 && (
<div className="text-center py-12">
<Puzzle className="w-16 h-16 text-text-muted mx-auto mb-4" />
<p className="text-white font-medium">No plugins found</p>
<p className="text-text-muted text-sm mt-1">Try adjusting your search</p>
</div>
)}
</div>
)
}
interface PluginCardProps {
plugin: Plugin
onActivate: () => void
onDeactivate: () => void
onClick: () => void
viewMode: 'grid' | 'list'
}
function PluginCard({ plugin, onActivate, onDeactivate, onClick, viewMode }: PluginCardProps) {
if (viewMode === 'list') {
return (
<div className="flex items-center gap-4 p-4 bg-surface rounded-lg border border-border hover:border-primary/30 transition-colors">
<div className="w-12 h-12 rounded-xl bg-primary/10 flex items-center justify-center flex-shrink-0">
<Puzzle className="w-6 h-6 text-primary" />
</div>
<div className="flex-1 min-w-0">
<h4 className="font-semibold text-white truncate">{plugin.name}</h4>
<p className="text-sm text-text-muted truncate">{plugin.description}</p>
</div>
<div className="flex items-center gap-3">
<span className="text-xs text-text-muted">v{plugin.version}</span>
{plugin.active ? (
<button
onClick={onDeactivate}
className="px-3 py-1.5 bg-accent text-white text-sm font-medium rounded-lg hover:bg-accent-hover transition-colors"
>
Deactivate
</button>
) : (
<button
onClick={onActivate}
className="px-3 py-1.5 bg-primary text-white text-sm font-medium rounded-lg hover:bg-primary-hover transition-colors"
>
Activate
</button>
)}
</div>
</div>
)
}
return (
<div className="bg-surface rounded-xl p-5 border border-border hover:border-primary/30 transition-colors group">
<div className="flex items-start justify-between mb-4">
<div className="w-12 h-12 rounded-xl bg-primary/10 flex items-center justify-center">
<Puzzle className="w-6 h-6 text-primary" />
</div>
{plugin.active ? (
<div className="flex items-center gap-1.5 px-2 py-1 bg-accent/10 rounded-full">
<div className="w-2 h-2 rounded-full bg-accent" />
<span className="text-xs font-medium text-accent">Active</span>
</div>
) : (
<div className="flex items-center gap-1.5 px-2 py-1 bg-surface-light rounded-full">
<div className="w-2 h-2 rounded-full bg-text-muted" />
<span className="text-xs font-medium text-text-muted">Inactive</span>
</div>
)}
</div>
<h4 className="font-semibold text-white mb-1">{plugin.name}</h4>
<p className="text-sm text-text-muted mb-4 line-clamp-2">{plugin.description}</p>
<div className="flex items-center justify-between text-xs text-text-muted mb-4">
<span>{plugin.author}</span>
<span>v{plugin.version}</span>
</div>
<div className="flex gap-2">
{plugin.active ? (
<>
<button
onClick={onDeactivate}
className="flex-1 px-3 py-2 bg-surface-light hover:bg-surface-light/80 text-text rounded-lg text-sm font-medium transition-colors border border-border"
>
Deactivate
</button>
<button
onClick={onClick}
className="px-3 py-2 bg-primary hover:bg-primary-hover text-white rounded-lg transition-colors"
>
<Settings2 className="w-4 h-4" />
</button>
</>
) : (
<button
onClick={onActivate}
className="w-full px-3 py-2 bg-primary hover:bg-primary-hover text-white rounded-lg text-sm font-medium transition-colors"
>
Activate
</button>
)}
</div>
</div>
)
}