refactor: enhance date handling across components

- Replaced direct date manipulations with utility functions for consistency and readability.
- Updated date formatting in `DailyCalendar`, `RecentTasks`, `CompletionRateChart`, and other components to use `formatDateShort` and `formatDateForDisplay`.
- Improved date parsing in `JiraLogs`, `JiraSchedulerConfig`, and `BackupSettingsPageClient` to ensure proper handling of date strings.
- Streamlined date initialization in `useDaily` and `DailyService` to utilize `getToday` and `getYesterday` for better clarity.
This commit is contained in:
Julien Froidefond
2025-09-21 12:02:06 +02:00
parent 557cdebc13
commit c3c1d24fa2
15 changed files with 39 additions and 46 deletions

View File

@@ -4,6 +4,8 @@ import React, { useState } from 'react';
import { Button } from '@/components/ui/Button';
import { Card } from '@/components/ui/Card';
import { formatDateForAPI, createDate, getToday } from '@/lib/date-utils';
import { format } from 'date-fns';
import { fr } from 'date-fns/locale';
interface DailyCalendarProps {
currentDate: Date;
@@ -97,10 +99,7 @@ export function DailyCalendar({
};
const formatMonthYear = () => {
return viewDate.toLocaleDateString('fr-FR', {
month: 'long',
year: 'numeric',
});
return format(viewDate, 'MMMM yyyy', { locale: fr });
};
const weekDays = ['Lun', 'Mar', 'Mer', 'Jeu', 'Ven', 'Sam', 'Dim'];

View File

@@ -3,6 +3,7 @@
import { Task } from '@/lib/types';
import { Card } from '@/components/ui/Card';
import { TagDisplay } from '@/components/ui/TagDisplay';
import { formatDateShort } from '@/lib/date-utils';
import { Badge } from '@/components/ui/Badge';
import { useTasksContext } from '@/contexts/TasksContext';
import { getPriorityConfig, getPriorityColorHex, getStatusBadgeClasses, getStatusLabel } from '@/lib/status-config';
@@ -18,7 +19,7 @@ export function RecentTasks({ tasks }: RecentTasksProps) {
// Prendre les 5 tâches les plus récentes (créées ou modifiées)
const recentTasks = tasks
.sort((a, b) => new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime())
.sort((a, b) => b.updatedAt.getTime() - a.updatedAt.getTime())
.slice(0, 5);
// Fonctions simplifiées utilisant la configuration centralisée
@@ -116,10 +117,7 @@ export function RecentTasks({ tasks }: RecentTasksProps) {
</div>
<div className="text-xs text-[var(--muted-foreground)] whitespace-nowrap">
{new Date(task.updatedAt).toLocaleDateString('fr-FR', {
day: 'numeric',
month: 'short'
})}
{formatDateShort(task.updatedAt)}
</div>
</div>
</div>

View File

@@ -2,6 +2,7 @@
import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer } from 'recharts';
import { DailyMetrics } from '@/services/metrics';
import { parseDate, formatDateShort } from '@/lib/date-utils';
interface CompletionRateChartProps {
data: DailyMetrics[];
@@ -12,7 +13,7 @@ export function CompletionRateChart({ data, className }: CompletionRateChartProp
// Transformer les données pour le graphique
const chartData = data.map(day => ({
day: day.dayName.substring(0, 3), // Lun, Mar, etc.
date: new Date(day.date).toLocaleDateString('fr-FR', { day: '2-digit', month: '2-digit' }),
date: formatDateShort(parseDate(day.date)),
completionRate: day.completionRate,
completed: day.completed,
total: day.totalTasks

View File

@@ -4,7 +4,7 @@ import { useState, useEffect, useCallback, useTransition } from 'react';
import { DailyCheckbox } from '@/lib/types';
import { tasksClient } from '@/clients/tasks-client';
import { Button } from '@/components/ui/Button';
import { formatDateSmart } from '@/lib/date-utils';
import { formatDateSmart, parseDate } from '@/lib/date-utils';
import { Input } from '@/components/ui/Input';
import { addTodoToTask, toggleCheckbox } from '@/actions/daily';
@@ -42,7 +42,7 @@ export function RelatedTodos({ taskId }: RelatedTodosProps) {
startTransition(async () => {
try {
// Si une date est spécifiée, l'utiliser, sinon undefined (aujourd'hui par défaut)
const targetDate = newTodoDate ? new Date(newTodoDate) : undefined;
const targetDate = newTodoDate ? parseDate(newTodoDate) : undefined;
const result = await addTodoToTask(taskId, newTodoText, targetDate);
@@ -79,7 +79,7 @@ export function RelatedTodos({ taskId }: RelatedTodosProps) {
const formatDate = (date: Date | string) => {
try {
const dateObj = typeof date === 'string' ? new Date(date) : date;
const dateObj = typeof date === 'string' ? parseDate(date) : date;
if (isNaN(dateObj.getTime())) {
return 'Date invalide';
}

View File

@@ -6,6 +6,7 @@ import { Badge } from '@/components/ui/Badge';
import { Button } from '@/components/ui/Button';
import { formatDistanceToNow } from 'date-fns';
import { fr } from 'date-fns/locale';
import { parseDate } from '@/lib/date-utils';
interface SyncLog {
id: string;
@@ -111,7 +112,7 @@ export function JiraLogs({ className = "" }: JiraLogsProps) {
<div className="flex items-center justify-between mb-2">
{getStatusBadge(log.status)}
<span className="text-xs text-[var(--muted-foreground)]">
{formatDistanceToNow(new Date(log.createdAt), {
{formatDistanceToNow(parseDate(log.createdAt), {
addSuffix: true,
locale: fr
})}

View File

@@ -5,6 +5,7 @@ import { Button } from '@/components/ui/Button';
import { Card, CardHeader, CardContent } from '@/components/ui/Card';
import { Badge } from '@/components/ui/Badge';
import { jiraClient, JiraSchedulerStatus } from '@/clients/jira-client';
import { parseDate, getToday } from '@/lib/date-utils';
interface JiraSchedulerConfigProps {
className?: string;
@@ -85,8 +86,8 @@ export function JiraSchedulerConfig({ className = "" }: JiraSchedulerConfigProps
const getNextSyncText = () => {
if (!schedulerStatus?.nextSync) return 'Aucune synchronisation planifiée';
const nextSync = new Date(schedulerStatus.nextSync);
const now = new Date();
const nextSync = parseDate(schedulerStatus.nextSync);
const now = getToday();
const diffMs = nextSync.getTime() - now.getTime();
if (diffMs <= 0) return 'Synchronisation en cours...';

View File

@@ -4,6 +4,7 @@ import { useState } from 'react';
import { Button } from '@/components/ui/Button';
import { Card, CardHeader, CardContent } from '@/components/ui/Card';
import { Badge } from '@/components/ui/Badge';
import { getToday } from '@/lib/date-utils';
import { Modal } from '@/components/ui/Modal';
import { jiraClient } from '@/clients/jira-client';
import { JiraSyncResult, JiraSyncAction } from '@/services/jira';
@@ -79,7 +80,7 @@ export function JiraSync({ onSyncComplete, className = "" }: JiraSyncProps) {
{success ? "✓ Succès" : "⚠ Erreurs"}
</Badge>
<span className="text-[var(--muted-foreground)] text-xs">
{new Date().toLocaleTimeString()}
{getToday().toLocaleTimeString()}
</span>
</div>
<div className="text-xs text-[var(--muted-foreground)]">

View File

@@ -4,6 +4,7 @@ import { useState, useEffect, useCallback } from 'react';
import { SprintVelocity, JiraTask, AssigneeDistribution, StatusDistribution } from '@/lib/types';
import { Modal } from '@/components/ui/Modal';
import { Card, CardHeader, CardContent } from '@/components/ui/Card';
import { parseDate, formatDateForDisplay } from '@/lib/date-utils';
import { Badge } from '@/components/ui/Badge';
import { Button } from '@/components/ui/Button';
@@ -144,7 +145,7 @@ export default function SprintDetailModal({
<div className="text-center">
<div className="text-sm text-gray-600">Période</div>
<div className="text-xs text-gray-500">
{new Date(sprint.startDate).toLocaleDateString('fr-FR')} - {new Date(sprint.endDate).toLocaleDateString('fr-FR')}
{formatDateForDisplay(parseDate(sprint.startDate))} - {formatDateForDisplay(parseDate(sprint.endDate))}
</div>
</div>
</div>
@@ -318,7 +319,7 @@ export default function SprintDetailModal({
<div className="flex items-center gap-4 text-xs text-gray-500">
<span>📋 {issue.issuetype.name}</span>
<span>👤 {issue.assignee?.displayName || 'Non assigné'}</span>
<span>📅 {new Date(issue.created).toLocaleDateString('fr-FR')}</span>
<span>📅 {formatDateForDisplay(parseDate(issue.created))}</span>
</div>
</div>
</div>

View File

@@ -427,7 +427,7 @@ export function TaskCard({ task, onEdit, compactView = false }: TaskCardProps) {
{task.dueDate ? (
<span className="flex items-center gap-1 text-[var(--muted-foreground)] font-mono">
<span className="text-[var(--primary)]"></span>
{formatDistanceToNow(new Date(task.dueDate), {
{formatDistanceToNow(task.dueDate, {
addSuffix: true,
locale: fr
})}

View File

@@ -8,6 +8,7 @@ import { Button } from '@/components/ui/Button';
import { UserPreferencesProvider } from '@/contexts/UserPreferencesContext';
import { backupClient, BackupListResponse } from '@/clients/backup-client';
import Link from 'next/link';
import { parseDate, getToday, formatDateForDisplay } from '@/lib/date-utils';
interface DatabaseStats {
taskCount: number;
@@ -86,22 +87,14 @@ export function AdvancedSettingsPageClient({
const formatTimeAgo = (date: Date): string => {
// Format fixe pour éviter les erreurs d'hydratation
const d = new Date(date);
return d.toLocaleDateString('fr-FR', {
day: '2-digit',
month: '2-digit',
year: 'numeric',
hour: '2-digit',
minute: '2-digit',
hour12: false
});
return formatDateForDisplay(date, 'DISPLAY_MEDIUM');
};
const getNextBackupTime = (): string => {
if (!backupData.scheduler.nextBackup) return 'Non planifiée';
const nextBackup = new Date(backupData.scheduler.nextBackup);
const now = new Date();
const nextBackup = parseDate(backupData.scheduler.nextBackup);
const now = getToday();
const diffMs = nextBackup.getTime() - now.getTime();
const diffMins = Math.floor(diffMs / (1000 * 60));
const diffHours = Math.floor(diffMins / 60);

View File

@@ -8,7 +8,7 @@ import { Card, CardHeader, CardContent } from '@/components/ui/Card';
import { Input } from '@/components/ui/Input';
import { Modal } from '@/components/ui/Modal';
import { Header } from '@/components/ui/Header';
import { formatDateForDisplay } from '@/lib/date-utils';
import { formatDateForDisplay, parseDate, getToday } from '@/lib/date-utils';
import Link from 'next/link';
interface BackupSettingsPageClientProps {
@@ -209,8 +209,8 @@ export default function BackupSettingsPageClient({ initialData }: BackupSettings
const getNextBackupTime = (): string => {
if (!data?.scheduler.nextBackup) return 'Non planifiée';
const nextBackup = new Date(data.scheduler.nextBackup);
const now = new Date();
const nextBackup = parseDate(data.scheduler.nextBackup);
const now = getToday();
const diffMs = nextBackup.getTime() - now.getTime();
const diffMins = Math.floor(diffMs / (1000 * 60));
const diffHours = Math.floor(diffMins / 60);

View File

@@ -10,6 +10,7 @@ import { Input } from '@/components/ui/Input';
import { TagForm } from '@/components/forms/TagForm';
import { UserPreferencesProvider } from '@/contexts/UserPreferencesContext';
import Link from 'next/link';
import { formatDateForDisplay } from '@/lib/date-utils';
interface GeneralSettingsPageClientProps {
initialPreferences: UserPreferences;
@@ -294,7 +295,7 @@ export function GeneralSettingsPageClient({ initialPreferences, initialTags }: G
</div>
{('createdAt' in tag && (tag as Tag & { createdAt: Date }).createdAt) && (
<div className="text-xs text-[var(--muted-foreground)]">
Créé le {new Date((tag as Tag & { createdAt: Date }).createdAt).toLocaleDateString('fr-FR')}
Créé le {formatDateForDisplay((tag as Tag & { createdAt: Date }).createdAt)}
</div>
)}
</div>

View File

@@ -43,7 +43,7 @@ interface UseDailyActions {
* Hook pour la gestion d'une vue daily spécifique
*/
export function useDaily(initialDate?: Date, initialDailyView?: DailyView): UseDailyState & UseDailyActions & { currentDate: Date } {
const [currentDate, setCurrentDate] = useState<Date>(initialDate || new Date());
const [currentDate, setCurrentDate] = useState<Date>(initialDate || getToday());
const [dailyView, setDailyView] = useState<DailyView | null>(initialDailyView || null);
const [loading, setLoading] = useState(!initialDailyView); // Pas de loading si on a des données SSR
const [refreshing, setRefreshing] = useState(false); // Pour les refresh silencieux

View File

@@ -1,7 +1,7 @@
import { prisma } from './database';
import { Prisma } from '@prisma/client';
import { DailyCheckbox, DailyView, CreateDailyCheckboxData, UpdateDailyCheckboxData, BusinessError, DailyCheckboxType, TaskStatus, TaskPriority, TaskSource } from '@/lib/types';
import { getPreviousWorkday, normalizeDate, formatDateForAPI } from '@/lib/date-utils';
import { getPreviousWorkday, normalizeDate, formatDateForAPI, getToday, getYesterday } from '@/lib/date-utils';
/**
* Service pour la gestion des checkboxes daily
@@ -180,7 +180,7 @@ export class DailyService {
* Récupère la vue daily d'aujourd'hui
*/
async getTodaysDailyView(): Promise<DailyView> {
return this.getDailyView(new Date());
return this.getDailyView(getToday());
}
/**
@@ -188,7 +188,7 @@ export class DailyService {
*/
async addTodayCheckbox(text: string, taskId?: string): Promise<DailyCheckbox> {
return this.addCheckbox({
date: new Date(),
date: getToday(),
text,
taskId
});
@@ -198,11 +198,8 @@ export class DailyService {
* Ajoute une checkbox pour hier
*/
async addYesterdayCheckbox(text: string, taskId?: string): Promise<DailyCheckbox> {
const yesterday = new Date();
yesterday.setDate(yesterday.getDate() - 1);
return this.addCheckbox({
date: yesterday,
date: getYesterday(),
text,
taskId
});

View File

@@ -5,7 +5,7 @@
import { JiraTask } from '@/lib/types';
import { prisma } from './database';
import { parseDate } from '@/lib/date-utils';
import { parseDate, formatDateForDisplay } from '@/lib/date-utils';
export interface JiraConfig {
baseUrl: string;
@@ -389,8 +389,8 @@ export class JiraService {
changes.push(`Priorité: préservée localement (${existingTask.priority})`);
}
if ((existingTask.dueDate?.getTime() || null) !== (taskData.dueDate?.getTime() || null)) {
const oldDate = existingTask.dueDate ? existingTask.dueDate.toLocaleDateString() : 'Aucune';
const newDate = taskData.dueDate ? taskData.dueDate.toLocaleDateString() : 'Aucune';
const oldDate = existingTask.dueDate ? formatDateForDisplay(existingTask.dueDate) : 'Aucune';
const newDate = taskData.dueDate ? formatDateForDisplay(taskData.dueDate) : 'Aucune';
changes.push(`Échéance: ${oldDate}${newDate}`);
}
if (existingTask.jiraProject !== taskData.jiraProject) {