chore: Unused package and entire files

This commit is contained in:
Julien Froidefond
2025-09-23 08:21:53 +02:00
parent 723a44df32
commit e36291a552
16 changed files with 544 additions and 3350 deletions

View File

@@ -1,146 +0,0 @@
'use client';
import { Card, CardHeader, CardContent } from '@/components/ui/Card';
import { Badge } from '@/components/ui/Badge';
interface CategoryData {
count: number;
percentage: number;
color: string;
icon: string;
}
interface CategoryBreakdownProps {
categoryData: { [categoryName: string]: CategoryData };
totalActivities: number;
}
export function CategoryBreakdown({ categoryData, totalActivities }: CategoryBreakdownProps) {
const categories = Object.entries(categoryData)
.filter(([, data]) => data.count > 0)
.sort((a, b) => b[1].count - a[1].count);
if (categories.length === 0) {
return (
<Card>
<CardHeader>
<h3 className="text-lg font-semibold">📊 Répartition par catégorie</h3>
</CardHeader>
<CardContent>
<p className="text-center text-[var(--muted-foreground)]">
Aucune activité à catégoriser
</p>
</CardContent>
</Card>
);
}
return (
<Card>
<CardHeader>
<h3 className="text-lg font-semibold">📊 Répartition par catégorie</h3>
<p className="text-sm text-[var(--muted-foreground)]">
Analyse automatique de vos {totalActivities} activités
</p>
</CardHeader>
<CardContent className="space-y-6">
{/* Légende des catégories */}
<div className="flex flex-wrap gap-3 justify-center">
{categories.map(([categoryName, data]) => (
<div
key={categoryName}
className="flex items-center gap-2 bg-[var(--card)] border border-[var(--border)] rounded-lg px-3 py-2 hover:border-[var(--primary)]/50 transition-colors"
>
<div
className="w-3 h-3 rounded-full"
style={{ backgroundColor: data.color }}
/>
<span className="text-sm font-medium text-[var(--foreground)]">
{data.icon} {categoryName}
</span>
<Badge className="bg-[var(--primary)]/10 text-[var(--primary)] text-xs">
{data.count}
</Badge>
</div>
))}
</div>
{/* Barres de progression */}
<div className="space-y-3">
{categories.map(([categoryName, data]) => (
<div key={categoryName} className="space-y-1">
<div className="flex items-center justify-between text-sm">
<span className="flex items-center gap-2">
<span>{data.icon}</span>
<span className="font-medium">{categoryName}</span>
</span>
<span className="text-[var(--muted-foreground)]">
{data.count} ({data.percentage.toFixed(1)}%)
</span>
</div>
<div className="w-full bg-[var(--border)] rounded-full h-2">
<div
className="h-2 rounded-full transition-all duration-500"
style={{
backgroundColor: data.color,
width: `${data.percentage}%`
}}
/>
</div>
</div>
))}
</div>
{/* Insights */}
<div className="bg-[var(--card)] p-4 rounded-lg border border-[var(--border)]">
<h4 className="font-medium mb-2">💡 Insights</h4>
<div className="text-sm text-[var(--muted-foreground)] space-y-1">
{categories.length > 0 && (
<>
<p>
🏆 <strong>{categories[0][0]}</strong> est votre activité principale
({categories[0][1].percentage.toFixed(1)}% de votre temps).
</p>
{categories.length > 1 && (
<p>
📈 Vous avez une bonne diversité avec {categories.length} catégories d&apos;activités.
</p>
)}
{/* Suggestions basées sur la répartition */}
{categories.some(([, data]) => data.percentage > 70) && (
<p>
Forte concentration sur une seule catégorie.
Pensez à diversifier vos activités pour un meilleur équilibre.
</p>
)}
{(() => {
const learningCategory = categories.find(([name]) => name === 'Learning');
return learningCategory && learningCategory[1].percentage > 0 && (
<p>
🎓 Excellent ! Vous consacrez du temps à l&apos;apprentissage
({learningCategory[1].percentage.toFixed(1)}%).
</p>
);
})()}
{(() => {
const devCategory = categories.find(([name]) => name === 'Dev');
return devCategory && devCategory[1].percentage > 50 && (
<p>
💻 Focus développement intense. N&apos;oubliez pas les pauses et la collaboration !
</p>
);
})()}
</>
)}
</div>
</div>
</CardContent>
</Card>
);
}

