Files
towercontrol/components/ui/TagDisplay.tsx
Julien Froidefond c5a7d16425 feat: complete tag management and UI integration
- Marked multiple tasks as completed in TODO.md related to tag management features.
- Replaced manual tag input with `TagInput` component in `CreateTaskForm`, `EditTaskForm`, and `QuickAddTask` for better UX.
- Updated `TaskCard` to display tags using `TagDisplay` with color support.
- Enhanced `TasksService` to manage task-tag relationships with CRUD operations.
- Integrated tag management into the global context for better accessibility across components.
2025-09-14 16:44:22 +02:00

158 lines
4.7 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
'use client';
import { Tag } from '@/lib/types';
import { Badge } from './Badge';
interface TagDisplayProps {
tags: string[];
availableTags?: Tag[]; // Tags avec couleurs depuis la DB
size?: 'sm' | 'md' | 'lg';
maxTags?: number;
showColors?: boolean;
onClick?: (tagName: string) => void;
className?: string;
}
export function TagDisplay({
tags,
availableTags = [],
size = 'sm',
maxTags,
showColors = true,
onClick,
className = ""
}: TagDisplayProps) {
if (!tags || tags.length === 0) {
return null;
}
const displayTags = maxTags ? tags.slice(0, maxTags) : tags;
const remainingCount = maxTags && tags.length > maxTags ? tags.length - maxTags : 0;
const getTagColor = (tagName: string): string => {
if (!showColors) return '#6b7280'; // gray-500
const tag = availableTags.find(t => t.name === tagName);
return tag?.color || '#6b7280';
};
const sizeClasses = {
sm: 'text-xs px-2 py-0.5',
md: 'text-sm px-2 py-1',
lg: 'text-base px-3 py-1.5'
};
return (
<div className={`flex flex-wrap gap-1 ${className}`}>
{displayTags.map((tagName, index) => {
const color = getTagColor(tagName);
return (
<div
key={index}
onClick={() => onClick?.(tagName)}
className={`inline-flex items-center gap-1.5 rounded-full border transition-colors ${sizeClasses[size]} ${
onClick ? 'cursor-pointer hover:opacity-80' : ''
}`}
style={{
backgroundColor: showColors ? `${color}20` : undefined,
borderColor: showColors ? `${color}60` : undefined,
color: showColors ? color : undefined
}}
>
{showColors && (
<div
className="w-2 h-2 rounded-full"
style={{ backgroundColor: color }}
/>
)}
<span className="font-medium">{tagName}</span>
</div>
);
})}
{remainingCount > 0 && (
<Badge variant="default" size="sm">
+{remainingCount}
</Badge>
)}
</div>
);
}
interface TagListProps {
tags: Tag[];
onTagClick?: (tag: Tag) => void;
onTagEdit?: (tag: Tag) => void;
onTagDelete?: (tag: Tag) => void;
className?: string;
}
/**
* Composant pour afficher une liste complète de tags avec actions
*/
export function TagList({
tags,
onTagClick,
onTagEdit,
onTagDelete,
className = ""
}: TagListProps) {
if (!tags || tags.length === 0) {
return (
<div className="text-center py-8 text-slate-400">
<div className="text-4xl mb-2">🏷</div>
<p className="text-sm">Aucun tag trouvé</p>
</div>
);
}
return (
<div className={`space-y-2 ${className}`}>
{tags.map((tag) => (
<div
key={tag.id}
className="flex items-center justify-between p-3 bg-slate-800 rounded-lg border border-slate-700 hover:border-slate-600 transition-colors group"
>
<div
className="flex items-center gap-3 flex-1 cursor-pointer"
onClick={() => onTagClick?.(tag)}
>
<div
className="w-4 h-4 rounded-full"
style={{ backgroundColor: tag.color }}
/>
<span className="text-slate-200 font-medium">{tag.name}</span>
</div>
<div className="flex items-center gap-2 opacity-0 group-hover:opacity-100 transition-opacity">
{onTagEdit && (
<button
onClick={() => onTagEdit(tag)}
className="p-1 text-slate-400 hover:text-cyan-400 transition-colors"
title="Éditer le tag"
>
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z" />
</svg>
</button>
)}
{onTagDelete && (
<button
onClick={() => onTagDelete(tag)}
className="p-1 text-slate-400 hover:text-red-400 transition-colors"
title="Supprimer le tag"
>
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" />
</svg>
</button>
)}
</div>
</div>
))}
</div>
);
}