feat: integrate emoji-mart and refactor emoji usage

- Added @emoji-mart/data and @emoji-mart/react dependencies for enhanced emoji support.
- Replaced static emoji characters with Emoji component in various UI components for consistency and improved rendering.
- Updated generateDateTitle function to return an object with emoji and text for better structure.
- Marked the task for removing emojis from the UI as complete in TODO.md.
This commit is contained in:
Julien Froidefond
2025-10-05 20:29:46 +02:00
parent 7490c38d55
commit 714f8ccd5e
54 changed files with 568 additions and 340 deletions

View File

@@ -13,8 +13,9 @@ import { DailySection } from '@/components/daily/DailySection';
import { PendingTasksSection } from '@/components/daily/PendingTasksSection';
import { dailyClient } from '@/clients/daily-client';
import { Header } from '@/components/ui/Header';
import { getPreviousWorkday, formatDateLong, isToday, generateDateTitle, formatDateShort, isYesterday } from '@/lib/date-utils';
import { getPreviousWorkday, formatDateLong, isToday, generateDateTitle } from '@/lib/date-utils';
import { useGlobalKeyboardShortcuts } from '@/hooks/useGlobalKeyboardShortcuts';
import { Emoji } from '@/components/ui/Emoji';
interface DailyPageClientProps {
initialDailyView?: DailyView;
@@ -142,15 +143,14 @@ export function DailyPageClient({
};
const getTodayTitle = () => {
return generateDateTitle(currentDate, '🎯');
const { emoji, text } = generateDateTitle(currentDate, '🎯');
return <><Emoji emoji={emoji} /> {text}</>;
};
const getYesterdayTitle = () => {
const yesterdayDate = getYesterdayDate();
if (isYesterday(yesterdayDate)) {
return "📋 Hier";
}
return `📋 ${formatDateShort(yesterdayDate)}`;
const { emoji, text } = generateDateTitle(yesterdayDate, '📋');
return <><Emoji emoji={emoji} /> {text}</>;
};
// Convertir les métriques de deadline en AlertItem

View File

@@ -29,6 +29,7 @@ import { getSprintDetails } from '../../actions/jira-sprint-details';
import { useJiraFilters } from '@/hooks/useJiraFilters';
import { SprintVelocity } from '@/lib/types';
import Link from 'next/link';
import { Emoji } from '@/components/ui/Emoji';
interface JiraDashboardPageClientProps {
initialJiraConfig: JiraConfig;
@@ -109,7 +110,7 @@ export function JiraDashboardPageClient({ initialJiraConfig, initialAnalytics }:
<div className="container mx-auto px-4 py-8">
<Card className="max-w-2xl mx-auto">
<CardHeader>
<h2 className="text-xl font-semibold"> Configuration requise</h2>
<h2 className="text-xl font-semibold"><Emoji emoji="⚙️" size={20} /> Configuration requise</h2>
</CardHeader>
<CardContent className="space-y-4">
<p className="text-[var(--muted-foreground)]">
@@ -139,7 +140,7 @@ export function JiraDashboardPageClient({ initialJiraConfig, initialAnalytics }:
<div className="container mx-auto px-4 py-8">
<Card className="max-w-2xl mx-auto">
<CardHeader>
<h2 className="text-xl font-semibold">🎯 Projet requis</h2>
<h2 className="text-xl font-semibold"><Emoji emoji="🎯" size={20} /> Projet requis</h2>
</CardHeader>
<CardContent className="space-y-4">
<p className="text-[var(--muted-foreground)]">
@@ -184,7 +185,7 @@ export function JiraDashboardPageClient({ initialJiraConfig, initialAnalytics }:
<div className="flex items-center justify-between mb-6">
<div>
<h1 className="text-2xl font-mono font-bold text-[var(--foreground)] mb-2">
📊 Analytics d&apos;équipe
<Emoji emoji="📊" size={18} /> Analytics d&apos;équipe
</h1>
<div className="space-y-1">
<p className="text-[var(--muted-foreground)]">
@@ -226,7 +227,7 @@ export function JiraDashboardPageClient({ initialJiraConfig, initialAnalytics }:
variant="ghost"
className="text-xs px-2 py-1 h-auto"
>
{isExporting ? '⏳' : '📊'} CSV
{isExporting ? <Emoji emoji="⏳" size={16} /> : <Emoji emoji="📊" size={16} />} CSV
</Button>
<Button
onClick={exportJSON}
@@ -245,7 +246,7 @@ export function JiraDashboardPageClient({ initialJiraConfig, initialAnalytics }:
disabled={isLoading}
variant="secondary"
>
{isLoading ? '🔄 Actualisation...' : '🔄 Actualiser'}
{isLoading ? <><Emoji emoji="🔄" size={16} /> Actualisation...</> : <><Emoji emoji="🔄" size={16} />&nbsp;Actualiser</>}
</Button>
</div>
</div>
@@ -284,13 +285,13 @@ export function JiraDashboardPageClient({ initialJiraConfig, initialAnalytics }:
<CardHeader className="pb-4">
<div className="flex flex-col lg:flex-row lg:items-center lg:justify-between gap-4">
<h2 className="text-lg font-semibold flex items-center gap-2">
🎯 {analytics.project.name}
<Emoji emoji="🎯" size={16} /> {analytics.project.name}
<span className="text-sm font-normal text-[var(--muted-foreground)]">
({periodInfo.label})
</span>
{hasActiveFilters && (
<Badge className="bg-purple-100 text-purple-800 text-xs">
🔍 Filtré
<Emoji emoji="🔍" size={12} /> Filtré
</Badge>
)}
</h2>
@@ -350,14 +351,14 @@ export function JiraDashboardPageClient({ initialJiraConfig, initialAnalytics }:
<div className="space-y-6">
{/* Info discrète sur le calcul des points */}
<div className="text-xs text-[var(--muted-foreground)] bg-[var(--card-column)] px-3 py-2 rounded border border-[var(--border)]">
💡 <strong>Points :</strong> Utilise les story points Jira si définis, sinon Epic(13), Story(5), Task(3), Bug(2), Subtask(1)
<Emoji emoji="💡" size={14} /> <strong>Points :</strong> Utilise les story points Jira si définis, sinon Epic(13), Story(5), Task(3), Bug(2), Subtask(1)
</div>
{/* Graphiques principaux */}
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
<Card>
<CardHeader>
<h3 className="font-semibold">👥 Répartition de l&apos;équipe</h3>
<h3 className="font-semibold"><Emoji emoji="👥" size={16} /> Répartition de l&apos;équipe</h3>
</CardHeader>
<CardContent>
<TeamDistributionChart
@@ -369,7 +370,7 @@ export function JiraDashboardPageClient({ initialJiraConfig, initialAnalytics }:
<Card>
<CardHeader>
<h3 className="font-semibold">🚀 Vélocité des sprints</h3>
<h3 className="font-semibold"><Emoji emoji="🚀" size={16} /> Vélocité des sprints</h3>
</CardHeader>
<CardContent>
<VelocityChart
@@ -385,7 +386,7 @@ export function JiraDashboardPageClient({ initialJiraConfig, initialAnalytics }:
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
<Card>
<CardHeader>
<h3 className="font-semibold"> Cycle Time par type</h3>
<h3 className="font-semibold"><Emoji emoji="⏱️" size={16} /> Cycle Time par type</h3>
</CardHeader>
<CardContent>
<CycleTimeChart
@@ -442,7 +443,7 @@ export function JiraDashboardPageClient({ initialJiraConfig, initialAnalytics }:
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
<Card>
<CardHeader>
<h3 className="font-semibold">📉 Burndown Chart</h3>
<h3 className="font-semibold"><Emoji emoji="📉" size={16} /> Burndown Chart</h3>
</CardHeader>
<CardContent className="p-4">
<div className="w-full h-96 overflow-hidden">
@@ -456,7 +457,7 @@ export function JiraDashboardPageClient({ initialJiraConfig, initialAnalytics }:
<Card>
<CardHeader>
<h3 className="font-semibold">📈 Throughput</h3>
<h3 className="font-semibold"><Emoji emoji="📈" size={16} /> Throughput</h3>
</CardHeader>
<CardContent className="p-4">
<div className="w-full h-96 overflow-hidden">
@@ -472,7 +473,7 @@ export function JiraDashboardPageClient({ initialJiraConfig, initialAnalytics }:
{/* Métriques de qualité */}
<Card>
<CardHeader>
<h3 className="font-semibold">🎯 Métriques de qualité</h3>
<h3 className="font-semibold"><Emoji emoji="🎯" size={16} /> Métriques de qualité</h3>
</CardHeader>
<CardContent className="p-4">
<div className="w-full overflow-hidden">
@@ -487,7 +488,7 @@ export function JiraDashboardPageClient({ initialJiraConfig, initialAnalytics }:
{/* Métriques de predictabilité */}
<Card>
<CardHeader>
<h3 className="font-semibold">📊 Predictabilité</h3>
<h3 className="font-semibold"><Emoji emoji="📊" size={16} /> Predictabilité</h3>
</CardHeader>
<CardContent className="p-4">
<div className="w-full overflow-hidden">
@@ -502,7 +503,7 @@ export function JiraDashboardPageClient({ initialJiraConfig, initialAnalytics }:
{/* Matrice de collaboration - ligne entière */}
<Card>
<CardHeader>
<h3 className="font-semibold">🤝 Matrice de collaboration</h3>
<h3 className="font-semibold"><Emoji emoji="🤝" size={16} /> Matrice de collaboration</h3>
</CardHeader>
<CardContent className="p-4">
<div className="w-full overflow-hidden">
@@ -517,7 +518,7 @@ export function JiraDashboardPageClient({ initialJiraConfig, initialAnalytics }:
{/* Comparaison inter-sprints */}
<Card>
<CardHeader>
<h3 className="font-semibold">📊 Comparaison inter-sprints</h3>
<h3 className="font-semibold"><Emoji emoji="📊" size={16} /> Comparaison inter-sprints</h3>
</CardHeader>
<CardContent className="p-4">
<div className="w-full overflow-hidden">
@@ -532,7 +533,7 @@ export function JiraDashboardPageClient({ initialJiraConfig, initialAnalytics }:
{/* Heatmap d'activité de l'équipe */}
<Card>
<CardHeader>
<h3 className="font-semibold">🔥 Heatmap d&apos;activité de l&apos;équipe</h3>
<h3 className="font-semibold"><Emoji emoji="🔥" size={16} /> Heatmap d&apos;activité de l&apos;équipe</h3>
</CardHeader>
<CardContent className="p-4">
<div className="w-full overflow-hidden">
@@ -552,7 +553,7 @@ export function JiraDashboardPageClient({ initialJiraConfig, initialAnalytics }:
{/* Graphique de vélocité */}
<Card>
<CardHeader>
<h3 className="font-semibold">🚀 Vélocité des sprints</h3>
<h3 className="font-semibold"><Emoji emoji="🚀" size={16} /> Vélocité des sprints</h3>
</CardHeader>
<CardContent className="p-4">
<div className="w-full h-64 overflow-hidden">
@@ -569,7 +570,7 @@ export function JiraDashboardPageClient({ initialJiraConfig, initialAnalytics }:
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
<Card>
<CardHeader>
<h3 className="font-semibold">📉 Burndown Chart</h3>
<h3 className="font-semibold"><Emoji emoji="📉" size={16} /> Burndown Chart</h3>
</CardHeader>
<CardContent className="p-4">
<div className="w-full h-96 overflow-hidden">
@@ -583,7 +584,7 @@ export function JiraDashboardPageClient({ initialJiraConfig, initialAnalytics }:
<Card>
<CardHeader>
<h3 className="font-semibold">📊 Throughput</h3>
<h3 className="font-semibold"><Emoji emoji="📊" size={16} /> Throughput</h3>
</CardHeader>
<CardContent className="p-4">
<div className="w-full h-96 overflow-hidden">
@@ -599,7 +600,7 @@ export function JiraDashboardPageClient({ initialJiraConfig, initialAnalytics }:
{/* Comparaison des sprints */}
<Card>
<CardHeader>
<h3 className="font-semibold">📊 Comparaison des sprints</h3>
<h3 className="font-semibold"><Emoji emoji="📊" size={16} /> Comparaison des sprints</h3>
</CardHeader>
<CardContent className="p-4">
<div className="w-full overflow-hidden">
@@ -619,7 +620,7 @@ export function JiraDashboardPageClient({ initialJiraConfig, initialAnalytics }:
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
<Card>
<CardHeader>
<h3 className="font-semibold"> Cycle Time par type</h3>
<h3 className="font-semibold"><Emoji emoji="⏱️" size={16} /> Cycle Time par type</h3>
</CardHeader>
<CardContent className="p-4">
<div className="w-full h-64 overflow-hidden">
@@ -659,7 +660,7 @@ export function JiraDashboardPageClient({ initialJiraConfig, initialAnalytics }:
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
<Card>
<CardHeader>
<h3 className="font-semibold">🎯 Métriques de qualité</h3>
<h3 className="font-semibold"><Emoji emoji="🎯" size={16} /> Métriques de qualité</h3>
</CardHeader>
<CardContent className="p-4">
<div className="w-full h-64 overflow-hidden">
@@ -673,7 +674,7 @@ export function JiraDashboardPageClient({ initialJiraConfig, initialAnalytics }:
<Card>
<CardHeader>
<h3 className="font-semibold">📈 Predictabilité</h3>
<h3 className="font-semibold"><Emoji emoji="📈" size={16} /> Predictabilité</h3>
</CardHeader>
<CardContent className="p-4">
<div className="w-full h-64 overflow-hidden">
@@ -694,7 +695,7 @@ export function JiraDashboardPageClient({ initialJiraConfig, initialAnalytics }:
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
<Card>
<CardHeader>
<h3 className="font-semibold">👥 Répartition de l&apos;équipe</h3>
<h3 className="font-semibold"><Emoji emoji="👥" size={16} /> Répartition de l&apos;équipe</h3>
</CardHeader>
<CardContent className="p-4">
<div className="w-full h-64 overflow-hidden">
@@ -708,7 +709,7 @@ export function JiraDashboardPageClient({ initialJiraConfig, initialAnalytics }:
<Card>
<CardHeader>
<h3 className="font-semibold">🤝 Matrice de collaboration</h3>
<h3 className="font-semibold"><Emoji emoji="🤝" size={16} /> Matrice de collaboration</h3>
</CardHeader>
<CardContent className="p-4">
<div className="w-full h-64 overflow-hidden">

View File

@@ -3,8 +3,8 @@
import { useSession, signOut } from 'next-auth/react'
import { useRouter } from 'next/navigation'
import { Button } from '@/components/ui/Button'
import { Emoji } from '@/components/ui/Emoji'
import { Avatar } from '@/components/ui/Avatar'
import { LogOut } from 'lucide-react'
export function AuthButton() {
const { data: session, status } = useSession()
@@ -53,7 +53,7 @@ export function AuthButton() {
className="p-1 h-auto"
title="Déconnexion"
>
<LogOut className="w-4 h-4" />
<Emoji emoji="🚪" size={16} />
</Button>
</div>
)

View File

@@ -95,7 +95,6 @@ export function ThemeSelector() {
<div className="flex-1 min-w-0">
<div className="font-medium text-[var(--foreground)] mb-1 flex items-center gap-2">
<span className="text-base">{themeOption.icon}</span>
{themeOption.name}
</div>
<div className="text-xs text-[var(--muted-foreground)] leading-relaxed">

View File

@@ -1,6 +1,7 @@
'use client';
import { Card } from '@/components/ui/Card';
import { Emoji } from '@/components/ui/Emoji';
interface WeeklyStats {
thisWeek: number;
@@ -51,7 +52,7 @@ export function WeeklyStatsCard({ stats, title = "Performance Hebdomadaire" }: W
{/* Changement */}
<div className="mt-6 pt-4 border-t border-[var(--border)]">
<div className={`flex items-center justify-center gap-2 p-3 rounded-lg ${changeBg}`}>
<span className="text-lg">{changeIcon}</span>
<span className="text-lg"><Emoji emoji={changeIcon} /></span>
<div className="text-center">
<div className={`font-bold ${changeColor}`}>
{isPositive ? '+' : ''}{stats.change} tâches
@@ -66,11 +67,11 @@ export function WeeklyStatsCard({ stats, title = "Performance Hebdomadaire" }: W
{/* Insight */}
<div className="mt-4 text-center">
<p className="text-xs text-[var(--muted-foreground)]">
{stats.changePercent > 20 ? 'Excellente progression ! 🚀' :
stats.changePercent > 0 ? 'Bonne progression 👍' :
stats.changePercent === 0 ? 'Performance stable 📊' :
stats.changePercent > -20 ? 'Légère baisse, restez motivé 💪' :
'Focus sur la productivité cette semaine 🎯'}
{stats.changePercent > 20 ? <><Emoji emoji="🚀" /> Excellente progression !</> :
stats.changePercent > 0 ? <><Emoji emoji="👍" /> Bonne progression</> :
stats.changePercent === 0 ? <><Emoji emoji="📊" /> Performance stable</> :
stats.changePercent > -20 ? <><Emoji emoji="💪" /> Légère baisse, restez motivé</> :
<><Emoji emoji="🎯" /> Focus sur la productivité cette semaine</>}
</p>
</div>
</Card>

View File

@@ -1,5 +1,6 @@
'use client';
import { ReactNode } from 'react';
import { DailyCheckbox, DailyCheckboxType } from '@/lib/types';
import { Card } from '@/components/ui/Card';
import { Button } from '@/components/ui/Button';
@@ -13,7 +14,7 @@ import { useState } from 'react';
import React from 'react';
interface DailySectionProps {
title: string;
title: ReactNode;
date: Date;
checkboxes: DailyCheckbox[];
onAddCheckbox: (text: string, type: DailyCheckboxType) => Promise<void>;
@@ -103,7 +104,7 @@ export function DailySection({
collisionDetection={closestCenter}
onDragStart={handleDragStart}
onDragEnd={handleDragEnd}
id={`daily-dnd-${title.replace(/[^a-zA-Z0-9]/g, '-')}`}
id={`daily-dnd-${String(title).replace(/[^a-zA-Z0-9]/g, '-')}`}
>
<Card variant="glass" className="p-0 flex flex-col h-[80vh] sm:h-[600px]">
{/* Header */}

View File

@@ -8,6 +8,7 @@ import { DailyCheckbox, DailyCheckboxType } from '@/lib/types';
import { dailyClient } from '@/clients/daily-client';
import { formatDateShort, getDaysAgo } from '@/lib/date-utils';
import { moveCheckboxToToday } from '@/actions/daily';
import { Emoji } from '@/components/ui/Emoji';
interface PendingTasksSectionProps {
onToggleCheckbox: (checkboxId: string) => Promise<void>;
@@ -128,7 +129,7 @@ export function PendingTasksSection({
// Obtenir l'icône selon le type
const getTypeIcon = (type: DailyCheckboxType) => {
return type === 'meeting' ? '🤝' : '📋';
return type === 'meeting' ? <Emoji emoji="🤝" /> : <Emoji emoji="📋" />;
};
const pendingCount = pendingTasks.length;
@@ -262,7 +263,7 @@ export function PendingTasksSection({
title="Déplacer à aujourd'hui"
className="text-xs px-2 py-1 text-[var(--primary)] hover:text-[var(--primary)] disabled:opacity-50"
>
📅
<Emoji emoji="📅" />
</Button>
<Button
variant="ghost"
@@ -271,7 +272,7 @@ export function PendingTasksSection({
title="Archiver cette tâche"
className="text-xs px-2 py-1"
>
📦
<Emoji emoji="📦" />
</Button>
</>
)}
@@ -282,7 +283,7 @@ export function PendingTasksSection({
title="Supprimer cette tâche"
className="text-xs px-2 py-1 text-[var(--destructive)] hover:text-[var(--destructive)]"
>
🗑
<Emoji emoji="🗑️" />
</Button>
</div>
</div>

View File

@@ -1,6 +1,7 @@
'use client';
import { useState, useMemo } from 'react';
import { Link, Circle, Square, Hand } from 'lucide-react';
import { useTasksContext } from '@/contexts/TasksContext';
import { Dropdown, Button } from '@/components/ui';
import type { KanbanFilters } from '@/lib/types';
@@ -23,7 +24,7 @@ interface IntegrationFilterProps {
interface SourceOption {
id: 'jira' | 'tfs' | 'manual';
label: string;
icon: string;
icon: React.ReactNode;
hasTasks: boolean;
}
@@ -53,19 +54,19 @@ export function IntegrationFilter({
{
id: 'jira' as const,
label: 'Jira',
icon: '🔹',
icon: <Circle size={14} />,
hasTasks: hasJiraTasks
},
{
id: 'tfs' as const,
label: 'TFS',
icon: '🔷',
icon: <Square size={14} />,
hasTasks: hasTfsTasks
},
{
id: 'manual' as const,
label: 'Manuel',
icon: '✋',
icon: <Hand size={14} />,
hasTasks: hasManualTasks
}
].filter(source => source.hasTasks);
@@ -264,7 +265,12 @@ export function IntegrationFilter({
<Dropdown
open={isOpen}
onOpenChange={setIsOpen}
trigger={`🔗 ${getMainButtonText()}`}
trigger={
<span className="flex items-center gap-1">
<Link className="w-4 h-4" />
{getMainButtonText()}
</span>
}
variant={getMainButtonVariant()}
content={dropdownContent}
placement={alignRight ? "bottom-end" : "bottom-start"}

View File

@@ -12,6 +12,7 @@ import { MetricsTab } from './MetricsTab';
import { format } from 'date-fns';
import { fr } from 'date-fns/locale';
import { Tag } from '@/lib/types';
import { Emoji } from '@/components/ui/Emoji';
interface ManagerWeeklySummaryProps {
initialSummary: ManagerSummary;
@@ -50,7 +51,7 @@ export default function ManagerWeeklySummary({ initialSummary }: ManagerWeeklySu
{/* Header avec navigation */}
<div className="flex items-center justify-between">
<div>
<h1 className="text-2xl font-bold text-[var(--foreground)]">👔 Weekly</h1>
<h1 className="text-2xl font-bold text-[var(--foreground)]"><Emoji emoji="👔" size={24} /> Weekly</h1>
<p className="text-[var(--muted-foreground)]">{formatPeriod()}</p>
</div>
{activeView !== 'metrics' && (
@@ -59,7 +60,7 @@ export default function ManagerWeeklySummary({ initialSummary }: ManagerWeeklySu
variant="secondary"
size="sm"
>
🔄 Actualiser
<Emoji emoji="🔄" size={16} />&nbsp;Actualiser
</Button>
)}
</div>
@@ -80,22 +81,22 @@ export default function ManagerWeeklySummary({ initialSummary }: ManagerWeeklySu
<Card variant="elevated">
<CardHeader>
<h2 className="text-lg font-semibold flex items-center gap-2">
📊 Résumé de la semaine
<Emoji emoji="📊" size={18} /> Résumé de la semaine
</h2>
</CardHeader>
<CardContent className="space-y-4">
<div className="outline-card-blue p-4">
<h3 className="font-medium mb-2">🎯 Points clés accomplis</h3>
<h3 className="font-medium mb-2"><Emoji emoji="🎯" size={16} /> Points clés accomplis</h3>
<p className="text-sm text-[var(--muted-foreground)]">{summary.narrative.weekHighlight}</p>
</div>
<div className="outline-card-orange p-4">
<h3 className="font-medium mb-2"> Défis traités</h3>
<h3 className="font-medium mb-2"><Emoji emoji="⚡" size={16} /> Défis traités</h3>
<p className="text-sm text-[var(--muted-foreground)]">{summary.narrative.mainChallenges}</p>
</div>
<div className="outline-card-green p-4">
<h3 className="font-medium mb-2">🔮 Focus 7 prochains jours</h3>
<h3 className="font-medium mb-2"><Emoji emoji="🔮" size={16} /> Focus 7 prochains jours</h3>
<p className="text-sm text-[var(--muted-foreground)]">{summary.narrative.nextWeekFocus}</p>
</div>
</CardContent>
@@ -104,7 +105,7 @@ export default function ManagerWeeklySummary({ initialSummary }: ManagerWeeklySu
{/* Métriques rapides */}
<Card variant="elevated">
<CardHeader>
<h2 className="text-lg font-semibold">📈 Métriques en bref</h2>
<h2 className="text-lg font-semibold"><Emoji emoji="📈" size={18} /> Métriques en bref</h2>
</CardHeader>
<CardContent>
<div className="grid grid-cols-2 gap-4">
@@ -143,7 +144,7 @@ export default function ManagerWeeklySummary({ initialSummary }: ManagerWeeklySu
borderBottomColor: 'color-mix(in srgb, var(--success) 10%, var(--border))'
}}>
<h2 className="text-lg font-semibold flex items-center gap-2" style={{ color: 'var(--success)' }}>
🏆 Top accomplissements
<Emoji emoji="🏆" size={18} /> Top accomplissements
</h2>
</CardHeader>
<CardContent>
@@ -176,7 +177,7 @@ export default function ManagerWeeklySummary({ initialSummary }: ManagerWeeklySu
borderBottomColor: 'color-mix(in srgb, var(--destructive) 10%, var(--border))'
}}>
<h2 className="text-lg font-semibold flex items-center gap-2" style={{ color: 'var(--destructive)' }}>
🎯 Top enjeux à venir
<Emoji emoji="🎯" size={18} /> Top enjeux à venir
</h2>
</CardHeader>
<CardContent>
@@ -208,7 +209,7 @@ export default function ManagerWeeklySummary({ initialSummary }: ManagerWeeklySu
{activeView === 'accomplishments' && (
<Card variant="elevated">
<CardHeader>
<h2 className="text-lg font-semibold"> Accomplissements des 7 derniers jours</h2>
<h2 className="text-lg font-semibold"><Emoji emoji="✅" size={18} /> Accomplissements des 7 derniers jours</h2>
<p className="text-sm text-[var(--muted-foreground)]">
{summary.keyAccomplishments.length} accomplissements significatifs {summary.metrics.totalTasksCompleted} tâches {summary.metrics.totalCheckboxesCompleted} todos complétés
</p>
@@ -220,7 +221,7 @@ export default function ManagerWeeklySummary({ initialSummary }: ManagerWeeklySu
borderColor: 'color-mix(in srgb, var(--muted) 40%, var(--border))',
color: 'var(--muted-foreground)'
}}>
<div className="text-4xl mb-4">📭</div>
<div className="text-4xl mb-4"><Emoji emoji="📭" size={32} /></div>
<p className="text-lg mb-2">Aucun accomplissement significatif trouvé cette semaine.</p>
<p className="text-sm">Ajoutez des tâches avec priorité haute/medium ou des meetings.</p>
</div>
@@ -246,7 +247,7 @@ export default function ManagerWeeklySummary({ initialSummary }: ManagerWeeklySu
{activeView === 'challenges' && (
<Card variant="elevated">
<CardHeader>
<h2 className="text-lg font-semibold">🎯 Enjeux et défis à venir</h2>
<h2 className="text-lg font-semibold"><Emoji emoji="🎯" size={18} /> Enjeux et défis à venir</h2>
<p className="text-sm text-[var(--muted-foreground)]">
{summary.upcomingChallenges.length} défis identifiés {summary.upcomingChallenges.filter(c => c.priority === 'high').length} priorité haute {summary.upcomingChallenges.filter(c => c.blockers.length > 0).length} avec blockers
</p>
@@ -258,7 +259,7 @@ export default function ManagerWeeklySummary({ initialSummary }: ManagerWeeklySu
borderColor: 'color-mix(in srgb, var(--muted) 40%, var(--border))',
color: 'var(--muted-foreground)'
}}>
<div className="text-4xl mb-4">🎯</div>
<div className="text-4xl mb-4"><Emoji emoji="🎯" size={32} /></div>
<p className="text-lg mb-2">Aucun enjeu prioritaire trouvé.</p>
<p className="text-sm">Ajoutez des tâches non complétées avec priorité haute/medium.</p>
</div>

View File

@@ -12,6 +12,7 @@ import { MetricsVelocitySection } from './charts/MetricsVelocitySection';
import { MetricsProductivitySection } from './charts/MetricsProductivitySection';
import { format } from 'date-fns';
import { fr } from 'date-fns/locale';
import { Emoji } from '@/components/ui/Emoji';
interface MetricsTabProps {
className?: string;
@@ -41,13 +42,13 @@ export function MetricsTab({ className }: MetricsTabProps) {
<Card>
<CardContent className="p-6 text-center">
<p className="text-red-500 mb-4">
Erreur lors du chargement des métriques
<Emoji emoji="❌" size={16} /> Erreur lors du chargement des métriques
</p>
<p className="text-sm text-[var(--muted-foreground)] mb-4">
{metricsError || trendsError}
</p>
<Button onClick={handleRefresh} variant="secondary" size="sm">
🔄 Réessayer
<Emoji emoji="🔄" size={14} /> Réessayer
</Button>
</CardContent>
</Card>
@@ -60,7 +61,7 @@ export function MetricsTab({ className }: MetricsTabProps) {
{/* Header avec période et contrôles */}
<div className="flex items-center justify-between mb-6">
<div>
<h2 className="text-xl font-bold text-[var(--foreground)]">📊 Métriques & Analytics</h2>
<h2 className="text-xl font-bold text-[var(--foreground)]"><Emoji emoji="📊" size={20} /> Métriques & Analytics</h2>
<p className="text-[var(--muted-foreground)]">{formatPeriod()}</p>
</div>
<div className="flex items-center gap-2">
@@ -70,7 +71,7 @@ export function MetricsTab({ className }: MetricsTabProps) {
size="sm"
disabled={metricsLoading || trendsLoading}
>
🔄 Actualiser
<Emoji emoji="🔄" size={14} /> Actualiser
</Button>
</div>
</div>

View File

@@ -9,6 +9,7 @@ import { WeeklyStatsCard } from '@/components/charts/WeeklyStatsCard';
import { TagDistributionChart } from '@/components/dashboard/TagDistributionChart';
import { Card, MetricCard } from '@/components/ui';
import { DeadlineOverview } from '@/components/deadline/DeadlineOverview';
import { Emoji } from '@/components/ui/Emoji';
interface ProductivityAnalyticsProps {
metrics: ProductivityMetrics;
@@ -90,7 +91,7 @@ export function ProductivityAnalytics({ metrics, deadlineMetrics, tagMetrics, se
{/* Titre de section Analytics */}
<div className="flex items-center justify-between">
<h2 className="text-2xl font-bold">📊 Analytics & Métriques</h2>
<h2 className="text-2xl font-bold"><Emoji emoji="📊" size={20} /> Analytics & Métriques</h2>
<div className="text-sm text-[var(--muted-foreground)]">
Derniers 30 jours
</div>
@@ -141,7 +142,7 @@ export function ProductivityAnalytics({ metrics, deadlineMetrics, tagMetrics, se
{/* Insights automatiques */}
<Card variant="glass" className="p-6">
<h3 className="text-lg font-semibold mb-4">💡 Insights</h3>
<h3 className="text-lg font-semibold mb-4"><Emoji emoji="💡" size={25} /> Insights</h3>
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
<MetricCard
title="Vélocité Moyenne"

View File

@@ -5,7 +5,8 @@ import { Card } from '@/components/ui/Card';
import { TaskCard } from '@/components/ui/TaskCard';
import { useTasksContext } from '@/contexts/TasksContext';
import Link from 'next/link';
import { Clipboard, Clock } from 'lucide-react';
import { Clipboard } from 'lucide-react';
import { Emoji } from '@/components/ui/Emoji';
interface RecentTasksProps {
tasks: Task[];
@@ -57,7 +58,7 @@ export function RecentTasks({ tasks, selectedSources = [], hiddenSources = [] }:
<Card variant="glass" className="p-4 sm:p-6 mt-8">
<div className="flex items-center justify-between mb-4">
<div className="flex items-center gap-3">
<Clock className="w-5 h-5 text-[var(--primary)]" />
<Emoji emoji="🕒" size={20} />
<h3 className="text-lg font-semibold text-[var(--foreground)]">Tâches Récentes</h3>
</div>
<Link href="/kanban">

View File

@@ -2,7 +2,8 @@
import { useSession } from 'next-auth/react';
import { useState, useEffect, useRef } from 'react';
import { Check, User, ArrowRight } from 'lucide-react';
import { Check, User, RefreshCw } from 'lucide-react';
import { Emoji } from '@/components/ui/Emoji';
const WELCOME_GREETINGS = [
"Bienvenue",
@@ -18,99 +19,99 @@ const WELCOME_GREETINGS = [
];
const WELCOME_MESSAGES = [
"Prêt à conquérir la journée ? 🚀",
"Votre productivité vous attend !",
"C'est parti pour une journée productive ! 💪",
"Organisons ensemble vos tâches ! 📋",
"Votre tableau de bord vous attend ! 🎯",
"Prêt à faire la différence ?",
"Concentrons-nous sur l'essentiel ! 🎯",
"Une nouvelle journée, de nouvelles opportunités ! 🌟",
"Votre succès commence ici ! 🏆",
"Transformons vos objectifs en réalité ! 🎪",
"C'est l'heure de briller !",
"Votre efficacité n'attend que vous ! 🔥",
"Organisons votre succès ! 📊",
"Prêt à dépasser vos limites ? 🚀",
"Votre productivité vous remercie ! 🙏",
"C'est parti pour une journée exceptionnelle ! 🌈",
"Votre organisation parfaite vous attend ! 🎨",
"Prêt à accomplir de grandes choses ? 🏅",
"Votre motivation est votre force ! 💎",
"Créons ensemble votre succès ! 🎭",
{ text: "Prêt à conquérir la journée ?", icon: "🚀" },
{ text: "Votre productivité vous attend !", icon: "⚡" },
{ text: "C'est parti pour une journée productive !", icon: "💪" },
{ text: "Organisons ensemble vos tâches !", icon: "📋" },
{ text: "Votre tableau de bord vous attend !", icon: "🎯" },
{ text: "Prêt à faire la différence ?", icon: "✨" },
{ text: "Concentrons-nous sur l'essentiel !", icon: "🎯" },
{ text: "Une nouvelle journée, de nouvelles opportunités !", icon: "🌟" },
{ text: "Votre succès commence ici !", icon: "🏆" },
{ text: "Transformons vos objectifs en réalité !", icon: "🎪" },
{ text: "C'est l'heure de briller !", icon: "⭐" },
{ text: "Votre efficacité n'attend que vous !", icon: "🔥" },
{ text: "Organisons votre succès !", icon: "📊" },
{ text: "Prêt à dépasser vos limites ?", icon: "🚀" },
{ text: "Votre productivité vous remercie !", icon: "🙏" },
{ text: "C'est parti pour une journée exceptionnelle !", icon: "🌈" },
{ text: "Votre organisation parfaite vous attend !", icon: "🎨" },
{ text: "Prêt à accomplir de grandes choses ?", icon: "🏅" },
{ text: "Votre motivation est votre force !", icon: "💎" },
{ text: "Créons ensemble votre succès !", icon: "🎭" },
// Messages humoristiques
"Attention, productivité en approche ! 🚨",
"Votre cerveau va être bien occupé ! 🧠",
"Préparez-vous à être impressionné par vous-même ! 😎",
"Mode super-héros activé ! 🦸‍♂️",
"Votre liste de tâches tremble déjà ! 😱",
"Prêt à faire exploser vos objectifs ? 💥",
"Votre procrastination n'a qu'à bien se tenir ! 😤",
"C'est l'heure de montrer qui est le boss ! 👑",
"Votre café peut attendre, vos tâches non !",
"Prêt à devenir la légende de la productivité ? 🏆",
"Attention, efficacité maximale détectée !",
"Votre motivation est plus forte que le café !",
"Prêt à faire rougir votre calendrier ? 📅",
"Votre énergie positive est contagieuse ! 😄",
"C'est parti pour une journée épique ! 🎬",
"Votre détermination brille plus que le soleil ! ☀️",
"Prêt à transformer le chaos en ordre ? 🎯",
"Votre focus est plus précis qu'un laser ! 🔴",
"C'est l'heure de montrer vos super-pouvoirs ! 🦸‍♀️",
"Votre organisation va faire des jaloux ! 😏",
"Prêt à devenir le héros de votre propre histoire ? 📚",
"Votre productivité va battre des records ! 🏃‍♂️",
"Attention, génie au travail ! 🧪",
"Votre créativité déborde ! 🎨",
"Prêt à faire trembler vos deadlines ?",
"Votre énergie positive illumine la pièce ! 💡",
"C'est parti pour une aventure productive ! 🗺️",
"Votre motivation est plus forte que la gravité ! 🌍",
"Prêt à devenir le maître de l'organisation ? 🎭",
"Votre efficacité va faire des étincelles !"
{ text: "Attention, productivité en approche !", icon: "🚨" },
{ text: "Votre cerveau va être bien occupé !", icon: "🧠" },
{ text: "Préparez-vous à être impressionné par vous-même !", icon: "😎" },
{ text: "Mode super-héros activé !", icon: "🦸‍♂️" },
{ text: "Votre liste de tâches tremble déjà !", icon: "😱" },
{ text: "Prêt à faire exploser vos objectifs ?", icon: "💥" },
{ text: "Votre procrastination n'a qu'à bien se tenir !", icon: "😤" },
{ text: "C'est l'heure de montrer qui est le boss !", icon: "👑" },
{ text: "Votre café peut attendre, vos tâches non !", icon: "☕" },
{ text: "Prêt à devenir la légende de la productivité ?", icon: "🏆" },
{ text: "Attention, efficacité maximale détectée !", icon: "⚡" },
{ text: "Votre motivation est plus forte que le café !", icon: "☕" },
{ text: "Prêt à faire rougir votre calendrier ?", icon: "📅" },
{ text: "Votre énergie positive est contagieuse !", icon: "😄" },
{ text: "C'est parti pour une journée épique !", icon: "🎬" },
{ text: "Votre détermination brille plus que le soleil !", icon: "☀️" },
{ text: "Prêt à transformer le chaos en ordre ?", icon: "🎯" },
{ text: "Votre focus est plus précis qu'un laser !", icon: "🔴" },
{ text: "C'est l'heure de montrer vos super-pouvoirs !", icon: "🦸‍♀️" },
{ text: "Votre organisation va faire des jaloux !", icon: "😏" },
{ text: "Prêt à devenir le héros de votre propre histoire ?", icon: "📚" },
{ text: "Votre productivité va battre des records !", icon: "🏃‍♂️" },
{ text: "Attention, génie au travail !", icon: "🧪" },
{ text: "Votre créativité déborde !", icon: "🎨" },
{ text: "Prêt à faire trembler vos deadlines ?", icon: "⏰" },
{ text: "Votre énergie positive illumine la pièce !", icon: "💡" },
{ text: "C'est parti pour une aventure productive !", icon: "🗺️" },
{ text: "Votre motivation est plus forte que la gravité !", icon: "🌍" },
{ text: "Prêt à devenir le maître de l'organisation ?", icon: "🎭" },
{ text: "Votre efficacité va faire des étincelles !", icon: "✨" }
];
const TIME_BASED_MESSAGES = {
morning: [
"Bonjour ! Une belle journée vous attend ! ☀️",
"Réveillez-vous, c'est l'heure de briller ! 🌅",
"Le matin est le moment parfait pour commencer ! 🌄",
"Une nouvelle journée, de nouvelles possibilités ! 🌞",
"Bonjour ! Votre café vous attend !",
"Réveillez-vous, les tâches n'attendent pas !",
"Bonjour ! Prêt à conquérir le monde ? 🌍",
"Le matin, tout est possible ! 🌅",
"Bonjour ! Votre motivation vous appelle ! 📞",
"Réveillez-vous, c'est l'heure de la productivité !"
{ text: "Bonjour ! Une belle journée vous attend !", icon: "☀️" },
{ text: "Réveillez-vous, c'est l'heure de briller !", icon: "🌅" },
{ text: "Le matin est le moment parfait pour commencer !", icon: "🌄" },
{ text: "Une nouvelle journée, de nouvelles possibilités !", icon: "🌞" },
{ text: "Bonjour ! Votre café vous attend !", icon: "☕" },
{ text: "Réveillez-vous, les tâches n'attendent pas !", icon: "⏰" },
{ text: "Bonjour ! Prêt à conquérir le monde ?", icon: "🌍" },
{ text: "Le matin, tout est possible !", icon: "🌅" },
{ text: "Bonjour ! Votre motivation vous appelle !", icon: "📞" },
{ text: "Réveillez-vous, c'est l'heure de la productivité !", icon: "⚡" }
],
afternoon: [
"Bon après-midi ! Continuons sur cette lancée ! 🌤️",
"L'après-midi est parfait pour avancer ! ☀️",
"Encore quelques heures pour accomplir vos objectifs !",
"L'énergie de l'après-midi vous porte ! 💪",
"Bon après-midi ! Le momentum continue ! 🚀",
"L'après-midi, c'est l'heure de l'efficacité !",
"Bon après-midi ! Votre café de 14h vous attend !",
"L'après-midi, tout s'accélère ! 🏃‍♂️",
"Bon après-midi ! Prêt pour la deuxième mi-temps ?",
"L'après-midi, c'est l'heure de briller !"
{ text: "Bon après-midi ! Continuons sur cette lancée !", icon: "🌤️" },
{ text: "L'après-midi est parfait pour avancer !", icon: "☀️" },
{ text: "Encore quelques heures pour accomplir vos objectifs !", icon: "⏰" },
{ text: "L'énergie de l'après-midi vous porte !", icon: "💪" },
{ text: "Bon après-midi ! Le momentum continue !", icon: "🚀" },
{ text: "L'après-midi, c'est l'heure de l'efficacité !", icon: "⚡" },
{ text: "Bon après-midi ! Votre café de 14h vous attend !", icon: "☕" },
{ text: "L'après-midi, tout s'accélère !", icon: "🏃‍♂️" },
{ text: "Bon après-midi ! Prêt pour la deuxième mi-temps ?", icon: "⚽" },
{ text: "L'après-midi, c'est l'heure de briller !", icon: "✨" }
],
evening: [
"Bonsoir ! Terminons la journée en beauté ! 🌆",
"Le soir est idéal pour finaliser vos tâches ! 🌇",
"Une dernière poussée avant la fin de journée ! 🌃",
"Le crépuscule vous accompagne ! 🌅",
"Bonsoir ! Prêt pour le sprint final ? 🏃‍♀️",
"Le soir, c'est l'heure de la victoire ! 🏆",
"Bonsoir ! Votre récompense vous attend ! 🎁",
"Le soir, on termine en héros ! 🦸‍♂️",
"Bonsoir ! Prêt à clôturer cette journée ? 📝",
"Le soir, c'est l'heure de la satisfaction ! 😌"
{ text: "Bonsoir ! Terminons la journée en beauté !", icon: "🌆" },
{ text: "Le soir est idéal pour finaliser vos tâches !", icon: "🌇" },
{ text: "Une dernière poussée avant la fin de journée !", icon: "🌃" },
{ text: "Le crépuscule vous accompagne !", icon: "🌅" },
{ text: "Bonsoir ! Prêt pour le sprint final ?", icon: "🏃‍♀️" },
{ text: "Le soir, c'est l'heure de la victoire !", icon: "🏆" },
{ text: "Bonsoir ! Votre récompense vous attend !", icon: "🎁" },
{ text: "Le soir, on termine en héros !", icon: "🦸‍♂️" },
{ text: "Bonsoir ! Prêt à clôturer cette journée ?", icon: "📝" },
{ text: "Le soir, c'est l'heure de la satisfaction !", icon: "😌" }
]
};
function getTimeBasedMessage(): string {
function getTimeBasedMessage(): { text: string; icon: string } {
const hour = new Date().getHours();
if (hour >= 5 && hour < 12) {
@@ -122,7 +123,7 @@ function getTimeBasedMessage(): string {
}
}
function getRandomWelcomeMessage(): string {
function getRandomWelcomeMessage(): { text: string; icon: string } {
return WELCOME_MESSAGES[Math.floor(Math.random() * WELCOME_MESSAGES.length)];
}
@@ -132,8 +133,8 @@ function getRandomGreeting(): string {
export function WelcomeSection() {
const { data: session } = useSession();
const [welcomeMessage, setWelcomeMessage] = useState<string>('');
const [timeMessage, setTimeMessage] = useState<string>('');
const [welcomeMessage, setWelcomeMessage] = useState<{ text: string; icon: string }>({ text: '', icon: '' });
const [timeMessage, setTimeMessage] = useState<{ text: string; icon: string }>({ text: '', icon: '' });
const [greeting, setGreeting] = useState<string>('');
const [isAnimating, setIsAnimating] = useState(false);
const [particleCount, setParticleCount] = useState(0);
@@ -254,7 +255,7 @@ export function WelcomeSection() {
}`}
style={{ transitionDelay: '0.3s' }}
>
{timeMessage}
{timeMessage.text} <Emoji emoji={timeMessage.icon} size={16} />
</p>
</div>
@@ -265,7 +266,7 @@ export function WelcomeSection() {
}`}
style={{ transitionDelay: '0.5s' }}
>
{welcomeMessage}
{welcomeMessage.text} <Emoji emoji={welcomeMessage.icon} size={16} />
</p>
</div>
</div>
@@ -279,7 +280,7 @@ export function WelcomeSection() {
>
<div className="absolute inset-0 bg-gradient-to-r from-[var(--primary)]/10 to-[var(--accent)]/10 rounded-2xl opacity-0 group-hover:opacity-100 transition-opacity duration-500" />
<div className="relative">
<ArrowRight
<RefreshCw
className="w-6 h-6 text-[var(--muted-foreground)] group-hover:text-[var(--primary)] transition-all duration-500 group-hover:rotate-180"
/>
</div>

View File

@@ -2,6 +2,7 @@
import { Card, CardHeader, CardContent } from '@/components/ui/Card';
import { WeeklyMetrics } from '@/hooks/use-metrics';
import { Emoji } from '@/components/ui/Emoji';
interface MetricsOverviewProps {
metrics: WeeklyMetrics;
@@ -10,26 +11,26 @@ interface MetricsOverviewProps {
export function MetricsOverview({ metrics }: MetricsOverviewProps) {
const getTrendIcon = (trend: string) => {
switch (trend) {
case 'improving': return '📈';
case 'declining': return '📉';
case 'stable': return '➡️';
default: return '📊';
case 'improving': return <Emoji emoji="📈" size={24} />;
case 'declining': return <Emoji emoji="📉" size={24} />;
case 'stable': return <Emoji emoji="➡️" size={24} />;
default: return <Emoji emoji="📊" size={24} />;
}
};
const getPatternIcon = (pattern: string) => {
switch (pattern) {
case 'consistent': return '🎯';
case 'variable': return '📊';
case 'weekend-heavy': return '📅';
default: return '📋';
case 'consistent': return <Emoji emoji="🎯" size={24} />;
case 'variable': return <Emoji emoji="📊" size={24} />;
case 'weekend-heavy': return <Emoji emoji="📅" size={24} />;
default: return <Emoji emoji="📋" size={24} />;
}
};
return (
<Card>
<CardHeader>
<h3 className="text-lg font-semibold">🎯 Vue d&apos;ensemble</h3>
<h3 className="text-lg font-semibold"><Emoji emoji="🎯" size={18} /> Vue d&apos;ensemble</h3>
</CardHeader>
<CardContent>
<div className="grid grid-cols-2 lg:grid-cols-5 gap-4">

View File

@@ -1,6 +1,7 @@
'use client';
import { DailyMetrics } from '@/services/analytics/metrics';
import { Emoji } from '@/components/ui/Emoji';
interface ProductivityInsightsProps {
data: DailyMetrics[];
@@ -46,24 +47,24 @@ export function ProductivityInsights({ data, className }: ProductivityInsightsPr
const getTrendIcon = () => {
switch (trend) {
case 'up': return { icon: '📈', color: 'text-green-600', label: 'En amélioration' };
case 'down': return { icon: '📉', color: 'text-red-600', label: 'En baisse' };
default: return { icon: '➡️', color: 'text-blue-600', label: 'Stable' };
case 'up': return { icon: <Emoji emoji="📈" size={24} />, color: 'text-green-600', label: 'En amélioration' };
case 'down': return { icon: <Emoji emoji="📉" size={24} />, color: 'text-red-600', label: 'En baisse' };
default: return { icon: <Emoji emoji="➡️" size={24} />, color: 'text-blue-600', label: 'Stable' };
}
};
const getConsistencyLevel = () => {
if (consistencyScore >= 80) return { label: 'Très régulier', color: 'text-green-600', icon: '🎯' };
if (consistencyScore >= 60) return { label: 'Assez régulier', color: 'text-blue-600', icon: '📊' };
if (consistencyScore >= 40) return { label: 'Variable', color: 'text-yellow-600', icon: '📊' };
return { label: 'Très variable', color: 'text-red-600', icon: '📊' };
if (consistencyScore >= 80) return { label: 'Très régulier', color: 'text-green-600', icon: <Emoji emoji="🎯" size={24} /> };
if (consistencyScore >= 60) return { label: 'Assez régulier', color: 'text-blue-600', icon: <Emoji emoji="📊" size={24} /> };
if (consistencyScore >= 40) return { label: 'Variable', color: 'text-yellow-600', icon: <Emoji emoji="📊" size={24} /> };
return { label: 'Très variable', color: 'text-red-600', icon: <Emoji emoji="📊" size={24} /> };
};
const getRatioStatus = () => {
if (creationRatio >= 100) return { label: 'Équilibré+', color: 'text-green-600', icon: '⚖️' };
if (creationRatio >= 80) return { label: 'Bien équilibré', color: 'text-blue-600', icon: '⚖️' };
if (creationRatio >= 60) return { label: 'Légèrement en retard', color: 'text-yellow-600', icon: '⚖️' };
return { label: 'Accumulation', color: 'text-red-600', icon: '⚖️' };
if (creationRatio >= 100) return { label: 'Équilibré+', color: 'text-green-600', icon: <Emoji emoji="⚖️" size={24} /> };
if (creationRatio >= 80) return { label: 'Bien équilibré', color: 'text-blue-600', icon: <Emoji emoji="⚖️" size={24} /> };
if (creationRatio >= 60) return { label: 'Légèrement en retard', color: 'text-yellow-600', icon: <Emoji emoji="⚖️" size={24} /> };
return { label: 'Accumulation', color: 'text-red-600', icon: <Emoji emoji="⚖️" size={24} /> };
};
const trendInfo = getTrendIcon();
@@ -79,7 +80,7 @@ export function ProductivityInsights({ data, className }: ProductivityInsightsPr
<div className="outline-card-green p-4">
<div className="flex items-center justify-between mb-2">
<h4 className="font-medium">
🏆 Jour champion
<Emoji emoji="🏆" size={16} /> Jour champion
</h4>
<span className="text-2xl font-bold">
{mostProductiveDay.completed}
@@ -97,7 +98,7 @@ export function ProductivityInsights({ data, className }: ProductivityInsightsPr
<div className="outline-card-blue p-4">
<div className="flex items-center justify-between mb-2">
<h4 className="font-medium">
💡 Jour créatif
<Emoji emoji="💡" size={16} /> Jour créatif
</h4>
<span className="text-2xl font-bold">
{mostCreativeDay.newTasks}
@@ -164,7 +165,7 @@ export function ProductivityInsights({ data, className }: ProductivityInsightsPr
{/* Recommandations */}
<div className="outline-card-yellow p-4">
<h4 className="font-medium mb-2 flex items-center gap-2">
💡 Recommandations
<Emoji emoji="💡" size={16} /> Recommandations
</h4>
<div className="space-y-1 text-sm">
{trend === 'down' && (

View File

@@ -2,6 +2,7 @@ import { DeadlineMetrics } from '@/services/analytics/deadline-analytics';
import { DeadlineRiskCard } from './DeadlineRiskCard';
import { CriticalDeadlinesCard } from './CriticalDeadlinesCard';
import { DeadlineSummaryCard } from './DeadlineSummaryCard';
import { Emoji } from '@/components/ui/Emoji';
interface DeadlineOverviewProps {
metrics: DeadlineMetrics;
@@ -13,7 +14,7 @@ export function DeadlineOverview({ metrics }: DeadlineOverviewProps) {
<div className="space-y-6">
{/* Titre de section */}
<div className="flex items-center justify-between">
<h2 className="text-2xl font-bold">🚨 Échéances Critiques</h2>
<h2 className="text-2xl font-bold"><Emoji emoji="🚨" size={25} /> Échéances Critiques</h2>
<div className="text-sm text-[var(--muted-foreground)]">
Surveillance temps réel
</div>

View File

@@ -2,6 +2,7 @@
import { DeadlineMetrics, DeadlineAnalyticsService } from '@/services/analytics/deadline-analytics';
import { Card } from '@/components/ui/Card';
import { Emoji } from '@/components/ui/Emoji';
interface DeadlineRiskCardProps {
metrics: DeadlineMetrics;
@@ -44,7 +45,7 @@ export function DeadlineRiskCard({ metrics }: DeadlineRiskCardProps) {
<Card variant="glass" className={`p-6 ${getRiskBgColor(riskAnalysis.riskLevel)} transition-all hover:shadow-lg`}>
<div className="flex items-center justify-between mb-4">
<div className="flex items-center gap-2">
<span className="text-2xl">{getRiskIcon(riskAnalysis.riskLevel)}</span>
<span className="text-2xl"><Emoji emoji={getRiskIcon(riskAnalysis.riskLevel)} size={25} /></span>
<h3 className="text-lg font-semibold">Niveau de Risque</h3>
</div>
<div className="text-3xl font-bold" style={getRiskColor(riskAnalysis.riskLevel)}>

View File

@@ -2,6 +2,7 @@
import { DeadlineMetrics } from '@/services/analytics/deadline-analytics';
import { Card } from '@/components/ui/Card';
import { Emoji } from '@/components/ui/Emoji';
interface DeadlineSummaryCardProps {
metrics: DeadlineMetrics;
@@ -55,7 +56,7 @@ export function DeadlineSummaryCard({ metrics }: DeadlineSummaryCardProps) {
<div key={index} className="flex items-center justify-between">
<div className="flex items-center gap-3">
<div className="w-8 h-8 rounded-full flex items-center justify-center text-sm" style={{ backgroundColor: item.bgColor }}>
{item.icon}
<Emoji emoji={item.icon} />
</div>
<span className="text-sm font-medium">{item.label}</span>
</div>

View File

@@ -6,6 +6,7 @@ import { JiraAdvancedFiltersService } from '@/services/integrations/jira/advance
import { Button } from '@/components/ui/Button';
import { Badge } from '@/components/ui/Badge';
import { Modal } from '@/components/ui/Modal';
import { Emoji } from '@/components/ui/Emoji';
interface FilterBarProps {
availableFilters: AvailableFilters;
@@ -64,10 +65,10 @@ export default function FilterBar({
<div className="flex items-center justify-between gap-4">
<div className="flex items-center gap-3">
<div className="flex items-center gap-2">
<span className="text-sm font-medium text-[var(--foreground)]">🔍 Filtres</span>
<span className="text-sm font-medium text-[var(--foreground)]"><Emoji emoji="🔍" size={14} /> Filtres</span>
{isLoading && (
<Badge className="bg-yellow-100 text-yellow-800 text-xs">
Chargement...
<Emoji emoji="⏳" size={12} /> Chargement...
</Badge>
)}
{hasActiveFilters && !isLoading && (
@@ -86,7 +87,7 @@ export default function FilterBar({
className="bg-purple-100 text-purple-800 text-xs cursor-pointer hover:bg-purple-200 transition-colors"
onClick={() => removeFilter('components', comp)}
>
📦 {comp} ×
<Emoji emoji="📦" size={12} /> {comp} ×
</Badge>
))}
{activeFilters.fixVersions?.slice(0, 2).map(version => (
@@ -95,7 +96,7 @@ export default function FilterBar({
className="bg-green-100 text-green-800 text-xs cursor-pointer hover:bg-green-200 transition-colors"
onClick={() => removeFilter('fixVersions', version)}
>
🏷 {version} ×
<Emoji emoji="🏷️" size={12} /> {version} ×
</Badge>
))}
{activeFilters.issueTypes?.slice(0, 3).map(type => (
@@ -104,7 +105,7 @@ export default function FilterBar({
className="bg-orange-100 text-orange-800 text-xs cursor-pointer hover:bg-orange-200 transition-colors"
onClick={() => removeFilter('issueTypes', type)}
>
📋 {type} ×
<Emoji emoji="📋" size={12} /> {type} ×
</Badge>
))}
{activeFilters.statuses?.slice(0, 2).map(status => (
@@ -113,7 +114,7 @@ export default function FilterBar({
className="bg-blue-100 text-blue-800 text-xs cursor-pointer hover:bg-blue-200 transition-colors"
onClick={() => removeFilter('statuses', status)}
>
🔄 {status} ×
<Emoji emoji="🔄" size={12} /> {status} ×
</Badge>
))}
{activeFilters.assignees?.slice(0, 2).map(assignee => (
@@ -122,7 +123,7 @@ export default function FilterBar({
className="bg-yellow-100 text-yellow-800 text-xs cursor-pointer hover:bg-yellow-200 transition-colors"
onClick={() => removeFilter('assignees', assignee)}
>
👤 {assignee} ×
<Emoji emoji="👤" size={12} /> {assignee} ×
</Badge>
))}
@@ -189,7 +190,7 @@ export default function FilterBar({
<div className="grid grid-cols-1 md:grid-cols-2 gap-6 max-h-96 overflow-y-auto">
{/* Types de tickets */}
<div>
<h4 className="font-medium text-sm mb-3">📋 Types de tickets</h4>
<h4 className="font-medium text-sm mb-3"><Emoji emoji="📋" size={14} /> Types de tickets</h4>
<div className="space-y-1 max-h-32 overflow-y-auto">
{availableFilters.issueTypes.map(option => (
<label
@@ -220,7 +221,7 @@ export default function FilterBar({
{/* Statuts */}
<div>
<h4 className="font-medium text-sm mb-3">🔄 Statuts</h4>
<h4 className="font-medium text-sm mb-3"><Emoji emoji="🔄" size={14} /> Statuts</h4>
<div className="space-y-1 max-h-32 overflow-y-auto">
{availableFilters.statuses.map(option => (
<label
@@ -251,7 +252,7 @@ export default function FilterBar({
{/* Assignés */}
<div>
<h4 className="font-medium text-sm mb-3">👤 Assignés</h4>
<h4 className="font-medium text-sm mb-3"><Emoji emoji="👤" size={14} /> Assignés</h4>
<div className="space-y-1 max-h-32 overflow-y-auto">
{availableFilters.assignees.map(option => (
<label
@@ -282,7 +283,7 @@ export default function FilterBar({
{/* Composants */}
<div>
<h4 className="font-medium text-sm mb-3">📦 Composants</h4>
<h4 className="font-medium text-sm mb-3"><Emoji emoji="📦" size={14} /> Composants</h4>
<div className="space-y-1 max-h-32 overflow-y-auto">
{availableFilters.components.map(option => (
<label
@@ -318,21 +319,21 @@ export default function FilterBar({
variant="secondary"
className="flex-1"
>
Annuler
<Emoji emoji="❌" size={14} /> Annuler
</Button>
<Button
onClick={clearAllFilters}
variant="secondary"
className="flex-1"
>
🗑 Effacer tout
<Emoji emoji="🗑️" size={14} /> Effacer tout
</Button>
<Button
onClick={applyPendingFilters}
className="flex-1"
disabled={isLoading}
>
{isLoading ? '⏳ Application...' : '✅ Appliquer'}
{isLoading ? <><Emoji emoji="⏳" size={14} /> Application...</> : <><Emoji emoji="✅" size={14} /> Appliquer</>}
</Button>
</div>
</Modal>

View File

@@ -8,6 +8,7 @@ import { getToday } from '@/lib/date-utils';
import { Modal } from '@/components/ui/Modal';
import { jiraClient } from '@/clients/jira-client';
import { JiraSyncResult, JiraSyncAction } from '@/services/integrations/jira/jira';
import { Emoji } from '@/components/ui/Emoji';
interface JiraSyncProps {
onSyncComplete?: () => void;
@@ -77,7 +78,7 @@ export function JiraSync({ onSyncComplete, className = "" }: JiraSyncProps) {
<div className="flex items-center justify-between">
<div className="flex items-center gap-2">
<Badge variant={success ? "success" : "danger"} size="sm">
{success ? "✓ Succès" : "⚠ Erreurs"}
{success ? <><Emoji emoji="✓" /> Succès</> : <><Emoji emoji="⚠" /> Erreurs</>}
</Badge>
<span className="text-[var(--muted-foreground)] text-xs">
{getToday().toLocaleTimeString()}
@@ -145,16 +146,16 @@ export function JiraSync({ onSyncComplete, className = "" }: JiraSyncProps) {
{unknownStatuses.length > 0 && (
<div className="p-2 bg-[var(--accent)]/10 border border-[var(--accent)]/20 rounded text-xs">
<div className="font-semibold text-[var(--accent)] mb-1 flex items-center gap-1">
Statuts inconnus ({unknownStatuses.length})
<Emoji emoji="⚠️" /> Statuts inconnus ({unknownStatuses.length})
</div>
<div className="text-[var(--muted-foreground)] mb-2 text-xs">
Ces statuts ont é mappés vers "todo" par défaut :
Ces statuts ont é mappés vers &quot;todo&quot; par défaut :
</div>
<div className="space-y-1 max-h-20 overflow-y-auto">
{unknownStatuses.map((status, i) => (
<div key={i} className="text-[var(--accent)] font-mono text-xs flex items-center gap-1">
<span className="text-[var(--muted-foreground)]"></span>
"{status}"
&quot;{status}&quot;
</div>
))}
</div>
@@ -211,7 +212,9 @@ export function JiraSync({ onSyncComplete, className = "" }: JiraSyncProps) {
Sync...
</div>
) : (
'🔄 Synchroniser'
<>
<Emoji emoji="🔄" />&nbsp;Synchroniser
</>
)}
</Button>
</div>
@@ -240,7 +243,7 @@ export function JiraSync({ onSyncComplete, className = "" }: JiraSyncProps) {
<Modal
isOpen={showDetails}
onClose={() => setShowDetails(false)}
title="📋 DÉTAILS DE SYNCHRONISATION"
title={<><Emoji emoji="📋" /> DÉTAILS DE SYNCHRONISATION</>}
size="xl"
>
<div className="space-y-4">
@@ -253,7 +256,7 @@ export function JiraSync({ onSyncComplete, className = "" }: JiraSyncProps) {
<SyncActionsList actions={lastSyncResult.actions || []} />
) : (
<div className="text-center py-8 text-[var(--muted-foreground)]">
<div className="text-2xl mb-2">📝</div>
<div className="text-2xl mb-2"><Emoji emoji="📝" size={32} /></div>
<div>Aucun détail disponible pour cette synchronisation</div>
<div className="text-sm mt-1">Les détails sont disponibles pour les nouvelles synchronisations</div>
</div>
@@ -270,11 +273,11 @@ export function JiraSync({ onSyncComplete, className = "" }: JiraSyncProps) {
function SyncActionsList({ actions }: { actions: JiraSyncAction[] }) {
const getActionIcon = (type: JiraSyncAction['type']) => {
switch (type) {
case 'created': return '';
case 'updated': return '🔄';
case 'skipped': return '⏭️';
case 'deleted': return '🗑️';
default: return '❓';
case 'created': return <Emoji emoji="" />;
case 'updated': return <Emoji emoji="🔄" />;
case 'skipped': return <Emoji emoji="⏭️" />;
case 'deleted': return <Emoji emoji="🗑️" />;
default: return <Emoji emoji="❓" />;
}
};
@@ -335,7 +338,7 @@ function SyncActionsList({ actions }: { actions: JiraSyncAction[] }) {
{action.reason && (
<div className="mt-1 text-xs text-[var(--muted-foreground)] italic">
💡 {action.reason}
<Emoji emoji="💡" /> {action.reason}
</div>
)}

View File

@@ -2,6 +2,7 @@
import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer, BarChart, Bar, Cell } from 'recharts';
import { SprintVelocity } from '@/lib/types';
import { Emoji } from '@/components/ui/Emoji';
interface PredictabilityMetricsProps {
sprintHistory: SprintVelocity[];
@@ -192,7 +193,7 @@ export function PredictabilityMetrics({ sprintHistory, className }: Predictabili
<div className="text-center p-3 bg-[var(--card)] rounded-lg border border-[var(--border)]">
<div className={`text-lg font-bold ${trend > 5 ? 'text-green-500' : trend < -5 ? 'text-red-500' : 'text-blue-500'}`}>
{trend > 0 ? '↗️' : trend < 0 ? '↘️' : '→'} {Math.abs(Math.round(trend))}%
{trend > 0 ? <Emoji emoji="↗️" size={18} /> : trend < 0 ? <Emoji emoji="↘️" size={18} /> : <Emoji emoji="→" size={18} />} {Math.abs(Math.round(trend))}%
</div>
<div className="text-xs text-[var(--muted-foreground)]">
Tendance récente
@@ -206,31 +207,31 @@ export function PredictabilityMetrics({ sprintHistory, className }: Predictabili
<div className="space-y-2 text-sm">
{averageAccuracy > 80 && (
<div className="flex items-center gap-2" style={{ color: 'var(--green)' }}>
<span></span>
<Emoji emoji="✅" size={16} />
<span>Excellente predictabilité - L&apos;équipe estime bien sa capacité</span>
</div>
)}
{averageAccuracy < 60 && (
<div className="flex items-center gap-2" style={{ color: 'var(--destructive)' }}>
<span></span>
<Emoji emoji="⚠️" size={16} />
<span>Predictabilité faible - Revoir les méthodes d&apos;estimation</span>
</div>
)}
{averageVariance > 25 && (
<div className="flex items-center gap-2" style={{ color: 'var(--accent)' }}>
<span>📊</span>
<Emoji emoji="📊" size={16} />
<span>Variance élevée - Considérer des sprints plus courts ou un meilleur découpage</span>
</div>
)}
{trend > 10 && (
<div className="flex items-center gap-2" style={{ color: 'var(--green)' }}>
<span>📈</span>
<Emoji emoji="📈" size={16} />
<span>Tendance positive - L&apos;équipe s&apos;améliore dans ses estimations</span>
</div>
)}
{trend < -10 && (
<div className="flex items-center gap-2" style={{ color: 'var(--destructive)' }}>
<span>📉</span>
<Emoji emoji="📉" size={16} />
<span>Tendance négative - Attention aux changements récents (équipe, processus)</span>
</div>
)}

View File

@@ -3,6 +3,7 @@
import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer, BarChart, Bar } from 'recharts';
import { SprintVelocity } from '@/lib/types';
import { Card } from '@/components/ui/Card';
import { Emoji } from '@/components/ui/Emoji';
interface SprintComparisonProps {
sprintHistory: SprintVelocity[];
@@ -132,8 +133,8 @@ export function SprintComparison({ sprintHistory, className }: SprintComparisonP
metrics.velocityTrend === 'improving' ? 'text-green-500' :
metrics.velocityTrend === 'declining' ? 'text-red-500' : 'text-blue-500'
}`}>
{metrics.velocityTrend === 'improving' ? '📈' :
metrics.velocityTrend === 'declining' ? '📉' : '➡️'}
{metrics.velocityTrend === 'improving' ? <Emoji emoji="📈" size={18} /> :
metrics.velocityTrend === 'declining' ? <Emoji emoji="📉" size={18} /> : <Emoji emoji="➡️" size={18} />}
</div>
<div className="text-xs text-[var(--muted-foreground)] mt-1">
Tendance générale
@@ -188,7 +189,7 @@ export function SprintComparison({ sprintHistory, className }: SprintComparisonP
{/* Insights et recommandations */}
<div className="mt-6 grid grid-cols-1 lg:grid-cols-2 gap-4">
<Card className="p-4">
<h4 className="text-sm font-medium mb-3">🏆 Meilleur sprint</h4>
<h4 className="text-sm font-medium mb-3"><Emoji emoji="🏆" size={16} /> Meilleur sprint</h4>
<div className="space-y-2 text-sm">
<div className="flex justify-between">
<span>Sprint:</span>
@@ -206,7 +207,7 @@ export function SprintComparison({ sprintHistory, className }: SprintComparisonP
</Card>
<Card className="p-4">
<h4 className="text-sm font-medium mb-3">📉 Sprint à améliorer</h4>
<h4 className="text-sm font-medium mb-3"><Emoji emoji="📉" size={16} /> Sprint à améliorer</h4>
<div className="space-y-2 text-sm">
<div className="flex justify-between">
<span>Sprint:</span>
@@ -226,7 +227,7 @@ export function SprintComparison({ sprintHistory, className }: SprintComparisonP
{/* Recommandations */}
<Card className="mt-4 p-4">
<h4 className="text-sm font-medium mb-2">💡 Recommandations</h4>
<h4 className="text-sm font-medium mb-2"><Emoji emoji="💡" size={16} /> Recommandations</h4>
<div className="space-y-2 text-sm">
{getRecommendations(metrics).map((recommendation, index) => (
<div key={index} className="flex items-start gap-2">

View File

@@ -2,6 +2,7 @@
import { Card, CardHeader, CardContent } from '@/components/ui/Card';
import { SprintDetails } from '../SprintDetailModal';
import { Emoji } from '@/components/ui/Emoji';
interface SprintMetricsProps {
sprintDetails: SprintDetails;
@@ -33,10 +34,10 @@ export function SprintMetrics({ sprintDetails }: SprintMetricsProps) {
const getVelocityTrendIcon = (trend: string) => {
switch (trend) {
case 'up': return '📈';
case 'down': return '📉';
case 'stable': return '➡️';
default: return '📊';
case 'up': return <Emoji emoji="📈" size={24} />;
case 'down': return <Emoji emoji="📉" size={24} />;
case 'stable': return <Emoji emoji="➡️" size={24} />;
default: return <Emoji emoji="📊" size={24} />;
}
};
@@ -45,7 +46,7 @@ export function SprintMetrics({ sprintDetails }: SprintMetricsProps) {
{/* Métriques de performance */}
<Card>
<CardHeader>
<h3 className="font-semibold"> Métriques de performance</h3>
<h3 className="font-semibold"><Emoji emoji="⚡" size={16} /> Métriques de performance</h3>
</CardHeader>
<CardContent>
<div className="grid grid-cols-2 lg:grid-cols-4 gap-4">
@@ -78,7 +79,7 @@ export function SprintMetrics({ sprintDetails }: SprintMetricsProps) {
{/* Répartition par statut avec pourcentages */}
<Card>
<CardHeader>
<h3 className="font-semibold">📊 Analyse des statuts</h3>
<h3 className="font-semibold"><Emoji emoji="📊" size={16} /> Analyse des statuts</h3>
</CardHeader>
<CardContent>
<div className="space-y-3">
@@ -112,7 +113,7 @@ export function SprintMetrics({ sprintDetails }: SprintMetricsProps) {
{/* Charge de travail par assigné */}
<Card>
<CardHeader>
<h3 className="font-semibold">👥 Charge de travail</h3>
<h3 className="font-semibold"><Emoji emoji="👥" size={16} /> Charge de travail</h3>
</CardHeader>
<CardContent>
<div className="space-y-2">
@@ -151,7 +152,7 @@ export function SprintMetrics({ sprintDetails }: SprintMetricsProps) {
{/* Insights et recommandations */}
<Card>
<CardHeader>
<h3 className="font-semibold">💡 Insights & Recommandations</h3>
<h3 className="font-semibold"><Emoji emoji="💡" size={16} /> Insights & Recommandations</h3>
</CardHeader>
<CardContent>
<div className="space-y-3">
@@ -159,7 +160,7 @@ export function SprintMetrics({ sprintDetails }: SprintMetricsProps) {
{parseFloat(completionRate) >= 80 && (
<div className="p-3 bg-green-50 border border-green-200 rounded">
<div className="flex items-center gap-2 mb-1">
<span className="text-green-600"></span>
<Emoji emoji="✅" size={16} />
<span className="font-medium text-green-800">Excellent taux de completion</span>
</div>
<p className="text-sm text-green-700">
@@ -171,7 +172,7 @@ export function SprintMetrics({ sprintDetails }: SprintMetricsProps) {
{parseFloat(completionRate) < 60 && (
<div className="p-3 bg-orange-50 border border-orange-200 rounded">
<div className="flex items-center gap-2 mb-1">
<span className="text-orange-600"></span>
<Emoji emoji="⚠️" size={16} />
<span className="font-medium text-orange-800">Taux de completion faible</span>
</div>
<p className="text-sm text-orange-700">
@@ -184,7 +185,7 @@ export function SprintMetrics({ sprintDetails }: SprintMetricsProps) {
{parseFloat(blockedRate) > 20 && (
<div className="p-3 bg-red-50 border border-red-200 rounded">
<div className="flex items-center gap-2 mb-1">
<span className="text-red-600">🚨</span>
<Emoji emoji="🚨" size={16} />
<span className="font-medium text-red-800">Trop d&apos;issues bloquées</span>
</div>
<p className="text-sm text-red-700">
@@ -197,7 +198,7 @@ export function SprintMetrics({ sprintDetails }: SprintMetricsProps) {
{assigneeDistribution.length > 0 && (
<div className="p-3 bg-blue-50 border border-blue-200 rounded">
<div className="flex items-center gap-2 mb-1">
<span className="text-blue-600">📊</span>
<Emoji emoji="📊" size={16} />
<span className="font-medium text-blue-800">Répartition de la charge</span>
</div>
<p className="text-sm text-blue-700">

View File

@@ -4,6 +4,7 @@ import { Card, CardHeader, CardContent } from '@/components/ui/Card';
import { Badge } from '@/components/ui/Badge';
import { SprintDetails } from '../SprintDetailModal';
import { formatDateForDisplay } from '@/lib/date-utils';
import { Emoji } from '@/components/ui/Emoji';
interface SprintOverviewProps {
sprintDetails: SprintDetails;
@@ -14,10 +15,10 @@ export function SprintOverview({ sprintDetails }: SprintOverviewProps) {
const getVelocityTrendIcon = (trend: string) => {
switch (trend) {
case 'up': return '📈';
case 'down': return '📉';
case 'stable': return '➡️';
default: return '📊';
case 'up': return <Emoji emoji="📈" size={16} />;
case 'down': return <Emoji emoji="📉" size={16} />;
case 'stable': return <Emoji emoji="➡️" size={16} />;
default: return <Emoji emoji="📊" size={16} />;
}
};
@@ -26,7 +27,7 @@ export function SprintOverview({ sprintDetails }: SprintOverviewProps) {
{/* Informations générales */}
<Card>
<CardHeader>
<h3 className="font-semibold">📋 Informations générales</h3>
<h3 className="font-semibold"><Emoji emoji="📋" size={16} /> Informations générales</h3>
</CardHeader>
<CardContent>
<div className="grid grid-cols-2 gap-4">
@@ -58,7 +59,7 @@ export function SprintOverview({ sprintDetails }: SprintOverviewProps) {
{/* Métriques clés */}
<Card>
<CardHeader>
<h3 className="font-semibold">📊 Métriques clés</h3>
<h3 className="font-semibold"><Emoji emoji="📊" size={16} /> Métriques clés</h3>
</CardHeader>
<CardContent>
<div className="grid grid-cols-2 lg:grid-cols-4 gap-4">
@@ -86,7 +87,7 @@ export function SprintOverview({ sprintDetails }: SprintOverviewProps) {
<div className="grid grid-cols-1 lg:grid-cols-2 gap-4">
<Card>
<CardHeader>
<h3 className="font-semibold">👥 Répartition par assigné</h3>
<h3 className="font-semibold"><Emoji emoji="👥" size={16} /> Répartition par assigné</h3>
</CardHeader>
<CardContent>
<div className="space-y-2">
@@ -107,7 +108,7 @@ export function SprintOverview({ sprintDetails }: SprintOverviewProps) {
{/* Répartition par statut */}
<Card>
<CardHeader>
<h3 className="font-semibold">📈 Répartition par statut</h3>
<h3 className="font-semibold"><Emoji emoji="📈" size={16} /> Répartition par statut</h3>
</CardHeader>
<CardContent>
<div className="space-y-2">

View File

@@ -3,6 +3,7 @@
import { useMemo } from 'react';
import { Task, TaskStatus } from '@/lib/types';
import { getAllStatuses } from '@/lib/status-config';
import { Eye, EyeOff } from 'lucide-react';
interface ColumnVisibilityToggleProps {
hiddenStatuses: Set<TaskStatus>;
@@ -44,7 +45,7 @@ export function ColumnVisibilityToggle({
}`}
title={hiddenStatuses.has(statusConfig.key) ? `Afficher ${statusConfig.label}` : `Masquer ${statusConfig.label}`}
>
{hiddenStatuses.has(statusConfig.key) ? '👁️‍🗨️' : '👁️'} {statusConfig.label}{statusCounts[statusConfig.key] ? ` (${statusCounts[statusConfig.key]})` : ''}
{hiddenStatuses.has(statusConfig.key) ? <EyeOff size={14} /> : <Eye size={14} />} {statusConfig.label}{statusCounts[statusConfig.key] ? ` (${statusCounts[statusConfig.key]})` : ''}
</button>
))}
</div>

View File

@@ -5,6 +5,7 @@ import { TaskPriority, TaskStatus } from '@/lib/types';
import { SearchInput, ToggleButton, ControlPanel, ControlSection, ControlGroup, FilterSummary, Dropdown } from '@/components/ui';
import { useTasksContext } from '@/contexts/TasksContext';
import { SORT_OPTIONS } from '@/lib/sort-config';
import { SortIcon } from '@/components/ui/SortIcon';
import { useUserPreferences } from '@/contexts/UserPreferencesContext';
import { useIsMobile } from '@/hooks/useIsMobile';
import { JiraFilters } from './filters/JiraFilters';
@@ -13,7 +14,7 @@ import { PriorityFilters } from './filters/PriorityFilters';
import { TagFilters } from './filters/TagFilters';
import { GeneralFilters } from './filters/GeneralFilters';
import { ColumnFilters } from './filters/ColumnFilters';
import { Layout, Grid3X3 } from 'lucide-react';
import { Layout, Grid3X3, Menu } from 'lucide-react';
import type { KanbanFilters } from '@/lib/types';
@@ -154,31 +155,34 @@ export function KanbanFilters({ filters, onFiltersChange, hiddenStatuses: propsH
{/* Bouton de tri */}
<Dropdown
open={isSortOpen}
onOpenChange={setIsSortOpen}
trigger="☰ Tris"
content={
<div className="max-h-64 overflow-y-auto">
{SORT_OPTIONS.map((option) => (
<button
key={option.key}
onClick={() => handleSortChange(option.key)}
className={`w-full px-3 py-2 text-left text-xs font-mono hover:bg-[var(--card-hover)] transition-colors flex items-center gap-2 ${
(filters.sortBy || 'priority-desc') === option.key
? 'bg-cyan-600/20 text-cyan-400 border-l-2 border-cyan-400'
: 'text-[var(--muted-foreground)]'
}`}
>
<span className="text-base">{option.icon}</span>
<span>{option.label}</span>
</button>
))}
</div>
}
placement="bottom-start"
className="w-80"
/>
<ControlGroup>
<Dropdown
open={isSortOpen}
onOpenChange={setIsSortOpen}
trigger={<><Menu size={14} className="inline mr-1" /> Tris</>}
triggerClassName="h-[34px] py-[5px]"
content={
<div className="max-h-64 overflow-y-auto">
{SORT_OPTIONS.map((option) => (
<button
key={option.key}
onClick={() => handleSortChange(option.key)}
className={`w-full px-3 py-2 text-left text-xs font-mono hover:bg-[var(--card-hover)] transition-colors flex items-center gap-2 ${
(filters.sortBy || 'priority-desc') === option.key
? 'bg-cyan-600/20 text-cyan-400 border-l-2 border-cyan-400'
: 'text-[var(--muted-foreground)]'
}`}
>
<span className="text-base"><SortIcon iconName={option.iconName} size={16} /></span>
<span>{option.label}</span>
</button>
))}
</div>
}
placement="bottom-start"
className="w-80"
/>
</ControlGroup>
</ControlSection>

View File

@@ -19,6 +19,7 @@ import {
verticalListSortingStrategy,
} from '@dnd-kit/sortable';
import { useDroppable } from '@dnd-kit/core';
import { Emoji } from '@/components/ui/Emoji';
interface ObjectivesBoardProps {
tasks: Task[];
@@ -65,7 +66,7 @@ function DroppableColumn({
{tasks.length === 0 ? (
<div className="text-center py-8 text-[var(--muted-foreground)] text-sm">
<div className="text-2xl mb-2">{icon}</div>
<div className="text-2xl mb-2"><Emoji emoji={icon} size={24} /></div>
Aucun objectif {title.toLowerCase()}
</div>
) : (
@@ -137,7 +138,7 @@ export function ObjectivesBoard({
>
<div className="w-3 h-3 bg-[var(--accent)] rounded-full animate-pulse shadow-[var(--accent)]/50 shadow-lg"></div>
<h2 className="text-lg font-mono font-bold text-[var(--accent)] uppercase tracking-wider">
🎯 Objectifs Principaux
<Emoji emoji="🎯" size={20} /> Objectifs Principaux
</h2>
{pinnedTagName && (
<Badge variant="outline" className="border-[var(--accent)]/50 text-[var(--accent)]">

View File

@@ -3,6 +3,7 @@
import { TaskStatus, Task } from '@/lib/types';
import { getAllStatuses } from '@/lib/status-config';
import { FilterChip } from '@/components/ui';
import { Eye, EyeOff } from 'lucide-react';
interface ColumnFiltersProps {
hiddenStatuses: Set<TaskStatus>;
@@ -26,7 +27,7 @@ export function ColumnFilters({ hiddenStatuses, onToggleStatus, tasks }: ColumnF
variant={hiddenStatuses.has(statusConfig.key) ? 'hidden' : 'default'}
count={statusCount}
icon={
hiddenStatuses.has(statusConfig.key) ? '👁️‍🗨️' : '👁️'
hiddenStatuses.has(statusConfig.key) ? <EyeOff size={16} /> : <Eye size={16} />
}
title={hiddenStatuses.has(statusConfig.key) ? `Afficher ${statusConfig.label}` : `Masquer ${statusConfig.label}`}
>

View File

@@ -4,6 +4,7 @@ import { useMemo } from 'react';
import { Button, FilterChip } from '@/components/ui';
import { useTasksContext } from '@/contexts/TasksContext';
import type { KanbanFilters } from '@/lib/types';
import { Plug, Circle, X, ClipboardList, Sparkles, BookOpen, FileText, Bug, Wrench, Settings } from 'lucide-react';
interface JiraFiltersProps {
filters: KanbanFilters;
@@ -118,7 +119,7 @@ export function JiraFilters({ filters, onFiltersChange }: JiraFiltersProps) {
<div className="border-t border-[var(--border)]/30 pt-4 mt-4">
<div className="flex items-center gap-4 mb-3">
<label className="block text-xs font-mono font-medium text-[var(--muted-foreground)] uppercase tracking-wider">
🔌 Jira
<Plug size={12} className="inline mr-1" /> Jira
</label>
{/* Toggle Jira Show/Hide - inline avec le titre */}
@@ -129,7 +130,7 @@ export function JiraFilters({ filters, onFiltersChange }: JiraFiltersProps) {
size="sm"
className="text-xs px-2 py-1 h-auto"
>
🔹 Seul
<Circle size={12} className="inline mr-1" /> Seul
</Button>
<Button
variant={filters.hideJiraTasks ? "danger" : "ghost"}
@@ -137,7 +138,7 @@ export function JiraFilters({ filters, onFiltersChange }: JiraFiltersProps) {
size="sm"
className="text-xs px-2 py-1 h-auto"
>
🚫 Mask
<X size={12} className="inline mr-1" /> Mask
</Button>
<Button
variant={(!filters.showJiraOnly && !filters.hideJiraTasks) ? "primary" : "ghost"}
@@ -145,7 +146,7 @@ export function JiraFilters({ filters, onFiltersChange }: JiraFiltersProps) {
size="sm"
className="text-xs px-2 py-1 h-auto"
>
📋 All
<ClipboardList size={12} className="inline mr-1" /> All
</Button>
</div>
</div>
@@ -165,7 +166,7 @@ export function JiraFilters({ filters, onFiltersChange }: JiraFiltersProps) {
onClick={() => handleJiraProjectToggle(project)}
variant={filters.jiraProjects?.includes(project) ? 'selected' : 'default'}
count={jiraProjectCounts[project]}
icon="📋"
icon={<ClipboardList size={14} />}
>
{project}
</FilterChip>
@@ -184,13 +185,13 @@ export function JiraFilters({ filters, onFiltersChange }: JiraFiltersProps) {
{availableJiraTypes.map((type) => {
const getTypeIcon = (type: string) => {
switch (type) {
case 'Feature': return '✨';
case 'Story': return '📖';
case 'Task': return '📝';
case 'Bug': return '🐛';
case 'Support': return '🛠️';
case 'Enabler': return '🔧';
default: return '📋';
case 'Feature': return <Sparkles size={14} />;
case 'Story': return <BookOpen size={14} />;
case 'Task': return <FileText size={14} />;
case 'Bug': return <Bug size={14} />;
case 'Support': return <Wrench size={14} />;
case 'Enabler': return <Settings size={14} />;
default: return <ClipboardList size={14} />;
}
};

View File

@@ -4,6 +4,7 @@ import { useMemo } from 'react';
import { Button, FilterChip } from '@/components/ui';
import { useTasksContext } from '@/contexts/TasksContext';
import type { KanbanFilters } from '@/lib/types';
import { Package, Square, X, ClipboardList } from 'lucide-react';
interface TfsFiltersProps {
filters: KanbanFilters;
@@ -85,7 +86,7 @@ export function TfsFilters({ filters, onFiltersChange }: TfsFiltersProps) {
<div className="border-t border-[var(--border)]/30 pt-4 mt-4">
<div className="flex items-center gap-4 mb-3">
<label className="block text-xs font-mono font-medium text-[var(--muted-foreground)] uppercase tracking-wider">
📦 TFS
<Package size={12} className="inline mr-1" /> TFS
</label>
{/* Toggle TFS Show/Hide - inline avec le titre */}
@@ -96,7 +97,7 @@ export function TfsFilters({ filters, onFiltersChange }: TfsFiltersProps) {
size="sm"
className="text-xs px-2 py-1 h-auto"
>
🔷 Seul
<Square size={12} className="inline mr-1" /> Seul
</Button>
<Button
variant={filters.hideTfsTasks ? "danger" : "ghost"}
@@ -104,7 +105,7 @@ export function TfsFilters({ filters, onFiltersChange }: TfsFiltersProps) {
size="sm"
className="text-xs px-2 py-1 h-auto"
>
🚫 Mask
<X size={12} className="inline mr-1" /> Mask
</Button>
<Button
variant={(!filters.showTfsOnly && !filters.hideTfsTasks) ? "primary" : "ghost"}
@@ -112,7 +113,7 @@ export function TfsFilters({ filters, onFiltersChange }: TfsFiltersProps) {
size="sm"
className="text-xs px-2 py-1 h-auto"
>
📋 All
<ClipboardList size={12} className="inline mr-1" /> All
</Button>
</div>
</div>
@@ -130,7 +131,7 @@ export function TfsFilters({ filters, onFiltersChange }: TfsFiltersProps) {
onClick={() => handleTfsProjectToggle(project)}
variant={filters.tfsProjects?.includes(project) ? 'selected' : 'default'}
count={tfsProjectCounts[project]}
icon="📦"
icon={<Package size={14} />}
>
{project}
</FilterChip>

View File

@@ -8,6 +8,7 @@ import { TagsManagement } from './tags/TagsManagement';
import { ThemeSelector } from '@/components/ThemeSelector';
import { BackgroundImageSelector } from './BackgroundImageSelector';
import Link from 'next/link';
import { Emoji } from '@/components/ui/Emoji';
interface GeneralSettingsPageClientProps {
initialTags: Tag[];
@@ -41,7 +42,7 @@ export function GeneralSettingsPageClient({ initialTags }: GeneralSettingsPageCl
{/* Page Header */}
<div className="mb-6">
<h1 className="text-2xl font-mono font-bold text-[var(--foreground)] mb-2">
Paramètres généraux
<Emoji emoji="⚙️" size={24} /> Paramètres généraux
</h1>
<p className="text-[var(--muted-foreground)]">
Configuration des préférences de l&apos;interface et du comportement général
@@ -69,7 +70,7 @@ export function GeneralSettingsPageClient({ initialTags }: GeneralSettingsPageCl
<div className="flex items-center justify-between">
<div>
<h3 className="text-lg font-medium text-[var(--foreground)] mb-2">
🎨 UI Components Showcase
<Emoji emoji="🎨" size={18} /> UI Components Showcase
</h3>
<p className="text-sm text-[var(--muted-foreground)]">
Visualisez tous les composants UI disponibles avec différents thèmes
@@ -97,7 +98,7 @@ export function GeneralSettingsPageClient({ initialTags }: GeneralSettingsPageCl
<CardContent className="p-4">
<div className="p-4 bg-[var(--warning)]/10 border border-[var(--warning)]/20 rounded">
<p className="text-sm text-[var(--warning)] font-medium mb-2">
🚧 Interface de configuration en développement
<Emoji emoji="🚧" size={16} /> Interface de configuration en développement
</p>
<p className="text-xs text-[var(--muted-foreground)]">
Les contrôles interactifs pour modifier les autres préférences seront disponibles dans une prochaine version.

View File

@@ -3,6 +3,7 @@
import { Card, CardContent } from '@/components/ui/Card';
import { UserPreferences } from '@/lib/types';
import { SystemInfo } from '@/services/core/system-info';
import { Emoji } from '@/components/ui/Emoji';
interface QuickStatsProps {
preferences: UserPreferences;
@@ -15,7 +16,7 @@ export function QuickStats({ preferences, systemInfo }: QuickStatsProps) {
<Card>
<CardContent className="p-4">
<div className="flex items-center gap-3">
<span className="text-2xl">🎨</span>
<Emoji emoji="🎨" size={24} />
<div>
<p className="text-sm text-[var(--muted-foreground)]">Thème actuel</p>
<p className="font-medium capitalize">{preferences.viewPreferences.theme}</p>
@@ -27,7 +28,7 @@ export function QuickStats({ preferences, systemInfo }: QuickStatsProps) {
<Card>
<CardContent className="p-4">
<div className="flex items-center gap-3">
<span className="text-2xl">🔌</span>
<Emoji emoji="🔌" size={24} />
<div>
<p className="text-sm text-[var(--muted-foreground)]">Jira</p>
<div className="flex items-center gap-2">
@@ -46,7 +47,7 @@ export function QuickStats({ preferences, systemInfo }: QuickStatsProps) {
<Card>
<CardContent className="p-4">
<div className="flex items-center gap-3">
<span className="text-2xl">📏</span>
<Emoji emoji="📏" size={24} />
<div>
<p className="text-sm text-[var(--muted-foreground)]">Taille police</p>
<p className="font-medium capitalize">{preferences.viewPreferences.fontSize}</p>
@@ -58,7 +59,7 @@ export function QuickStats({ preferences, systemInfo }: QuickStatsProps) {
<Card>
<CardContent className="p-4">
<div className="flex items-center gap-3">
<span className="text-2xl">💾</span>
<Emoji emoji="💾" size={24} />
<div>
<p className="text-sm text-[var(--muted-foreground)]">Sauvegardes</p>
<p className="font-medium">

View File

@@ -3,6 +3,7 @@
import { Card, CardContent } from '@/components/ui/Card';
import Link from 'next/link';
import { ChevronRight } from 'lucide-react';
import { Emoji } from '@/components/ui/Emoji';
interface SettingsPage {
href: string;
@@ -30,7 +31,7 @@ export function SettingsNavigation({ settingsPages }: SettingsNavigationProps) {
<CardContent className="p-6">
<div className="flex items-start justify-between">
<div className="flex items-start gap-4">
<span className="text-3xl">{page.icon}</span>
<span className="text-3xl"><Emoji emoji={page.icon} size={30} /></span>
<div className="flex-1">
<h3 className="text-lg font-semibold text-[var(--foreground)] mb-1">
{page.title}

View File

@@ -3,6 +3,7 @@
import { Input } from '@/components/ui/Input';
import { Button } from '@/components/ui/Button';
import { Tag } from '@/lib/types';
import { Emoji } from '@/components/ui/Emoji';
interface TagsFiltersProps {
searchQuery: string;
@@ -46,7 +47,7 @@ export function TagsFilters({
onClick={onToggleUnused}
className="flex items-center gap-2"
>
<span className="text-xs"></span>
<Emoji emoji="⚠️" size={12} />
Tags non utilisés ({unusedCount})
</Button>
@@ -56,7 +57,7 @@ export function TagsFilters({
onClick={onToggleWithoutIcons}
className="flex items-center gap-2"
>
<span className="text-xs">🚫</span>
<Emoji emoji="🚫" size={12} />
Tags sans icônes ({withoutIconsCount})
</Button>

View File

@@ -5,6 +5,7 @@ import { fr } from 'date-fns/locale';
import { TagDisplay } from '@/components/ui/TagDisplay';
import { PriorityBadge } from '@/components/ui/PriorityBadge';
import { Tag } from '@/lib/types';
import { Emoji } from '@/components/ui/Emoji';
export interface AchievementData {
id: string;
@@ -89,7 +90,7 @@ export function AchievementCard({
{/* Count de todos - seulement pour les tâches, pas pour les todos standalone */}
{!isTodo && achievement.todosCount !== undefined && achievement.todosCount > 0 && (
<div className="flex items-center gap-1 text-xs text-[var(--muted-foreground)]">
<span>📋</span>
<span><Emoji emoji="📋" size={16} /></span>
<span>{achievement.todosCount} todo{achievement.todosCount > 1 ? 's' : ''}</span>
</div>
)}

View File

@@ -1,6 +1,7 @@
'use client';
import { Card } from '@/components/ui/Card';
import { Emoji } from './Emoji';
export interface AlertItem {
id: string;
@@ -79,7 +80,7 @@ export function AlertBanner({
<Card className={`mb-2 ${className}`}>
<div className={`${getVariantClasses()} p-4`}>
<div className="flex items-start gap-3">
<div className="text-2xl">{icon}</div>
<div className="text-2xl"><Emoji emoji={icon} /></div>
<div className="flex-1 min-w-0">
<h3 className="text-sm font-semibold mb-2">
{title} ({items.length})
@@ -101,7 +102,7 @@ export function AlertBanner({
onClick={() => onItemClick?.(item)}
title={item.title}
>
<span>{item.icon || getSourceIcon(item.source)}</span>
<Emoji emoji={item.icon || getSourceIcon(item.source)} />
<span className="truncate max-w-[200px]">
{item.title}
</span>
@@ -112,9 +113,9 @@ export function AlertBanner({
)}
{item.urgency && (
<span className={`text-[10px] ${getUrgencyColor(item.urgency)}`}>
{item.urgency === 'critical' ? '🔴' :
item.urgency === 'high' ? '🟠' :
item.urgency === 'medium' ? '🟡' : '🟢'}
{item.urgency === 'critical' ? <Emoji emoji="🔴" /> :
item.urgency === 'high' ? <Emoji emoji="🟠" /> :
item.urgency === 'medium' ? <Emoji emoji="🟡" /> : <Emoji emoji="🟢" />}
</span>
)}
{index < items.length - 1 && (

View File

@@ -5,6 +5,7 @@ import { fr } from 'date-fns/locale';
import { TagDisplay } from '@/components/ui/TagDisplay';
import { PriorityBadge } from '@/components/ui/PriorityBadge';
import { Tag } from '@/lib/types';
import { Emoji } from '@/components/ui/Emoji';
export interface ChallengeData {
id: string;
@@ -83,7 +84,7 @@ export function ChallengeCard({
{/* Count de todos */}
{challenge.todosCount !== undefined && challenge.todosCount > 0 && (
<div className="flex items-center gap-1 text-xs text-[var(--muted-foreground)]">
<span>📋</span>
<span><Emoji emoji="📋" size={16} /></span>
<span>{challenge.todosCount} todo{challenge.todosCount > 1 ? 's' : ''}</span>
</div>
)}

View File

@@ -1,6 +1,7 @@
import { HTMLAttributes, forwardRef } from 'react';
import { cn } from '@/lib/utils';
import { Badge } from './Badge';
import { Emoji } from '@/components/ui/Emoji';
interface ColumnHeaderProps extends HTMLAttributes<HTMLDivElement> {
title: string;
@@ -45,7 +46,7 @@ const ColumnHeader = forwardRef<HTMLDivElement, ColumnHeaderProps>(
accentColor || "text-[var(--foreground)]"
)}
>
{title} {icon}
{title} {icon && <Emoji emoji={icon} size={16} />}
</h3>
</div>

View File

@@ -29,7 +29,7 @@ export type DropdownVariant =
interface DropdownProps {
/** Texte du bouton déclencheur */
trigger: string;
trigger: ReactNode;
/** Variant du bouton trigger */
variant?: DropdownVariant;
/** Contenu du dropdown */

View File

View File

@@ -0,0 +1,58 @@
'use client';
import Picker from '@emoji-mart/react';
import data from '@emoji-mart/data';
interface EmojiProps {
emoji: string;
className?: string;
size?: number;
}
export function Emoji({ emoji, className = '', size = 16 }: EmojiProps) {
// Utiliser les fonts système pour un rendu cohérent et performant
return (
<span
className={`inline-block ${className}`}
style={{
fontSize: `${size}px`,
fontFamily: 'Apple Color Emoji, Segoe UI Emoji, Noto Color Emoji, sans-serif',
lineHeight: 1,
verticalAlign: 'middle'
}}
role="img"
aria-label={emoji}
>
{emoji}
</span>
);
}
// Composant pour afficher un picker d'emojis
interface EmojiPickerProps {
onEmojiSelect: (emoji: string) => void;
className?: string;
}
interface EmojiData {
native: string;
[key: string]: unknown;
}
export function EmojiPicker({ onEmojiSelect, className = '' }: EmojiPickerProps) {
return (
<div className={className}>
<Picker
data={data}
onEmojiSelect={(emoji: EmojiData) => onEmojiSelect(emoji.native)}
theme="light"
previewPosition="none"
searchPosition="top"
navPosition="bottom"
skinTonePosition="none"
perLine={8}
maxFrequentRows={2}
/>
</div>
);
}

View File

@@ -8,7 +8,7 @@ interface ModalProps {
isOpen: boolean;
onClose: () => void;
children: ReactNode;
title?: string;
title?: ReactNode;
size?: 'sm' | 'md' | 'lg' | 'xl';
}

View File

@@ -0,0 +1,27 @@
'use client';
import { Flame, Circle, Tag, FileText, Calendar, Clock } from 'lucide-react';
interface SortIconProps {
iconName: string;
size?: number;
}
export function SortIcon({ iconName, size = 16 }: SortIconProps) {
const iconMap = {
'flame': Flame,
'circle': Circle,
'tag': Tag,
'file-text': FileText,
'calendar': Calendar,
'clock': Clock
};
const IconComponent = iconMap[iconName as keyof typeof iconMap];
if (!IconComponent) {
return null;
}
return <IconComponent size={size} />;
}

View File

@@ -1,11 +1,12 @@
import { ReactNode } from 'react';
import { Card } from './Card';
import { cn } from '@/lib/utils';
import { Emoji } from '@/components/ui/Emoji';
interface StatCardProps {
title: string;
value: number | string;
icon?: ReactNode;
icon?: string;
color?: 'default' | 'primary' | 'success' | 'warning' | 'destructive';
className?: string;
}
@@ -38,7 +39,7 @@ export function StatCard({
</div>
{icon && (
<div className="text-3xl">
{icon}
<Emoji emoji={icon} size={32} />
</div>
)}
</div>

View File

@@ -1,5 +1,7 @@
'use client';
import { Emoji } from './Emoji';
export interface TabItem {
id: string;
label: string;
@@ -31,7 +33,7 @@ export function Tabs({ items, activeTab, onTabChange, className = '' }: TabsProp
} ${item.disabled ? 'opacity-50 cursor-not-allowed' : 'cursor-pointer'}`}
>
<span className="flex items-center gap-2">
{item.icon && <span>{item.icon}</span>}
{item.icon && <Emoji emoji={item.icon} />}
<span>{item.label}</span>
{item.count !== undefined && (
<span className="text-xs opacity-75">({item.count})</span>

View File

@@ -4,6 +4,7 @@ import { AuthButton } from '@/components/AuthButton';
import { HeaderControls } from './HeaderControls';
import { ThemeDropdown } from './ThemeDropdown';
import { useKeyboardShortcutsModal } from '@/contexts/KeyboardShortcutsContext';
import { Emoji } from '@/components/ui/Emoji';
interface HeaderDesktopProps {
title: string;
@@ -47,7 +48,7 @@ export function HeaderDesktop({ title, subtitle, syncing }: HeaderDesktopProps)
className="text-[var(--muted-foreground)] hover:text-[var(--primary)] transition-colors p-1 rounded-md hover:bg-[var(--card-hover)]"
title="Raccourcis clavier (Cmd+?)"
>
<Emoji emoji="⌨️" size={16} />
</button>
<ThemeDropdown variant="desktop" />
<AuthButton />

View File

@@ -4,6 +4,7 @@ import { useState } from 'react';
import { Check } from 'lucide-react';
import { useTheme } from '@/contexts/ThemeContext';
import { Theme, THEME_CONFIG, getThemeIcon } from '@/lib/ui-config';
import { Emoji } from '@/components/ui/Emoji';
interface ThemeDropdownProps {
variant: 'desktop' | 'mobile';
@@ -32,7 +33,7 @@ export function ThemeDropdown({ variant, className = '' }: ThemeDropdownProps) {
className={`text-[var(--muted-foreground)] hover:text-[var(--primary)] transition-colors ${buttonSize} rounded-md hover:bg-[var(--card-hover)]`}
title="Select theme"
>
{themes.find(t => t.value === theme)?.icon || '🎨'}
<Emoji emoji={themes.find(t => t.value === theme)?.icon || '🎨'} />
</button>
{themeDropdownOpen && (
@@ -58,7 +59,7 @@ export function ThemeDropdown({ variant, className = '' }: ThemeDropdownProps) {
: 'text-[var(--muted-foreground)] hover:text-[var(--foreground)] hover:bg-[var(--card-hover)]'
}`}
>
<span className="text-base">{themeOption.icon}</span>
<Emoji emoji={themeOption.icon} />
<span className="font-mono">{themeOption.label}</span>
{theme === themeOption.value && (
<Check className="w-4 h-4 ml-auto" />

View File

@@ -123,3 +123,4 @@ export function validateCustomAvatarUrl(url: string): boolean {
return false
}
}

View File

@@ -251,16 +251,16 @@ export function formatDateSmart(date: Date): string {
/**
* Génère un titre intelligent pour une date (avec emojis)
*/
export function generateDateTitle(date: Date, emoji: string = '📅'): string {
export function generateDateTitle(date: Date, emoji: string = '📅'): { emoji: string; text: string } {
if (isToday(date)) {
return `${emoji} Aujourd'hui`;
return { emoji, text: 'Aujourd\'hui' };
}
if (isYesterday(date)) {
return `${emoji} Hier`;
return { emoji, text: 'Hier' };
}
return `${emoji} ${formatDateShort(date)}`;
return { emoji, text: formatDateShort(date) };
}
/**

View File

@@ -66,3 +66,4 @@ export async function checkGravatarExists(email: string): Promise<boolean> {
return false
}
}

View File

@@ -14,7 +14,7 @@ export interface SortOption {
label: string;
field: SortField;
direction: SortDirection;
icon: string;
iconName: string;
}
// Configuration des options de tri disponibles
@@ -24,63 +24,63 @@ export const SORT_OPTIONS: SortOption[] = [
label: 'Priorité (Urgente → Faible)',
field: 'priority',
direction: 'desc',
icon: '🔥'
iconName: 'flame'
},
{
key: 'priority-asc',
label: 'Priorité (Faible → Urgente)',
field: 'priority',
direction: 'asc',
icon: '🔵'
iconName: 'circle'
},
{
key: 'tags-asc',
label: 'Tags (A → Z)',
field: 'tags',
direction: 'asc',
icon: '🏷️'
iconName: 'tag'
},
{
key: 'title-asc',
label: 'Titre (A → Z)',
field: 'title',
direction: 'asc',
icon: '📝'
iconName: 'file-text'
},
{
key: 'title-desc',
label: 'Titre (Z → A)',
field: 'title',
direction: 'desc',
icon: '📝'
iconName: 'file-text'
},
{
key: 'createdAt-desc',
label: 'Date création (Récent → Ancien)',
field: 'createdAt',
direction: 'desc',
icon: '📅'
iconName: 'calendar'
},
{
key: 'createdAt-asc',
label: 'Date création (Ancien → Récent)',
field: 'createdAt',
direction: 'asc',
icon: '📅'
iconName: 'calendar'
},
{
key: 'dueDate-asc',
label: 'Échéance (Proche → Lointaine)',
field: 'dueDate',
direction: 'asc',
icon: ''
iconName: 'clock'
},
{
key: 'dueDate-desc',
label: 'Échéance (Lointaine → Proche)',
field: 'dueDate',
direction: 'desc',
icon: ''
iconName: 'clock'
}
];