View File

@@ -1,193 +0,0 @@
'use client';
import type { JiraWeeklyMetrics } from '@/services/jira-summary';
import { Card, CardHeader, CardContent } from '@/components/ui/Card';
import { Badge } from '@/components/ui/Badge';
import { JiraSummaryService } from '@/services/jira-summary';
interface JiraWeeklyMetricsProps {
jiraMetrics: JiraWeeklyMetrics | null;
}
export function JiraWeeklyMetrics({ jiraMetrics }: JiraWeeklyMetricsProps) {
if (!jiraMetrics) {
return (
<Card>
<CardHeader>
<h3 className="text-lg font-semibold">🔗 Contexte business Jira</h3>
</CardHeader>
<CardContent>
<p className="text-center text-[var(--muted-foreground)]">
Configuration Jira non disponible
</p>
</CardContent>
</Card>
);
}
if (jiraMetrics.totalJiraTasks === 0) {
return (
<Card>
<CardHeader>
<h3 className="text-lg font-semibold">🔗 Contexte business Jira</h3>
</CardHeader>
<CardContent>
<p className="text-center text-[var(--muted-foreground)]">
Aucune tâche Jira cette semaine
</p>
</CardContent>
</Card>
);
}
const completionRate = (jiraMetrics.completedJiraTasks / jiraMetrics.totalJiraTasks) * 100;
const insights = JiraSummaryService.generateBusinessInsights(jiraMetrics);
return (
<Card>
<CardHeader>
<h3 className="text-lg font-semibold">🔗 Contexte business Jira</h3>
<p className="text-sm text-[var(--muted-foreground)]">
Impact business et métriques projet
</p>
</CardHeader>
<CardContent className="space-y-6">
{/* Métriques principales */}
<div className="grid grid-cols-2 lg:grid-cols-4 gap-4">
<div className="bg-[var(--card)] p-4 rounded-lg border border-[var(--border)] hover:border-[var(--primary)]/50 transition-colors text-center">
<div className="text-2xl font-bold text-[var(--primary)]">
{jiraMetrics.totalJiraTasks}
</div>
<div className="text-sm text-[var(--muted-foreground)]">Tickets Jira</div>
</div>
<div className="bg-[var(--card)] p-4 rounded-lg border border-[var(--border)] hover:border-[var(--success)]/50 transition-colors text-center">
<div className="text-2xl font-bold text-[var(--success)]">
{completionRate.toFixed(0)}%
</div>
<div className="text-sm text-[var(--muted-foreground)]">Taux completion</div>
</div>
<div className="bg-[var(--card)] p-4 rounded-lg border border-[var(--border)] hover:border-[var(--accent)]/50 transition-colors text-center">
<div className="text-2xl font-bold text-[var(--accent)]">
{jiraMetrics.totalStoryPoints}
</div>
<div className="text-sm text-[var(--muted-foreground)]">Story Points*</div>
</div>
<div className="bg-[var(--card)] p-4 rounded-lg border border-[var(--border)] hover:border-[var(--warning)]/50 transition-colors text-center">
<div className="text-2xl font-bold text-[var(--warning)]">
{jiraMetrics.projectsContributed.length}
</div>
<div className="text-sm text-[var(--muted-foreground)]">Projet(s)</div>
</div>
</div>
{/* Projets contributés */}
{jiraMetrics.projectsContributed.length > 0 && (
<div>
<h4 className="font-medium mb-2">📂 Projets contributés</h4>
<div className="flex flex-wrap gap-2">
{jiraMetrics.projectsContributed.map(project => (
<Badge key={project} className="bg-[var(--primary)]/10 text-[var(--primary)]">
{project}
</Badge>
))}
</div>
</div>
)}
{/* Types de tickets */}
<div>
<h4 className="font-medium mb-3">🎯 Types de tickets</h4>
<div className="space-y-2">
{Object.entries(jiraMetrics.ticketTypes)
.sort(([,a], [,b]) => b - a)
.map(([type, count]) => {
const percentage = (count / jiraMetrics.totalJiraTasks) * 100;
return (
<div key={type} className="flex items-center justify-between">
<span className="text-sm text-[var(--foreground)]">{type}</span>
<div className="flex items-center gap-2">
<div className="w-20 bg-[var(--border)] rounded-full h-2">
<div
className="h-2 bg-[var(--primary)] rounded-full transition-all"
style={{ width: `${percentage}%` }}
/>
</div>
<span className="text-sm text-[var(--muted-foreground)] w-8">
{count}
</span>
</div>
</div>
);
})}
</div>
</div>
{/* Liens vers les tickets */}
<div>
<h4 className="font-medium mb-3">🎫 Tickets traités</h4>
<div className="space-y-2 max-h-40 overflow-y-auto">
{jiraMetrics.jiraLinks.map((link) => (
<div
key={link.key}
className="flex items-center justify-between p-2 rounded border hover:bg-[var(--muted)] transition-colors"
>
<div className="flex-1 min-w-0">
<div className="flex items-center gap-2 mb-1">
<a
href={link.url}
target="_blank"
rel="noopener noreferrer"
className="text-[var(--primary)] hover:underline font-medium text-sm"
>
{link.key}
</a>
<Badge
className={`text-xs ${
link.status === 'done'
? 'bg-[var(--success)]/10 text-[var(--success)]'
: 'bg-[var(--muted)]/50 text-[var(--muted-foreground)]'
}`}
>
{link.status}
</Badge>
</div>
<p className="text-xs text-[var(--muted-foreground)] truncate">
{link.title}
</p>
</div>
<div className="flex items-center gap-2 text-xs text-[var(--muted-foreground)]">
<span>{link.type}</span>
<span>{link.estimatedPoints}pts</span>
</div>
</div>
))}
</div>
</div>
{/* Insights business */}
{insights.length > 0 && (
<div className="bg-[var(--card)] p-4 rounded-lg border border-[var(--border)]">
<h4 className="font-medium mb-2">💡 Insights business</h4>
<div className="text-sm text-[var(--muted-foreground)] space-y-1">
{insights.map((insight, index) => (
<p key={index}>{insight}</p>
))}
</div>
</div>
)}
{/* Note sur les story points */}
<div className="text-xs text-[var(--muted-foreground)] bg-[var(--card)] border border-[var(--border)] p-2 rounded">
<p>
* Story Points estimés automatiquement basés sur le type de ticket
(Epic: 8pts, Story: 3pts, Task: 2pts, Bug: 1pt)
</p>
</div>
</CardContent>
</Card>
);
}

