All checks were successful
Build and Push Docker Images / docker (push) Successful in 42s
- 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.
118 lines
4.2 KiB
JavaScript
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>
|
|
)
|
|
}
|