diff --git a/src/components/daily/DailyCheckboxItem.tsx b/src/components/daily/DailyCheckboxItem.tsx index 6dc50a7..e1da1ef 100644 --- a/src/components/daily/DailyCheckboxItem.tsx +++ b/src/components/daily/DailyCheckboxItem.tsx @@ -5,6 +5,8 @@ import Link from 'next/link'; import { DailyCheckbox, DailyCheckboxType } from '@/lib/types'; import { Input } from '@/components/ui/Input'; import { EditCheckboxModal } from './EditCheckboxModal'; +import { getTaskEmoji } from '@/lib/task-emoji'; +import { Emoji } from '@/components/ui/Emoji'; interface DailyCheckboxItemProps { checkbox: DailyCheckbox; @@ -129,6 +131,11 @@ export function DailyCheckboxItem({ // Vérifier si la tâche est archivée const isArchived = checkbox.isArchived; + // Obtenir l'emoji de la tâche associée si elle existe + const taskEmoji = checkbox.task + ? getTaskEmoji(checkbox.task, undefined) + : null; + return ( <>
) : ( -
+
+ {/* Emoji de la tâche associée */} + {taskEmoji && ( + + + + )} + {/* Texte cliquable pour édition inline */} { // Variants @@ -216,35 +217,22 @@ const TaskCard = forwardRef( return colors[priority as keyof typeof colors] || colors.medium; }; - // Fonction pour extraire les emojis avec la lib emoji-regex - const extractEmojis = (text: string): string[] => { - const regex = emojiRegex(); - return text.match(regex) || []; - }; - - const titleEmojis = extractEmojis(title); + // Utiliser getTaskEmoji avec les propriétés de la tâche disponibles + const taskEmoji = getTaskEmoji( + { + title, + tags: tags || [], + primaryTagId, + tagDetails: availableTags?.filter((tag) => tags?.includes(tag.name)), + primaryTag: primaryTagId + ? availableTags?.find((tag) => tag.id === primaryTagId) + : undefined, + }, + availableTags + ); + const displayEmojis: string[] = taskEmoji ? [taskEmoji] : []; const titleWithoutEmojis = title.replace(emojiRegex(), '').trim(); - // Si pas d'emoji dans le titre, utiliser l'emoji du tag prioritaire ou du premier tag - let displayEmojis: string[] = titleEmojis; - if (displayEmojis.length === 0 && tags && tags.length > 0) { - // Priorité au tag prioritaire, sinon premier tag - let tagToUse = null; - if (primaryTagId && availableTags) { - tagToUse = availableTags.find((tag) => tag.id === primaryTagId); - } - if (!tagToUse) { - tagToUse = availableTags.find((tag) => tag.name === tags[0]); - } - - if (tagToUse) { - const tagEmojis = extractEmojis(tagToUse.name); - if (tagEmojis.length > 0) { - displayEmojis = [tagEmojis[0]]; // Prendre seulement le premier emoji du tag - } - } - } - const sourceStyles = getSourceStyles(); const priorityColor = getPriorityColor(priority); diff --git a/src/lib/task-emoji.ts b/src/lib/task-emoji.ts new file mode 100644 index 0000000..43b6b2a --- /dev/null +++ b/src/lib/task-emoji.ts @@ -0,0 +1,96 @@ +import emojiRegex from 'emoji-regex'; +import { Task, Tag } from './types'; + +/** + * Extrait les emojis d'un texte + */ +export function extractEmojis(text: string): string[] { + const regex = emojiRegex(); + return text.match(regex) || []; +} + +/** + * Type pour représenter une tâche partielle avec les propriétés nécessaires pour extraire l'emoji + */ +type TaskForEmoji = Pick & + Partial>; + +/** + * Extrait l'emoji principal d'une tâche en suivant la logique du TaskCard: + * 1. D'abord, cherche les emojis dans le titre de la tâche + * 2. Si pas trouvé, cherche dans le tag prioritaire (primaryTag) + * 3. Si pas trouvé, cherche dans le premier tag disponible + * + * @param task La tâche (complète ou partielle) à partir de laquelle extraire l'emoji + * @param availableTags Les tags disponibles (optionnel, pour la recherche par tag) + * @returns Le premier emoji trouvé, ou null si aucun emoji n'est trouvé + */ +export function getTaskEmoji( + task: TaskForEmoji | Task | null | undefined, + availableTags?: Tag[] +): string | null { + if (!task || !task.title) { + return null; + } + + // 1. Chercher les emojis dans le titre + const titleEmojis = extractEmojis(task.title); + if (titleEmojis.length > 0) { + return titleEmojis[0]; // Prendre seulement le premier emoji + } + + // 2. Si pas d'emoji dans le titre, utiliser l'emoji du tag prioritaire ou du premier tag + if (task.tags && task.tags.length > 0 && availableTags) { + // Priorité au tag prioritaire, sinon premier tag + let tagToUse: Tag | null = null; + + if (task.primaryTagId) { + tagToUse = + availableTags.find((tag) => tag.id === task.primaryTagId) || null; + } + + if (!tagToUse && task.primaryTag) { + tagToUse = task.primaryTag; + } + + if (!tagToUse) { + // Chercher par nom du premier tag + const firstTagName = task.tags[0]; + tagToUse = availableTags.find((tag) => tag.name === firstTagName) || null; + } + + if (tagToUse) { + const tagEmojis = extractEmojis(tagToUse.name); + if (tagEmojis.length > 0) { + return tagEmojis[0]; // Prendre seulement le premier emoji du tag + } + } + } + + // 3. Si la tâche a tagDetails directement (sans avoir besoin de availableTags) + if (task.tagDetails && task.tagDetails.length > 0) { + let tagToUse: Tag | null = null; + + if (task.primaryTagId) { + tagToUse = + task.tagDetails.find((tag) => tag.id === task.primaryTagId) || null; + } + + if (!tagToUse && task.primaryTag) { + tagToUse = task.primaryTag; + } + + if (!tagToUse) { + tagToUse = task.tagDetails[0]; + } + + if (tagToUse) { + const tagEmojis = extractEmojis(tagToUse.name); + if (tagEmojis.length > 0) { + return tagEmojis[0]; + } + } + } + + return null; +} diff --git a/src/services/task-management/daily.ts b/src/services/task-management/daily.ts index 84e8ee8..c013f57 100644 --- a/src/services/task-management/daily.ts +++ b/src/services/task-management/daily.ts @@ -61,7 +61,19 @@ export class DailyService { date: normalizedDate, userId: userId, }, - include: { task: true, user: true }, + include: { + task: { + include: { + taskTags: { + include: { + tag: true, + }, + }, + primaryTag: true, + }, + }, + user: true, + }, orderBy: { order: 'asc' }, }); @@ -93,7 +105,19 @@ export class DailyService { order, isChecked: data.isChecked ?? false, }, - include: { task: true, user: true }, + include: { + task: { + include: { + taskTags: { + include: { + tag: true, + }, + }, + primaryTag: true, + }, + }, + user: true, + }, }); return this.mapPrismaCheckbox(checkbox); @@ -124,7 +148,19 @@ export class DailyService { const checkbox = await prisma.dailyCheckbox.update({ where: { id: checkboxId }, data: updateData, - include: { task: true, user: true }, + include: { + task: { + include: { + taskTags: { + include: { + tag: true, + }, + }, + primaryTag: true, + }, + }, + user: true, + }, }); return this.mapPrismaCheckbox(checkbox); @@ -145,7 +181,19 @@ export class DailyService { const updated = await prisma.dailyCheckbox.update({ where: { id: checkboxId }, data: { isChecked: !existing.isChecked, updatedAt: new Date() }, - include: { task: true, user: true }, + include: { + task: { + include: { + taskTags: { + include: { + tag: true, + }, + }, + primaryTag: true, + }, + }, + user: true, + }, }); return this.mapPrismaCheckbox(updated); @@ -195,7 +243,19 @@ export class DailyService { contains: query, }, }, - include: { task: true, user: true }, + include: { + task: { + include: { + taskTags: { + include: { + tag: true, + }, + }, + primaryTag: true, + }, + }, + user: true, + }, orderBy: { date: 'desc' }, take: limit, }); @@ -271,12 +331,76 @@ export class DailyService { /** * Mappe une checkbox Prisma vers notre interface + * Accepte les checkboxes avec ou sans les relations taskTags et primaryTag */ private mapPrismaCheckbox( checkbox: Prisma.DailyCheckboxGetPayload<{ - include: { task: true; user: true }; + include: { + task: + | true + | { + include: { + taskTags: { + include: { + tag: true; + }; + }; + primaryTag: true; + }; + }; + user: true; + }; }> ): DailyCheckbox { + // Extraire les tags de la tâche si elle existe + let taskTags: string[] = []; + let taskTagDetails: + | Array<{ id: string; name: string; color: string; isPinned?: boolean }> + | undefined = undefined; + let taskPrimaryTag: + | { id: string; name: string; color: string; isPinned?: boolean } + | undefined = undefined; + + if (checkbox.task) { + // Vérifier si taskTags est disponible (peut être true ou un objet avec include) + const taskWithTags = checkbox.task as unknown as { + taskTags?: Array<{ + tag: { id: string; name: string; color: string; isPinned: boolean }; + }>; + primaryTag?: { + id: string; + name: string; + color: string; + isPinned: boolean; + } | null; + }; + + if ( + 'taskTags' in taskWithTags && + taskWithTags.taskTags && + Array.isArray(taskWithTags.taskTags) + ) { + // Utiliser les relations Prisma pour récupérer les noms et détails des tags + taskTags = taskWithTags.taskTags.map((tt) => tt.tag.name); + taskTagDetails = taskWithTags.taskTags.map((tt) => ({ + id: tt.tag.id, + name: tt.tag.name, + color: tt.tag.color, + isPinned: tt.tag.isPinned, + })); + } + + // Extraire le primaryTag si disponible + if ('primaryTag' in taskWithTags && taskWithTags.primaryTag) { + taskPrimaryTag = { + id: taskWithTags.primaryTag.id, + name: taskWithTags.primaryTag.name, + color: taskWithTags.primaryTag.color, + isPinned: taskWithTags.primaryTag.isPinned, + }; + } + } + return { id: checkbox.id, date: checkbox.date, @@ -295,7 +419,10 @@ export class DailyService { priority: checkbox.task.priority as TaskPriority, source: checkbox.task.source as TaskSource, sourceId: checkbox.task.sourceId || undefined, - tags: [], // Les tags seront chargés séparément si nécessaire + tags: taskTags, + tagDetails: taskTagDetails, + primaryTagId: checkbox.task.primaryTagId || undefined, + primaryTag: taskPrimaryTag, dueDate: checkbox.task.dueDate || undefined, completedAt: checkbox.task.completedAt || undefined, createdAt: checkbox.task.createdAt, @@ -384,7 +511,19 @@ export class DailyService { const checkboxes = await prisma.dailyCheckbox.findMany({ where: whereConditions, - include: { task: true, user: true }, + include: { + task: { + include: { + taskTags: { + include: { + tag: true, + }, + }, + primaryTag: true, + }, + }, + user: true, + }, orderBy: [{ date: 'desc' }, { order: 'asc' }], ...(options?.limit ? { take: options.limit } : {}), }); @@ -406,7 +545,19 @@ export class DailyService { ?.text + ' [ARCHIVÉ]', updatedAt: new Date(), }, - include: { task: true, user: true }, + include: { + task: { + include: { + taskTags: { + include: { + tag: true, + }, + }, + primaryTag: true, + }, + }, + user: true, + }, }); return this.mapPrismaCheckbox(checkbox); @@ -450,7 +601,19 @@ export class DailyService { order: newOrder, updatedAt: new Date(), }, - include: { task: true, user: true }, + include: { + task: { + include: { + taskTags: { + include: { + tag: true, + }, + }, + primaryTag: true, + }, + }, + user: true, + }, }); return this.mapPrismaCheckbox(updatedCheckbox);