Files
towercontrol/src/components/ui/CheckboxItem.tsx
Julien Froidefond d45a04d347 feat: refactor Daily components and enhance UI integration
- Replaced `DailyCalendar` with a new `Calendar` component for improved functionality and consistency.
- Introduced `AlertBanner` to replace `DeadlineReminder`, providing a more flexible way to display urgent tasks.
- Updated `DailyAddForm` to use new options for task types, enhancing user experience when adding tasks.
- Removed unused state and components, streamlining the DailyPageClient for better performance and maintainability.
- Enhanced `DailySection` to utilize new `CheckboxItem` format for better integration with the UI.
- Cleaned up imports and improved overall structure for better readability.
2025-09-29 09:47:13 +02:00

194 lines
6.3 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
'use client';
import { useState, useEffect } from 'react';
import Link from 'next/link';
import { Input } from '@/components/ui/Input';
export interface CheckboxItemData {
id: string;
text: string;
isChecked: boolean;
type?: 'task' | 'meeting' | string;
taskId?: string;
task?: {
id: string;
title: string;
};
}
interface CheckboxItemProps {
item: CheckboxItemData;
onToggle: (itemId: string) => Promise<void>;
onUpdate: (itemId: string, text: string, type?: string, taskId?: string) => Promise<void>;
onDelete: (itemId: string) => Promise<void>;
saving?: boolean;
showTypeIndicator?: boolean;
showTaskLink?: boolean;
showEditButton?: boolean;
showDeleteButton?: boolean;
className?: string;
}
export function CheckboxItem({
item,
onToggle,
onUpdate,
onDelete,
saving = false,
showTaskLink = true,
showEditButton = true,
showDeleteButton = true,
className = ''
}: CheckboxItemProps) {
const [inlineEditingId, setInlineEditingId] = useState<string | null>(null);
const [inlineEditingText, setInlineEditingText] = useState('');
const [optimisticChecked, setOptimisticChecked] = useState<boolean | null>(null);
// État optimiste local pour une réponse immédiate
const isChecked = optimisticChecked !== null ? optimisticChecked : item.isChecked;
// Synchroniser l'état optimiste avec les changements externes
useEffect(() => {
if (optimisticChecked !== null && optimisticChecked === item.isChecked) {
// L'état serveur a été mis à jour, on peut reset l'optimiste
setOptimisticChecked(null);
}
}, [item.isChecked, optimisticChecked]);
// Handler optimiste pour le toggle
const handleOptimisticToggle = async () => {
const newCheckedState = !isChecked;
// Mise à jour optimiste immédiate
setOptimisticChecked(newCheckedState);
try {
await onToggle(item.id);
// Reset l'état optimiste après succès
setOptimisticChecked(null);
} catch (error) {
// Rollback en cas d'erreur
setOptimisticChecked(null);
console.error('Erreur lors du toggle:', error);
}
};
// Édition inline simple
const handleStartInlineEdit = () => {
setInlineEditingId(item.id);
setInlineEditingText(item.text);
};
const handleSaveInlineEdit = async () => {
if (!inlineEditingText.trim()) return;
try {
await onUpdate(item.id, inlineEditingText.trim(), item.type, item.taskId);
setInlineEditingId(null);
setInlineEditingText('');
} catch (error) {
console.error('Erreur lors de la modification:', error);
}
};
const handleCancelInlineEdit = () => {
setInlineEditingId(null);
setInlineEditingText('');
};
const handleInlineEditKeyPress = (e: React.KeyboardEvent) => {
if (e.key === 'Enter') {
e.preventDefault();
handleSaveInlineEdit();
} else if (e.key === 'Escape') {
e.preventDefault();
handleCancelInlineEdit();
}
};
// Obtenir la couleur de bordure selon le type
const getTypeBorderColor = () => {
if (item.type === 'meeting') return 'border-l-blue-500';
return 'border-l-green-500';
};
return (
<div className={`flex items-center gap-3 px-3 py-2 sm:py-1.5 sm:gap-2 rounded border transition-colors group border-l-4 ${getTypeBorderColor()} border-t-[var(--border)]/30 border-r-[var(--border)]/30 border-b-[var(--border)]/30 hover:border-t-[var(--border)] hover:border-r-[var(--border)] hover:border-b-[var(--border)] ${className}`}>
{/* Checkbox */}
<input
type="checkbox"
checked={isChecked}
onChange={handleOptimisticToggle}
disabled={saving}
className="w-4 h-4 md:w-3.5 md:h-3.5 rounded border border-[var(--border)] text-[var(--primary)] focus:ring-[var(--primary)]/20 focus:ring-1"
/>
{/* Contenu principal */}
{inlineEditingId === item.id ? (
<Input
value={inlineEditingText}
onChange={(e) => setInlineEditingText(e.target.value)}
onKeyDown={handleInlineEditKeyPress}
onBlur={handleSaveInlineEdit}
autoFocus
className="flex-1 h-7 text-sm"
/>
) : (
<div className="flex-1 flex items-center gap-2">
{/* Texte cliquable pour édition inline */}
<span
className={`flex-1 text-sm sm:text-xs font-mono transition-all cursor-pointer hover:bg-[var(--muted)]/50 py-0.5 px-1 rounded ${
item.isChecked
? 'line-through text-[var(--muted-foreground)]'
: 'text-[var(--foreground)]'
}`}
onClick={handleStartInlineEdit}
title="Cliquer pour éditer le texte"
>
{item.text}
</span>
{/* Icône d'édition avancée */}
{showEditButton && (
<button
onClick={() => {
// Pour l'instant, on utilise l'édition inline
// Plus tard, on pourra ajouter une modal d'édition avancée
handleStartInlineEdit();
}}
disabled={saving}
className="opacity-0 group-hover:opacity-100 w-5 h-5 rounded-full bg-[var(--muted)]/50 hover:bg-[var(--muted)] border border-[var(--border)]/30 hover:border-[var(--border)] flex items-center justify-center transition-all duration-200 text-[var(--foreground)] text-xs"
title="Éditer le texte"
>
</button>
)}
</div>
)}
{/* Lien vers la tâche si liée */}
{showTaskLink && item.task && (
<Link
href={`/kanban?taskId=${item.task.id}`}
className="text-xs text-[var(--primary)] hover:text-[var(--primary)]/80 font-mono truncate max-w-[100px]"
title={`Tâche: ${item.task.title}`}
>
{item.task.title}
</Link>
)}
{/* Bouton de suppression */}
{showDeleteButton && (
<button
onClick={() => onDelete(item.id)}
disabled={saving}
className="opacity-0 group-hover:opacity-100 w-5 h-5 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>
);
}