View File

@@ -1,110 +0,0 @@
'use client';
import { useState, useEffect } from 'react';
import { JiraAnalyticsFilters, AvailableFilters } from '@/lib/types';
import { Card, CardHeader, CardContent } from '@/components/ui/Card';
import { FilterSummary } from './filters/FilterSummary';
import { FilterModal } from './filters/FilterModal';
interface AdvancedFiltersPanelProps {
availableFilters: AvailableFilters;
activeFilters: Partial<JiraAnalyticsFilters>;
onFiltersChange: (filters: Partial<JiraAnalyticsFilters>) => void;
className?: string;
}
export default function AdvancedFiltersPanel({
availableFilters,
activeFilters,
onFiltersChange,
className = ''
}: AdvancedFiltersPanelProps) {
const [showModal, setShowModal] = useState(false);
const [isExpanded, setIsExpanded] = useState(false);
// Auto-expand si des filtres sont actifs
useEffect(() => {
const hasActiveFilters = Object.values(activeFilters).some(
filterArray => Array.isArray(filterArray) && filterArray.length > 0
);
if (hasActiveFilters) {
setIsExpanded(true);
}
}, [activeFilters]);
const handleClearAll = () => {
onFiltersChange({});
};
const handleSavePreset = async () => {
try {
// TODO: Implement savePreset method
console.log('Saving preset:', activeFilters);
// TODO: Afficher une notification de succès
} catch (error) {
console.error('Erreur lors de la sauvegarde:', error);
// TODO: Afficher une notification d'erreur
}
};
// Compter le nombre total de filtres actifs
const totalActiveFilters = Object.values(activeFilters).reduce((count, filterArray) => {
return count + (Array.isArray(filterArray) ? filterArray.length : 0);
}, 0);
return (
<Card className={className}>
<CardHeader
className="cursor-pointer hover:bg-[var(--muted)] transition-colors"
onClick={() => setIsExpanded(!isExpanded)}
>
<div className="flex items-center justify-between">
<div className="flex items-center gap-2">
<span className="transition-transform duration-200" style={{ transform: isExpanded ? 'rotate(90deg)' : 'rotate(0deg)' }}>
</span>
<h3 className="font-semibold">🔍 Filtres avancés</h3>
{totalActiveFilters > 0 && (
<span className="bg-blue-100 text-blue-800 text-xs px-2 py-1 rounded-full">
{totalActiveFilters} actif{totalActiveFilters > 1 ? 's' : ''}
</span>
)}
</div>
</div>
</CardHeader>
{isExpanded && (
<CardContent>
<FilterSummary
activeFilters={activeFilters}
onClearAll={handleClearAll}
onShowModal={() => setShowModal(true)}
/>
{/* Actions rapides */}
{totalActiveFilters > 0 && (
<div className="mt-3 pt-3 border-t border-gray-200">
<div className="flex items-center justify-between text-xs text-gray-600">
<span>💡 Vous pouvez sauvegarder cette configuration</span>
<button
onClick={handleSavePreset}
className="text-blue-600 hover:text-blue-700 underline"
>
Sauvegarder comme preset
</button>
</div>
</div>
)}
</CardContent>
)}
<FilterModal
isOpen={showModal}
onClose={() => setShowModal(false)}
availableFilters={availableFilters}
activeFilters={activeFilters}
onFiltersChange={onFiltersChange}
/>
</Card>
);
}

