feat: integrate emoji-mart and refactor emoji usage

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

View File

@@ -21,7 +21,7 @@
- [x] **EditModal task couleur calendrier** - Problème de couleur en ajout de taches dans tous les icones calendriers; colmler au thème - [x] **EditModal task couleur calendrier** - Problème de couleur en ajout de taches dans tous les icones calendriers; colmler au thème
- [x] **Weekly deux boutons actualiser** - Corriger la duplication des boutons d'actualisation - [x] **Weekly deux boutons actualiser** - Corriger la duplication des boutons d'actualisation
- [x] **Solarized ne doit pas être un soleil** - Corriger l'icône du thème Solarized - [x] **Solarized ne doit pas être un soleil** - Corriger l'icône du thème Solarized
- [ ] **Emoji interdit dans UI** - Vérifier et supprimer toutes les emojis dans l'interface, remplacer par lucide-react - [x] **Emoji interdit dans UI** - Vérifier et supprimer toutes les emojis dans l'interface, remplacer par lucide-react
- [ ] **Settings intégration : icônes** - Problème avec les icônes "Arrêté" et "Actif" : doivent etre les memes - [ ] **Settings intégration : icônes** - Problème avec les icônes "Arrêté" et "Actif" : doivent etre les memes
- [ ] **Settings backup UI** - Revoir l'UI pour coller au style des intégrations - [ ] **Settings backup UI** - Revoir l'UI pour coller au style des intégrations
- [ ] **AlertBanner : hover et bug** - Corriger les problèmes de hover et bugs - [ ] **AlertBanner : hover et bug** - Corriger les problèmes de hover et bugs

98
package-lock.json generated
View File

