feat: integrate ConfirmModal for delete confirmations across components
- Added `ConfirmModal` to `TaskCard`, `JiraConfigForm`, `TfsConfigForm`, and `TagsManagement` for improved user experience during delete actions. - Replaced direct confirmation prompts with modals, enhancing UI consistency and usability. - Updated state management to handle modal visibility and confirmation logic effectively.
This commit is contained in:
@@ -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 (
|
||||
<UITaskCard
|
||||
ref={setNodeRef}
|
||||
style={style}
|
||||
variant={compactView ? 'compact' : 'detailed'}
|
||||
source={task.source || 'manual'}
|
||||
title={task.title}
|
||||
description={task.description}
|
||||
tags={task.tags}
|
||||
priority={task.priority}
|
||||
status={task.status}
|
||||
dueDate={task.dueDate}
|
||||
completedAt={task.completedAt}
|
||||
jiraKey={task.jiraKey}
|
||||
jiraProject={task.jiraProject}
|
||||
jiraType={task.jiraType}
|
||||
tfsPullRequestId={task.tfsPullRequestId}
|
||||
tfsProject={task.tfsProject}
|
||||
tfsRepository={task.tfsRepository}
|
||||
todosCount={task.todosCount}
|
||||
isDragging={isDragging}
|
||||
isPending={isPending}
|
||||
onEdit={handleEdit}
|
||||
onDelete={handleDelete}
|
||||
onTitleSave={handleTitleSave}
|
||||
fontSize={preferences.viewPreferences.fontSize}
|
||||
availableTags={availableTags}
|
||||
jiraConfig={preferences.jiraConfig}
|
||||
tfsConfig={preferences.tfsConfig}
|
||||
{...attributes}
|
||||
{...listeners}
|
||||
/>
|
||||
<>
|
||||
<UITaskCard
|
||||
ref={setNodeRef}
|
||||
style={style}
|
||||
variant={compactView ? 'compact' : 'detailed'}
|
||||
source={task.source || 'manual'}
|
||||
title={task.title}
|
||||
description={task.description}
|
||||
tags={task.tags}
|
||||
priority={task.priority}
|
||||
status={task.status}
|
||||
dueDate={task.dueDate}
|
||||
completedAt={task.completedAt}
|
||||
jiraKey={task.jiraKey}
|
||||
jiraProject={task.jiraProject}
|
||||
jiraType={task.jiraType}
|
||||
tfsPullRequestId={task.tfsPullRequestId}
|
||||
tfsProject={task.tfsProject}
|
||||
tfsRepository={task.tfsRepository}
|
||||
todosCount={task.todosCount}
|
||||
isDragging={isDragging}
|
||||
isPending={isPending}
|
||||
onEdit={handleEdit}
|
||||
onDelete={handleDelete}
|
||||
onTitleSave={handleTitleSave}
|
||||
fontSize={preferences.viewPreferences.fontSize}
|
||||
availableTags={availableTags}
|
||||
jiraConfig={preferences.jiraConfig}
|
||||
tfsConfig={preferences.tfsConfig}
|
||||
{...attributes}
|
||||
{...listeners}
|
||||
/>
|
||||
|
||||
<ConfirmModal
|
||||
isOpen={showDeleteConfirm}
|
||||
onClose={() => 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}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<ConfirmModal
|
||||
isOpen={showDeleteConfirm}
|
||||
onClose={() => 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}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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<TfsConfig>({
|
||||
@@ -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}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<ConfirmModal
|
||||
isOpen={showDeleteConfigConfirm}
|
||||
onClose={() => 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}
|
||||
/>
|
||||
|
||||
<ConfirmModal
|
||||
isOpen={showDeleteTasksConfirm}
|
||||
onClose={() => 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}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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<Tag | null>(null);
|
||||
const [deletingTagId, setDeletingTagId] = useState<string | null>(null);
|
||||
const [showDeleteConfirm, setShowDeleteConfirm] = useState(false);
|
||||
const [tagToDelete, setTagToDelete] = useState<Tag | null>(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
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
<ConfirmModal
|
||||
isOpen={showDeleteConfirm}
|
||||
onClose={() => {
|
||||
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}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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 (
|
||||
<section id="feedback" className="space-y-8">
|
||||
<h2 className="text-2xl font-mono font-semibold text-[var(--foreground)] border-b border-[var(--border)] pb-3">
|
||||
@@ -169,6 +194,37 @@ export function FeedbackSection() {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Modals */}
|
||||
<div className="space-y-4">
|
||||
<h3 className="text-lg font-medium text-[var(--foreground)]">Modals</h3>
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<p className="text-sm text-[var(--muted-foreground)] mb-3">Modal de base</p>
|
||||
<Button onClick={() => setShowModal(true)}>
|
||||
Ouvrir Modal
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<p className="text-sm text-[var(--muted-foreground)] mb-3">Modal de confirmation</p>
|
||||
<div className="flex gap-2">
|
||||
<Button
|
||||
variant="primary"
|
||||
onClick={() => setShowConfirmModal(true)}
|
||||
>
|
||||
Confirmation Standard
|
||||
</Button>
|
||||
<Button
|
||||
variant="destructive"
|
||||
onClick={() => setShowDestructiveConfirm(true)}
|
||||
>
|
||||
Confirmation Destructive
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Drop Zone */}
|
||||
<div className="space-y-4">
|
||||
<h3 className="text-lg font-medium text-[var(--foreground)]">Drop Zone</h3>
|
||||
@@ -183,6 +239,49 @@ export function FeedbackSection() {
|
||||
</DropZone>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Modals */}
|
||||
<Modal
|
||||
isOpen={showModal}
|
||||
onClose={() => setShowModal(false)}
|
||||
title="Modal de démonstration"
|
||||
size="md"
|
||||
>
|
||||
<div className="space-y-4">
|
||||
<p className="text-[var(--foreground)]">
|
||||
Ceci est un exemple de modal de base. Vous pouvez y mettre n'importe quel contenu.
|
||||
</p>
|
||||
<div className="flex justify-end">
|
||||
<Button onClick={() => setShowModal(false)}>
|
||||
Fermer
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
|
||||
<ConfirmModal
|
||||
isOpen={showConfirmModal}
|
||||
onClose={() => 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}
|
||||
/>
|
||||
|
||||
<ConfirmModal
|
||||
isOpen={showDestructiveConfirm}
|
||||
onClose={() => 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}
|
||||
/>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
60
src/components/ui/ConfirmModal.tsx
Normal file
60
src/components/ui/ConfirmModal.tsx
Normal file
@@ -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 (
|
||||
<Modal isOpen={isOpen} onClose={onClose} title={title} size="sm">
|
||||
<div className="space-y-4">
|
||||
<p className="text-[var(--foreground)] text-sm leading-relaxed">
|
||||
{message}
|
||||
</p>
|
||||
|
||||
<div className="flex justify-end gap-2">
|
||||
<Button
|
||||
variant="secondary"
|
||||
onClick={onClose}
|
||||
disabled={isLoading}
|
||||
>
|
||||
{cancelText}
|
||||
</Button>
|
||||
<Button
|
||||
variant={variant}
|
||||
onClick={handleConfirm}
|
||||
disabled={isLoading}
|
||||
>
|
||||
{isLoading ? 'Chargement...' : confirmText}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
@@ -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';
|
||||
|
||||
Reference in New Issue
Block a user