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:
@@ -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'];
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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';
|
||||
}
|
||||
|
||||
@@ -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
|
||||
})}
|
||||
|
||||
@@ -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...';
|
||||
|
||||
@@ -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)]">
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
})}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
});
|
||||
|
||||
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user