View File

@@ -1,144 +0,0 @@
'use client';
import { useState } from 'react';
import { Modal } from '@/components/ui/Modal';
import { Button } from '@/components/ui/Button';
import { FilterSection } from './FilterSection';
import { JiraAnalyticsFilters, AvailableFilters } from '@/lib/types';
interface FilterModalProps {
isOpen: boolean;
onClose: () => void;
availableFilters: AvailableFilters;
activeFilters: Partial<JiraAnalyticsFilters>;
onFiltersChange: (filters: Partial<JiraAnalyticsFilters>) => void;
}
export function FilterModal({
isOpen,
onClose,
availableFilters,
activeFilters,
onFiltersChange
}: FilterModalProps) {
const [tempFilters, setTempFilters] = useState<Partial<JiraAnalyticsFilters>>(activeFilters);
const updateTempFilter = (key: keyof JiraAnalyticsFilters, values: string[]) => {
setTempFilters(prev => ({
...prev,
[key]: values
}));
};
const handleApply = () => {
onFiltersChange(tempFilters);
onClose();
};
const handleReset = () => {
const emptyFilters: Partial<JiraAnalyticsFilters> = {};
setTempFilters(emptyFilters);
onFiltersChange(emptyFilters);
onClose();
};
const handleCancel = () => {
setTempFilters(activeFilters);
onClose();
};
return (
<Modal
isOpen={isOpen}
onClose={handleCancel}
title="Configuration des filtres avancés"
size="lg"
>
<div className="space-y-6">
<div className="grid grid-cols-1 md:grid-cols-2 gap-6 max-h-96 overflow-y-auto">
<FilterSection
title="Composants"
icon="📦"
options={availableFilters.components}
selectedValues={tempFilters.components || []}
onSelectionChange={(values) => updateTempFilter('components', values)}
/>
<FilterSection
title="Versions"
icon="🏷️"
options={availableFilters.fixVersions}
selectedValues={tempFilters.fixVersions || []}
onSelectionChange={(values) => updateTempFilter('fixVersions', values)}
/>
<FilterSection
title="Types de tickets"
icon="📋"
options={availableFilters.issueTypes}
selectedValues={tempFilters.issueTypes || []}
onSelectionChange={(values) => updateTempFilter('issueTypes', values)}
/>
<FilterSection
title="Statuts"
icon="🔄"
options={availableFilters.statuses}
selectedValues={tempFilters.statuses || []}
onSelectionChange={(values) => updateTempFilter('statuses', values)}
/>
<FilterSection
title="Assignés"
icon="👤"
options={availableFilters.assignees}
selectedValues={tempFilters.assignees || []}
onSelectionChange={(values) => updateTempFilter('assignees', values)}
/>
<FilterSection
title="Labels"
icon="🏷️"
options={availableFilters.labels}
selectedValues={tempFilters.labels || []}
onSelectionChange={(values) => updateTempFilter('labels', values)}
/>
<FilterSection
title="Priorités"
icon="⚡"
options={availableFilters.priorities}
selectedValues={tempFilters.priorities || []}
onSelectionChange={(values) => updateTempFilter('priorities', values)}
/>
</div>
{/* Actions */}
<div className="flex justify-between pt-4 border-t">
<Button
variant="ghost"
onClick={handleReset}
className="text-red-600 hover:text-red-700 hover:bg-red-50"
>
🗑 Tout effacer
</Button>
<div className="flex gap-2">
<Button
variant="ghost"
onClick={handleCancel}
>
Annuler
</Button>
<Button
variant="primary"
onClick={handleApply}
>
Appliquer les filtres
</Button>
</div>
</div>
</div>
</Modal>
);
}

