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:
@@ -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
|
||||
|
||||
@@ -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'équipe
|
||||
<Emoji emoji="📊" size={18} /> Analytics d'é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} /> 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'équipe</h3>
|
||||
<h3 className="font-semibold"><Emoji emoji="👥" size={16} /> Répartition de l'é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'activité de l'équipe</h3>
|
||||
<h3 className="font-semibold"><Emoji emoji="🔥" size={16} /> Heatmap d'activité de l'é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'équipe</h3>
|
||||
<h3 className="font-semibold"><Emoji emoji="👥" size={16} /> Répartition de l'é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">
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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 */}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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"}
|
||||
|
||||
@@ -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} /> 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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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'ensemble</h3>
|
||||
<h3 className="text-lg font-semibold"><Emoji emoji="🎯" size={18} /> Vue d'ensemble</h3>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="grid grid-cols-2 lg:grid-cols-5 gap-4">
|
||||
|
||||
@@ -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' && (
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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)}>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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 été mappés vers "todo" par défaut :
|
||||
Ces statuts ont été mappés vers "todo" 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}"
|
||||
"{status}"
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
@@ -211,7 +212,9 @@ export function JiraSync({ onSyncComplete, className = "" }: JiraSyncProps) {
|
||||
Sync...
|
||||
</div>
|
||||
) : (
|
||||
'🔄 Synchroniser'
|
||||
<>
|
||||
<Emoji emoji="🔄" /> 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>
|
||||
)}
|
||||
|
||||
|
||||
@@ -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'é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'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'équipe s'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>
|
||||
)}
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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'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">
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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)]">
|
||||
|
||||
@@ -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}`}
|
||||
>
|
||||
|
||||
@@ -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} />;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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'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.
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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>
|
||||
)}
|
||||
|
||||
@@ -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 && (
|
||||
|
||||
@@ -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>
|
||||
)}
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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 */
|
||||
|
||||
0
src/components/ui/DynamicIcon.tsx
Normal file
0
src/components/ui/DynamicIcon.tsx
Normal file
58
src/components/ui/Emoji.tsx
Normal file
58
src/components/ui/Emoji.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
@@ -8,7 +8,7 @@ interface ModalProps {
|
||||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
children: ReactNode;
|
||||
title?: string;
|
||||
title?: ReactNode;
|
||||
size?: 'sm' | 'md' | 'lg' | 'xl';
|
||||
}
|
||||
|
||||
|
||||
27
src/components/ui/SortIcon.tsx
Normal file
27
src/components/ui/SortIcon.tsx
Normal 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} />;
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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 />
|
||||
|
||||
@@ -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" />
|
||||
|
||||
@@ -123,3 +123,4 @@ export function validateCustomAvatarUrl(url: string): boolean {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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) };
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -66,3 +66,4 @@ export async function checkGravatarExists(email: string): Promise<boolean> {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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'
|
||||
}
|
||||
];
|
||||
|
||||
|
||||
Reference in New Issue
Block a user