feat: enhance DailyCheckbox model and service for type management

- Added `DailyCheckboxType` to define checkbox types ('task' | 'meeting') in TypeScript.
- Updated `DailyCheckbox` model in Prisma schema to include `type` field with a default value of 'task'.
- Modified `DailyService` to handle checkbox type during creation and updates.
- Adjusted API route to accept checkbox type in requests.
- Refactored `DailyPageClient` to support type management in checkbox operations.
This commit is contained in:
Julien Froidefond
2025-09-15 22:16:34 +02:00
parent 08d344652f
commit adfef551ab
11 changed files with 744 additions and 211 deletions

View File

@@ -88,6 +88,7 @@ export async function POST(request: Request) {
const checkbox = await dailyService.addCheckbox({
date,
text: body.text,
type: body.type,
taskId: body.taskId,
order: body.order,
isChecked: body.isChecked

View File

@@ -1,216 +1,16 @@
'use client';
import { useState, useRef, useEffect } from 'react';
import { useState, useEffect } from 'react';
import React from 'react';
import { useDaily } from '@/hooks/useDaily';
import { DailyCheckbox, DailyView } from '@/lib/types';
import { DailyView, DailyCheckboxType } from '@/lib/types';
import { Button } from '@/components/ui/Button';
import { Input } from '@/components/ui/Input';
import { Card } from '@/components/ui/Card';
import Link from 'next/link';
import { DailyCalendar } from '@/components/daily/DailyCalendar';
import { DailySection } from '@/components/daily/DailySection';
import { dailyClient } from '@/clients/daily-client';
import { SimpleHeader } from '@/components/ui/SimpleHeader';
interface DailySectionProps {
title: string;
date: Date;
checkboxes: DailyCheckbox[];
onAddCheckbox: (text: string) => Promise<void>;
onToggleCheckbox: (checkboxId: string) => Promise<void>;
onUpdateCheckbox: (checkboxId: string, text: string) => Promise<void>;
onDeleteCheckbox: (checkboxId: string) => Promise<void>;
saving: boolean;
refreshing?: boolean;
}
function DailySectionComponent({
title,
date,
checkboxes,
onAddCheckbox,
onToggleCheckbox,
onUpdateCheckbox,
onDeleteCheckbox,
saving,
refreshing = false
}: DailySectionProps) {
const [newCheckboxText, setNewCheckboxText] = useState('');
const [addingCheckbox, setAddingCheckbox] = useState(false);
const [editingCheckboxId, setEditingCheckboxId] = useState<string | null>(null);
const [editingText, setEditingText] = useState('');
const inputRef = useRef<HTMLInputElement>(null);
const formatShortDate = (date: Date) => {
return date.toLocaleDateString('fr-FR', {
day: '2-digit',
month: '2-digit',
year: 'numeric'
});
};
const handleAddCheckbox = async () => {
if (!newCheckboxText.trim()) return;
setAddingCheckbox(true);
try {
await onAddCheckbox(newCheckboxText.trim());
setNewCheckboxText('');
// Garder le focus sur l'input pour enchainer les entrées
setTimeout(() => {
inputRef.current?.focus();
}, 100);
} finally {
setAddingCheckbox(false);
}
};
const handleStartEdit = (checkbox: DailyCheckbox) => {
setEditingCheckboxId(checkbox.id);
setEditingText(checkbox.text);
};
const handleSaveEdit = async () => {
if (!editingCheckboxId || !editingText.trim()) return;
try {
await onUpdateCheckbox(editingCheckboxId, editingText.trim());
setEditingCheckboxId(null);
setEditingText('');
} catch (error) {
console.error('Erreur lors de la modification:', error);
}
};
const handleCancelEdit = () => {
setEditingCheckboxId(null);
setEditingText('');
};
const handleEditKeyPress = (e: React.KeyboardEvent) => {
if (e.key === 'Enter') {
e.preventDefault();
handleSaveEdit();
} else if (e.key === 'Escape') {
e.preventDefault();
handleCancelEdit();
}
};
const handleKeyPress = (e: React.KeyboardEvent) => {
if (e.key === 'Enter') {
e.preventDefault();
handleAddCheckbox();
}
};
return (
<Card className="p-4">
<div className="flex items-center justify-between mb-4">
<h2 className="text-lg font-bold text-[var(--foreground)] font-mono flex items-center gap-2">
{title} <span className="text-sm font-normal text-[var(--muted-foreground)]">({formatShortDate(date)})</span>
{refreshing && (
<div className="w-4 h-4 border-2 border-[var(--primary)] border-t-transparent rounded-full animate-spin"></div>
)}
</h2>
<span className="text-xs text-[var(--muted-foreground)] font-mono">
{checkboxes.filter(cb => cb.isChecked).length}/{checkboxes.length}
</span>
</div>
{/* Liste des checkboxes */}
<div className="space-y-2 mb-4">
{checkboxes.map((checkbox) => (
<div
key={checkbox.id}
className="flex items-center gap-3 p-2 rounded border border-[var(--border)]/30 hover:border-[var(--border)] transition-colors group"
>
<input
type="checkbox"
checked={checkbox.isChecked}
onChange={() => onToggleCheckbox(checkbox.id)}
disabled={saving}
className="w-4 h-4 rounded border border-[var(--border)] text-[var(--primary)] focus:ring-[var(--primary)]/20 focus:ring-2"
/>
{editingCheckboxId === checkbox.id ? (
<Input
value={editingText}
onChange={(e) => setEditingText(e.target.value)}
onKeyDown={handleEditKeyPress}
onBlur={handleSaveEdit}
autoFocus
className="flex-1 h-8 text-sm"
/>
) : (
<span
className={`flex-1 text-sm font-mono transition-all cursor-pointer hover:bg-[var(--muted)]/50 p-1 rounded ${
checkbox.isChecked
? 'line-through text-[var(--muted-foreground)]'
: 'text-[var(--foreground)]'
}`}
onClick={() => handleStartEdit(checkbox)}
>
{checkbox.text}
</span>
)}
{/* Lien vers la tâche si liée */}
{checkbox.task && (
<Link
href={`/?highlight=${checkbox.task.id}`}
className="text-xs text-[var(--primary)] hover:text-[var(--primary)]/80 font-mono"
title={`Tâche: ${checkbox.task.title}`}
>
#{checkbox.task.id.slice(-6)}
</Link>
)}
{/* Bouton de suppression */}
<button
onClick={() => onDeleteCheckbox(checkbox.id)}
disabled={saving}
className="opacity-0 group-hover:opacity-100 w-6 h-6 rounded-full bg-[var(--destructive)]/20 hover:bg-[var(--destructive)]/30 border border-[var(--destructive)]/30 hover:border-[var(--destructive)]/50 flex items-center justify-center transition-all duration-200 text-[var(--destructive)] text-xs"
title="Supprimer"
>
×
</button>
</div>
))}
{checkboxes.length === 0 && (
<div className="text-center py-8 text-[var(--muted-foreground)] text-sm font-mono">
Aucune tâche pour cette période
</div>
)}
</div>
{/* Formulaire d'ajout */}
<div className="flex gap-2">
<Input
ref={inputRef}
type="text"
placeholder={`Ajouter une tâche...`}
value={newCheckboxText}
onChange={(e) => setNewCheckboxText(e.target.value)}
onKeyDown={handleKeyPress}
disabled={addingCheckbox || saving}
className="flex-1 min-w-[300px]"
/>
<Button
onClick={handleAddCheckbox}
disabled={!newCheckboxText.trim() || addingCheckbox || saving}
variant="primary"
size="sm"
className="min-w-[40px]"
>
{addingCheckbox ? '...' : '+'}
</Button>
</div>
</Card>
);
}
interface DailyPageClientProps {
initialDailyView?: DailyView;
initialDailyDates?: string[];
@@ -260,12 +60,14 @@ export function DailyPageClient({
}
}, [initialDailyDates.length]);
const handleAddTodayCheckbox = async (text: string) => {
const handleAddTodayCheckbox = async (text: string, type: DailyCheckboxType) => {
try {
const { dailyClient } = await import('@/clients/daily-client');
await dailyClient.addCheckbox({
date: currentDate,
text,
type,
// Pas de taskId lors de l'ajout - sera ajouté via l'édition
isChecked: false
});
// Recharger silencieusement la vue daily (sans clignotement)
@@ -277,7 +79,7 @@ export function DailyPageClient({
}
};
const handleAddYesterdayCheckbox = async (text: string) => {
const handleAddYesterdayCheckbox = async (text: string, type: DailyCheckboxType) => {
try {
const yesterday = new Date(currentDate);
yesterday.setDate(yesterday.getDate() - 1);
@@ -286,6 +88,8 @@ export function DailyPageClient({
await dailyClient.addCheckbox({
date: yesterday,
text,
type,
// Pas de taskId lors de l'ajout - sera ajouté via l'édition
isChecked: false
});
// Recharger silencieusement la vue daily (sans clignotement)
@@ -307,8 +111,12 @@ export function DailyPageClient({
await refreshDailyDates();
};
const handleUpdateCheckbox = async (checkboxId: string, text: string) => {
await updateCheckbox(checkboxId, { text });
const handleUpdateCheckbox = async (checkboxId: string, text: string, type: DailyCheckboxType, taskId?: string) => {
await updateCheckbox(checkboxId, {
text,
type,
taskId: type === 'task' ? taskId : undefined // Supprimer la liaison tâche si on passe en réunion
});
};
const getYesterdayDate = () => {
@@ -430,7 +238,7 @@ export function DailyPageClient({
{dailyView && (
<div className="xl:col-span-2 grid grid-cols-1 lg:grid-cols-2 gap-6">
{/* Section Hier */}
<DailySectionComponent
<DailySection
title="📋 Hier"
date={getYesterdayDate()}
checkboxes={dailyView.yesterday}
@@ -443,7 +251,7 @@ export function DailyPageClient({
/>
{/* Section Aujourd'hui */}
<DailySectionComponent
<DailySection
title="🎯 Aujourd'hui"
date={getTodayDate()}
checkboxes={dailyView.today}
@@ -458,7 +266,7 @@ export function DailyPageClient({
)}
</div>
{/* Footer avec stats */}
{/* Footer avec stats - dans le flux normal */}
{dailyView && (
<Card className="mt-8 p-4">
<div className="text-center text-sm text-[var(--muted-foreground)] font-mono">