View File

@@ -1,97 +0,0 @@
'use client';
import { useState } from 'react';
import { Button } from '@/components/ui/Button';
import { FilterOption } from '@/lib/types';
interface FilterSectionProps {
title: string;
icon: string;
options: FilterOption[];
selectedValues: string[];
onSelectionChange: (values: string[]) => void;
maxDisplay?: number;
}
export function FilterSection({
title,
icon,
options,
selectedValues,
onSelectionChange,
maxDisplay = 10
}: FilterSectionProps) {
const [showAll, setShowAll] = useState(false);
const displayOptions = showAll ? options : options.slice(0, maxDisplay);
const hasMore = options.length > maxDisplay;
const handleToggle = (value: string) => {
const newValues = selectedValues.includes(value)
? selectedValues.filter(v => v !== value)
: [...selectedValues, value];
onSelectionChange(newValues);
};
const selectAll = () => {
onSelectionChange(options.map(opt => opt.value));
};
const clearAll = () => {
onSelectionChange([]);
};
return (
<div className="space-y-3">
<div className="flex items-center justify-between">
<h4 className="font-medium text-sm flex items-center gap-2">
<span>{icon}</span>
{title}
</h4>
<div className="flex gap-1">
<Button
variant="ghost"
size="sm"
onClick={selectAll}
className="text-xs px-2 py-1 h-6"
>
Tout
</Button>
<Button
variant="ghost"
size="sm"
onClick={clearAll}
className="text-xs px-2 py-1 h-6"
>
Rien
</Button>
</div>
</div>
<div className="space-y-1 max-h-48 overflow-y-auto">
{displayOptions.map((option) => (
<label key={option.value} className="flex items-center gap-2 cursor-pointer hover:bg-gray-50 p-1 rounded">
<input
type="checkbox"
checked={selectedValues.includes(option.value)}
onChange={() => handleToggle(option.value)}
className="rounded border-gray-300"
/>
<span className="text-sm flex-1 truncate">{option.label}</span>
<span className="text-xs text-gray-500">({option.count})</span>
</label>
))}
</div>
{hasMore && (
<Button
variant="ghost"
size="sm"
onClick={() => setShowAll(!showAll)}
className="text-xs w-full"
>
{showAll ? 'Voir moins' : `Voir ${options.length - maxDisplay} de plus`}
</Button>
)}
</div>
);
}

View File

@@ -1,74 +0,0 @@
'use client';
import { Badge } from '@/components/ui/Badge';
import { Button } from '@/components/ui/Button';
import { JiraAnalyticsFilters } from '@/lib/types';
interface FilterSummaryProps {
activeFilters: Partial<JiraAnalyticsFilters>;
onClearAll: () => void;
onShowModal: () => void;
}
export function FilterSummary({ activeFilters, onClearAll, onShowModal }: FilterSummaryProps) {
// Compter le nombre total de filtres actifs
const totalActiveFilters = Object.values(activeFilters).reduce((count, filterArray) => {
return count + (Array.isArray(filterArray) ? filterArray.length : 0);
}, 0);
const getFilterLabel = (key: string): string => {
const labels: Record<string, string> = {
components: 'Composants',
fixVersions: 'Versions',
issueTypes: 'Types',
statuses: 'Statuts',
assignees: 'Assignés',
labels: 'Labels',
priorities: 'Priorités'
};
return labels[key] || key;
};
return (
<div className="flex items-center justify-between p-3 bg-gray-50 rounded-lg">
<div className="flex items-center gap-2 flex-wrap">
<span className="text-sm font-medium">Filtres actifs:</span>
{totalActiveFilters === 0 ? (
<Badge variant="outline" size="sm">Aucun filtre</Badge>
) : (
Object.entries(activeFilters).map(([key, values]) => {
if (!Array.isArray(values) || values.length === 0) return null;
return (
<Badge key={key} variant="outline" size="sm" className="bg-blue-50 text-blue-700 border-blue-200">
{getFilterLabel(key)}: {values.length}
</Badge>
);
}).filter(Boolean)
)}
</div>
<div className="flex items-center gap-2">
{totalActiveFilters > 0 && (
<Button
variant="ghost"
size="sm"
onClick={onClearAll}
className="text-xs text-gray-600 hover:text-red-600"
>
🗑 Tout effacer
</Button>
)}
<Button
variant="secondary"
size="sm"
onClick={onShowModal}
className="text-xs"
>
Configurer ({totalActiveFilters})
</Button>
</div>
</div>
);
}

