diff --git a/src/components/kanban/TaskCard.tsx b/src/components/kanban/TaskCard.tsx index 4e21fed..339b14d 100644 --- a/src/components/kanban/TaskCard.tsx +++ b/src/components/kanban/TaskCard.tsx @@ -1,6 +1,6 @@ -import { useTransition } from 'react'; +import { useTransition, useState } from 'react'; import { Task } from '@/lib/types'; -import { TaskCard as UITaskCard } from '@/components/ui/TaskCard'; +import { TaskCard as UITaskCard, ConfirmModal } from '@/components/ui'; import { useTasksContext } from '@/contexts/TasksContext'; import { useUserPreferences } from '@/contexts/UserPreferencesContext'; import { useDraggable } from '@dnd-kit/core'; @@ -14,6 +14,7 @@ interface TaskCardProps { export function TaskCard({ task, onEdit, compactView = false }: TaskCardProps) { const [isPending, startTransition] = useTransition(); + const [showDeleteConfirm, setShowDeleteConfirm] = useState(false); const { tags: availableTags, refreshTasks } = useTasksContext(); const { preferences } = useUserPreferences(); @@ -23,17 +24,19 @@ export function TaskCard({ task, onEdit, compactView = false }: TaskCardProps) { id: task.id, }); - const handleDelete = async () => { - if (window.confirm('Êtes-vous sûr de vouloir supprimer cette tâche ?')) { - startTransition(async () => { - const result = await deleteTask(task.id); - if (!result.success) { - console.error('Error deleting task:', result.error); - } else { - await refreshTasks(); - } - }); - } + const handleDelete = () => { + setShowDeleteConfirm(true); + }; + + const confirmDelete = async () => { + startTransition(async () => { + const result = await deleteTask(task.id); + if (!result.success) { + console.error('Error deleting task:', result.error); + } else { + await refreshTasks(); + } + }); }; const handleEdit = () => { @@ -61,36 +64,50 @@ export function TaskCard({ task, onEdit, compactView = false }: TaskCardProps) { : undefined; return ( - + <> + + + setShowDeleteConfirm(false)} + onConfirm={confirmDelete} + title="Supprimer la tâche" + message="Êtes-vous sûr de vouloir supprimer cette tâche ? Cette action est irréversible." + confirmText="Supprimer" + cancelText="Annuler" + variant="destructive" + isLoading={isPending} + /> + ); } diff --git a/src/components/settings/JiraConfigForm.tsx b/src/components/settings/JiraConfigForm.tsx index 2e89eac..89c6361 100644 --- a/src/components/settings/JiraConfigForm.tsx +++ b/src/components/settings/JiraConfigForm.tsx @@ -3,6 +3,7 @@ import { useState, useEffect } from 'react'; import { Button } from '@/components/ui/Button'; import { Badge } from '@/components/ui/Badge'; +import { ConfirmModal } from '@/components/ui/ConfirmModal'; import { useJiraConfig } from '@/hooks/useJiraConfig'; import { jiraConfigClient } from '@/clients/jira-config-client'; @@ -21,6 +22,7 @@ export function JiraConfigForm() { const [isValidating, setIsValidating] = useState(false); const [validationResult, setValidationResult] = useState<{ type: 'success' | 'error', text: string } | null>(null); const [message, setMessage] = useState<{ type: 'success' | 'error', text: string } | null>(null); + const [showDeleteConfirm, setShowDeleteConfirm] = useState(false); const [showForm, setShowForm] = useState(false); // Charger les données existantes dans le formulaire @@ -77,10 +79,10 @@ export function JiraConfigForm() { }; const handleDelete = async () => { - if (!confirm('Êtes-vous sûr de vouloir supprimer la configuration Jira ?')) { - return; - } + setShowDeleteConfirm(true); + }; + const confirmDelete = async () => { setIsSubmitting(true); setMessage(null); @@ -444,6 +446,18 @@ export function JiraConfigForm() { {message.text} )} + + setShowDeleteConfirm(false)} + onConfirm={confirmDelete} + title="Supprimer la configuration Jira" + message="Êtes-vous sûr de vouloir supprimer la configuration Jira ?" + confirmText="Supprimer" + cancelText="Annuler" + variant="destructive" + isLoading={isSubmitting} + /> ); } diff --git a/src/components/settings/TfsConfigForm.tsx b/src/components/settings/TfsConfigForm.tsx index bec17c1..103d6fc 100644 --- a/src/components/settings/TfsConfigForm.tsx +++ b/src/components/settings/TfsConfigForm.tsx @@ -5,6 +5,7 @@ import { TfsConfig } from '@/services/integrations/tfs'; import { getTfsConfig, saveTfsConfig, deleteAllTfsTasks } from '@/actions/tfs'; import { Button } from '@/components/ui/Button'; import { Badge } from '@/components/ui/Badge'; +import { ConfirmModal } from '@/components/ui/ConfirmModal'; export function TfsConfigForm() { const [config, setConfig] = useState({ @@ -24,6 +25,8 @@ export function TfsConfigForm() { const [showForm, setShowForm] = useState(false); const [isLoading, setIsLoading] = useState(true); const [deletingTasks, setDeletingTasks] = useState(false); + const [showDeleteConfigConfirm, setShowDeleteConfigConfirm] = useState(false); + const [showDeleteTasksConfirm, setShowDeleteTasksConfirm] = useState(false); // Charger la configuration existante useEffect(() => { @@ -85,10 +88,10 @@ export function TfsConfigForm() { }; const handleDelete = async () => { - if (!confirm('Êtes-vous sûr de vouloir supprimer la configuration TFS ?')) { - return; - } + setShowDeleteConfigConfirm(true); + }; + const confirmDeleteConfig = async () => { startTransition(async () => { setMessage(null); // Réinitialiser la config @@ -171,17 +174,10 @@ export function TfsConfigForm() { }; const handleDeleteAllTasks = async () => { - const confirmation = confirm( - 'Êtes-vous sûr de vouloir supprimer TOUTES les tâches TFS de la base locale ?\n\n' + - 'Cette action est irréversible et supprimera définitivement toutes les tâches ' + - 'synchronisées depuis Azure DevOps/TFS.\n\n' + - 'Cliquez sur OK pour confirmer la suppression.' - ); - - if (!confirmation) { - return; - } + setShowDeleteTasksConfirm(true); + }; + const confirmDeleteAllTasks = async () => { try { setDeletingTasks(true); setMessage(null); @@ -634,6 +630,30 @@ export function TfsConfigForm() { {message.text} )} + + setShowDeleteConfigConfirm(false)} + onConfirm={confirmDeleteConfig} + title="Supprimer la configuration TFS" + message="Êtes-vous sûr de vouloir supprimer la configuration TFS ?" + confirmText="Supprimer" + cancelText="Annuler" + variant="destructive" + isLoading={isPending} + /> + + setShowDeleteTasksConfirm(false)} + onConfirm={confirmDeleteAllTasks} + title="Supprimer toutes les tâches TFS" + message="Êtes-vous sûr de vouloir supprimer TOUTES les tâches TFS de la base locale ? Cette action est irréversible et supprimera définitivement toutes les tâches synchronisées depuis Azure DevOps/TFS." + confirmText="Supprimer définitivement" + cancelText="Annuler" + variant="destructive" + isLoading={deletingTasks} + /> ); } diff --git a/src/components/settings/tags/TagsManagement.tsx b/src/components/settings/tags/TagsManagement.tsx index 37eb47e..88e3154 100644 --- a/src/components/settings/tags/TagsManagement.tsx +++ b/src/components/settings/tags/TagsManagement.tsx @@ -4,6 +4,7 @@ import { useState, useMemo } from 'react'; import { Tag } from '@/lib/types'; import { Card, CardContent, CardHeader } from '@/components/ui/Card'; import { Button } from '@/components/ui/Button'; +import { ConfirmModal } from '@/components/ui/ConfirmModal'; import { TagForm } from '@/components/forms/TagForm'; import { TagsStats } from './TagsStats'; import { TagsFilters } from './TagsFilters'; @@ -22,6 +23,8 @@ export function TagsManagement({ tags, onRefreshTags, onDeleteTag }: TagsManagem const [isCreateModalOpen, setIsCreateModalOpen] = useState(false); const [editingTag, setEditingTag] = useState(null); const [deletingTagId, setDeletingTagId] = useState(null); + const [showDeleteConfirm, setShowDeleteConfirm] = useState(false); + const [tagToDelete, setTagToDelete] = useState(null); // Filtrer et trier les tags const filteredTags = useMemo(() => { @@ -65,18 +68,22 @@ export function TagsManagement({ tags, onRefreshTags, onDeleteTag }: TagsManagem }; const handleDeleteTag = async (tag: Tag) => { - if (!confirm(`Êtes-vous sûr de vouloir supprimer le tag "${tag.name}" ?`)) { - return; - } + setTagToDelete(tag); + setShowDeleteConfirm(true); + }; + + const confirmDeleteTag = async () => { + if (!tagToDelete) return; - setDeletingTagId(tag.id); + setDeletingTagId(tagToDelete.id); try { - await onDeleteTag(tag.id); + await onDeleteTag(tagToDelete.id); await onRefreshTags(); } catch (error) { console.error('Erreur lors de la suppression:', error); } finally { setDeletingTagId(null); + setTagToDelete(null); } }; @@ -164,6 +171,21 @@ export function TagsManagement({ tags, onRefreshTags, onDeleteTag }: TagsManagem }} /> )} + + { + setShowDeleteConfirm(false); + setTagToDelete(null); + }} + onConfirm={confirmDeleteTag} + title="Supprimer le tag" + message={`Êtes-vous sûr de vouloir supprimer le tag "${tagToDelete?.name}" ?`} + confirmText="Supprimer" + cancelText="Annuler" + variant="destructive" + isLoading={!!deletingTagId} + /> ); } diff --git a/src/components/ui-showcase/sections/FeedbackSection.tsx b/src/components/ui-showcase/sections/FeedbackSection.tsx index b7a894a..d9e923b 100644 --- a/src/components/ui-showcase/sections/FeedbackSection.tsx +++ b/src/components/ui-showcase/sections/FeedbackSection.tsx @@ -6,14 +6,39 @@ import { LoadingSpinner } from '@/components/ui/LoadingSpinner'; import { ProgressBar } from '@/components/ui/ProgressBar'; import { EmptyState } from '@/components/ui/EmptyState'; import { DropZone } from '@/components/ui/DropZone'; +import { Modal } from '@/components/ui/Modal'; +import { ConfirmModal } from '@/components/ui/ConfirmModal'; +import { Button } from '@/components/ui/Button'; +import { useState } from 'react'; export function FeedbackSection() { + const [showModal, setShowModal] = useState(false); + const [showConfirmModal, setShowConfirmModal] = useState(false); + const [showDestructiveConfirm, setShowDestructiveConfirm] = useState(false); + const [isLoading, setIsLoading] = useState(false); + const alertItems: AlertItem[] = [ { id: '1', title: 'Tâche critique', icon: '🔴', urgency: 'critical', metadata: 'Dans 1 jour' }, { id: '2', title: 'Réunion urgente', icon: '🟠', urgency: 'high', metadata: 'Dans 2 jours' }, { id: '3', title: 'Rappel', icon: '🟡', urgency: 'medium', metadata: 'Dans 5 jours' } ]; + const handleConfirm = () => { + setIsLoading(true); + setTimeout(() => { + setIsLoading(false); + setShowConfirmModal(false); + }, 2000); + }; + + const handleDestructiveConfirm = () => { + setIsLoading(true); + setTimeout(() => { + setIsLoading(false); + setShowDestructiveConfirm(false); + }, 2000); + }; + return (

@@ -169,6 +194,37 @@ export function FeedbackSection() { + {/* Modals */} +
+

Modals

+
+
+

Modal de base

+ +
+ +
+

Modal de confirmation

+
+ + +
+
+
+
+ {/* Drop Zone */}

Drop Zone

@@ -183,6 +239,49 @@ export function FeedbackSection() {
+ + {/* Modals */} + setShowModal(false)} + title="Modal de démonstration" + size="md" + > +
+

+ Ceci est un exemple de modal de base. Vous pouvez y mettre n'importe quel contenu. +

+
+ +
+
+
+ + setShowConfirmModal(false)} + onConfirm={handleConfirm} + title="Confirmer l'action" + message="Êtes-vous sûr de vouloir effectuer cette action ?" + confirmText="Confirmer" + cancelText="Annuler" + variant="primary" + isLoading={isLoading} + /> + + setShowDestructiveConfirm(false)} + onConfirm={handleDestructiveConfirm} + title="Supprimer définitivement" + message="Cette action est irréversible. Êtes-vous sûr de vouloir continuer ?" + confirmText="Supprimer" + cancelText="Annuler" + variant="destructive" + isLoading={isLoading} + />

); } diff --git a/src/components/ui/ConfirmModal.tsx b/src/components/ui/ConfirmModal.tsx new file mode 100644 index 0000000..e4c1c02 --- /dev/null +++ b/src/components/ui/ConfirmModal.tsx @@ -0,0 +1,60 @@ +'use client'; + +import { Modal } from './Modal'; +import { Button } from './Button'; + +interface ConfirmModalProps { + isOpen: boolean; + onClose: () => void; + onConfirm: () => void; + title?: string; + message: string; + confirmText?: string; + cancelText?: string; + variant?: 'primary' | 'destructive'; + isLoading?: boolean; +} + +export function ConfirmModal({ + isOpen, + onClose, + onConfirm, + title = 'Confirmation', + message, + confirmText = 'Confirmer', + cancelText = 'Annuler', + variant = 'primary', + isLoading = false +}: ConfirmModalProps) { + const handleConfirm = () => { + onConfirm(); + onClose(); + }; + + return ( + +
+

+ {message} +

+ +
+ + +
+
+
+ ); +} diff --git a/src/components/ui/index.ts b/src/components/ui/index.ts index c29e1d3..f53b8a9 100644 --- a/src/components/ui/index.ts +++ b/src/components/ui/index.ts @@ -45,3 +45,5 @@ export { MetricsGrid } from './MetricsGrid'; // Composants existants export { Card, CardHeader, CardTitle, CardContent, CardFooter } from './Card'; export { FontSizeToggle } from './FontSizeToggle'; +export { Modal } from './Modal'; +export { ConfirmModal } from './ConfirmModal';