@@ -11,10 +11,13 @@
"@dnd-kit/core": "^6.3.1", "@dnd-kit/core": "^6.3.1",
"@dnd-kit/sortable": "^10.0.0", "@dnd-kit/sortable": "^10.0.0",
"@dnd-kit/utilities": "^3.2.2", "@dnd-kit/utilities": "^3.2.2",
"@emoji-mart/data": "^1.2.1",
"@emoji-mart/react": "^1.1.1",
"@prisma/client": "^6.16.1", "@prisma/client": "^6.16.1",
"bcryptjs": "^3.0.2", "bcryptjs": "^3.0.2",
"clsx": "^2.1.1", "clsx": "^2.1.1",
"date-fns": "^4.1.0", "date-fns": "^4.1.0",
"emoji-mart": "^5.6.0",
"emoji-regex": "^10.5.0", "emoji-regex": "^10.5.0",
"lucide-react": "^0.544.0", "lucide-react": "^0.544.0",
"next": "15.5.3", "next": "15.5.3",
@@ -23,7 +26,8 @@
"react": "19.1.0", "react": "19.1.0",
"react-dom": "19.1.0", "react-dom": "19.1.0",
"recharts": "^3.2.1", "recharts": "^3.2.1",
"tailwind-merge": "^3.3.1" "tailwind-merge": "^3.3.1",
"twemoji": "^14.0.2"
}, },
"devDependencies": { "devDependencies": {
"@eslint/eslintrc": "^3", "@eslint/eslintrc": "^3",
@@ -147,6 +151,22 @@
"tslib": "^2.4.0" "tslib": "^2.4.0"
} }
}, },
"node_modules/@emoji-mart/data": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/@emoji-mart/data/-/data-1.2.1.tgz",
"integrity": "sha512-no2pQMWiBy6gpBEiqGeU77/bFejDqUTRY7KX+0+iur13op3bqUsXdnwoZs6Xb1zbv0gAj5VvS1PWoUUckSr5Dw==",
"license": "MIT"
},
"node_modules/@emoji-mart/react": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/@emoji-mart/react/-/react-1.1.1.tgz",
"integrity": "sha512-NMlFNeWgv1//uPsvLxvGQoIerPuVdXwK/EUek8OOkJ6wVOWPUizRBJU0hDqWZCOROVpfBgCemaC3m6jDOXi03g==",
"license": "MIT",
"peerDependencies": {
"emoji-mart": "^5.2",
"react": "^16.8 || ^17 || ^18"
}
},
"node_modules/@esbuild/aix-ppc64": { "node_modules/@esbuild/aix-ppc64": {
"version": "0.25.10", "version": "0.25.10",
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.10.tgz", "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.10.tgz",
@@ -2301,7 +2321,7 @@
"version": "19.1.13", "version": "19.1.13",
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.13.tgz", "resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.13.tgz",
"integrity": "sha512-hHkbU/eoO3EG5/MZkuFSKmYqPbSVk5byPFa3e7y/8TybHiLMACgI8seVYlicwk7H5K/rI2px9xrQp/C+AUDTiQ==", "integrity": "sha512-hHkbU/eoO3EG5/MZkuFSKmYqPbSVk5byPFa3e7y/8TybHiLMACgI8seVYlicwk7H5K/rI2px9xrQp/C+AUDTiQ==",
"devOptional": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"csstype": "^3.0.2" "csstype": "^3.0.2"
@@ -3465,7 +3485,7 @@
"version": "3.1.3", "version": "3.1.3",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
"devOptional": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/d3-array": { "node_modules/d3-array": {
@@ -3808,6 +3828,12 @@
"fast-check": "^3.23.1" "fast-check": "^3.23.1"
} }
}, },
"node_modules/emoji-mart": {
"version": "5.6.0",
"resolved": "https://registry.npmjs.org/emoji-mart/-/emoji-mart-5.6.0.tgz",
"integrity": "sha512-eJp3QRe79pjwa+duv+n7+5YsNhRcMl812EcFVwrnRvYKoNPoQb5qxU8DG6Bgwji0akHdp6D4Ln6tYLG58MFSow==",
"license": "MIT"
},
"node_modules/emoji-regex": { "node_modules/emoji-regex": {
"version": "10.5.0", "version": "10.5.0",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.5.0.tgz", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.5.0.tgz",
@@ -4713,6 +4739,29 @@
"node": ">=18.3.0" "node": ">=18.3.0"
} }
}, },
"node_modules/fs-extra": {
"version": "8.1.0",
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz",
"integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==",
"license": "MIT",
"dependencies": {
"graceful-fs": "^4.2.0",
"jsonfile": "^4.0.0",
"universalify": "^0.1.0"
},
"engines": {
"node": ">=6 <7 || >=8"
}
},
"node_modules/fs-extra/node_modules/jsonfile": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz",
"integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==",
"license": "MIT",
"optionalDependencies": {
"graceful-fs": "^4.1.6"
}
},
"node_modules/fsevents": { "node_modules/fsevents": {
"version": "2.3.3", "version": "2.3.3",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
@@ -4916,7 +4965,6 @@
"version": "4.2.11", "version": "4.2.11",
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
"integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
"dev": true,
"license": "ISC" "license": "ISC"
}, },
"node_modules/graphemer": { "node_modules/graphemer": {
@@ -5603,6 +5651,18 @@
"json5": "lib/cli.js" "json5": "lib/cli.js"
} }
}, },
"node_modules/jsonfile": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-5.0.0.tgz",
"integrity": "sha512-NQRZ5CRo74MhMMC3/3r5g2k4fjodJ/wh8MxjFbCViWKFjxrnudWSY5vomh+23ZaXzAS7J3fBZIR2dV6WbmfM0w==",
"license": "MIT",
"dependencies": {
"universalify": "^0.1.2"
},
"optionalDependencies": {
"graceful-fs": "^4.1.6"
}
},
"node_modules/jsx-ast-utils": { "node_modules/jsx-ast-utils": {
"version": "3.3.5", "version": "3.3.5",
"resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz", "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz",
@@ -6858,6 +6918,7 @@
"version": "16.13.1", "version": "16.13.1",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
"dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/react-redux": { "node_modules/react-redux": {
@@ -7800,6 +7861,24 @@
"fsevents": "~2.3.3" "fsevents": "~2.3.3"
} }
}, },
"node_modules/twemoji": {
"version": "14.0.2",
"resolved": "https://registry.npmjs.org/twemoji/-/twemoji-14.0.2.tgz",
"integrity": "sha512-BzOoXIe1QVdmsUmZ54xbEH+8AgtOKUiG53zO5vVP2iUu6h5u9lN15NcuS6te4OY96qx0H7JK9vjjl9WQbkTRuA==",
"license": "MIT",
"dependencies": {
"fs-extra": "^8.0.1",
"jsonfile": "^5.0.0",
"twemoji-parser": "14.0.0",
"universalify": "^0.1.2"
}
},
"node_modules/twemoji-parser": {
"version": "14.0.0",
"resolved": "https://registry.npmjs.org/twemoji-parser/-/twemoji-parser-14.0.0.tgz",
"integrity": "sha512-9DUOTGLOWs0pFWnh1p6NF+C3CkQ96PWmEFwhOVmT3WbecRC+68AIqpsnJXygfkFcp4aXbOp8Dwbhh/HQgvoRxA==",
"license": "MIT"
},
"node_modules/type-check": { "node_modules/type-check": {
"version": "0.4.0", "version": "0.4.0",
"resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
@@ -7895,7 +7974,7 @@
"version": "5.9.2", "version": "5.9.2",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.2.tgz", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.2.tgz",
"integrity": "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==", "integrity": "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==",
"devOptional": true, "dev": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"bin": { "bin": {
"tsc": "bin/tsc", "tsc": "bin/tsc",
@@ -7931,6 +8010,15 @@
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/universalify": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz",
"integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==",
"license": "MIT",
"engines": {
"node": ">= 4.0.0"
}
},
"node_modules/unrs-resolver": { "node_modules/unrs-resolver": {
"version": "1.11.1", "version": "1.11.1",
"resolved": "https://registry.npmjs.org/unrs-resolver/-/unrs-resolver-1.11.1.tgz", "resolved": "https://registry.npmjs.org/unrs-resolver/-/unrs-resolver-1.11.1.tgz",

View File

@@ -25,10 +25,13 @@
"@dnd-kit/core": "^6.3.1", "@dnd-kit/core": "^6.3.1",
"@dnd-kit/sortable": "^10.0.0", "@dnd-kit/sortable": "^10.0.0",
"@dnd-kit/utilities": "^3.2.2", "@dnd-kit/utilities": "^3.2.2",
"@emoji-mart/data": "^1.2.1",
"@emoji-mart/react": "^1.1.1",
"@prisma/client": "^6.16.1", "@prisma/client": "^6.16.1",
"bcryptjs": "^3.0.2", "bcryptjs": "^3.0.2",
"clsx": "^2.1.1", "clsx": "^2.1.1",
"date-fns": "^4.1.0", "date-fns": "^4.1.0",
"emoji-mart": "^5.6.0",
"emoji-regex": "^10.5.0", "emoji-regex": "^10.5.0",
"lucide-react": "^0.544.0", "lucide-react": "^0.544.0",
"next": "15.5.3", "next": "15.5.3",
@@ -37,7 +40,8 @@
"react": "19.1.0", "react": "19.1.0",
"react-dom": "19.1.0", "react-dom": "19.1.0",
"recharts": "^3.2.1", "recharts": "^3.2.1",
"tailwind-merge": "^3.3.1" "tailwind-merge": "^3.3.1",
"twemoji": "^14.0.2"
}, },
"devDependencies": { "devDependencies": {
"@eslint/eslintrc": "^3", "@eslint/eslintrc": "^3",

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -12,6 +12,7 @@ import { MetricsTab } from './MetricsTab';
import { format } from 'date-fns'; import { format } from 'date-fns';
import { fr } from 'date-fns/locale'; import { fr } from 'date-fns/locale';
import { Tag } from '@/lib/types'; import { Tag } from '@/lib/types';
import { Emoji } from '@/components/ui/Emoji';
interface ManagerWeeklySummaryProps { interface ManagerWeeklySummaryProps {
initialSummary: ManagerSummary; initialSummary: ManagerSummary;
@@ -50,7 +51,7 @@ export default function ManagerWeeklySummary({ initialSummary }: ManagerWeeklySu
{/* Header avec navigation */} {/* Header avec navigation */}
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<div> <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> <p className="text-[var(--muted-foreground)]">{formatPeriod()}</p>
</div> </div>
{activeView !== 'metrics' && ( {activeView !== 'metrics' && (
@@ -59,7 +60,7 @@ export default function ManagerWeeklySummary({ initialSummary }: ManagerWeeklySu
variant="secondary" variant="secondary"
size="sm" size="sm"
> >
🔄 Actualiser <Emoji emoji="🔄" size={16} />&nbsp;Actualiser
</Button> </Button>
)} )}
</div> </div>
@@ -80,22 +81,22 @@ export default function ManagerWeeklySummary({ initialSummary }: ManagerWeeklySu
<Card variant="elevated"> <Card variant="elevated">
<CardHeader> <CardHeader>
<h2 className="text-lg font-semibold flex items-center gap-2"> <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> </h2>
</CardHeader> </CardHeader>
<CardContent className="space-y-4"> <CardContent className="space-y-4">
<div className="outline-card-blue p-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> <p className="text-sm text-[var(--muted-foreground)]">{summary.narrative.weekHighlight}</p>
</div> </div>
<div className="outline-card-orange p-4"> <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> <p className="text-sm text-[var(--muted-foreground)]">{summary.narrative.mainChallenges}</p>
</div> </div>
<div className="outline-card-green p-4"> <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> <p className="text-sm text-[var(--muted-foreground)]">{summary.narrative.nextWeekFocus}</p>
</div> </div>
</CardContent> </CardContent>
@@ -104,7 +105,7 @@ export default function ManagerWeeklySummary({ initialSummary }: ManagerWeeklySu
{/* Métriques rapides */} {/* Métriques rapides */}
<Card variant="elevated"> <Card variant="elevated">
<CardHeader> <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> </CardHeader>
<CardContent> <CardContent>
<div className="grid grid-cols-2 gap-4"> <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))' 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)' }}> <h2 className="text-lg font-semibold flex items-center gap-2" style={{ color: 'var(--success)' }}>
🏆 Top accomplissements <Emoji emoji="🏆" size={18} /> Top accomplissements
</h2> </h2>
</CardHeader> </CardHeader>
<CardContent> <CardContent>
@@ -176,7 +177,7 @@ export default function ManagerWeeklySummary({ initialSummary }: ManagerWeeklySu
borderBottomColor: 'color-mix(in srgb, var(--destructive) 10%, var(--border))' 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)' }}> <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> </h2>
</CardHeader> </CardHeader>
<CardContent> <CardContent>
@@ -208,7 +209,7 @@ export default function ManagerWeeklySummary({ initialSummary }: ManagerWeeklySu
{activeView === 'accomplishments' && ( {activeView === 'accomplishments' && (
<Card variant="elevated"> <Card variant="elevated">
<CardHeader> <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)]"> <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 {summary.keyAccomplishments.length} accomplissements significatifs {summary.metrics.totalTasksCompleted} tâches {summary.metrics.totalCheckboxesCompleted} todos complétés
</p> </p>
@@ -220,7 +221,7 @@ export default function ManagerWeeklySummary({ initialSummary }: ManagerWeeklySu
borderColor: 'color-mix(in srgb, var(--muted) 40%, var(--border))', borderColor: 'color-mix(in srgb, var(--muted) 40%, var(--border))',
color: 'var(--muted-foreground)' 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-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> <p className="text-sm">Ajoutez des tâches avec priorité haute/medium ou des meetings.</p>
</div> </div>
@@ -246,7 +247,7 @@ export default function ManagerWeeklySummary({ initialSummary }: ManagerWeeklySu
{activeView === 'challenges' && ( {activeView === 'challenges' && (
<Card variant="elevated"> <Card variant="elevated">
<CardHeader> <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)]"> <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 {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> </p>
@@ -258,7 +259,7 @@ export default function ManagerWeeklySummary({ initialSummary }: ManagerWeeklySu
borderColor: 'color-mix(in srgb, var(--muted) 40%, var(--border))', borderColor: 'color-mix(in srgb, var(--muted) 40%, var(--border))',
color: 'var(--muted-foreground)' 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-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> <p className="text-sm">Ajoutez des tâches non complétées avec priorité haute/medium.</p>
</div> </div>

View File

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

View File

@@ -9,6 +9,7 @@ import { WeeklyStatsCard } from '@/components/charts/WeeklyStatsCard';
import { TagDistributionChart } from '@/components/dashboard/TagDistributionChart'; import { TagDistributionChart } from '@/components/dashboard/TagDistributionChart';
import { Card, MetricCard } from '@/components/ui'; import { Card, MetricCard } from '@/components/ui';
import { DeadlineOverview } from '@/components/deadline/DeadlineOverview'; import { DeadlineOverview } from '@/components/deadline/DeadlineOverview';
import { Emoji } from '@/components/ui/Emoji';
interface ProductivityAnalyticsProps { interface ProductivityAnalyticsProps {
metrics: ProductivityMetrics; metrics: ProductivityMetrics;
@@ -90,7 +91,7 @@ export function ProductivityAnalytics({ metrics, deadlineMetrics, tagMetrics, se
{/* Titre de section Analytics */} {/* Titre de section Analytics */}
<div className="flex items-center justify-between"> <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)]"> <div className="text-sm text-[var(--muted-foreground)]">
Derniers 30 jours Derniers 30 jours
</div> </div>
@@ -141,7 +142,7 @@ export function ProductivityAnalytics({ metrics, deadlineMetrics, tagMetrics, se
{/* Insights automatiques */} {/* Insights automatiques */}
<Card variant="glass" className="p-6"> <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"> <div className="grid grid-cols-1 md:grid-cols-3 gap-4">
<MetricCard <MetricCard
title="Vélocité Moyenne" title="Vélocité Moyenne"

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -2,6 +2,7 @@
import { DeadlineMetrics, DeadlineAnalyticsService } from '@/services/analytics/deadline-analytics'; import { DeadlineMetrics, DeadlineAnalyticsService } from '@/services/analytics/deadline-analytics';
import { Card } from '@/components/ui/Card'; import { Card } from '@/components/ui/Card';
import { Emoji } from '@/components/ui/Emoji';
interface DeadlineRiskCardProps { interface DeadlineRiskCardProps {
metrics: DeadlineMetrics; 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`}> <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 justify-between mb-4">
<div className="flex items-center gap-2"> <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> <h3 className="text-lg font-semibold">Niveau de Risque</h3>
</div> </div>
<div className="text-3xl font-bold" style={getRiskColor(riskAnalysis.riskLevel)}> <div className="text-3xl font-bold" style={getRiskColor(riskAnalysis.riskLevel)}>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -5,6 +5,7 @@ import { TaskPriority, TaskStatus } from '@/lib/types';
import { SearchInput, ToggleButton, ControlPanel, ControlSection, ControlGroup, FilterSummary, Dropdown } from '@/components/ui'; import { SearchInput, ToggleButton, ControlPanel, ControlSection, ControlGroup, FilterSummary, Dropdown } from '@/components/ui';
import { useTasksContext } from '@/contexts/TasksContext'; import { useTasksContext } from '@/contexts/TasksContext';
import { SORT_OPTIONS } from '@/lib/sort-config'; import { SORT_OPTIONS } from '@/lib/sort-config';
import { SortIcon } from '@/components/ui/SortIcon';
import { useUserPreferences } from '@/contexts/UserPreferencesContext'; import { useUserPreferences } from '@/contexts/UserPreferencesContext';
import { useIsMobile } from '@/hooks/useIsMobile'; import { useIsMobile } from '@/hooks/useIsMobile';
import { JiraFilters } from './filters/JiraFilters'; import { JiraFilters } from './filters/JiraFilters';
@@ -13,7 +14,7 @@ import { PriorityFilters } from './filters/PriorityFilters';
import { TagFilters } from './filters/TagFilters'; import { TagFilters } from './filters/TagFilters';
import { GeneralFilters } from './filters/GeneralFilters'; import { GeneralFilters } from './filters/GeneralFilters';
import { ColumnFilters } from './filters/ColumnFilters'; import { ColumnFilters } from './filters/ColumnFilters';
import { Layout, Grid3X3 } from 'lucide-react'; import { Layout, Grid3X3, Menu } from 'lucide-react';
import type { KanbanFilters } from '@/lib/types'; import type { KanbanFilters } from '@/lib/types';
@@ -154,10 +155,12 @@ export function KanbanFilters({ filters, onFiltersChange, hiddenStatuses: propsH
{/* Bouton de tri */} {/* Bouton de tri */}
<ControlGroup>
<Dropdown <Dropdown
open={isSortOpen} open={isSortOpen}
onOpenChange={setIsSortOpen} onOpenChange={setIsSortOpen}
trigger="☰ Tris" trigger={<><Menu size={14} className="inline mr-1" /> Tris</>}
triggerClassName="h-[34px] py-[5px]"
content={ content={
<div className="max-h-64 overflow-y-auto"> <div className="max-h-64 overflow-y-auto">
{SORT_OPTIONS.map((option) => ( {SORT_OPTIONS.map((option) => (
@@ -170,7 +173,7 @@ export function KanbanFilters({ filters, onFiltersChange, hiddenStatuses: propsH
: 'text-[var(--muted-foreground)]' : 'text-[var(--muted-foreground)]'
}`} }`}
> >
<span className="text-base">{option.icon}</span> <span className="text-base"><SortIcon iconName={option.iconName} size={16} /></span>
<span>{option.label}</span> <span>{option.label}</span>
</button> </button>
))} ))}
@@ -179,6 +182,7 @@ export function KanbanFilters({ filters, onFiltersChange, hiddenStatuses: propsH
placement="bottom-start" placement="bottom-start"
className="w-80" className="w-80"
/> />
</ControlGroup>
</ControlSection> </ControlSection>

View File

@@ -19,6 +19,7 @@ import {
verticalListSortingStrategy, verticalListSortingStrategy,
} from '@dnd-kit/sortable'; } from '@dnd-kit/sortable';
import { useDroppable } from '@dnd-kit/core'; import { useDroppable } from '@dnd-kit/core';
import { Emoji } from '@/components/ui/Emoji';
interface ObjectivesBoardProps { interface ObjectivesBoardProps {
tasks: Task[]; tasks: Task[];
@@ -65,7 +66,7 @@ function DroppableColumn({
{tasks.length === 0 ? ( {tasks.length === 0 ? (
<div className="text-center py-8 text-[var(--muted-foreground)] text-sm"> <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()} Aucun objectif {title.toLowerCase()}
</div> </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> <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"> <h2 className="text-lg font-mono font-bold text-[var(--accent)] uppercase tracking-wider">
🎯 Objectifs Principaux <Emoji emoji="🎯" size={20} /> Objectifs Principaux
</h2> </h2>
{pinnedTagName && ( {pinnedTagName && (
<Badge variant="outline" className="border-[var(--accent)]/50 text-[var(--accent)]"> <Badge variant="outline" className="border-[var(--accent)]/50 text-[var(--accent)]">

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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