From 4ba6ba2c0bea9879995af978b372e6732d2b4b96 Mon Sep 17 00:00:00 2001 From: Julien Froidefond Date: Sun, 21 Sep 2025 13:04:34 +0200 Subject: [PATCH] refactor: unify date handling with utility functions - Replaced direct date manipulations with utility functions like `getToday`, `parseDate`, and `createDateFromParts` across various components and services for consistency. - Updated date initialization in `JiraAnalyticsService`, `BackupService`, and `DailyClient` to improve clarity and maintainability. - Enhanced date parsing in forms and API routes to ensure proper handling of date strings. --- src/actions/jira-export.ts | 2 +- src/actions/jira-sprint-details.ts | 11 ++++--- src/app/api/daily/route.ts | 6 ++-- src/app/daily/page.tsx | 3 +- src/clients/daily-client.ts | 15 +++++---- src/components/forms/CreateTaskForm.tsx | 5 +-- src/components/forms/EditTaskForm.tsx | 7 +++-- src/components/kanban/QuickAddTask.tsx | 5 +-- .../settings/BackupSettingsPageClient.tsx | 2 +- src/lib/backup-utils.ts | 6 ++-- src/lib/date-utils.ts | 31 +++++++++++++++++++ src/lib/jira-period-filter.ts | 11 ++++--- src/lib/sort-config.ts | 12 +++---- src/services/analytics.ts | 19 ++++++------ src/services/backup-scheduler.ts | 3 +- src/services/backup.ts | 7 +++-- src/services/jira-advanced-filters.ts | 3 +- src/services/jira-analytics.ts | 15 ++++----- src/services/jira-anomaly-detection.ts | 3 +- src/services/jira-scheduler.ts | 3 +- src/services/manager-summary.ts | 7 +++-- src/services/metrics.ts | 4 +-- src/services/system-info.ts | 5 +-- 23 files changed, 117 insertions(+), 68 deletions(-) diff --git a/src/actions/jira-export.ts b/src/actions/jira-export.ts index 49ed8be..be1fd71 100644 --- a/src/actions/jira-export.ts +++ b/src/actions/jira-export.ts @@ -104,7 +104,7 @@ export async function exportJiraAnalytics(format: ExportFormat = 'csv'): Promise } const analytics = analyticsResult.data; - const timestamp = new Date().toISOString().slice(0, 16).replace(/:/g, '-'); + const timestamp = getToday().toISOString().slice(0, 16).replace(/:/g, '-'); const projectKey = analytics.project.key; if (format === 'json') { diff --git a/src/actions/jira-sprint-details.ts b/src/actions/jira-sprint-details.ts index 052fb68..0997442 100644 --- a/src/actions/jira-sprint-details.ts +++ b/src/actions/jira-sprint-details.ts @@ -4,6 +4,7 @@ import { JiraAnalyticsService, JiraAnalyticsConfig } from '@/services/jira-analy import { userPreferencesService } from '@/services/user-preferences'; import { SprintDetails } from '@/components/jira/SprintDetailModal'; import { JiraTask, AssigneeDistribution, StatusDistribution, SprintVelocity } from '@/lib/types'; +import { parseDate } from '@/lib/date-utils'; export interface SprintDetailsResult { success: boolean; @@ -48,11 +49,11 @@ export async function getSprintDetails(sprintName: string): Promise { - const issueDate = new Date(issue.created); + const issueDate = parseDate(issue.created); return issueDate >= sprintStart && issueDate <= sprintEnd; }); @@ -116,8 +117,8 @@ function calculateSprintMetrics(issues: JiraTask[], sprint: SprintVelocity) { let averageCycleTime = 0; if (completedIssuesWithDates.length > 0) { const totalCycleTime = completedIssuesWithDates.reduce((total, issue) => { - const created = new Date(issue.created); - const updated = new Date(issue.updated); + const created = parseDate(issue.created); + const updated = parseDate(issue.updated); const cycleTime = (updated.getTime() - created.getTime()) / (1000 * 60 * 60 * 24); // en jours return total + cycleTime; }, 0); diff --git a/src/app/api/daily/route.ts b/src/app/api/daily/route.ts index 06716b0..8500ef9 100644 --- a/src/app/api/daily/route.ts +++ b/src/app/api/daily/route.ts @@ -1,6 +1,6 @@ import { NextResponse } from 'next/server'; import { dailyService } from '@/services/daily'; -import { getToday, parseDate, isValidAPIDate } from '@/lib/date-utils'; +import { getToday, parseDate, isValidAPIDate, createDateFromParts } from '@/lib/date-utils'; /** * API route pour récupérer la vue daily (hier + aujourd'hui) @@ -79,9 +79,9 @@ export async function POST(request: Request) { if (typeof body.date === 'string') { // Si c'est une string YYYY-MM-DD, créer une date locale const [year, month, day] = body.date.split('-').map(Number); - date = new Date(year, month - 1, day); // month est 0-indexé + date = createDateFromParts(year, month, day); } else { - date = new Date(body.date); + date = parseDate(body.date); } if (isNaN(date.getTime())) { diff --git a/src/app/daily/page.tsx b/src/app/daily/page.tsx index 1b6b521..61a6bec 100644 --- a/src/app/daily/page.tsx +++ b/src/app/daily/page.tsx @@ -1,6 +1,7 @@ import { Metadata } from 'next'; import { DailyPageClient } from './DailyPageClient'; import { dailyService } from '@/services/daily'; +import { getToday } from '@/lib/date-utils'; // Force dynamic rendering (no static generation) export const dynamic = 'force-dynamic'; @@ -12,7 +13,7 @@ export const metadata: Metadata = { export default async function DailyPage() { // Récupérer les données côté serveur - const today = new Date(); + const today = getToday(); try { const [dailyView, dailyDates] = await Promise.all([ diff --git a/src/clients/daily-client.ts b/src/clients/daily-client.ts index 4f828e0..5fe8840 100644 --- a/src/clients/daily-client.ts +++ b/src/clients/daily-client.ts @@ -1,6 +1,6 @@ import { httpClient } from './base/http-client'; import { DailyCheckbox, DailyView, Task } from '@/lib/types'; -import { formatDateForAPI, parseDate } from '@/lib/date-utils'; +import { formatDateForAPI, parseDate, getToday, addDays, subtractDays } from '@/lib/date-utils'; // Types pour les réponses API (avec dates en string) interface ApiCheckbox { @@ -74,7 +74,7 @@ export class DailyClient { const result = await httpClient.get(`/daily?${params}`); return result.map(item => ({ - date: new Date(item.date), + date: parseDate(item.date), checkboxes: item.checkboxes.map((cb: ApiCheckbox) => this.transformCheckboxDates(cb)) })); } @@ -128,16 +128,19 @@ export class DailyClient { * Récupère la vue daily d'une date relative (hier, aujourd'hui, demain) */ async getDailyViewByRelativeDate(relative: 'yesterday' | 'today' | 'tomorrow'): Promise { - const date = new Date(); + let date: Date; switch (relative) { case 'yesterday': - date.setDate(date.getDate() - 1); + date = subtractDays(getToday(), 1); break; case 'tomorrow': - date.setDate(date.getDate() + 1); + date = addDays(getToday(), 1); + break; + case 'today': + default: + date = getToday(); break; - // 'today' ne change rien } return this.getDailyView(date); diff --git a/src/components/forms/CreateTaskForm.tsx b/src/components/forms/CreateTaskForm.tsx index 93dfb65..fda1c94 100644 --- a/src/components/forms/CreateTaskForm.tsx +++ b/src/components/forms/CreateTaskForm.tsx @@ -8,6 +8,7 @@ import { TagInput } from '@/components/ui/TagInput'; import { TaskPriority, TaskStatus } from '@/lib/types'; import { CreateTaskData } from '@/clients/tasks-client'; import { getAllStatuses, getAllPriorities } from '@/lib/status-config'; +import { formatDateForDateTimeInput, parseDateTimeInput } from '@/lib/date-utils'; interface CreateTaskFormProps { isOpen: boolean; @@ -151,10 +152,10 @@ export function CreateTaskForm({ isOpen, onClose, onSubmit, loading = false }: C setFormData((prev: CreateTaskData) => ({ ...prev, - dueDate: e.target.value ? new Date(e.target.value) : undefined + dueDate: e.target.value ? parseDateTimeInput(e.target.value) : undefined }))} disabled={loading} /> diff --git a/src/components/forms/EditTaskForm.tsx b/src/components/forms/EditTaskForm.tsx index 5e2066b..840769f 100644 --- a/src/components/forms/EditTaskForm.tsx +++ b/src/components/forms/EditTaskForm.tsx @@ -11,6 +11,7 @@ import { Task, TaskPriority, TaskStatus } from '@/lib/types'; import { useUserPreferences } from '@/contexts/UserPreferencesContext'; // UpdateTaskData removed - using Server Actions directly import { getAllStatuses, getAllPriorities } from '@/lib/status-config'; +import { formatDateForDateTimeInput, parseDateTimeInput } from '@/lib/date-utils'; interface EditTaskFormProps { isOpen: boolean; @@ -56,7 +57,7 @@ export function EditTaskForm({ isOpen, onClose, onSubmit, task, loading = false status: task.status, priority: task.priority, tags: task.tags || [], - dueDate: task.dueDate ? new Date(task.dueDate) : undefined + dueDate: task.dueDate }); } }, [task]); @@ -181,10 +182,10 @@ export function EditTaskForm({ isOpen, onClose, onSubmit, task, loading = false setFormData(prev => ({ ...prev, - dueDate: e.target.value ? new Date(e.target.value) : undefined + dueDate: e.target.value ? parseDateTimeInput(e.target.value) : undefined }))} disabled={loading} /> diff --git a/src/components/kanban/QuickAddTask.tsx b/src/components/kanban/QuickAddTask.tsx index a479ed4..0bb65e6 100644 --- a/src/components/kanban/QuickAddTask.tsx +++ b/src/components/kanban/QuickAddTask.tsx @@ -6,6 +6,7 @@ import { TagInput } from '@/components/ui/TagInput'; import { TaskStatus, TaskPriority } from '@/lib/types'; import { CreateTaskData } from '@/clients/tasks-client'; import { getAllPriorities } from '@/lib/status-config'; +import { formatDateForDateTimeInput, parseDateTimeInput } from '@/lib/date-utils'; interface QuickAddTaskProps { status: TaskStatus; @@ -189,10 +190,10 @@ export function QuickAddTask({ status, onSubmit, onCancel, swimlaneContext }: Qu
setFormData(prev => ({ ...prev, - dueDate: e.target.value ? new Date(e.target.value) : undefined + dueDate: e.target.value ? parseDateTimeInput(e.target.value) : undefined }))} onFocus={() => setActiveField('date')} disabled={isSubmitting} diff --git a/src/components/settings/BackupSettingsPageClient.tsx b/src/components/settings/BackupSettingsPageClient.tsx index f98d920..d8fe0a9 100644 --- a/src/components/settings/BackupSettingsPageClient.tsx +++ b/src/components/settings/BackupSettingsPageClient.tsx @@ -194,7 +194,7 @@ 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 = typeof date === 'string' ? new Date(date) : date; + const d = typeof date === 'string' ? parseDate(date) : date; return formatDateForDisplay(d, 'DISPLAY_MEDIUM'); }; diff --git a/src/lib/backup-utils.ts b/src/lib/backup-utils.ts index 13232d0..3dfd762 100644 --- a/src/lib/backup-utils.ts +++ b/src/lib/backup-utils.ts @@ -3,7 +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'; +import { formatDateForDisplay, getToday, parseDate } from './date-utils'; const execAsync = promisify(exec); @@ -160,7 +160,7 @@ export class BackupUtils { // Format: 2025-09-18T14-12-05-737Z -> 2025-09-18T14:12:05.737Z const isoString = dateMatch[2] .replace(/T(\d{2})-(\d{2})-(\d{2})-(\d{3})Z/, 'T$1:$2:$3.$4Z'); - date = new Date(isoString); + date = parseDate(isoString); } return { type, date }; @@ -170,7 +170,7 @@ export class BackupUtils { * Génère un nom de fichier de backup */ static generateBackupFilename(type: 'manual' | 'automatic'): string { - const timestamp = new Date().toISOString().replace(/[:.]/g, '-'); + const timestamp = getToday().toISOString().replace(/[:.]/g, '-'); return `towercontrol_${type}_${timestamp}.db`; } diff --git a/src/lib/date-utils.ts b/src/lib/date-utils.ts index 482c46f..f3f2346 100644 --- a/src/lib/date-utils.ts +++ b/src/lib/date-utils.ts @@ -117,6 +117,28 @@ export function createDate(date: Date): Date { return new Date(date); } +/** + * Crée une date à partir de composants année/mois/jour + */ +export function createDateFromParts(year: number, month: number, day: number): Date { + return new Date(year, month - 1, day); // month est 0-indexé en JavaScript +} + +/** + * Convertit une date pour un input datetime-local (gestion timezone) + */ +export function formatDateForDateTimeInput(date: Date): string { + const adjustedDate = new Date(date.getTime() - date.getTimezoneOffset() * 60000); + return adjustedDate.toISOString().slice(0, 16); +} + +/** + * Parse une valeur d'input datetime-local vers une Date + */ +export function parseDateTimeInput(value: string): Date { + return new Date(value); +} + /** * Ajoute des jours à une date */ @@ -126,6 +148,15 @@ export function addDays(date: Date, days: number): Date { return result; } +/** + * Ajoute des minutes à une date + */ +export function addMinutes(date: Date, minutes: number): Date { + const result = createDate(date); + result.setMinutes(result.getMinutes() + minutes); + return result; +} + /** * Soustrait des jours à une date */ diff --git a/src/lib/jira-period-filter.ts b/src/lib/jira-period-filter.ts index a6fa8ac..adbf83a 100644 --- a/src/lib/jira-period-filter.ts +++ b/src/lib/jira-period-filter.ts @@ -1,4 +1,5 @@ import { JiraAnalytics } from './types'; +import { getToday, subtractDays, parseDate } from './date-utils'; export type PeriodFilter = '7d' | '30d' | '3m' | 'current'; @@ -9,18 +10,18 @@ export function filterAnalyticsByPeriod( analytics: JiraAnalytics, period: PeriodFilter ): JiraAnalytics { - const now = new Date(); + const now = getToday(); let cutoffDate: Date; switch (period) { case '7d': - cutoffDate = new Date(now.getTime() - (7 * 24 * 60 * 60 * 1000)); + cutoffDate = subtractDays(now, 7); break; case '30d': - cutoffDate = new Date(now.getTime() - (30 * 24 * 60 * 60 * 1000)); + cutoffDate = subtractDays(now, 30); break; case '3m': - cutoffDate = new Date(now.getTime() - (90 * 24 * 60 * 60 * 1000)); + cutoffDate = subtractDays(now, 90); break; case 'current': default: @@ -56,7 +57,7 @@ function filterCurrentSprintAnalytics(analytics: JiraAnalytics): JiraAnalytics { function filterAnalyticsByDate(analytics: JiraAnalytics, cutoffDate: Date): JiraAnalytics { // Filtrer l'historique des sprints const filteredSprintHistory = analytics.velocityMetrics.sprintHistory.filter(sprint => { - const sprintEndDate = new Date(sprint.endDate); + const sprintEndDate = parseDate(sprint.endDate); return sprintEndDate >= cutoffDate; }); diff --git a/src/lib/sort-config.ts b/src/lib/sort-config.ts index e599b34..d5e4ef8 100644 --- a/src/lib/sort-config.ts +++ b/src/lib/sort-config.ts @@ -151,22 +151,22 @@ function compareTasksByField(a: Task, b: Task, sortConfig: SortConfig): number { case 'createdAt': return compareValues( - new Date(a.createdAt).getTime(), - new Date(b.createdAt).getTime(), + a.createdAt.getTime(), + b.createdAt.getTime(), direction ); case 'updatedAt': return compareValues( - new Date(a.updatedAt).getTime(), - new Date(b.updatedAt).getTime(), + a.updatedAt.getTime(), + b.updatedAt.getTime(), direction ); case 'dueDate': return compareValues( - a.dueDate ? new Date(a.dueDate).getTime() : null, - b.dueDate ? new Date(b.dueDate).getTime() : null, + a.dueDate ? a.dueDate.getTime() : null, + b.dueDate ? b.dueDate.getTime() : null, direction ); diff --git a/src/services/analytics.ts b/src/services/analytics.ts index 61878b4..c1ae71d 100644 --- a/src/services/analytics.ts +++ b/src/services/analytics.ts @@ -1,5 +1,6 @@ import { Task, TaskStatus, TaskPriority, TaskSource } from '@/lib/types'; import { prisma } from './database'; +import { getToday, parseDate, subtractDays, addDays } from '@/lib/date-utils'; export interface ProductivityMetrics { completionTrend: Array<{ @@ -42,8 +43,8 @@ export class AnalyticsService { */ static async getProductivityMetrics(timeRange?: TimeRange): Promise { try { - const now = new Date(); - const defaultStart = new Date(now.getTime() - 30 * 24 * 60 * 60 * 1000); // 30 jours + const now = getToday(); + const defaultStart = subtractDays(now, 30); // 30 jours const start = timeRange?.start || defaultStart; const end = timeRange?.end || now; @@ -99,7 +100,7 @@ export class AnalyticsService { const trend: Array<{ date: string; completed: number; created: number; total: number }> = []; // Générer les dates pour la période - const currentDate = new Date(start); + const currentDate = new Date(start.getTime()); while (currentDate <= end) { const dateStr = currentDate.toISOString().split('T')[0]; @@ -116,7 +117,7 @@ export class AnalyticsService { // Total cumulé jusqu'à ce jour const totalUntilThisDay = tasks.filter(task => - new Date(task.createdAt) <= currentDate + task.createdAt <= currentDate ).length; trend.push({ @@ -156,7 +157,7 @@ export class AnalyticsService { // Convertir en format pour le graphique weekGroups.forEach((count, weekKey) => { - const weekDate = new Date(weekKey); + const weekDate = parseDate(weekKey); weeklyData.push({ week: `Sem. ${this.getWeekNumber(weekDate)}`, completed: count, @@ -209,10 +210,10 @@ export class AnalyticsService { * Calcule les statistiques hebdomadaires */ private static calculateWeeklyStats(tasks: Task[]) { - const now = new Date(); + const now = getToday(); const thisWeekStart = this.getWeekStart(now); - const lastWeekStart = new Date(thisWeekStart.getTime() - 7 * 24 * 60 * 60 * 1000); - const lastWeekEnd = new Date(thisWeekStart.getTime() - 1); + const lastWeekStart = subtractDays(thisWeekStart, 7); + const lastWeekEnd = subtractDays(thisWeekStart, 1); const thisWeekCompleted = tasks.filter(task => task.completedAt && @@ -243,7 +244,7 @@ export class AnalyticsService { * Obtient le début de la semaine pour une date */ private static getWeekStart(date: Date): Date { - const d = new Date(date); + const d = new Date(date.getTime()); const day = d.getDay(); const diff = d.getDate() - day + (day === 0 ? -6 : 1); // Lundi = début de semaine return new Date(d.setDate(diff)); diff --git a/src/services/backup-scheduler.ts b/src/services/backup-scheduler.ts index 2da03a1..14ca65a 100644 --- a/src/services/backup-scheduler.ts +++ b/src/services/backup-scheduler.ts @@ -1,4 +1,5 @@ import { backupService, BackupConfig } from './backup'; +import { addMinutes, getToday } from '@/lib/date-utils'; export class BackupScheduler { private timer: NodeJS.Timeout | null = null; @@ -106,7 +107,7 @@ export class BackupScheduler { const config = backupService.getConfig(); const intervalMs = this.getIntervalMs(config.interval); - return new Date(Date.now() + intervalMs); + return addMinutes(getToday(), Math.floor(intervalMs / (1000 * 60))); } /** diff --git a/src/services/backup.ts b/src/services/backup.ts index b509209..6433d29 100644 --- a/src/services/backup.ts +++ b/src/services/backup.ts @@ -3,6 +3,7 @@ import path from 'path'; import { prisma } from './database'; import { userPreferencesService } from './user-preferences'; import { BackupUtils } from '../lib/backup-utils'; +import { getToday } from '@/lib/date-utils'; export interface BackupConfig { enabled: boolean; @@ -280,7 +281,7 @@ export class BackupService { id: backupId, filename: path.basename(finalPath), size: stats.size, - createdAt: new Date(), + createdAt: getToday(), type, status: 'success', databaseHash, @@ -289,7 +290,7 @@ export class BackupService { // Sauvegarder les métadonnées du backup await this.saveBackupMetadata(path.basename(finalPath), { databaseHash, - createdAt: new Date(), + createdAt: getToday(), type, }); @@ -313,7 +314,7 @@ export class BackupService { id: backupId, filename, size: 0, - createdAt: new Date(), + createdAt: getToday(), type, status: 'failed', error: errorMessage, diff --git a/src/services/jira-advanced-filters.ts b/src/services/jira-advanced-filters.ts index e8b3b32..a109779 100644 --- a/src/services/jira-advanced-filters.ts +++ b/src/services/jira-advanced-filters.ts @@ -4,6 +4,7 @@ */ import { JiraTask, JiraAnalytics, JiraAnalyticsFilters, AvailableFilters, FilterOption } from '@/lib/types'; +import { parseDate } from '@/lib/date-utils'; export class JiraAdvancedFiltersService { @@ -137,7 +138,7 @@ export class JiraAdvancedFiltersService { // Filtrage par date if (filters.dateRange) { - const issueDate = new Date(issue.created); + const issueDate = parseDate(issue.created); if (issueDate < filters.dateRange.from || issueDate > filters.dateRange.to) { return false; } diff --git a/src/services/jira-analytics.ts b/src/services/jira-analytics.ts index b437f35..bc6b1a4 100644 --- a/src/services/jira-analytics.ts +++ b/src/services/jira-analytics.ts @@ -5,6 +5,7 @@ import { JiraService } from './jira'; import { jiraAnalyticsCache } from './jira-analytics-cache'; +import { getToday, parseDate, addDays, subtractDays } from '@/lib/date-utils'; import { JiraAnalytics, JiraTask, @@ -248,23 +249,23 @@ export class JiraAnalyticsService { * Génère un historique de sprints basé sur les dates de création/résolution des tickets */ private generateSprintHistoryFromIssues(allIssues: JiraTask[], completedIssues: JiraTask[]): SprintVelocity[] { - const now = new Date(); + const now = getToday(); const sprintHistory: SprintVelocity[] = []; // Créer 4 périodes de 2 semaines (8 semaines au total) for (let i = 3; i >= 0; i--) { - const endDate = new Date(now.getTime() - (i * 14 * 24 * 60 * 60 * 1000)); - const startDate = new Date(endDate.getTime() - (14 * 24 * 60 * 60 * 1000)); + const endDate = subtractDays(now, i * 14); + const startDate = subtractDays(endDate, 14); // Compter les tickets complétés dans cette période const completedInPeriod = completedIssues.filter(issue => { - const updatedDate = new Date(issue.updated); + const updatedDate = parseDate(issue.updated); return updatedDate >= startDate && updatedDate <= endDate; }); // Compter les tickets créés dans cette période (approximation du planifié) const createdInPeriod = allIssues.filter(issue => { - const createdDate = new Date(issue.created); + const createdDate = parseDate(issue.created); return createdDate >= startDate && createdDate <= endDate; }); @@ -315,8 +316,8 @@ export class JiraAnalyticsService { const cycleTimes = completedIssues .filter(issue => issue.created && issue.updated) // S'assurer qu'on a les dates .map(issue => { - const created = new Date(issue.created); - const resolved = new Date(issue.updated); + const created = parseDate(issue.created); + const resolved = parseDate(issue.updated); const days = Math.max(0.1, (resolved.getTime() - created.getTime()) / (1000 * 60 * 60 * 24)); // Minimum 0.1 jour return Math.round(days * 10) / 10; // Arrondir à 1 décimale }) diff --git a/src/services/jira-anomaly-detection.ts b/src/services/jira-anomaly-detection.ts index fa81adb..c0449fa 100644 --- a/src/services/jira-anomaly-detection.ts +++ b/src/services/jira-anomaly-detection.ts @@ -4,6 +4,7 @@ */ import { JiraAnalytics, SprintVelocity, CycleTimeByType, AssigneeWorkload } from '@/lib/types'; +import { getToday } from '@/lib/date-utils'; export interface JiraAnomaly { id: string; @@ -44,7 +45,7 @@ export class JiraAnomalyDetectionService { */ async detectAnomalies(analytics: JiraAnalytics): Promise { const anomalies: JiraAnomaly[] = []; - const timestamp = new Date().toISOString(); + const timestamp = getToday().toISOString(); // 1. Détection d'anomalies de vélocité const velocityAnomalies = this.detectVelocityAnomalies(analytics.velocityMetrics, timestamp); diff --git a/src/services/jira-scheduler.ts b/src/services/jira-scheduler.ts index 45ab347..62bfc75 100644 --- a/src/services/jira-scheduler.ts +++ b/src/services/jira-scheduler.ts @@ -1,5 +1,6 @@ import { userPreferencesService } from './user-preferences'; import { JiraService } from './jira'; +import { addMinutes, getToday } from '@/lib/date-utils'; export interface JiraSchedulerConfig { enabled: boolean; @@ -143,7 +144,7 @@ export class JiraScheduler { const config = await this.getConfig(); const intervalMs = this.getIntervalMs(config.interval); - return new Date(Date.now() + intervalMs); + return addMinutes(getToday(), Math.floor(intervalMs / (1000 * 60))); } /** diff --git a/src/services/manager-summary.ts b/src/services/manager-summary.ts index 90408e5..1a7676c 100644 --- a/src/services/manager-summary.ts +++ b/src/services/manager-summary.ts @@ -1,5 +1,6 @@ import { prisma } from './database'; import { startOfWeek, endOfWeek } from 'date-fns'; +import { getToday } from '@/lib/date-utils'; type TaskType = { id: string; @@ -84,7 +85,7 @@ export class ManagerSummaryService { /** * Génère un résumé orienté manager pour la semaine */ - static async getManagerSummary(date: Date = new Date()): Promise { + static async getManagerSummary(date: Date = getToday()): Promise { const weekStart = startOfWeek(date, { weekStartsOn: 1 }); // Lundi const weekEnd = endOfWeek(date, { weekStartsOn: 1 }); // Dimanche @@ -249,7 +250,7 @@ export class ManagerSummaryService { description: task.description || undefined, tags: task.taskTags?.map(tt => tt.tag.name) || [], impact, - completedAt: task.completedAt || new Date(), + completedAt: task.completedAt || getToday(), relatedItems: [task.id, ...relatedTodos.map(t => t.id)], todosCount: relatedTodos.length // Nombre réel de todos associés }); @@ -322,7 +323,7 @@ export class ManagerSummaryService { where: { isChecked: false, date: { - gte: new Date() + gte: getToday() }, OR: [ { type: 'meeting' }, diff --git a/src/services/metrics.ts b/src/services/metrics.ts index dcd246c..2423756 100644 --- a/src/services/metrics.ts +++ b/src/services/metrics.ts @@ -1,7 +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'; +import { formatDateForAPI, getDayName, getToday, subtractDays } from '@/lib/date-utils'; export interface DailyMetrics { date: string; // Format ISO @@ -326,7 +326,7 @@ export class MetricsService { const trends = []; for (let i = weeksBack - 1; i >= 0; i--) { - const weekStart = startOfWeek(new Date(Date.now() - i * 7 * 24 * 60 * 60 * 1000), { weekStartsOn: 1 }); + const weekStart = startOfWeek(subtractDays(getToday(), i * 7), { weekStartsOn: 1 }); const weekEnd = endOfWeek(weekStart, { weekStartsOn: 1 }); const [completed, created] = await Promise.all([ diff --git a/src/services/system-info.ts b/src/services/system-info.ts index 7b31ec2..0ab9de7 100644 --- a/src/services/system-info.ts +++ b/src/services/system-info.ts @@ -1,6 +1,7 @@ import { prisma } from './database'; import { readFile } from 'fs/promises'; import { join } from 'path'; +import { getToday, parseDate } from '@/lib/date-utils'; export interface SystemInfo { version: string; @@ -168,8 +169,8 @@ export class SystemInfoService { const fs = require('fs'); // eslint-disable-line @typescript-eslint/no-require-imports const packagePath = join(process.cwd(), 'package.json'); const stats = fs.statSync(packagePath); - const now = new Date(); - const lastModified = new Date(stats.mtime); + const now = getToday(); + const lastModified = parseDate(stats.mtime.toISOString()); const diffTime = Math.abs(now.getTime() - lastModified.getTime()); const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24));