refactor: date utils and all calls
This commit is contained in:
4
TODO.md
4
TODO.md
@@ -2,9 +2,9 @@
|
|||||||
|
|
||||||
## Autre Todos #2
|
## Autre Todos #2
|
||||||
- [x] Synchro Jira auto en background timé comme pour la synchro de sauvegarde
|
- [x] Synchro Jira auto en background timé comme pour la synchro de sauvegarde
|
||||||
- [ ] refacto des allpreferences : ca devrait eter un contexte dans le layout qui balance serverside dans le hook
|
- [ ] refacto des getallpreferences en frontend : ca devrait eter un contexte dans le layout qui balance serverside dans le hook
|
||||||
- [x] backups : ne backuper que si il y a eu un changement entre le dernier backup et la base actuelle
|
- [x] backups : ne backuper que si il y a eu un changement entre le dernier backup et la base actuelle
|
||||||
- [ ] refacto des dates avec le utils qui pour l'instant n'est pas utilisé
|
- [x] refacto des dates avec le utils qui pour l'instant n'est pas utilisé
|
||||||
- [ ] split de certains gros composants.
|
- [ ] split de certains gros composants.
|
||||||
- [x] Page jira-dashboard : onglets analytics avancés et Qualité et collaboration : les charts sortent des cards; il faut reprendre la UI pour que ce soit consistant.
|
- [x] Page jira-dashboard : onglets analytics avancés et Qualité et collaboration : les charts sortent des cards; il faut reprendre la UI pour que ce soit consistant.
|
||||||
- [x] Page Daily : les mots aujourd'hui et hier ne fonctionnent dans les titres que si c'est vraiment aujourd'hui :)
|
- [x] Page Daily : les mots aujourd'hui et hier ne fonctionnent dans les titres que si c'est vraiment aujourd'hui :)
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
|
|
||||||
import { backupService, BackupConfig } from '../src/services/backup';
|
import { backupService, BackupConfig } from '../src/services/backup';
|
||||||
import { backupScheduler } from '../src/services/backup-scheduler';
|
import { backupScheduler } from '../src/services/backup-scheduler';
|
||||||
|
import { formatDateForDisplay } from '../src/lib/date-utils';
|
||||||
|
|
||||||
interface CliOptions {
|
interface CliOptions {
|
||||||
command: string;
|
command: string;
|
||||||
@@ -92,7 +93,7 @@ OPTIONS:
|
|||||||
}
|
}
|
||||||
|
|
||||||
private formatDate(date: Date): string {
|
private formatDate(date: Date): string {
|
||||||
return new Date(date).toLocaleString('fr-FR');
|
return formatDateForDisplay(date, 'DISPLAY_LONG');
|
||||||
}
|
}
|
||||||
|
|
||||||
async run(args: string[]): Promise<void> {
|
async run(args: string[]): Promise<void> {
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
import { dailyService } from '@/services/daily';
|
import { dailyService } from '@/services/daily';
|
||||||
import { UpdateDailyCheckboxData, DailyCheckbox, CreateDailyCheckboxData } from '@/lib/types';
|
import { UpdateDailyCheckboxData, DailyCheckbox, CreateDailyCheckboxData } from '@/lib/types';
|
||||||
import { revalidatePath } from 'next/cache';
|
import { revalidatePath } from 'next/cache';
|
||||||
|
import { getToday, getPreviousWorkday, parseDate, normalizeDate } from '@/lib/date-utils';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Toggle l'état d'une checkbox
|
* 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)
|
// (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
|
// 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);
|
const dailyView = await dailyService.getDailyView(today);
|
||||||
|
|
||||||
let checkbox = dailyView.today.find(cb => cb.id === checkboxId);
|
let checkbox = dailyView.today.find(cb => cb.id === checkboxId);
|
||||||
@@ -57,7 +58,7 @@ export async function addCheckboxToDaily(dailyId: string, content: string, taskI
|
|||||||
}> {
|
}> {
|
||||||
try {
|
try {
|
||||||
// Le dailyId correspond à la date au format YYYY-MM-DD
|
// Le dailyId correspond à la date au format YYYY-MM-DD
|
||||||
const date = new Date(dailyId);
|
const date = parseDate(dailyId);
|
||||||
|
|
||||||
const newCheckbox = await dailyService.addCheckbox({
|
const newCheckbox = await dailyService.addCheckbox({
|
||||||
date,
|
date,
|
||||||
@@ -86,7 +87,7 @@ export async function addTodayCheckbox(content: string, type?: 'task' | 'meeting
|
|||||||
}> {
|
}> {
|
||||||
try {
|
try {
|
||||||
const newCheckbox = await dailyService.addCheckbox({
|
const newCheckbox = await dailyService.addCheckbox({
|
||||||
date: new Date(),
|
date: getToday(),
|
||||||
text: content,
|
text: content,
|
||||||
type: type || 'task',
|
type: type || 'task',
|
||||||
taskId
|
taskId
|
||||||
@@ -112,8 +113,7 @@ export async function addYesterdayCheckbox(content: string, type?: 'task' | 'mee
|
|||||||
error?: string;
|
error?: string;
|
||||||
}> {
|
}> {
|
||||||
try {
|
try {
|
||||||
const yesterday = new Date();
|
const yesterday = getPreviousWorkday(getToday());
|
||||||
yesterday.setDate(yesterday.getDate() - 1);
|
|
||||||
|
|
||||||
const newCheckbox = await dailyService.addCheckbox({
|
const newCheckbox = await dailyService.addCheckbox({
|
||||||
date: yesterday,
|
date: yesterday,
|
||||||
@@ -209,8 +209,7 @@ export async function addTodoToTask(taskId: string, text: string, date?: Date):
|
|||||||
error?: string;
|
error?: string;
|
||||||
}> {
|
}> {
|
||||||
try {
|
try {
|
||||||
const targetDate = date || new Date();
|
const targetDate = normalizeDate(date || getToday());
|
||||||
targetDate.setHours(0, 0, 0, 0);
|
|
||||||
|
|
||||||
const checkboxData: CreateDailyCheckboxData = {
|
const checkboxData: CreateDailyCheckboxData = {
|
||||||
date: targetDate,
|
date: targetDate,
|
||||||
@@ -243,7 +242,7 @@ export async function reorderCheckboxes(dailyId: string, checkboxIds: string[]):
|
|||||||
}> {
|
}> {
|
||||||
try {
|
try {
|
||||||
// Le dailyId correspond à la date au format YYYY-MM-DD
|
// Le dailyId correspond à la date au format YYYY-MM-DD
|
||||||
const date = new Date(dailyId);
|
const date = parseDate(dailyId);
|
||||||
|
|
||||||
await dailyService.reorderCheckboxes(date, checkboxIds);
|
await dailyService.reorderCheckboxes(date, checkboxIds);
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
'use server';
|
'use server';
|
||||||
|
|
||||||
import { getJiraAnalytics } from './jira-analytics';
|
import { getJiraAnalytics } from './jira-analytics';
|
||||||
|
import { formatDateForDisplay, getToday } from '@/lib/date-utils';
|
||||||
|
|
||||||
export type ExportFormat = 'csv' | 'json';
|
export type ExportFormat = 'csv' | 'json';
|
||||||
|
|
||||||
@@ -142,7 +143,7 @@ function generateCSV(analytics: JiraAnalytics): string {
|
|||||||
// Header du rapport
|
// Header du rapport
|
||||||
lines.push('# Rapport Analytics Jira');
|
lines.push('# Rapport Analytics Jira');
|
||||||
lines.push(`# Projet: ${analytics.project.name} (${analytics.project.key})`);
|
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(`# Total tickets: ${analytics.project.totalIssues}`);
|
||||||
lines.push('');
|
lines.push('');
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
'use server';
|
'use server';
|
||||||
|
|
||||||
import { MetricsService, WeeklyMetricsOverview, VelocityTrend } from '@/services/metrics';
|
import { MetricsService, WeeklyMetricsOverview, VelocityTrend } from '@/services/metrics';
|
||||||
|
import { getToday } from '@/lib/date-utils';
|
||||||
import { revalidatePath } from 'next/cache';
|
import { revalidatePath } from 'next/cache';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -12,7 +13,7 @@ export async function getWeeklyMetrics(date?: Date): Promise<{
|
|||||||
error?: string;
|
error?: string;
|
||||||
}> {
|
}> {
|
||||||
try {
|
try {
|
||||||
const targetDate = date || new Date();
|
const targetDate = date || getToday();
|
||||||
const metrics = await MetricsService.getWeeklyMetrics(targetDate);
|
const metrics = await MetricsService.getWeeklyMetrics(targetDate);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { NextResponse } from 'next/server';
|
import { NextResponse } from 'next/server';
|
||||||
import { dailyService } from '@/services/daily';
|
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)
|
* 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)
|
// 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())) {
|
if (date) {
|
||||||
return NextResponse.json(
|
if (!isValidAPIDate(date)) {
|
||||||
{ error: 'Format de date invalide. Utilisez YYYY-MM-DD' },
|
return NextResponse.json(
|
||||||
{ status: 400 }
|
{ error: 'Format de date invalide. Utilisez YYYY-MM-DD' },
|
||||||
);
|
{ status: 400 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
targetDate = parseDate(date);
|
||||||
|
} else {
|
||||||
|
targetDate = getToday();
|
||||||
}
|
}
|
||||||
|
|
||||||
const dailyView = await dailyService.getDailyView(targetDate);
|
const dailyView = await dailyService.getDailyView(targetDate);
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import { DailyCalendar } from '@/components/daily/DailyCalendar';
|
|||||||
import { DailySection } from '@/components/daily/DailySection';
|
import { DailySection } from '@/components/daily/DailySection';
|
||||||
import { dailyClient } from '@/clients/daily-client';
|
import { dailyClient } from '@/clients/daily-client';
|
||||||
import { Header } from '@/components/ui/Header';
|
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 {
|
interface DailyPageClientProps {
|
||||||
initialDailyView?: DailyView;
|
initialDailyView?: DailyView;
|
||||||
@@ -112,37 +112,23 @@ export function DailyPageClient({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const formatCurrentDate = () => {
|
const formatCurrentDate = () => {
|
||||||
return currentDate.toLocaleDateString('fr-FR', {
|
return formatDateLong(currentDate);
|
||||||
weekday: 'long',
|
|
||||||
year: 'numeric',
|
|
||||||
month: 'long',
|
|
||||||
day: 'numeric'
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const isToday = () => {
|
const isTodayDate = () => {
|
||||||
const today = new Date();
|
return isToday(currentDate);
|
||||||
return currentDate.toDateString() === today.toDateString();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const getTodayTitle = () => {
|
const getTodayTitle = () => {
|
||||||
const today = new Date();
|
return generateDateTitle(currentDate, '🎯');
|
||||||
if (currentDate.toDateString() === today.toDateString()) {
|
|
||||||
return "🎯 Aujourd'hui";
|
|
||||||
}
|
|
||||||
return `🎯 ${currentDate.toLocaleDateString('fr-FR', { day: '2-digit', month: '2-digit', year: '2-digit' })}`;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const getYesterdayTitle = () => {
|
const getYesterdayTitle = () => {
|
||||||
const today = new Date();
|
|
||||||
const yesterday = new Date(today);
|
|
||||||
yesterday.setDate(yesterday.getDate() - 1);
|
|
||||||
|
|
||||||
const yesterdayDate = getYesterdayDate();
|
const yesterdayDate = getYesterdayDate();
|
||||||
if (yesterdayDate.toDateString() === yesterday.toDateString()) {
|
if (isYesterday(yesterdayDate)) {
|
||||||
return "📋 Hier";
|
return "📋 Hier";
|
||||||
}
|
}
|
||||||
return `📋 ${yesterdayDate.toLocaleDateString('fr-FR', { day: '2-digit', month: '2-digit', year: '2-digit' })}`;
|
return `📋 ${formatDateShort(yesterdayDate)}`;
|
||||||
};
|
};
|
||||||
|
|
||||||
if (loading) {
|
if (loading) {
|
||||||
@@ -198,7 +184,7 @@ export function DailyPageClient({
|
|||||||
<div className="text-sm font-bold text-[var(--foreground)] font-mono">
|
<div className="text-sm font-bold text-[var(--foreground)] font-mono">
|
||||||
{formatCurrentDate()}
|
{formatCurrentDate()}
|
||||||
</div>
|
</div>
|
||||||
{!isToday() && (
|
{!isTodayDate() && (
|
||||||
<button
|
<button
|
||||||
onClick={goToToday}
|
onClick={goToToday}
|
||||||
className="text-xs text-[var(--primary)] hover:text-[var(--primary)]/80 font-mono"
|
className="text-xs text-[var(--primary)] hover:text-[var(--primary)]/80 font-mono"
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { httpClient } from './base/http-client';
|
import { httpClient } from './base/http-client';
|
||||||
import { DailyCheckbox, DailyView, Task } from '@/lib/types';
|
import { DailyCheckbox, DailyView, Task } from '@/lib/types';
|
||||||
|
import { formatDateForAPI, parseDate } from '@/lib/date-utils';
|
||||||
|
|
||||||
// Types pour les réponses API (avec dates en string)
|
// Types pour les réponses API (avec dates en string)
|
||||||
interface ApiCheckbox {
|
interface ApiCheckbox {
|
||||||
@@ -97,10 +98,7 @@ export class DailyClient {
|
|||||||
* Formate une date pour l'API (évite les décalages timezone)
|
* Formate une date pour l'API (évite les décalages timezone)
|
||||||
*/
|
*/
|
||||||
formatDateForAPI(date: Date): string {
|
formatDateForAPI(date: Date): string {
|
||||||
const year = date.getFullYear();
|
return formatDateForAPI(date);
|
||||||
const month = String(date.getMonth() + 1).padStart(2, '0');
|
|
||||||
const day = String(date.getDate()).padStart(2, '0');
|
|
||||||
return `${year}-${month}-${day}`; // YYYY-MM-DD
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -109,9 +107,9 @@ export class DailyClient {
|
|||||||
private transformCheckboxDates(checkbox: ApiCheckbox): DailyCheckbox {
|
private transformCheckboxDates(checkbox: ApiCheckbox): DailyCheckbox {
|
||||||
return {
|
return {
|
||||||
...checkbox,
|
...checkbox,
|
||||||
date: new Date(checkbox.date),
|
date: parseDate(checkbox.date),
|
||||||
createdAt: new Date(checkbox.createdAt),
|
createdAt: parseDate(checkbox.createdAt),
|
||||||
updatedAt: new Date(checkbox.updatedAt)
|
updatedAt: parseDate(checkbox.updatedAt)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -120,7 +118,7 @@ export class DailyClient {
|
|||||||
*/
|
*/
|
||||||
private transformDailyViewDates(view: ApiDailyView): DailyView {
|
private transformDailyViewDates(view: ApiDailyView): DailyView {
|
||||||
return {
|
return {
|
||||||
date: new Date(view.date),
|
date: parseDate(view.date),
|
||||||
yesterday: view.yesterday.map((cb: ApiCheckbox) => this.transformCheckboxDates(cb)),
|
yesterday: view.yesterday.map((cb: ApiCheckbox) => this.transformCheckboxDates(cb)),
|
||||||
today: view.today.map((cb: ApiCheckbox) => this.transformCheckboxDates(cb))
|
today: view.today.map((cb: ApiCheckbox) => this.transformCheckboxDates(cb))
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer } from 'recharts';
|
import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer } from 'recharts';
|
||||||
import { Card } from '@/components/ui/Card';
|
import { Card } from '@/components/ui/Card';
|
||||||
|
import { parseDate, formatDateShort } from '@/lib/date-utils';
|
||||||
|
|
||||||
interface CompletionTrendData {
|
interface CompletionTrendData {
|
||||||
date: string;
|
date: string;
|
||||||
@@ -18,11 +19,11 @@ interface CompletionTrendChartProps {
|
|||||||
export function CompletionTrendChart({ data, title = "Tendance de Completion" }: CompletionTrendChartProps) {
|
export function CompletionTrendChart({ data, title = "Tendance de Completion" }: CompletionTrendChartProps) {
|
||||||
// Formatter pour les dates
|
// Formatter pour les dates
|
||||||
const formatDate = (dateStr: string) => {
|
const formatDate = (dateStr: string) => {
|
||||||
const date = new Date(dateStr);
|
try {
|
||||||
return date.toLocaleDateString('fr-FR', {
|
return formatDateShort(parseDate(dateStr));
|
||||||
day: 'numeric',
|
} catch {
|
||||||
month: 'short'
|
return dateStr;
|
||||||
});
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Tooltip personnalisé
|
// Tooltip personnalisé
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import { Button } from '@/components/ui/Button';
|
import { Button } from '@/components/ui/Button';
|
||||||
import { Card } from '@/components/ui/Card';
|
import { Card } from '@/components/ui/Card';
|
||||||
|
import { formatDateForAPI, createDate, getToday } from '@/lib/date-utils';
|
||||||
|
|
||||||
interface DailyCalendarProps {
|
interface DailyCalendarProps {
|
||||||
currentDate: Date;
|
currentDate: Date;
|
||||||
@@ -15,33 +16,30 @@ export function DailyCalendar({
|
|||||||
onDateSelect,
|
onDateSelect,
|
||||||
dailyDates,
|
dailyDates,
|
||||||
}: DailyCalendarProps) {
|
}: DailyCalendarProps) {
|
||||||
const [viewDate, setViewDate] = useState(new Date(currentDate));
|
const [viewDate, setViewDate] = useState(createDate(currentDate));
|
||||||
|
|
||||||
// Formatage des dates pour comparaison (éviter le décalage timezone)
|
// Formatage des dates pour comparaison (éviter le décalage timezone)
|
||||||
const formatDateKey = (date: Date) => {
|
const formatDateKey = (date: Date) => {
|
||||||
const year = date.getFullYear();
|
return formatDateForAPI(date);
|
||||||
const month = String(date.getMonth() + 1).padStart(2, '0');
|
|
||||||
const day = String(date.getDate()).padStart(2, '0');
|
|
||||||
return `${year}-${month}-${day}`;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const currentDateKey = formatDateKey(currentDate);
|
const currentDateKey = formatDateKey(currentDate);
|
||||||
|
|
||||||
// Navigation mois
|
// Navigation mois
|
||||||
const goToPreviousMonth = () => {
|
const goToPreviousMonth = () => {
|
||||||
const newDate = new Date(viewDate);
|
const newDate = createDate(viewDate);
|
||||||
newDate.setMonth(newDate.getMonth() - 1);
|
newDate.setMonth(newDate.getMonth() - 1);
|
||||||
setViewDate(newDate);
|
setViewDate(newDate);
|
||||||
};
|
};
|
||||||
|
|
||||||
const goToNextMonth = () => {
|
const goToNextMonth = () => {
|
||||||
const newDate = new Date(viewDate);
|
const newDate = createDate(viewDate);
|
||||||
newDate.setMonth(newDate.getMonth() + 1);
|
newDate.setMonth(newDate.getMonth() + 1);
|
||||||
setViewDate(newDate);
|
setViewDate(newDate);
|
||||||
};
|
};
|
||||||
|
|
||||||
const goToToday = () => {
|
const goToToday = () => {
|
||||||
const today = new Date();
|
const today = getToday();
|
||||||
setViewDate(today);
|
setViewDate(today);
|
||||||
onDateSelect(today);
|
onDateSelect(today);
|
||||||
};
|
};
|
||||||
@@ -57,18 +55,18 @@ export function DailyCalendar({
|
|||||||
const lastDay = new Date(year, month + 1, 0);
|
const lastDay = new Date(year, month + 1, 0);
|
||||||
|
|
||||||
// Premier lundi de la semaine contenant le premier jour
|
// Premier lundi de la semaine contenant le premier jour
|
||||||
const startDate = new Date(firstDay);
|
const startDate = createDate(firstDay);
|
||||||
const dayOfWeek = firstDay.getDay();
|
const dayOfWeek = firstDay.getDay();
|
||||||
const daysToSubtract = dayOfWeek === 0 ? 6 : dayOfWeek - 1; // Lundi = 0
|
const daysToSubtract = dayOfWeek === 0 ? 6 : dayOfWeek - 1; // Lundi = 0
|
||||||
startDate.setDate(firstDay.getDate() - daysToSubtract);
|
startDate.setDate(firstDay.getDate() - daysToSubtract);
|
||||||
|
|
||||||
// Générer toutes les dates du calendrier (6 semaines)
|
// Générer toutes les dates du calendrier (6 semaines)
|
||||||
const days = [];
|
const days = [];
|
||||||
const currentDay = new Date(startDate);
|
const currentDay = createDate(startDate);
|
||||||
|
|
||||||
for (let i = 0; i < 42; i++) {
|
for (let i = 0; i < 42; i++) {
|
||||||
// 6 semaines × 7 jours
|
// 6 semaines × 7 jours
|
||||||
days.push(new Date(currentDay));
|
days.push(createDate(currentDay));
|
||||||
currentDay.setDate(currentDay.getDate() + 1);
|
currentDay.setDate(currentDay.getDate() + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -81,8 +79,8 @@ export function DailyCalendar({
|
|||||||
onDateSelect(date);
|
onDateSelect(date);
|
||||||
};
|
};
|
||||||
|
|
||||||
const isToday = (date: Date) => {
|
const isTodayDate = (date: Date) => {
|
||||||
const today = new Date();
|
const today = getToday();
|
||||||
return formatDateKey(date) === formatDateKey(today);
|
return formatDateKey(date) === formatDateKey(today);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -157,7 +155,7 @@ export function DailyCalendar({
|
|||||||
<div className="grid grid-cols-7 gap-1">
|
<div className="grid grid-cols-7 gap-1">
|
||||||
{days.map((date, index) => {
|
{days.map((date, index) => {
|
||||||
const isCurrentMonthDay = isCurrentMonth(date);
|
const isCurrentMonthDay = isCurrentMonth(date);
|
||||||
const isTodayDay = isToday(date);
|
const isTodayDay = isTodayDate(date);
|
||||||
const hasCheckboxes = hasDaily(date);
|
const hasCheckboxes = hasDaily(date);
|
||||||
const isSelectedDay = isSelected(date);
|
const isSelectedDay = isSelected(date);
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { useWeeklyMetrics, useVelocityTrends } from '@/hooks/use-metrics';
|
import { useWeeklyMetrics, useVelocityTrends } from '@/hooks/use-metrics';
|
||||||
|
import { getToday } from '@/lib/date-utils';
|
||||||
import { Card, CardHeader, CardContent } from '@/components/ui/Card';
|
import { Card, CardHeader, CardContent } from '@/components/ui/Card';
|
||||||
import { Button } from '@/components/ui/Button';
|
import { Button } from '@/components/ui/Button';
|
||||||
import { DailyStatusChart } from './charts/DailyStatusChart';
|
import { DailyStatusChart } from './charts/DailyStatusChart';
|
||||||
@@ -19,7 +20,7 @@ interface MetricsTabProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function MetricsTab({ className }: MetricsTabProps) {
|
export function MetricsTab({ className }: MetricsTabProps) {
|
||||||
const [selectedDate] = useState<Date>(new Date());
|
const [selectedDate] = useState<Date>(getToday());
|
||||||
const [weeksBack, setWeeksBack] = useState(4);
|
const [weeksBack, setWeeksBack] = useState(4);
|
||||||
|
|
||||||
const { metrics, loading: metricsLoading, error: metricsError, refetch: refetchMetrics } = useWeeklyMetrics(selectedDate);
|
const { metrics, loading: metricsLoading, error: metricsError, refetch: refetchMetrics } = useWeeklyMetrics(selectedDate);
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
import { BarChart, Bar, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer, Legend } from 'recharts';
|
import { BarChart, Bar, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer, Legend } from 'recharts';
|
||||||
import { DailyMetrics } from '@/services/metrics';
|
import { DailyMetrics } from '@/services/metrics';
|
||||||
|
import { parseDate, formatDateShort } from '@/lib/date-utils';
|
||||||
|
|
||||||
interface DailyStatusChartProps {
|
interface DailyStatusChartProps {
|
||||||
data: DailyMetrics[];
|
data: DailyMetrics[];
|
||||||
@@ -12,7 +13,7 @@ export function DailyStatusChart({ data, className }: DailyStatusChartProps) {
|
|||||||
// Transformer les données pour le graphique
|
// Transformer les données pour le graphique
|
||||||
const chartData = data.map(day => ({
|
const chartData = data.map(day => ({
|
||||||
day: day.dayName.substring(0, 3), // Lun, Mar, etc.
|
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,
|
'Complétées': day.completed,
|
||||||
'En cours': day.inProgress,
|
'En cours': day.inProgress,
|
||||||
'Bloquées': day.blocked,
|
'Bloquées': day.blocked,
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { DailyMetrics } from '@/services/metrics';
|
import { DailyMetrics } from '@/services/metrics';
|
||||||
|
import { parseDate, isToday } from '@/lib/date-utils';
|
||||||
|
|
||||||
interface WeeklyActivityHeatmapProps {
|
interface WeeklyActivityHeatmapProps {
|
||||||
data: DailyMetrics[];
|
data: DailyMetrics[];
|
||||||
@@ -67,7 +68,7 @@ export function WeeklyActivityHeatmap({ data, className }: WeeklyActivityHeatmap
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Indicator si jour actuel */}
|
{/* 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 className="w-2 h-2 bg-blue-500 rounded-full"></div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { useState, useEffect, useCallback, useTransition } from 'react';
|
|||||||
import { DailyCheckbox } from '@/lib/types';
|
import { DailyCheckbox } from '@/lib/types';
|
||||||
import { tasksClient } from '@/clients/tasks-client';
|
import { tasksClient } from '@/clients/tasks-client';
|
||||||
import { Button } from '@/components/ui/Button';
|
import { Button } from '@/components/ui/Button';
|
||||||
|
import { formatDateSmart } from '@/lib/date-utils';
|
||||||
import { Input } from '@/components/ui/Input';
|
import { Input } from '@/components/ui/Input';
|
||||||
import { addTodoToTask, toggleCheckbox } from '@/actions/daily';
|
import { addTodoToTask, toggleCheckbox } from '@/actions/daily';
|
||||||
|
|
||||||
@@ -82,11 +83,7 @@ export function RelatedTodos({ taskId }: RelatedTodosProps) {
|
|||||||
if (isNaN(dateObj.getTime())) {
|
if (isNaN(dateObj.getTime())) {
|
||||||
return 'Date invalide';
|
return 'Date invalide';
|
||||||
}
|
}
|
||||||
return new Intl.DateTimeFormat('fr-FR', {
|
return formatDateSmart(dateObj);
|
||||||
day: 'numeric',
|
|
||||||
month: 'short',
|
|
||||||
year: 'numeric'
|
|
||||||
}).format(dateObj);
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Erreur formatage date:', error, date);
|
console.error('Erreur formatage date:', error, date);
|
||||||
return 'Date invalide';
|
return 'Date invalide';
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import { Button } from '@/components/ui/Button';
|
|||||||
import { Badge } from '@/components/ui/Badge';
|
import { Badge } from '@/components/ui/Badge';
|
||||||
import { Modal } from '@/components/ui/Modal';
|
import { Modal } from '@/components/ui/Modal';
|
||||||
import { Card, CardHeader, CardContent } from '@/components/ui/Card';
|
import { Card, CardHeader, CardContent } from '@/components/ui/Card';
|
||||||
|
import { formatDateForDisplay, getToday } from '@/lib/date-utils';
|
||||||
|
|
||||||
interface AnomalyDetectionPanelProps {
|
interface AnomalyDetectionPanelProps {
|
||||||
className?: string;
|
className?: string;
|
||||||
@@ -42,7 +43,7 @@ export default function AnomalyDetectionPanel({ className = '' }: AnomalyDetecti
|
|||||||
|
|
||||||
if (result.success && result.data) {
|
if (result.success && result.data) {
|
||||||
setAnomalies(result.data);
|
setAnomalies(result.data);
|
||||||
setLastUpdate(new Date().toLocaleString('fr-FR'));
|
setLastUpdate(formatDateForDisplay(getToday(), 'DISPLAY_LONG'));
|
||||||
} else {
|
} else {
|
||||||
setError(result.error || 'Erreur lors de la détection');
|
setError(result.error || 'Erreur lors de la détection');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import { Card, CardHeader, CardContent } from '@/components/ui/Card';
|
|||||||
import { Input } from '@/components/ui/Input';
|
import { Input } from '@/components/ui/Input';
|
||||||
import { Modal } from '@/components/ui/Modal';
|
import { Modal } from '@/components/ui/Modal';
|
||||||
import { Header } from '@/components/ui/Header';
|
import { Header } from '@/components/ui/Header';
|
||||||
|
import { formatDateForDisplay } from '@/lib/date-utils';
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
|
|
||||||
interface BackupSettingsPageClientProps {
|
interface BackupSettingsPageClientProps {
|
||||||
@@ -193,16 +194,8 @@ export default function BackupSettingsPageClient({ initialData }: BackupSettings
|
|||||||
|
|
||||||
const formatDate = (date: string | Date): string => {
|
const formatDate = (date: string | Date): string => {
|
||||||
// Format cohérent serveur/client pour éviter les erreurs d'hydratation
|
// Format cohérent serveur/client pour éviter les erreurs d'hydratation
|
||||||
const d = new Date(date);
|
const d = typeof date === 'string' ? new Date(date) : date;
|
||||||
return d.toLocaleDateString('fr-FR', {
|
return formatDateForDisplay(d, 'DISPLAY_MEDIUM');
|
||||||
day: '2-digit',
|
|
||||||
month: '2-digit',
|
|
||||||
year: 'numeric',
|
|
||||||
hour: '2-digit',
|
|
||||||
minute: '2-digit',
|
|
||||||
second: '2-digit',
|
|
||||||
hour12: false
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
if (isLoading) {
|
if (isLoading) {
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
import { useState, useEffect, useCallback, useTransition } from 'react';
|
import { useState, useEffect, useCallback, useTransition } from 'react';
|
||||||
import { dailyClient, DailyHistoryFilters, DailySearchFilters, ReorderCheckboxesData } from '@/clients/daily-client';
|
import { dailyClient, DailyHistoryFilters, DailySearchFilters, ReorderCheckboxesData } from '@/clients/daily-client';
|
||||||
import { DailyView, DailyCheckbox, UpdateDailyCheckboxData, DailyCheckboxType } from '@/lib/types';
|
import { DailyView, DailyCheckbox, UpdateDailyCheckboxData, DailyCheckboxType } from '@/lib/types';
|
||||||
|
import { addDays, subtractDays, getToday } from '@/lib/date-utils';
|
||||||
import {
|
import {
|
||||||
toggleCheckbox as toggleCheckboxAction,
|
toggleCheckbox as toggleCheckboxAction,
|
||||||
addTodayCheckbox as addTodayCheckboxAction,
|
addTodayCheckbox as addTodayCheckboxAction,
|
||||||
@@ -341,19 +342,17 @@ export function useDaily(initialDate?: Date, initialDailyView?: DailyView): UseD
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const goToPreviousDay = useCallback(async (): Promise<void> => {
|
const goToPreviousDay = useCallback(async (): Promise<void> => {
|
||||||
const previousDay = new Date(currentDate);
|
const previousDay = subtractDays(currentDate, 1);
|
||||||
previousDay.setDate(previousDay.getDate() - 1);
|
|
||||||
setCurrentDate(previousDay);
|
setCurrentDate(previousDay);
|
||||||
}, [currentDate]);
|
}, [currentDate]);
|
||||||
|
|
||||||
const goToNextDay = useCallback(async (): Promise<void> => {
|
const goToNextDay = useCallback(async (): Promise<void> => {
|
||||||
const nextDay = new Date(currentDate);
|
const nextDay = addDays(currentDate, 1);
|
||||||
nextDay.setDate(nextDay.getDate() + 1);
|
|
||||||
setCurrentDate(nextDay);
|
setCurrentDate(nextDay);
|
||||||
}, [currentDate]);
|
}, [currentDate]);
|
||||||
|
|
||||||
const goToToday = useCallback(async (): Promise<void> => {
|
const goToToday = useCallback(async (): Promise<void> => {
|
||||||
setCurrentDate(new Date());
|
setCurrentDate(getToday());
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const setDate = useCallback(async (date: Date): Promise<void> => {
|
const setDate = useCallback(async (date: Date): Promise<void> => {
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { exec } from 'child_process';
|
|||||||
import { promisify } from 'util';
|
import { promisify } from 'util';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import { createHash } from 'crypto';
|
import { createHash } from 'crypto';
|
||||||
|
import { formatDateForDisplay, getToday } from './date-utils';
|
||||||
|
|
||||||
const execAsync = promisify(exec);
|
const execAsync = promisify(exec);
|
||||||
|
|
||||||
@@ -184,7 +185,7 @@ export class BackupUtils {
|
|||||||
extra?: { hash?: string; size?: number; previousHash?: string }
|
extra?: { hash?: string; size?: number; previousHash?: string }
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
try {
|
try {
|
||||||
const date = new Date().toLocaleString('fr-FR');
|
const date = formatDateForDisplay(getToday(), 'DISPLAY_LONG');
|
||||||
|
|
||||||
let logEntry = `[${date}] ${type.toUpperCase()} BACKUP ${action.toUpperCase()}: ${details}`;
|
let logEntry = `[${date}] ${type.toUpperCase()} BACKUP ${action.toUpperCase()}: ${details}`;
|
||||||
|
|
||||||
|
|||||||
206
src/lib/date-utils.ts
Normal file
206
src/lib/date-utils.ts
Normal 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)}`;
|
||||||
|
}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import { prisma } from './database';
|
import { prisma } from './database';
|
||||||
import { Prisma } from '@prisma/client';
|
import { Prisma } from '@prisma/client';
|
||||||
import { DailyCheckbox, DailyView, CreateDailyCheckboxData, UpdateDailyCheckboxData, BusinessError, DailyCheckboxType, TaskStatus, TaskPriority, TaskSource } from '@/lib/types';
|
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
|
* Service pour la gestion des checkboxes daily
|
||||||
@@ -13,8 +13,7 @@ export class DailyService {
|
|||||||
*/
|
*/
|
||||||
async getDailyView(date: Date): Promise<DailyView> {
|
async getDailyView(date: Date): Promise<DailyView> {
|
||||||
// Normaliser la date (début de journée)
|
// Normaliser la date (début de journée)
|
||||||
const today = new Date(date);
|
const today = normalizeDate(date);
|
||||||
today.setHours(0, 0, 0, 0);
|
|
||||||
|
|
||||||
// Utiliser la logique de jour de travail précédent au lieu de jour-1
|
// Utiliser la logique de jour de travail précédent au lieu de jour-1
|
||||||
const yesterday = getPreviousWorkday(today);
|
const yesterday = getPreviousWorkday(today);
|
||||||
@@ -37,8 +36,7 @@ export class DailyService {
|
|||||||
*/
|
*/
|
||||||
async getCheckboxesByDate(date: Date): Promise<DailyCheckbox[]> {
|
async getCheckboxesByDate(date: Date): Promise<DailyCheckbox[]> {
|
||||||
// Normaliser la date (début de journée)
|
// Normaliser la date (début de journée)
|
||||||
const normalizedDate = new Date(date);
|
const normalizedDate = normalizeDate(date);
|
||||||
normalizedDate.setHours(0, 0, 0, 0);
|
|
||||||
|
|
||||||
const checkboxes = await prisma.dailyCheckbox.findMany({
|
const checkboxes = await prisma.dailyCheckbox.findMany({
|
||||||
where: { date: normalizedDate },
|
where: { date: normalizedDate },
|
||||||
@@ -54,8 +52,7 @@ export class DailyService {
|
|||||||
*/
|
*/
|
||||||
async addCheckbox(data: CreateDailyCheckboxData): Promise<DailyCheckbox> {
|
async addCheckbox(data: CreateDailyCheckboxData): Promise<DailyCheckbox> {
|
||||||
// Normaliser la date
|
// Normaliser la date
|
||||||
const normalizedDate = new Date(data.date);
|
const normalizedDate = normalizeDate(data.date);
|
||||||
normalizedDate.setHours(0, 0, 0, 0);
|
|
||||||
|
|
||||||
// Calculer l'ordre suivant pour cette date
|
// Calculer l'ordre suivant pour cette date
|
||||||
const maxOrder = await prisma.dailyCheckbox.aggregate({
|
const maxOrder = await prisma.dailyCheckbox.aggregate({
|
||||||
@@ -128,10 +125,6 @@ export class DailyService {
|
|||||||
* Réordonne les checkboxes d'une date donnée
|
* Réordonne les checkboxes d'une date donnée
|
||||||
*/
|
*/
|
||||||
async reorderCheckboxes(date: Date, checkboxIds: string[]): Promise<void> {
|
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) => {
|
await prisma.$transaction(async (prisma) => {
|
||||||
for (let i = 0; i < checkboxIds.length; i++) {
|
for (let i = 0; i < checkboxIds.length; i++) {
|
||||||
await prisma.dailyCheckbox.update({
|
await prisma.dailyCheckbox.update({
|
||||||
@@ -264,11 +257,7 @@ export class DailyService {
|
|||||||
});
|
});
|
||||||
|
|
||||||
return checkboxes.map(checkbox => {
|
return checkboxes.map(checkbox => {
|
||||||
const date = checkbox.date;
|
return formatDateForAPI(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}`;
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
|
|
||||||
import { JiraTask } from '@/lib/types';
|
import { JiraTask } from '@/lib/types';
|
||||||
import { prisma } from './database';
|
import { prisma } from './database';
|
||||||
|
import { parseDate } from '@/lib/date-utils';
|
||||||
|
|
||||||
export interface JiraConfig {
|
export interface JiraConfig {
|
||||||
baseUrl: string;
|
baseUrl: string;
|
||||||
@@ -339,12 +340,12 @@ export class JiraService {
|
|||||||
priority: this.mapJiraPriorityToInternal(jiraTask.priority?.name),
|
priority: this.mapJiraPriorityToInternal(jiraTask.priority?.name),
|
||||||
source: 'jira' as const,
|
source: 'jira' as const,
|
||||||
sourceId: jiraTask.id,
|
sourceId: jiraTask.id,
|
||||||
dueDate: jiraTask.duedate ? new Date(jiraTask.duedate) : null,
|
dueDate: jiraTask.duedate ? parseDate(jiraTask.duedate) : null,
|
||||||
jiraProject: jiraTask.project.key,
|
jiraProject: jiraTask.project.key,
|
||||||
jiraKey: jiraTask.key,
|
jiraKey: jiraTask.key,
|
||||||
jiraType: this.mapJiraTypeToDisplay(jiraTask.issuetype.name),
|
jiraType: this.mapJiraTypeToDisplay(jiraTask.issuetype.name),
|
||||||
assignee: jiraTask.assignee?.displayName || null,
|
assignee: jiraTask.assignee?.displayName || null,
|
||||||
updatedAt: new Date(jiraTask.updated)
|
updatedAt: parseDate(jiraTask.updated)
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!existingTask) {
|
if (!existingTask) {
|
||||||
@@ -352,7 +353,7 @@ export class JiraService {
|
|||||||
const newTask = await prisma.task.create({
|
const newTask = await prisma.task.create({
|
||||||
data: {
|
data: {
|
||||||
...taskData,
|
...taskData,
|
||||||
createdAt: new Date(jiraTask.created)
|
createdAt: parseDate(jiraTask.created)
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { prisma } from './database';
|
import { prisma } from './database';
|
||||||
import { startOfWeek, endOfWeek, eachDayOfInterval, format, startOfDay, endOfDay } from 'date-fns';
|
import { startOfWeek, endOfWeek, eachDayOfInterval, format, startOfDay, endOfDay } from 'date-fns';
|
||||||
import { fr } from 'date-fns/locale';
|
import { fr } from 'date-fns/locale';
|
||||||
|
import { formatDateForAPI, getDayName, getToday } from '@/lib/date-utils';
|
||||||
|
|
||||||
export interface DailyMetrics {
|
export interface DailyMetrics {
|
||||||
date: string; // Format ISO
|
date: string; // Format ISO
|
||||||
@@ -58,7 +59,7 @@ export class MetricsService {
|
|||||||
/**
|
/**
|
||||||
* Récupère les métriques journalières de la semaine
|
* 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 weekStart = startOfWeek(date, { weekStartsOn: 1 }); // Lundi
|
||||||
const weekEnd = endOfWeek(date, { weekStartsOn: 1 }); // Dimanche
|
const weekEnd = endOfWeek(date, { weekStartsOn: 1 }); // Dimanche
|
||||||
|
|
||||||
@@ -163,8 +164,8 @@ export class MetricsService {
|
|||||||
const completionRate = totalTasks > 0 ? (completed / totalTasks) * 100 : 0;
|
const completionRate = totalTasks > 0 ? (completed / totalTasks) * 100 : 0;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
date: date.toISOString(),
|
date: formatDateForAPI(date),
|
||||||
dayName: format(date, 'EEEE', { locale: fr }),
|
dayName: getDayName(date),
|
||||||
completed,
|
completed,
|
||||||
inProgress,
|
inProgress,
|
||||||
blocked,
|
blocked,
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { prisma } from './database';
|
import { prisma } from './database';
|
||||||
import { Task, TaskStatus, TaskPriority, TaskSource, BusinessError, DailyCheckbox, DailyCheckboxType } from '@/lib/types';
|
import { Task, TaskStatus, TaskPriority, TaskSource, BusinessError, DailyCheckbox, DailyCheckboxType } from '@/lib/types';
|
||||||
import { Prisma } from '@prisma/client';
|
import { Prisma } from '@prisma/client';
|
||||||
|
import { getToday } from '@/lib/date-utils';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Service pour la gestion des tâches (version standalone)
|
* Service pour la gestion des tâches (version standalone)
|
||||||
@@ -126,12 +127,12 @@ export class TasksService {
|
|||||||
status: updates.status,
|
status: updates.status,
|
||||||
priority: updates.priority,
|
priority: updates.priority,
|
||||||
dueDate: updates.dueDate,
|
dueDate: updates.dueDate,
|
||||||
updatedAt: new Date()
|
updatedAt: getToday()
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
if (updates.status === 'done' && !task.completedAt) {
|
if (updates.status === 'done' && !task.completedAt) {
|
||||||
updateData.completedAt = new Date();
|
updateData.completedAt = getToday();
|
||||||
} else if (updates.status && updates.status !== 'done' && task.completedAt) {
|
} else if (updates.status && updates.status !== 'done' && task.completedAt) {
|
||||||
updateData.completedAt = null;
|
updateData.completedAt = null;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user