Files
ScriptVPS/vps-monitor/frontend/src/components/DashboardToolbar.jsx
jeanotx32 f37b639226
All checks were successful
Build and Push Docker Images / docker (push) Successful in 42s
feat: enhance VPS management interface with search, filter, and toast notifications
- Added a DashboardToolbar component for searching, filtering, and sorting VPS instances.
- Implemented a ConfirmDialog component for destructive actions confirmation.
- Introduced a Modal component for displaying modals with focus management.
- Created a Toast component for displaying notifications with different types (success, error, info).
- Refactored VpsCard to utilize new Metric component for displaying CPU and RAM usage.
- Improved user experience with local storage for collapsed state in VpsCard.
- Added clipboard utility functions for copying text and downloading content.
- Enhanced CSS styles for better dark mode support and animations.
- Updated various UI controls for consistency and improved accessibility.
2026-08-01 01:22:45 -04:00

118 lines
4.2 KiB
JavaScript

import { Search, X, ArrowDownUp } from 'lucide-react'
import { tagColor } from './TagInput'
export const STATUS_FILTERS = [
{ value: 'all', label: 'Tous' },
{ value: 'online', label: 'En ligne' },
{ value: 'offline', label: 'Hors ligne' },
{ value: 'issues', label: 'Problèmes' },
]
export const SORT_OPTIONS = [
{ value: 'name', label: 'Nom' },
{ value: 'status', label: 'État' },
{ value: 'cpu', label: 'CPU' },
{ value: 'ram', label: 'RAM' },
{ value: 'containers', label: 'Conteneurs' },
]
export default function DashboardToolbar({
search, onSearchChange, searchRef,
status, onStatusChange,
tags, activeTags, onToggleTag,
sort, onSortChange,
shown, total,
}) {
const filtering = search.trim() !== '' || status !== 'all' || activeTags.length > 0
return (
<div className="mb-6 flex flex-col gap-3">
<div className="flex flex-wrap items-center gap-2">
{/* Recherche */}
<div className="relative flex-1 min-w-[200px]">
<Search size={14} className="absolute left-3 top-1/2 -translate-y-1/2 text-gray-500 pointer-events-none" />
<input
ref={searchRef}
type="search"
value={search}
onChange={e => onSearchChange(e.target.value)}
placeholder="Rechercher un VPS, un conteneur, une image… ( / )"
aria-label="Rechercher un VPS ou un conteneur"
className="w-full bg-gray-900 border border-gray-800 rounded-xl pl-9 pr-9 py-2 text-sm placeholder-gray-600 focus:outline-none focus:border-indigo-500 transition-colors"
/>
{search && (
<button
onClick={() => onSearchChange('')}
aria-label="Effacer la recherche"
className="absolute right-2.5 top-1/2 -translate-y-1/2 p-0.5 rounded text-gray-500 hover:text-gray-200 transition-colors"
>
<X size={14} />
</button>
)}
</div>
{/* Filtre d'état */}
<div className="flex rounded-xl border border-gray-800 bg-gray-900 overflow-hidden" role="group" aria-label="Filtrer par état">
{STATUS_FILTERS.map(opt => (
<button
key={opt.value}
onClick={() => onStatusChange(opt.value)}
aria-pressed={status === opt.value}
className={`px-3 py-2 text-xs font-medium transition-colors ${
status === opt.value
? 'bg-indigo-600 text-white'
: 'text-gray-400 hover:bg-gray-800 hover:text-gray-200'
}`}
>
{opt.label}
</button>
))}
</div>
{/* Tri */}
<div className="flex items-center gap-1.5 px-2.5 py-2 rounded-xl bg-gray-900 border border-gray-800">
<ArrowDownUp size={13} className="text-gray-500 flex-shrink-0" />
<select
value={sort}
onChange={e => onSortChange(e.target.value)}
aria-label="Trier les VPS"
className="bg-transparent text-xs text-gray-300 outline-none cursor-pointer"
>
{SORT_OPTIONS.map(o => (
<option key={o.value} value={o.value}>Trier : {o.label}</option>
))}
</select>
</div>
</div>
{/* Tags + compteur */}
{(tags.length > 0 || filtering) && (
<div className="flex flex-wrap items-center gap-1.5">
{tags.map(tag => {
const active = activeTags.includes(tag)
return (
<button
key={tag}
onClick={() => onToggleTag(tag)}
aria-pressed={active}
className={`px-2 py-0.5 rounded-full text-xs font-medium border transition-all ${
active
? tagColor(tag)
: 'bg-gray-900 border-gray-800 text-gray-500 hover:text-gray-300 hover:border-gray-700'
}`}
>
{tag}
</button>
)
})}
{filtering && (
<span className="text-xs text-gray-500 ml-auto">
{shown} / {total} VPS affiché{shown !== 1 ? 's' : ''}
</span>
)}
</div>
)}
</div>
)
}