refactor: date utils and all calls

This commit is contained in:
Julien Froidefond
2025-09-21 11:41:17 +02:00
parent 799a21df5c
commit 557cdebc13
23 changed files with 300 additions and 117 deletions

View File

@@ -3,6 +3,7 @@
import { dailyService } from '@/services/daily';
import { UpdateDailyCheckboxData, DailyCheckbox, CreateDailyCheckboxData } from '@/lib/types';
import { revalidatePath } from 'next/cache';
import { getToday, getPreviousWorkday, parseDate, normalizeDate } from '@/lib/date-utils';
/**
* Toggle l'état d'une checkbox
@@ -19,7 +20,7 @@ export async function toggleCheckbox(checkboxId: string): Promise<{
// (le front-end gère déjà l'état optimiste)
// Récupérer toutes les checkboxes d'aujourd'hui et hier pour trouver celle à toggle
const today = new Date();
const today = getToday();
const dailyView = await dailyService.getDailyView(today);
let checkbox = dailyView.today.find(cb => cb.id === checkboxId);
@@ -57,7 +58,7 @@ export async function addCheckboxToDaily(dailyId: string, content: string, taskI
}> {
try {
// Le dailyId correspond à la date au format YYYY-MM-DD
const date = new Date(dailyId);
const date = parseDate(dailyId);
const newCheckbox = await dailyService.addCheckbox({
date,
@@ -86,7 +87,7 @@ export async function addTodayCheckbox(content: string, type?: 'task' | 'meeting
}> {
try {
const newCheckbox = await dailyService.addCheckbox({
date: new Date(),
date: getToday(),
text: content,
type: type || 'task',
taskId
@@ -112,8 +113,7 @@ export async function addYesterdayCheckbox(content: string, type?: 'task' | 'mee
error?: string;
}> {
try {
const yesterday = new Date();
yesterday.setDate(yesterday.getDate() - 1);
const yesterday = getPreviousWorkday(getToday());
const newCheckbox = await dailyService.addCheckbox({
date: yesterday,
@@ -209,8 +209,7 @@ export async function addTodoToTask(taskId: string, text: string, date?: Date):
error?: string;
}> {
try {
const targetDate = date || new Date();
targetDate.setHours(0, 0, 0, 0);
const targetDate = normalizeDate(date || getToday());
const checkboxData: CreateDailyCheckboxData = {
date: targetDate,
@@ -243,7 +242,7 @@ export async function reorderCheckboxes(dailyId: string, checkboxIds: string[]):
}> {
try {
// Le dailyId correspond à la date au format YYYY-MM-DD
const date = new Date(dailyId);
const date = parseDate(dailyId);
await dailyService.reorderCheckboxes(date, checkboxIds);

View File

@@ -1,6 +1,7 @@
'use server';
import { getJiraAnalytics } from './jira-analytics';
import { formatDateForDisplay, getToday } from '@/lib/date-utils';
export type ExportFormat = 'csv' | 'json';
@@ -142,7 +143,7 @@ function generateCSV(analytics: JiraAnalytics): string {
// Header du rapport
lines.push('# Rapport Analytics Jira');
lines.push(`# Projet: ${analytics.project.name} (${analytics.project.key})`);
lines.push(`# Généré le: ${new Date().toLocaleString('fr-FR')}`);
lines.push(`# Généré le: ${formatDateForDisplay(getToday(), 'DISPLAY_LONG')}`);
lines.push(`# Total tickets: ${analytics.project.totalIssues}`);
lines.push('');

View File

@@ -1,6 +1,7 @@
'use server';
import { MetricsService, WeeklyMetricsOverview, VelocityTrend } from '@/services/metrics';
import { getToday } from '@/lib/date-utils';
import { revalidatePath } from 'next/cache';
/**
@@ -12,7 +13,7 @@ export async function getWeeklyMetrics(date?: Date): Promise<{
error?: string;
}> {
try {
const targetDate = date || new Date();
const targetDate = date || getToday();
const metrics = await MetricsService.getWeeklyMetrics(targetDate);
return {

View File

@@ -1,5 +1,6 @@
import { NextResponse } from 'next/server';
import { dailyService } from '@/services/daily';
import { getToday, parseDate, isValidAPIDate } from '@/lib/date-utils';
/**
* API route pour récupérer la vue daily (hier + aujourd'hui)
@@ -32,13 +33,18 @@ export async function GET(request: Request) {
}
// Vue daily pour une date donnée (ou aujourd'hui par défaut)
const targetDate = date ? new Date(date) : new Date();
let targetDate: Date;
if (date && isNaN(targetDate.getTime())) {
return NextResponse.json(
{ error: 'Format de date invalide. Utilisez YYYY-MM-DD' },
{ status: 400 }
);
if (date) {
if (!isValidAPIDate(date)) {
return NextResponse.json(
{ error: 'Format de date invalide. Utilisez YYYY-MM-DD' },
{ status: 400 }
);
}
targetDate = parseDate(date);
} else {
targetDate = getToday();
}
const dailyView = await dailyService.getDailyView(targetDate);

View File

@@ -10,7 +10,7 @@ import { DailyCalendar } from '@/components/daily/DailyCalendar';
import { DailySection } from '@/components/daily/DailySection';
import { dailyClient } from '@/clients/daily-client';
import { Header } from '@/components/ui/Header';
import { getPreviousWorkday } from '@/lib/workday-utils';
import { getPreviousWorkday, formatDateLong, isToday, generateDateTitle, formatDateShort, isYesterday } from '@/lib/date-utils';
interface DailyPageClientProps {
initialDailyView?: DailyView;
@@ -112,37 +112,23 @@ export function DailyPageClient({
};
const formatCurrentDate = () => {
return currentDate.toLocaleDateString('fr-FR', {
weekday: 'long',
year: 'numeric',
month: 'long',
day: 'numeric'
});
return formatDateLong(currentDate);
};
const isToday = () => {
const today = new Date();
return currentDate.toDateString() === today.toDateString();
const isTodayDate = () => {
return isToday(currentDate);
};
const getTodayTitle = () => {
const today = new Date();
if (currentDate.toDateString() === today.toDateString()) {
return "🎯 Aujourd'hui";
}
return `🎯 ${currentDate.toLocaleDateString('fr-FR', { day: '2-digit', month: '2-digit', year: '2-digit' })}`;
return generateDateTitle(currentDate, '🎯');
};
const getYesterdayTitle = () => {
const today = new Date();
const yesterday = new Date(today);
yesterday.setDate(yesterday.getDate() - 1);
const yesterdayDate = getYesterdayDate();
if (yesterdayDate.toDateString() === yesterday.toDateString()) {
if (isYesterday(yesterdayDate)) {
return "📋 Hier";
}
return `📋 ${yesterdayDate.toLocaleDateString('fr-FR', { day: '2-digit', month: '2-digit', year: '2-digit' })}`;
return `📋 ${formatDateShort(yesterdayDate)}`;
};
if (loading) {
@@ -198,7 +184,7 @@ export function DailyPageClient({
<div className="text-sm font-bold text-[var(--foreground)] font-mono">
{formatCurrentDate()}
</div>
{!isToday() && (
{!isTodayDate() && (
<button
onClick={goToToday}
className="text-xs text-[var(--primary)] hover:text-[var(--primary)]/80 font-mono"

View File

@@ -1,5 +1,6 @@
import { httpClient } from './base/http-client';
import { DailyCheckbox, DailyView, Task } from '@/lib/types';
import { formatDateForAPI, parseDate } from '@/lib/date-utils';
// Types pour les réponses API (avec dates en string)
interface ApiCheckbox {
@@ -97,10 +98,7 @@ export class DailyClient {
* Formate une date pour l'API (évite les décalages timezone)
*/
formatDateForAPI(date: Date): string {
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate()).padStart(2, '0');
return `${year}-${month}-${day}`; // YYYY-MM-DD
return formatDateForAPI(date);
}
/**
@@ -109,9 +107,9 @@ export class DailyClient {
private transformCheckboxDates(checkbox: ApiCheckbox): DailyCheckbox {
return {
...checkbox,
date: new Date(checkbox.date),
createdAt: new Date(checkbox.createdAt),
updatedAt: new Date(checkbox.updatedAt)
date: parseDate(checkbox.date),
createdAt: parseDate(checkbox.createdAt),
updatedAt: parseDate(checkbox.updatedAt)
};
}
@@ -120,7 +118,7 @@ export class DailyClient {
*/
private transformDailyViewDates(view: ApiDailyView): DailyView {
return {
date: new Date(view.date),
date: parseDate(view.date),
yesterday: view.yesterday.map((cb: ApiCheckbox) => this.transformCheckboxDates(cb)),
today: view.today.map((cb: ApiCheckbox) => this.transformCheckboxDates(cb))
};

View File

@@ -2,6 +2,7 @@
import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer } from 'recharts';
import { Card } from '@/components/ui/Card';
import { parseDate, formatDateShort } from '@/lib/date-utils';
interface CompletionTrendData {
date: string;
@@ -18,11 +19,11 @@ interface CompletionTrendChartProps {
export function CompletionTrendChart({ data, title = "Tendance de Completion" }: CompletionTrendChartProps) {
// Formatter pour les dates
const formatDate = (dateStr: string) => {
const date = new Date(dateStr);
return date.toLocaleDateString('fr-FR', {
day: 'numeric',
month: 'short'
});
try {
return formatDateShort(parseDate(dateStr));
} catch {
return dateStr;
}
};
// Tooltip personnalisé

View File

@@ -3,6 +3,7 @@
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';
interface DailyCalendarProps {
currentDate: Date;
@@ -15,33 +16,30 @@ export function DailyCalendar({
onDateSelect,
dailyDates,
}: DailyCalendarProps) {
const [viewDate, setViewDate] = useState(new Date(currentDate));
const [viewDate, setViewDate] = useState(createDate(currentDate));
// Formatage des dates pour comparaison (éviter le décalage timezone)
const formatDateKey = (date: Date) => {
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate()).padStart(2, '0');
return `${year}-${month}-${day}`;
return formatDateForAPI(date);
};
const currentDateKey = formatDateKey(currentDate);
// Navigation mois
const goToPreviousMonth = () => {
const newDate = new Date(viewDate);
const newDate = createDate(viewDate);
newDate.setMonth(newDate.getMonth() - 1);
setViewDate(newDate);
};
const goToNextMonth = () => {
const newDate = new Date(viewDate);
const newDate = createDate(viewDate);
newDate.setMonth(newDate.getMonth() + 1);
setViewDate(newDate);
};
const goToToday = () => {
const today = new Date();
const today = getToday();
setViewDate(today);
onDateSelect(today);
};
@@ -57,18 +55,18 @@ export function DailyCalendar({
const lastDay = new Date(year, month + 1, 0);
// Premier lundi de la semaine contenant le premier jour
const startDate = new Date(firstDay);
const startDate = createDate(firstDay);
const dayOfWeek = firstDay.getDay();
const daysToSubtract = dayOfWeek === 0 ? 6 : dayOfWeek - 1; // Lundi = 0
startDate.setDate(firstDay.getDate() - daysToSubtract);
// Générer toutes les dates du calendrier (6 semaines)
const days = [];
const currentDay = new Date(startDate);
const currentDay = createDate(startDate);
for (let i = 0; i < 42; i++) {
// 6 semaines × 7 jours
days.push(new Date(currentDay));
days.push(createDate(currentDay));
currentDay.setDate(currentDay.getDate() + 1);
}
@@ -81,8 +79,8 @@ export function DailyCalendar({
onDateSelect(date);
};
const isToday = (date: Date) => {
const today = new Date();
const isTodayDate = (date: Date) => {
const today = getToday();
return formatDateKey(date) === formatDateKey(today);
};
@@ -157,7 +155,7 @@ export function DailyCalendar({
<div className="grid grid-cols-7 gap-1">
{days.map((date, index) => {
const isCurrentMonthDay = isCurrentMonth(date);
const isTodayDay = isToday(date);
const isTodayDay = isTodayDate(date);
const hasCheckboxes = hasDaily(date);
const isSelectedDay = isSelected(date);

View File

@@ -2,6 +2,7 @@
import { useState } from 'react';
import { useWeeklyMetrics, useVelocityTrends } from '@/hooks/use-metrics';
import { getToday } from '@/lib/date-utils';
import { Card, CardHeader, CardContent } from '@/components/ui/Card';
import { Button } from '@/components/ui/Button';
import { DailyStatusChart } from './charts/DailyStatusChart';
@@ -19,7 +20,7 @@ interface MetricsTabProps {
}
export function MetricsTab({ className }: MetricsTabProps) {
const [selectedDate] = useState<Date>(new Date());
const [selectedDate] = useState<Date>(getToday());
const [weeksBack, setWeeksBack] = useState(4);
const { metrics, loading: metricsLoading, error: metricsError, refetch: refetchMetrics } = useWeeklyMetrics(selectedDate);

View File

@@ -2,6 +2,7 @@
import { BarChart, Bar, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer, Legend } from 'recharts';
import { DailyMetrics } from '@/services/metrics';
import { parseDate, formatDateShort } from '@/lib/date-utils';
interface DailyStatusChartProps {
data: DailyMetrics[];
@@ -12,7 +13,7 @@ export function DailyStatusChart({ data, className }: DailyStatusChartProps) {
// 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)),
'Complétées': day.completed,
'En cours': day.inProgress,
'Bloquées': day.blocked,

View File

@@ -1,6 +1,7 @@
'use client';
import { DailyMetrics } from '@/services/metrics';
import { parseDate, isToday } from '@/lib/date-utils';
interface WeeklyActivityHeatmapProps {
data: DailyMetrics[];
@@ -67,7 +68,7 @@ export function WeeklyActivityHeatmap({ data, className }: WeeklyActivityHeatmap
</div>
{/* Indicator si jour actuel */}
{new Date(day.date).toDateString() === new Date().toDateString() && (
{isToday(parseDate(day.date)) && (
<div className="w-2 h-2 bg-blue-500 rounded-full"></div>
)}
</div>

View File

@@ -4,6 +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 { Input } from '@/components/ui/Input';
import { addTodoToTask, toggleCheckbox } from '@/actions/daily';
@@ -82,11 +83,7 @@ export function RelatedTodos({ taskId }: RelatedTodosProps) {
if (isNaN(dateObj.getTime())) {
return 'Date invalide';
}
return new Intl.DateTimeFormat('fr-FR', {
day: 'numeric',
month: 'short',
year: 'numeric'
}).format(dateObj);
return formatDateSmart(dateObj);
} catch (error) {
console.error('Erreur formatage date:', error, date);
return 'Date invalide';

View File

@@ -7,6 +7,7 @@ import { Button } from '@/components/ui/Button';
import { Badge } from '@/components/ui/Badge';
import { Modal } from '@/components/ui/Modal';
import { Card, CardHeader, CardContent } from '@/components/ui/Card';
import { formatDateForDisplay, getToday } from '@/lib/date-utils';
interface AnomalyDetectionPanelProps {
className?: string;
@@ -42,7 +43,7 @@ export default function AnomalyDetectionPanel({ className = '' }: AnomalyDetecti
if (result.success && result.data) {
setAnomalies(result.data);
setLastUpdate(new Date().toLocaleString('fr-FR'));
setLastUpdate(formatDateForDisplay(getToday(), 'DISPLAY_LONG'));
} else {
setError(result.error || 'Erreur lors de la détection');
}

View File

@@ -8,6 +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 Link from 'next/link';
interface BackupSettingsPageClientProps {
@@ -193,16 +194,8 @@ export default function BackupSettingsPageClient({ initialData }: BackupSettings
const formatDate = (date: string | Date): string => {
// Format cohérent serveur/client 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',
second: '2-digit',
hour12: false
});
const d = typeof date === 'string' ? new Date(date) : date;
return formatDateForDisplay(d, 'DISPLAY_MEDIUM');
};
if (isLoading) {

View File

@@ -3,6 +3,7 @@
import { useState, useEffect, useCallback, useTransition } from 'react';
import { dailyClient, DailyHistoryFilters, DailySearchFilters, ReorderCheckboxesData } from '@/clients/daily-client';
import { DailyView, DailyCheckbox, UpdateDailyCheckboxData, DailyCheckboxType } from '@/lib/types';
import { addDays, subtractDays, getToday } from '@/lib/date-utils';
import {
toggleCheckbox as toggleCheckboxAction,
addTodayCheckbox as addTodayCheckboxAction,
@@ -341,19 +342,17 @@ export function useDaily(initialDate?: Date, initialDailyView?: DailyView): UseD
}, []);
const goToPreviousDay = useCallback(async (): Promise<void> => {
const previousDay = new Date(currentDate);
previousDay.setDate(previousDay.getDate() - 1);
const previousDay = subtractDays(currentDate, 1);
setCurrentDate(previousDay);
}, [currentDate]);
const goToNextDay = useCallback(async (): Promise<void> => {
const nextDay = new Date(currentDate);
nextDay.setDate(nextDay.getDate() + 1);
const nextDay = addDays(currentDate, 1);
setCurrentDate(nextDay);
}, [currentDate]);
const goToToday = useCallback(async (): Promise<void> => {
setCurrentDate(new Date());
setCurrentDate(getToday());
}, []);
const setDate = useCallback(async (date: Date): Promise<void> => {

View File

@@ -3,6 +3,7 @@ import { exec } from 'child_process';
import { promisify } from 'util';
import path from 'path';
import { createHash } from 'crypto';
import { formatDateForDisplay, getToday } from './date-utils';
const execAsync = promisify(exec);
@@ -184,7 +185,7 @@ export class BackupUtils {
extra?: { hash?: string; size?: number; previousHash?: string }
): Promise<void> {
try {
const date = new Date().toLocaleString('fr-FR');
const date = formatDateForDisplay(getToday(), 'DISPLAY_LONG');
let logEntry = `[${date}] ${type.toUpperCase()} BACKUP ${action.toUpperCase()}: ${details}`;

206
src/lib/date-utils.ts Normal file
View File

@@ -0,0 +1,206 @@
/**
* Utilitaires centralisés pour la gestion des dates
* Regroupe toutes les fonctions de formatage, manipulation et validation de dates
*/
import { format, startOfDay, endOfDay, isValid } from 'date-fns';
import { fr } from 'date-fns/locale';
// Re-export des utilitaires workday existants
export { getPreviousWorkday, getNextWorkday, isWorkday, getDayName } from './workday-utils';
/**
* Formats de dates standardisés
*/
export const DATE_FORMATS = {
API: 'yyyy-MM-dd', // Format API (YYYY-MM-DD)
DISPLAY_SHORT: 'dd/MM/yy', // Format court (01/12/25)
DISPLAY_LONG: 'EEEE d MMMM yyyy', // Format long (lundi 1 décembre 2025)
DISPLAY_MEDIUM: 'dd/MM/yyyy', // Format moyen (01/12/2025)
ISO: "yyyy-MM-dd'T'HH:mm:ss.SSSxxx" // Format ISO complet
} as const;
/**
* Normalise une date au début de la journée (00:00:00.000)
*/
export function normalizeDate(date: Date): Date {
const normalized = new Date(date);
normalized.setHours(0, 0, 0, 0);
return normalized;
}
/**
* Formate une date pour l'API (évite les décalages timezone)
* @param date - Date à formater
* @returns Format YYYY-MM-DD
*/
export function formatDateForAPI(date: Date): string {
if (!isValid(date)) {
throw new Error('Date invalide fournie à formatDateForAPI');
}
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate()).padStart(2, '0');
return `${year}-${month}-${day}`;
}
/**
* Formate une date pour l'affichage en français
*/
export function formatDateForDisplay(date: Date, formatType: keyof typeof DATE_FORMATS = 'DISPLAY_MEDIUM'): string {
if (!isValid(date)) {
throw new Error('Date invalide fournie à formatDateForDisplay');
}
return format(date, DATE_FORMATS[formatType], { locale: fr });
}
/**
* Formate une date courte pour l'affichage (dd/MM/yy)
*/
export function formatDateShort(date: Date): string {
return formatDateForDisplay(date, 'DISPLAY_SHORT');
}
/**
* Formate une date longue pour l'affichage (lundi 1 décembre 2025)
*/
export function formatDateLong(date: Date): string {
return formatDateForDisplay(date, 'DISPLAY_LONG');
}
/**
* Vérifie si une date est aujourd'hui
*/
export function isToday(date: Date): boolean {
const today = new Date();
return normalizeDate(date).getTime() === normalizeDate(today).getTime();
}
/**
* Vérifie si une date est hier
*/
export function isYesterday(date: Date): boolean {
const yesterday = new Date();
yesterday.setDate(yesterday.getDate() - 1);
return normalizeDate(date).getTime() === normalizeDate(yesterday).getTime();
}
/**
* Compare deux dates (sans tenir compte de l'heure)
*/
export function isSameDay(date1: Date, date2: Date): boolean {
return normalizeDate(date1).getTime() === normalizeDate(date2).getTime();
}
/**
* Obtient la date d'aujourd'hui normalisée
*/
export function getToday(): Date {
return normalizeDate(new Date());
}
/**
* Obtient la date d'hier normalisée
*/
export function getYesterday(): Date {
const yesterday = new Date();
yesterday.setDate(yesterday.getDate() - 1);
return normalizeDate(yesterday);
}
/**
* Crée une nouvelle date à partir d'une date existante
*/
export function createDate(date: Date): Date {
return new Date(date);
}
/**
* Ajoute des jours à une date
*/
export function addDays(date: Date, days: number): Date {
const result = createDate(date);
result.setDate(result.getDate() + days);
return result;
}
/**
* Soustrait des jours à une date
*/
export function subtractDays(date: Date, days: number): Date {
return addDays(date, -days);
}
/**
* Parse une date depuis une string avec validation
*/
export function parseDate(dateString: string): Date {
const parsed = new Date(dateString);
if (!isValid(parsed)) {
throw new Error(`Date invalide: ${dateString}`);
}
return parsed;
}
/**
* Valide qu'une string est une date valide au format API (YYYY-MM-DD)
*/
export function isValidAPIDate(dateString: string): boolean {
const regex = /^\d{4}-\d{2}-\d{2}$/;
if (!regex.test(dateString)) {
return false;
}
try {
const date = parseDate(dateString);
return formatDateForAPI(date) === dateString;
} catch {
return false;
}
}
/**
* Obtient le début de la journée pour une date
*/
export function getStartOfDay(date: Date): Date {
return startOfDay(date);
}
/**
* Obtient la fin de la journée pour une date
*/
export function getEndOfDay(date: Date): Date {
return endOfDay(date);
}
/**
* Formate une date pour l'affichage avec gestion des cas spéciaux (aujourd'hui, hier)
*/
export function formatDateSmart(date: Date): string {
if (isToday(date)) {
return "Aujourd'hui";
}
if (isYesterday(date)) {
return "Hier";
}
return formatDateForDisplay(date, 'DISPLAY_MEDIUM');
}
/**
* Génère un titre intelligent pour une date (avec emojis)
*/
export function generateDateTitle(date: Date, emoji: string = '📅'): string {
if (isToday(date)) {
return `${emoji} Aujourd'hui`;
}
if (isYesterday(date)) {
return `${emoji} Hier`;
}
return `${emoji} ${formatDateShort(date)}`;
}

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 } from '@/lib/workday-utils';
import { getPreviousWorkday, normalizeDate, formatDateForAPI } from '@/lib/date-utils';
/**
* Service pour la gestion des checkboxes daily
@@ -13,8 +13,7 @@ export class DailyService {
*/
async getDailyView(date: Date): Promise<DailyView> {
// Normaliser la date (début de journée)
const today = new Date(date);
today.setHours(0, 0, 0, 0);
const today = normalizeDate(date);
// Utiliser la logique de jour de travail précédent au lieu de jour-1
const yesterday = getPreviousWorkday(today);
@@ -37,8 +36,7 @@ export class DailyService {
*/
async getCheckboxesByDate(date: Date): Promise<DailyCheckbox[]> {
// Normaliser la date (début de journée)
const normalizedDate = new Date(date);
normalizedDate.setHours(0, 0, 0, 0);
const normalizedDate = normalizeDate(date);
const checkboxes = await prisma.dailyCheckbox.findMany({
where: { date: normalizedDate },
@@ -54,8 +52,7 @@ export class DailyService {
*/
async addCheckbox(data: CreateDailyCheckboxData): Promise<DailyCheckbox> {
// Normaliser la date
const normalizedDate = new Date(data.date);
normalizedDate.setHours(0, 0, 0, 0);
const normalizedDate = normalizeDate(data.date);
// Calculer l'ordre suivant pour cette date
const maxOrder = await prisma.dailyCheckbox.aggregate({
@@ -128,10 +125,6 @@ export class DailyService {
* Réordonne les checkboxes d'une date donnée
*/
async reorderCheckboxes(date: Date, checkboxIds: string[]): Promise<void> {
// Normaliser la date
const normalizedDate = new Date(date);
normalizedDate.setHours(0, 0, 0, 0);
await prisma.$transaction(async (prisma) => {
for (let i = 0; i < checkboxIds.length; i++) {
await prisma.dailyCheckbox.update({
@@ -264,11 +257,7 @@ export class DailyService {
});
return checkboxes.map(checkbox => {
const date = checkbox.date;
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate()).padStart(2, '0');
return `${year}-${month}-${day}`;
return formatDateForAPI(checkbox.date);
});
}
}

View File

@@ -5,6 +5,7 @@
import { JiraTask } from '@/lib/types';
import { prisma } from './database';
import { parseDate } from '@/lib/date-utils';
export interface JiraConfig {
baseUrl: string;
@@ -339,12 +340,12 @@ export class JiraService {
priority: this.mapJiraPriorityToInternal(jiraTask.priority?.name),
source: 'jira' as const,
sourceId: jiraTask.id,
dueDate: jiraTask.duedate ? new Date(jiraTask.duedate) : null,
dueDate: jiraTask.duedate ? parseDate(jiraTask.duedate) : null,
jiraProject: jiraTask.project.key,
jiraKey: jiraTask.key,
jiraType: this.mapJiraTypeToDisplay(jiraTask.issuetype.name),
assignee: jiraTask.assignee?.displayName || null,
updatedAt: new Date(jiraTask.updated)
updatedAt: parseDate(jiraTask.updated)
};
if (!existingTask) {
@@ -352,7 +353,7 @@ export class JiraService {
const newTask = await prisma.task.create({
data: {
...taskData,
createdAt: new Date(jiraTask.created)
createdAt: parseDate(jiraTask.created)
}
});

View File

@@ -1,6 +1,7 @@
import { prisma } from './database';
import { startOfWeek, endOfWeek, eachDayOfInterval, format, startOfDay, endOfDay } from 'date-fns';
import { fr } from 'date-fns/locale';
import { formatDateForAPI, getDayName, getToday } from '@/lib/date-utils';
export interface DailyMetrics {
date: string; // Format ISO
@@ -58,7 +59,7 @@ export class MetricsService {
/**
* Récupère les métriques journalières de la semaine
*/
static async getWeeklyMetrics(date: Date = new Date()): Promise<WeeklyMetricsOverview> {
static async getWeeklyMetrics(date: Date = getToday()): Promise<WeeklyMetricsOverview> {
const weekStart = startOfWeek(date, { weekStartsOn: 1 }); // Lundi
const weekEnd = endOfWeek(date, { weekStartsOn: 1 }); // Dimanche
@@ -163,8 +164,8 @@ export class MetricsService {
const completionRate = totalTasks > 0 ? (completed / totalTasks) * 100 : 0;
return {
date: date.toISOString(),
dayName: format(date, 'EEEE', { locale: fr }),
date: formatDateForAPI(date),
dayName: getDayName(date),
completed,
inProgress,
blocked,

View File

@@ -1,6 +1,7 @@
import { prisma } from './database';
import { Task, TaskStatus, TaskPriority, TaskSource, BusinessError, DailyCheckbox, DailyCheckboxType } from '@/lib/types';
import { Prisma } from '@prisma/client';
import { getToday } from '@/lib/date-utils';
/**
* Service pour la gestion des tâches (version standalone)
@@ -126,12 +127,12 @@ export class TasksService {
status: updates.status,
priority: updates.priority,
dueDate: updates.dueDate,
updatedAt: new Date()
updatedAt: getToday()
};
if (updates.status === 'done' && !task.completedAt) {
updateData.completedAt = new Date();
updateData.completedAt = getToday();
} else if (updates.status && updates.status !== 'done' && task.completedAt) {
updateData.completedAt = null;
}