View File

@@ -1,143 +0,0 @@
'use client';
import { useState } from 'react';
import { Header } from '@/components/ui/Header';
import { Card, CardHeader, CardContent } from '@/components/ui/Card';
import { JiraConfigForm } from '@/components/settings/JiraConfigForm';
import { JiraSync } from '@/components/jira/JiraSync';
import { JiraLogs } from '@/components/jira/JiraLogs';
import { useJiraConfig } from '@/hooks/useJiraConfig';
export function SettingsPageClient() {
const { config: jiraConfig } = useJiraConfig();
const [activeTab, setActiveTab] = useState<'general' | 'integrations' | 'advanced'>('general');
const tabs = [
{ id: 'general' as const, label: 'Général', icon: '⚙️' },
{ id: 'integrations' as const, label: 'Intégrations', icon: '🔌' },
{ id: 'advanced' as const, label: 'Avancé', icon: '🛠️' }
];
return (
<div className="min-h-screen bg-[var(--background)]">
<Header
title="TowerControl"
subtitle="Configuration & Paramètres"
/>
<div className="container mx-auto px-4 py-4">
<div className="max-w-7xl mx-auto">
{/* En-tête compact */}
<div className="mb-4">
<h1 className="text-xl font-mono font-bold text-[var(--foreground)] mb-1">
Paramètres
</h1>
<p className="text-sm text-[var(--muted-foreground)]">
Configuration de TowerControl et de ses intégrations
</p>
</div>
<div className="flex gap-6">
{/* Navigation latérale compacte */}
<div className="w-56 flex-shrink-0">
<Card>
<CardContent className="p-0">
{tabs.map((tab) => (
<button
key={tab.id}
onClick={() => setActiveTab(tab.id)}
className={`w-full flex items-center gap-2 px-3 py-2 text-left transition-colors ${
activeTab === tab.id
? 'bg-[var(--primary)]/10 text-[var(--primary)] border-r-2 border-[var(--primary)]'
: 'text-[var(--muted-foreground)] hover:bg-[var(--card-hover)] hover:text-[var(--foreground)]'
}`}
>
<span className="text-base">{tab.icon}</span>
<span className="font-medium text-sm">{tab.label}</span>
</button>
))}
</CardContent>
</Card>
</div>
{/* Contenu principal */}
<div className="flex-1 min-h-0">
{activeTab === 'general' && (
<div className="space-y-6">
<Card>
<CardHeader>
<h2 className="text-lg font-semibold">Préférences générales</h2>
</CardHeader>
<CardContent className="space-y-4">
<div className="p-4 bg-[var(--card)] rounded border border-dashed border-[var(--border)]">
<p className="text-sm text-[var(--muted-foreground)]">
Les paramètres généraux seront disponibles dans une prochaine version.
</p>
</div>
</CardContent>
</Card>
</div>
)}
{activeTab === 'integrations' && (
<div className="h-full">
{/* Layout en 2 colonnes pour optimiser l'espace */}
<div className="grid grid-cols-1 xl:grid-cols-3 gap-4 h-full">
{/* Colonne 1: Configuration Jira */}
<div className="xl:col-span-2">
<Card className="h-fit">
<CardHeader className="pb-3">
<h2 className="text-base font-semibold">🔌 Intégration Jira Cloud</h2>
<p className="text-xs text-[var(--muted-foreground)]">
Synchronisation automatique des tickets
</p>
</CardHeader>
<CardContent>
<JiraConfigForm />
</CardContent>
</Card>
</div>
{/* Colonne 2: Actions et Logs */}
<div className="space-y-4">
{jiraConfig?.enabled && (
<>
<JiraSync />
<JiraLogs />
</>
)}
</div>
</div>
</div>
)}
{activeTab === 'advanced' && (
<div className="space-y-6">
<Card>
<CardHeader>
<h2 className="text-lg font-semibold">Paramètres avancés</h2>
</CardHeader>
<CardContent className="space-y-4">
<div className="p-4 bg-[var(--card)] rounded border border-dashed border-[var(--border)]">
<p className="text-sm text-[var(--muted-foreground)]">
Les paramètres avancés seront disponibles dans une prochaine version.
</p>
<ul className="mt-2 text-xs text-[var(--muted-foreground)] space-y-1">
<li> Configuration de la base de données</li>
<li> Logs de debug</li>
<li> Export/Import des données</li>
<li> Réinitialisation</li>
</ul>
</div>
</CardContent>
</Card>
</div>
)}
</div>
</div>
</div>
</div>
</div>
);
}

