refactor: update theme management and enhance UI components

- Refactored theme imports in `preferences.ts` and `ThemeSelector.tsx` to use centralized `theme-config`.
- Added new CSS variables for special cards in `globals.css` to improve theme consistency.
- Enhanced `Header` and `TaskCard` components with theme dropdown functionality for better user experience.
- Updated `ThemeProvider` to support cycling through dark themes, improving theme selection flexibility.
- Cleaned up unused imports and streamlined component structures for better maintainability.
This commit is contained in:
Julien Froidefond
2025-09-29 08:51:20 +02:00
parent 641a009b34
commit 8d657872c0
14 changed files with 554 additions and 180 deletions

View File

@@ -0,0 +1,8 @@
'use client';
import { useKeyboardShortcuts } from '@/hooks/useKeyboardShortcuts';
export function KeyboardShortcuts() {
useKeyboardShortcuts();
return null; // Ce composant ne rend rien, il gère juste les raccourcis
}

View File

@@ -1,23 +1,20 @@
'use client';
import { useTheme } from '@/contexts/ThemeContext';
import { Theme } from '@/lib/types';
import { Theme } from '@/lib/theme-config';
import { Button } from '@/components/ui/Button';
import { THEME_CONFIG, getThemeMetadata } from '@/lib/theme-config';
const themes: { id: Theme; name: string; description: string }[] = [
{ id: 'dark', name: 'Dark', description: 'Thème sombre par défaut' },
{ id: 'dracula', name: 'Dracula', description: 'Thème Dracula coloré' },
{ id: 'monokai', name: 'Monokai', description: 'Thème Monokai vibrant' },
{ id: 'nord', name: 'Nord', description: 'Thème Nord minimaliste' },
{ id: 'gruvbox', name: 'Gruvbox', description: 'Thème Gruvbox chaleureux' },
{ id: 'tokyo_night', name: 'Tokyo Night', description: 'Thème Tokyo Night moderne' },
{ id: 'catppuccin', name: 'Catppuccin', description: 'Thème Catppuccin pastel' },
{ id: 'rose_pine', name: 'Rose Pine', description: 'Thème Rose Pine élégant' },
{ id: 'one_dark', name: 'One Dark', description: 'Thème One Dark populaire' },
{ id: 'material', name: 'Material', description: 'Thème Material Design' },
{ id: 'solarized', name: 'Solarized', description: 'Thème Solarized scientifique' },
];
// Génération des thèmes à partir de la configuration centralisée
const themes: { id: Theme; name: string; description: string }[] = THEME_CONFIG.allThemes.map(themeId => {
const metadata = getThemeMetadata(themeId);
return {
id: themeId,
name: metadata.name,
description: metadata.description
};
});
// Composant pour l'aperçu du thème
function ThemePreview({ themeId, isSelected }: { themeId: Theme; isSelected: boolean }) {

View File

@@ -2,12 +2,8 @@
import { Task } from '@/lib/types';
import { Card } from '@/components/ui/Card';
import { TagDisplay } from '@/components/ui/TagDisplay';
import { formatDateShort } from '@/lib/date-utils';
import { TaskCard } from '@/components/ui';
import { useTasksContext } from '@/contexts/TasksContext';
import { getPriorityConfig, getStatusLabel } from '@/lib/status-config';
import { TaskPriority } from '@/lib/types';
import Link from 'next/link';
interface RecentTasksProps {
@@ -45,46 +41,44 @@ export function RecentTasks({ tasks }: RecentTasksProps) {
) : (
<div className="space-y-3">
{recentTasks.map((task) => (
<TaskCard
key={task.id}
title={task.title}
description={task.description}
status={getStatusLabel(task.status)}
priority={task.priority ? (() => {
try {
return getPriorityConfig(task.priority as TaskPriority).label;
} catch {
return task.priority;
}
})() : undefined}
tags={task.tags && task.tags.length > 0 ? [
<TagDisplay
key="tags"
tags={task.tags.slice(0, 2)}
availableTags={availableTags}
size="sm"
maxTags={2}
showColors={true}
/>,
...(task.tags.length > 2 ? [
<span key="more" className="text-xs text-[var(--muted-foreground)]">
+{task.tags.length - 2}
</span>
] : [])
] : undefined}
metadata={formatDateShort(task.updatedAt)}
actions={
<Link
href={`/kanban?taskId=${task.id}`}
className="p-1 rounded hover:bg-[var(--muted)]/50 transition-colors"
title="Ouvrir dans le Kanban"
>
<svg className="w-3 h-3 text-[var(--primary)] hover:text-[var(--primary)]/80" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<div key={task.id} className="relative group">
<TaskCard
variant="detailed"
source={task.source || 'manual'}
title={task.title}
description={task.description}
status={task.status}
priority={task.priority as 'low' | 'medium' | 'high' | 'urgent'}
tags={task.tags || []}
dueDate={task.dueDate}
completedAt={task.completedAt}
jiraKey={task.jiraKey}
jiraProject={task.jiraProject}
jiraType={task.jiraType}
tfsPullRequestId={task.tfsPullRequestId}
tfsProject={task.tfsProject}
tfsRepository={task.tfsRepository}
availableTags={availableTags}
fontSize="small"
onTitleClick={() => {
// Navigation vers le kanban avec la tâche sélectionnée
window.location.href = `/kanban?taskId=${task.id}`;
}}
/>
{/* Overlay avec lien vers le kanban */}
<Link
href={`/kanban?taskId=${task.id}`}
className="absolute inset-0 opacity-0 group-hover:opacity-100 transition-opacity duration-200 bg-[var(--primary)]/5 rounded-lg flex items-center justify-center"
title="Ouvrir dans le Kanban"
>
<div className="bg-[var(--primary)]/20 backdrop-blur-sm rounded-full p-2 border border-[var(--primary)]/30">
<svg className="w-4 h-4 text-[var(--primary)]" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" />
</svg>
</Link>
}
/>
</div>
</Link>
</div>
))}
</div>
)}

View File

@@ -9,41 +9,21 @@ import { Input } from '@/components/ui/Input';
import { StyledCard } from '@/components/ui/StyledCard';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/Card';
import { StatCard, ProgressBar, ActionCard, TaskCard, MetricCard, ToggleButton, SearchInput, ControlPanel, ControlSection, ControlGroup, FilterSummary, FilterChip, ColumnHeader, EmptyState, DropZone } from '@/components/ui';
import { ThemeSelector } from '@/components/ThemeSelector';
import { Header } from '@/components/ui/Header';
export function UIShowcaseClient() {
const [inputValue, setInputValue] = useState('');
return (
<div className="min-h-screen bg-[var(--background)] p-8">
<div className="max-w-6xl mx-auto space-y-16">
{/* Header */}
<div className="text-center space-y-6">
<h1 className="text-4xl font-mono font-bold text-[var(--foreground)]">
🎨 UI Components Showcase
</h1>
<p className="text-lg text-[var(--muted-foreground)]">
Démonstration de tous les composants UI disponibles
</p>
</div>
<div className="min-h-screen bg-[var(--background)]">
{/* Header avec navigation et dropdown de thèmes */}
<Header
title="🎨 UI Showcase"
subtitle="Démonstration de tous les composants UI disponibles"
/>
<div className="max-w-6xl mx-auto p-8 space-y-16">
{/* Theme Selector */}
<section className="space-y-8">
<div className="text-center">
<h2 className="text-2xl font-mono font-semibold text-[var(--foreground)] mb-6">
🎨 Sélecteur de Thèmes
</h2>
<p className="text-[var(--muted-foreground)] mb-8">
Changez de thème pour voir comment tous les composants s'adaptent
</p>
</div>
<div className="max-w-4xl mx-auto">
<div className="bg-[var(--card)]/30 border border-[var(--border)]/50 rounded-lg p-6 backdrop-blur-sm">
<ThemeSelector />
</div>
</div>
</section>
{/* Buttons Section */}
<section className="space-y-8">
@@ -530,43 +510,145 @@ export function UIShowcaseClient() {
{/* Task Cards */}
<div className="space-y-6">
<h3 className="text-lg font-medium text-[var(--foreground)]">Task Cards</h3>
<div className="space-y-3 max-w-2xl">
<TaskCard
title="Refactoriser le système de thèmes"
description="Améliorer la gestion des thèmes avec CSS variables"
status="En Cours"
priority="Haute"
tags={[
<Badge key="tag1" variant="primary" className="text-xs">Frontend</Badge>,
<Badge key="tag2" variant="success" className="text-xs">UI</Badge>
]}
metadata="Il y a 2h"
actions={
<button className="p-1 rounded hover:bg-[var(--muted)]/50 transition-colors">
<svg className="w-3 h-3 text-[var(--primary)]" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" />
</svg>
</button>
}
/>
<TaskCard
title="Créer les composants UI réutilisables"
description="Développer une bibliothèque de composants cohérente"
status="Terminé"
priority="Moyenne"
tags={[
<Badge key="tag1" variant="accent" className="text-xs">Design</Badge>
]}
metadata="Hier"
actions={
<button className="p-1 rounded hover:bg-[var(--muted)]/50 transition-colors">
<svg className="w-3 h-3 text-[var(--primary)]" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" />
</svg>
</button>
}
/>
<div className="space-y-4 max-w-2xl">
{/* Task Card Compacte */}
<div className="space-y-2">
<div className="text-xs font-mono text-[var(--muted-foreground)] bg-[var(--card)] px-2 py-1 rounded">
variant="compact" - Vue compacte
</div>
<TaskCard
variant="compact"
title="🎨 Refactoriser le système de thèmes"
description="Améliorer la gestion des thèmes avec CSS variables"
status="in_progress"
priority="high"
tags={["Frontend", "UI"]}
source="manual"
fontSize="medium"
availableTags={[
{ id: "1", name: "Frontend", color: "#3b82f6" },
{ id: "2", name: "UI", color: "#10b981" }
]}
/>
</div>
{/* Task Card Détaillée */}
<div className="space-y-2">
<div className="text-xs font-mono text-[var(--muted-foreground)] bg-[var(--card)] px-2 py-1 rounded">
variant="detailed" - Vue détaillée
</div>
<TaskCard
variant="detailed"
title="🚀 Créer les composants UI réutilisables"
description="Développer une bibliothèque de composants cohérente et maintenable"
status="done"
priority="medium"
tags={["Design", "Components"]}
source="manual"
dueDate={new Date(Date.now() + 7 * 24 * 60 * 60 * 1000)}
fontSize="medium"
availableTags={[
{ id: "1", name: "Design", color: "#f59e0b" },
{ id: "2", name: "Components", color: "#8b5cf6" }
]}
/>
</div>
{/* Task Card Jira */}
<div className="space-y-2">
<div className="text-xs font-mono text-[var(--muted-foreground)] bg-[var(--card)] px-2 py-1 rounded">
source="jira" - Tâche Jira avec styles spéciaux
</div>
<TaskCard
variant="detailed"
title="🐛 Corriger le bug de synchronisation"
description="Résoudre le problème de synchronisation des données entre les services"
status="todo"
priority="urgent"
tags={["Bug", "Backend"]}
source="jira"
jiraKey="PROJ-123"
jiraProject="PROJ"
jiraType="Bug"
dueDate={new Date(Date.now() + 3 * 24 * 60 * 60 * 1000)}
fontSize="medium"
availableTags={[
{ id: "1", name: "Bug", color: "#ef4444" },
{ id: "2", name: "Backend", color: "#6b7280" }
]}
jiraConfig={{ baseUrl: "https://company.atlassian.net" }}
/>
</div>
{/* Task Card TFS */}
<div className="space-y-2">
<div className="text-xs font-mono text-[var(--muted-foreground)] bg-[var(--card)] px-2 py-1 rounded">
source="tfs" - Tâche TFS avec styles spéciaux
</div>
<TaskCard
variant="detailed"
title="📦 Implémenter la nouvelle API"
description="Développer l'API REST pour la gestion des utilisateurs"
status="review"
priority="high"
tags={["API", "Backend"]}
source="tfs"
tfsPullRequestId={456}
tfsProject="MyProject"
tfsRepository="backend-api"
fontSize="medium"
availableTags={[
{ id: "1", name: "API", color: "#06b6d4" },
{ id: "2", name: "Backend", color: "#6b7280" }
]}
tfsConfig={{ organizationUrl: "https://dev.azure.com/company" }}
/>
</div>
{/* Task Card avec différents statuts */}
<div className="space-y-2">
<div className="text-xs font-mono text-[var(--muted-foreground)] bg-[var(--card)] px-2 py-1 rounded">
Statuts spéciaux - freeze, archived, cancelled
</div>
<div className="grid grid-cols-1 md:grid-cols-3 gap-3">
<TaskCard
variant="compact"
title="❄️ Tâche gelée"
status="freeze"
priority="low"
tags={["Maintenance"]}
source="manual"
fontSize="small"
availableTags={[
{ id: "1", name: "Maintenance", color: "#64748b" }
]}
/>
<TaskCard
variant="compact"
title="📁 Tâche archivée"
status="archived"
priority="medium"
tags={["Archive"]}
source="manual"
fontSize="small"
availableTags={[
{ id: "1", name: "Archive", color: "#6b7280" }
]}
/>
<TaskCard
variant="compact"
title="❌ Tâche annulée"
status="cancelled"
priority="high"
tags={["Cancelled"]}
source="manual"
fontSize="small"
availableTags={[
{ id: "1", name: "Cancelled", color: "#ef4444" }
]}
/>
</div>
</div>
</div>
</div>

View File

@@ -5,6 +5,8 @@ import { useJiraConfig } from '@/contexts/JiraConfigContext';
import { usePathname } from 'next/navigation';
import Link from 'next/link';
import { useState } from 'react';
import { Theme } from '@/lib/theme-config';
import { THEME_CONFIG, getThemeMetadata } from '@/lib/theme-config';
interface HeaderProps {
title?: string;
@@ -13,10 +15,22 @@ interface HeaderProps {
}
export function Header({ title = "TowerControl", subtitle = "Task Management", syncing = false }: HeaderProps) {
const { theme, toggleTheme, userPreferredTheme } = useTheme();
const { theme, setTheme } = useTheme();
const { isConfigured: isJiraConfigured, config: jiraConfig } = useJiraConfig();
const pathname = usePathname();
const [mobileMenuOpen, setMobileMenuOpen] = useState(false);
const [themeDropdownOpen, setThemeDropdownOpen] = useState(false);
// Liste des thèmes disponibles avec leurs labels et icônes
const themes: { value: Theme; label: string; icon: string }[] = THEME_CONFIG.allThemes.map(themeValue => {
const metadata = getThemeMetadata(themeValue);
return {
value: themeValue,
label: metadata.name,
icon: metadata.icon
};
});
// Fonction pour déterminer si un lien est actif
const isActiveLink = (href: string) => {
@@ -83,24 +97,53 @@ export function Header({ title = "TowerControl", subtitle = "Task Management", s
{/* Controls mobile/tablette */}
<div className="flex items-center gap-2 flex-shrink-0">
{/* Theme Toggle */}
<button
onClick={toggleTheme}
className="text-[var(--muted-foreground)] hover:text-[var(--primary)] transition-colors p-2 rounded-md hover:bg-[var(--card-hover)]"
title={theme === 'light' ? `Switch to ${userPreferredTheme} theme` : 'Switch to light theme'}
>
{theme === 'light' ? (
// Soleil pour le thème clair
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 3v1m0 16v1m9-9h-1M4 12H3m15.364 6.364l-.707-.707M6.343 6.343l-.707-.707m12.728 0l-.707.707M6.343 17.657l-.707.707M16 12a4 4 0 11-8 0 4 4 0 018 0z" />
</svg>
) : (
// Lune pour tous les thèmes sombres
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M20.354 15.354A9 9 0 018.646 3.646 9.003 9.003 0 0012 21a9.003 9.003 0 008.354-5.646z" />
</svg>
{/* Theme Dropdown */}
<div className="relative">
<button
onClick={() => setThemeDropdownOpen(!themeDropdownOpen)}
className="text-[var(--muted-foreground)] hover:text-[var(--primary)] transition-colors p-2 rounded-md hover:bg-[var(--card-hover)]"
title="Select theme"
>
{themes.find(t => t.value === theme)?.icon || '🎨'}
</button>
{themeDropdownOpen && (
<>
{/* Backdrop */}
<div
className="fixed inset-0 z-[200]"
onClick={() => setThemeDropdownOpen(false)}
/>
{/* Dropdown */}
<div className="absolute right-0 top-full mt-2 w-48 bg-[var(--card)] border border-[var(--border)] rounded-lg shadow-lg z-[201] overflow-hidden">
<div className="py-2">
{themes.map((themeOption) => (
<button
key={themeOption.value}
onClick={() => {
setTheme(themeOption.value);
setThemeDropdownOpen(false);
}}
className={`w-full text-left px-4 py-2 text-sm transition-colors flex items-center gap-3 ${
theme === themeOption.value
? 'text-[var(--primary)] bg-[var(--primary)]/10'
: 'text-[var(--muted-foreground)] hover:text-[var(--foreground)] hover:bg-[var(--card-hover)]'
}`}
>
<span className="text-base">{themeOption.icon}</span>
<span className="font-mono">{themeOption.label}</span>
{theme === themeOption.value && (
<svg className="w-4 h-4 ml-auto" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M5 13l4 4L19 7" />
</svg>
)}
</button>
))}
</div>
</div>
</>
)}
</button>
</div>
{/* Menu burger */}
<button
@@ -154,22 +197,53 @@ export function Header({ title = "TowerControl", subtitle = "Task Management", s
</Link>
))}
{/* Theme Toggle desktop */}
<button
onClick={toggleTheme}
className="text-[var(--muted-foreground)] hover:text-[var(--primary)] transition-colors p-1 rounded-md hover:bg-[var(--card-hover)]"
title={`Switch to ${theme === 'dark' ? 'light' : 'dark'} theme`}
>
{theme === 'dark' ? (
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 3v1m0 16v1m9-9h-1M4 12H3m15.364 6.364l-.707-.707M6.343 6.343l-.707-.707m12.728 0l-.707.707M6.343 17.657l-.707.707M16 12a4 4 0 11-8 0 4 4 0 018 0z" />
</svg>
) : (
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M20.354 15.354A9 9 0 018.646 3.646 9.003 9.003 0 0012 21a9.003 9.003 0 008.354-5.646z" />
</svg>
{/* Theme Dropdown desktop */}
<div className="relative">
<button
onClick={() => setThemeDropdownOpen(!themeDropdownOpen)}
className="text-[var(--muted-foreground)] hover:text-[var(--primary)] transition-colors p-1 rounded-md hover:bg-[var(--card-hover)]"
title="Select theme"
>
{themes.find(t => t.value === theme)?.icon || '🎨'}
</button>
{themeDropdownOpen && (
<>
{/* Backdrop */}
<div
className="fixed inset-0 z-[200]"
onClick={() => setThemeDropdownOpen(false)}
/>
{/* Dropdown */}
<div className="absolute right-0 top-full mt-2 w-48 bg-[var(--card)] border border-[var(--border)] rounded-lg shadow-lg z-[201] overflow-hidden">
<div className="py-2">
{themes.map((themeOption) => (
<button
key={themeOption.value}
onClick={() => {
setTheme(themeOption.value);
setThemeDropdownOpen(false);
}}
className={`w-full text-left px-4 py-2 text-sm transition-colors flex items-center gap-3 ${
theme === themeOption.value
? 'text-[var(--primary)] bg-[var(--primary)]/10'
: 'text-[var(--muted-foreground)] hover:text-[var(--foreground)] hover:bg-[var(--card-hover)]'
}`}
>
<span className="text-base">{themeOption.icon}</span>
<span className="font-mono">{themeOption.label}</span>
{theme === themeOption.value && (
<svg className="w-4 h-4 ml-auto" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M5 13l4 4L19 7" />
</svg>
)}
</button>
))}
</div>
</div>
</>
)}
</button>
</div>
</nav>
</div>

View File

@@ -2,6 +2,7 @@ import { HTMLAttributes, forwardRef, useState, useEffect, useRef } from 'react';
import { cn } from '@/lib/utils';
import { Card } from './Card';
import { Badge } from './Badge';
import { formatDateForDisplay } from '@/lib/date-utils';
interface TaskCardProps extends HTMLAttributes<HTMLDivElement> {
// Variants
@@ -171,18 +172,16 @@ const TaskCard = forwardRef<HTMLDivElement, TaskCardProps>(
const getSourceStyles = () => {
if (source === 'jira') {
return {
border: '2px solid rgba(0, 130, 201, 0.8)',
borderLeft: '6px solid #0052CC',
background: 'linear-gradient(135deg, rgba(0, 130, 201, 0.3) 0%, rgba(0, 130, 201, 0.2) 100%)',
boxShadow: '0 4px 12px rgba(0, 130, 201, 0.4), inset 0 1px 0 rgba(255, 255, 255, 0.3)',
backgroundColor: 'var(--jira-card, #dbeafe)',
borderLeft: '3px solid var(--jira-border, #3b82f6)',
color: 'var(--jira-text, #1e40af)',
};
}
if (source === 'tfs') {
return {
border: '2px solid rgba(255, 165, 0, 0.8)',
borderLeft: '6px solid #FF8C00',
background: 'linear-gradient(135deg, rgba(255, 165, 0, 0.3) 0%, rgba(255, 165, 0, 0.2) 100%)',
boxShadow: '0 4px 12px rgba(255, 165, 0, 0.4), inset 0 1px 0 rgba(255, 255, 255, 0.3)',
backgroundColor: 'var(--tfs-card, #fed7aa)',
borderLeft: '3px solid var(--tfs-border, #f59e0b)',
color: 'var(--tfs-text, #92400e)',
};
}
return {};
@@ -224,20 +223,17 @@ const TaskCard = forwardRef<HTMLDivElement, TaskCardProps>(
return (
<Card
ref={ref}
style={sourceStyles}
className={cn(
'hover:border-[var(--primary)]/30 hover:shadow-lg hover:shadow-[var(--primary)]/10 transition-all duration-300 cursor-pointer group',
isDragging && 'opacity-50 rotate-3 scale-105',
(status === 'done' || status === 'archived') && 'opacity-60',
status === 'freeze' && 'opacity-60 bg-gradient-to-br from-transparent via-[var(--muted)]/10 to-transparent bg-[length:4px_4px] bg-[linear-gradient(45deg,transparent_25%,var(--border)_25%,var(--border)_50%,transparent_50%,transparent_75%,var(--border)_75%,var(--border))]',
source === 'jira' && 'bg-blue-50 dark:bg-blue-900/20',
source === 'tfs' && 'bg-orange-50 dark:bg-orange-900/20',
isPending && 'opacity-70 pointer-events-none',
className
)}
{...props}
>
<div className="p-2">
<div className="p-2" style={sourceStyles}>
<div className="flex items-center gap-2">
{/* Emojis */}
{displayEmojis.length > 0 && (
@@ -318,20 +314,17 @@ const TaskCard = forwardRef<HTMLDivElement, TaskCardProps>(
return (
<Card
ref={ref}
style={sourceStyles}
className={cn(
'hover:border-[var(--primary)]/30 hover:shadow-lg hover:shadow-[var(--primary)]/10 transition-all duration-300 cursor-pointer group',
isDragging && 'opacity-50 rotate-3 scale-105',
(status === 'done' || status === 'archived') && 'opacity-60',
status === 'freeze' && 'opacity-60 bg-gradient-to-br from-transparent via-[var(--muted)]/10 to-transparent bg-[length:4px_4px] bg-[linear-gradient(45deg,transparent_25%,var(--border)_25%,var(--border)_50%,transparent_50%,transparent_75%,var(--border)_75%,var(--border))]',
source === 'jira' && 'bg-blue-50 dark:bg-blue-900/20',
source === 'tfs' && 'bg-orange-50 dark:bg-orange-900/20',
isPending && 'opacity-70 pointer-events-none',
className
)}
{...props}
>
<div className={`px-3 pt-3 ${(dueDate || (source && source !== 'manual') || completedAt) ? 'pb-3' : 'pb-0'}`}>
<div className={`px-3 pt-3 ${(dueDate || (source && source !== 'manual') || completedAt) ? 'pb-3' : 'pb-0'}`} style={sourceStyles}>
{/* Header */}
<div className="flex items-start gap-2 mb-2">
{/* Emojis */}
@@ -452,7 +445,7 @@ const TaskCard = forwardRef<HTMLDivElement, TaskCardProps>(
{dueDate ? (
<span className="flex items-center gap-1 text-[var(--muted-foreground)] font-mono">
<span className="text-[var(--primary)]"></span>
{dueDate.toLocaleDateString()}
{formatDateForDisplay(dueDate, 'DISPLAY_MEDIUM')}
</span>
) : (
<div></div>

View File

@@ -24,5 +24,4 @@ export { DropZone } from './DropZone';
// Composants existants
export { Card, CardHeader, CardTitle, CardContent, CardFooter } from './Card';
export { Header } from './Header';
export { FontSizeToggle } from './FontSizeToggle';