View File

@@ -1,21 +0,0 @@
'use client';
import { Header } from './Header';
import { useTasks } from '@/hooks/useTasks';
interface HeaderContainerProps {
title: string;
subtitle: string;
}
export function HeaderContainer({ title, subtitle }: HeaderContainerProps) {
const { syncing } = useTasks();
return (
<Header
title={title}
subtitle={subtitle}
syncing={syncing}
/>
);
}

View File

@@ -1,95 +0,0 @@
import { Tag } from '@/lib/types';
interface TagListProps {
tags: (Tag & { usage?: number })[];
onTagEdit?: (tag: Tag) => void;
onTagDelete?: (tag: Tag) => void;
showActions?: boolean;
showUsage?: boolean;
deletingTagId?: string | null;
}
export function TagList({
tags,
onTagEdit,
onTagDelete,
showActions = true,
deletingTagId
}: TagListProps) {
if (tags.length === 0) {
return (
<div className="text-center py-12 text-slate-400">
<div className="text-6xl mb-4">🏷</div>
<p className="text-lg mb-2">Aucun tag trouvé</p>
<p className="text-sm">Créez votre premier tag pour commencer</p>
</div>
);
}
return (
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-4">
{tags.map((tag) => {
const isDeleting = deletingTagId === tag.id;
return (
<div
key={tag.id}
className={`group relative bg-slate-800/50 rounded-lg border border-slate-700 hover:border-slate-600 transition-all duration-200 hover:shadow-lg hover:shadow-slate-900/20 p-3 ${
isDeleting ? 'opacity-50 pointer-events-none' : ''
}`}
>
{/* Contenu principal */}
<div className="flex items-center gap-3">
<div
className="w-5 h-5 rounded-full shadow-sm"
style={{ backgroundColor: tag.color }}
/>
<div className="flex-1 min-w-0">
<div className="flex items-center justify-between">
<h3 className="text-slate-200 font-medium truncate">
{tag.name}
</h3>
{tag.usage !== undefined && (
<span className="text-xs text-slate-400 bg-slate-700/50 px-2 py-1 rounded-full ml-2 flex-shrink-0">
{tag.usage}
</span>
)}
</div>
</div>
</div>
{/* Actions (apparaissent au hover) */}
{showActions && (onTagEdit || onTagDelete) && (
<div className="absolute top-2 right-2 flex gap-1 opacity-0 group-hover:opacity-100 transition-opacity">
{onTagEdit && (
<button
onClick={() => onTagEdit(tag)}
className="h-7 px-2 text-xs bg-slate-800/50 backdrop-blur-sm border border-slate-700 hover:border-slate-600 hover:bg-slate-700/50 rounded-md transition-all duration-200 text-slate-300 hover:text-slate-200"
>
</button>
)}
{onTagDelete && (
<button
onClick={() => onTagDelete(tag)}
disabled={isDeleting}
className="h-7 px-2 text-xs bg-slate-800/50 backdrop-blur-sm border border-slate-700 hover:border-red-500/50 hover:text-red-400 hover:bg-red-900/20 rounded-md transition-all duration-200 text-slate-300 disabled:opacity-50 disabled:cursor-not-allowed"
>
{isDeleting ? '⏳' : '🗑️'}
</button>
)}
</div>
)}
{/* Indicateur de couleur en bas */}
<div
className="absolute bottom-0 left-0 right-0 h-1 rounded-b-lg opacity-30"
style={{ backgroundColor: tag.color }}
/>
</div>
);
})}
</div>
);
}