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 { 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 { useTasksContext } from '@/contexts/TasksContext';
|
||||||
import { useUserPreferences } from '@/contexts/UserPreferencesContext';
|
import { useUserPreferences } from '@/contexts/UserPreferencesContext';
|
||||||
import { useDraggable } from '@dnd-kit/core';
|
import { useDraggable } from '@dnd-kit/core';
|
||||||
@@ -14,6 +14,7 @@ interface TaskCardProps {
|
|||||||
|
|
||||||
export function TaskCard({ task, onEdit, compactView = false }: TaskCardProps) {
|
export function TaskCard({ task, onEdit, compactView = false }: TaskCardProps) {
|
||||||
const [isPending, startTransition] = useTransition();
|
const [isPending, startTransition] = useTransition();
|
||||||
|
const [showDeleteConfirm, setShowDeleteConfirm] = useState(false);
|
||||||
const { tags: availableTags, refreshTasks } = useTasksContext();
|
const { tags: availableTags, refreshTasks } = useTasksContext();
|
||||||
const { preferences } = useUserPreferences();
|
const { preferences } = useUserPreferences();
|
||||||
|
|
||||||
@@ -23,17 +24,19 @@ export function TaskCard({ task, onEdit, compactView = false }: TaskCardProps) {
|
|||||||
id: task.id,
|
id: task.id,
|
||||||
});
|
});
|
||||||
|
|
||||||
const handleDelete = async () => {
|
const handleDelete = () => {
|
||||||
if (window.confirm('Êtes-vous sûr de vouloir supprimer cette tâche ?')) {
|
setShowDeleteConfirm(true);
|
||||||
startTransition(async () => {
|
};
|
||||||
const result = await deleteTask(task.id);
|
|
||||||
if (!result.success) {
|
const confirmDelete = async () => {
|
||||||
console.error('Error deleting task:', result.error);
|
startTransition(async () => {
|
||||||
} else {
|
const result = await deleteTask(task.id);
|
||||||
await refreshTasks();
|
if (!result.success) {
|
||||||
}
|
console.error('Error deleting task:', result.error);
|
||||||
});
|
} else {
|
||||||
}
|
await refreshTasks();
|
||||||
|
}
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleEdit = () => {
|
const handleEdit = () => {
|
||||||
@@ -61,36 +64,50 @@ export function TaskCard({ task, onEdit, compactView = false }: TaskCardProps) {
|
|||||||
: undefined;
|
: undefined;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<UITaskCard
|
<>
|
||||||
ref={setNodeRef}
|
<UITaskCard
|
||||||
style={style}
|
ref={setNodeRef}
|
||||||
variant={compactView ? 'compact' : 'detailed'}
|
style={style}
|
||||||
source={task.source || 'manual'}
|
variant={compactView ? 'compact' : 'detailed'}
|
||||||
title={task.title}
|
source={task.source || 'manual'}
|
||||||
description={task.description}
|
title={task.title}
|
||||||
tags={task.tags}
|
description={task.description}
|
||||||
priority={task.priority}
|
tags={task.tags}
|
||||||
status={task.status}
|
priority={task.priority}
|
||||||
dueDate={task.dueDate}
|
status={task.status}
|
||||||
completedAt={task.completedAt}
|
dueDate={task.dueDate}
|
||||||
jiraKey={task.jiraKey}
|
completedAt={task.completedAt}
|
||||||
jiraProject={task.jiraProject}
|
jiraKey={task.jiraKey}
|
||||||
jiraType={task.jiraType}
|
jiraProject={task.jiraProject}
|
||||||
tfsPullRequestId={task.tfsPullRequestId}
|
jiraType={task.jiraType}
|
||||||
tfsProject={task.tfsProject}
|
tfsPullRequestId={task.tfsPullRequestId}
|
||||||
tfsRepository={task.tfsRepository}
|
tfsProject={task.tfsProject}
|
||||||
todosCount={task.todosCount}
|
tfsRepository={task.tfsRepository}
|
||||||
isDragging={isDragging}
|
todosCount={task.todosCount}
|
||||||
isPending={isPending}
|
isDragging={isDragging}
|
||||||
onEdit={handleEdit}
|
isPending={isPending}
|
||||||
onDelete={handleDelete}
|
onEdit={handleEdit}
|
||||||
onTitleSave={handleTitleSave}
|
onDelete={handleDelete}
|
||||||
fontSize={preferences.viewPreferences.fontSize}
|
onTitleSave={handleTitleSave}
|
||||||
availableTags={availableTags}
|
fontSize={preferences.viewPreferences.fontSize}
|
||||||
jiraConfig={preferences.jiraConfig}
|
availableTags={availableTags}
|
||||||
tfsConfig={preferences.tfsConfig}
|
jiraConfig={preferences.jiraConfig}
|
||||||
{...attributes}
|
tfsConfig={preferences.tfsConfig}
|
||||||
{...listeners}
|
{...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 { useState, useEffect } from 'react';
|
||||||
import { Button } from '@/components/ui/Button';
|
import { Button } from '@/components/ui/Button';
|
||||||
import { Badge } from '@/components/ui/Badge';
|
import { Badge } from '@/components/ui/Badge';
|
||||||
|
import { ConfirmModal } from '@/components/ui/ConfirmModal';
|
||||||
import { useJiraConfig } from '@/hooks/useJiraConfig';
|
import { useJiraConfig } from '@/hooks/useJiraConfig';
|
||||||
import { jiraConfigClient } from '@/clients/jira-config-client';
|
import { jiraConfigClient } from '@/clients/jira-config-client';
|
||||||
|
|
||||||
@@ -21,6 +22,7 @@ export function JiraConfigForm() {
|
|||||||
const [isValidating, setIsValidating] = useState(false);
|
const [isValidating, setIsValidating] = useState(false);
|
||||||
const [validationResult, setValidationResult] = useState<{ type: 'success' | 'error', text: string } | null>(null);
|
const [validationResult, setValidationResult] = useState<{ type: 'success' | 'error', text: string } | null>(null);
|
||||||
const [message, setMessage] = 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);
|
const [showForm, setShowForm] = useState(false);
|
||||||
|
|
||||||
// Charger les données existantes dans le formulaire
|
// Charger les données existantes dans le formulaire
|
||||||
@@ -77,10 +79,10 @@ export function JiraConfigForm() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleDelete = async () => {
|
const handleDelete = async () => {
|
||||||
if (!confirm('Êtes-vous sûr de vouloir supprimer la configuration Jira ?')) {
|
setShowDeleteConfirm(true);
|
||||||
return;
|
};
|
||||||
}
|
|
||||||
|
|
||||||
|
const confirmDelete = async () => {
|
||||||
setIsSubmitting(true);
|
setIsSubmitting(true);
|
||||||
setMessage(null);
|
setMessage(null);
|
||||||
|
|
||||||
@@ -444,6 +446,18 @@ export function JiraConfigForm() {
|
|||||||
{message.text}
|
{message.text}
|
||||||
</div>
|
</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>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { TfsConfig } from '@/services/integrations/tfs';
|
|||||||
import { getTfsConfig, saveTfsConfig, deleteAllTfsTasks } from '@/actions/tfs';
|
import { getTfsConfig, saveTfsConfig, deleteAllTfsTasks } from '@/actions/tfs';
|
||||||
import { Button } from '@/components/ui/Button';
|
import { Button } from '@/components/ui/Button';
|
||||||
import { Badge } from '@/components/ui/Badge';
|
import { Badge } from '@/components/ui/Badge';
|
||||||
|
import { ConfirmModal } from '@/components/ui/ConfirmModal';
|
||||||
|
|
||||||
export function TfsConfigForm() {
|
export function TfsConfigForm() {
|
||||||
const [config, setConfig] = useState<TfsConfig>({
|
const [config, setConfig] = useState<TfsConfig>({
|
||||||
@@ -24,6 +25,8 @@ export function TfsConfigForm() {
|
|||||||
const [showForm, setShowForm] = useState(false);
|
const [showForm, setShowForm] = useState(false);
|
||||||
const [isLoading, setIsLoading] = useState(true);
|
const [isLoading, setIsLoading] = useState(true);
|
||||||
const [deletingTasks, setDeletingTasks] = useState(false);
|
const [deletingTasks, setDeletingTasks] = useState(false);
|
||||||
|
const [showDeleteConfigConfirm, setShowDeleteConfigConfirm] = useState(false);
|
||||||
|
const [showDeleteTasksConfirm, setShowDeleteTasksConfirm] = useState(false);
|
||||||
|
|
||||||
// Charger la configuration existante
|
// Charger la configuration existante
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -85,10 +88,10 @@ export function TfsConfigForm() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleDelete = async () => {
|
const handleDelete = async () => {
|
||||||
if (!confirm('Êtes-vous sûr de vouloir supprimer la configuration TFS ?')) {
|
setShowDeleteConfigConfirm(true);
|
||||||
return;
|
};
|
||||||
}
|
|
||||||
|
|
||||||
|
const confirmDeleteConfig = async () => {
|
||||||
startTransition(async () => {
|
startTransition(async () => {
|
||||||
setMessage(null);
|
setMessage(null);
|
||||||
// Réinitialiser la config
|
// Réinitialiser la config
|
||||||
@@ -171,17 +174,10 @@ export function TfsConfigForm() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleDeleteAllTasks = async () => {
|
const handleDeleteAllTasks = async () => {
|
||||||
const confirmation = confirm(
|
setShowDeleteTasksConfirm(true);
|
||||||
'Ê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;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
const confirmDeleteAllTasks = async () => {
|
||||||
try {
|
try {
|
||||||
setDeletingTasks(true);
|
setDeletingTasks(true);
|
||||||
setMessage(null);
|
setMessage(null);
|
||||||
@@ -634,6 +630,30 @@ export function TfsConfigForm() {
|
|||||||
{message.text}
|
{message.text}
|
||||||
</div>
|
</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>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { useState, useMemo } from 'react';
|
|||||||
import { Tag } from '@/lib/types';
|
import { Tag } from '@/lib/types';
|
||||||
import { Card, CardContent, CardHeader } from '@/components/ui/Card';
|
import { Card, CardContent, CardHeader } from '@/components/ui/Card';
|
||||||
import { Button } from '@/components/ui/Button';
|
import { Button } from '@/components/ui/Button';
|
||||||
|
import { ConfirmModal } from '@/components/ui/ConfirmModal';
|
||||||
import { TagForm } from '@/components/forms/TagForm';
|
import { TagForm } from '@/components/forms/TagForm';
|
||||||
import { TagsStats } from './TagsStats';
|
import { TagsStats } from './TagsStats';
|
||||||
import { TagsFilters } from './TagsFilters';
|
import { TagsFilters } from './TagsFilters';
|
||||||
@@ -22,6 +23,8 @@ export function TagsManagement({ tags, onRefreshTags, onDeleteTag }: TagsManagem
|
|||||||
const [isCreateModalOpen, setIsCreateModalOpen] = useState(false);
|
const [isCreateModalOpen, setIsCreateModalOpen] = useState(false);
|
||||||
const [editingTag, setEditingTag] = useState<Tag | null>(null);
|
const [editingTag, setEditingTag] = useState<Tag | null>(null);
|
||||||
const [deletingTagId, setDeletingTagId] = useState<string | 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
|
// Filtrer et trier les tags
|
||||||
const filteredTags = useMemo(() => {
|
const filteredTags = useMemo(() => {
|
||||||
@@ -65,18 +68,22 @@ export function TagsManagement({ tags, onRefreshTags, onDeleteTag }: TagsManagem
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleDeleteTag = async (tag: Tag) => {
|
const handleDeleteTag = async (tag: Tag) => {
|
||||||
if (!confirm(`Êtes-vous sûr de vouloir supprimer le tag "${tag.name}" ?`)) {
|
setTagToDelete(tag);
|
||||||
return;
|
setShowDeleteConfirm(true);
|
||||||
}
|
};
|
||||||
|
|
||||||
setDeletingTagId(tag.id);
|
const confirmDeleteTag = async () => {
|
||||||
|
if (!tagToDelete) return;
|
||||||
|
|
||||||
|
setDeletingTagId(tagToDelete.id);
|
||||||
try {
|
try {
|
||||||
await onDeleteTag(tag.id);
|
await onDeleteTag(tagToDelete.id);
|
||||||
await onRefreshTags();
|
await onRefreshTags();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Erreur lors de la suppression:', error);
|
console.error('Erreur lors de la suppression:', error);
|
||||||
} finally {
|
} finally {
|
||||||
setDeletingTagId(null);
|
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 { ProgressBar } from '@/components/ui/ProgressBar';
|
||||||
import { EmptyState } from '@/components/ui/EmptyState';
|
import { EmptyState } from '@/components/ui/EmptyState';
|
||||||
import { DropZone } from '@/components/ui/DropZone';
|
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() {
|
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[] = [
|
const alertItems: AlertItem[] = [
|
||||||
{ id: '1', title: 'Tâche critique', icon: '🔴', urgency: 'critical', metadata: 'Dans 1 jour' },
|
{ 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: '2', title: 'Réunion urgente', icon: '🟠', urgency: 'high', metadata: 'Dans 2 jours' },
|
||||||
{ id: '3', title: 'Rappel', icon: '🟡', urgency: 'medium', metadata: 'Dans 5 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 (
|
return (
|
||||||
<section id="feedback" className="space-y-8">
|
<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">
|
<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>
|
||||||
</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 */}
|
{/* Drop Zone */}
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<h3 className="text-lg font-medium text-[var(--foreground)]">Drop Zone</h3>
|
<h3 className="text-lg font-medium text-[var(--foreground)]">Drop Zone</h3>
|
||||||
@@ -183,6 +239,49 @@ export function FeedbackSection() {
|
|||||||
</DropZone>
|
</DropZone>
|
||||||
</div>
|
</div>
|
||||||
</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>
|
</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
|
// Composants existants
|
||||||
export { Card, CardHeader, CardTitle, CardContent, CardFooter } from './Card';
|
export { Card, CardHeader, CardTitle, CardContent, CardFooter } from './Card';
|
||||||
export { FontSizeToggle } from './FontSizeToggle';
|
export { FontSizeToggle } from './FontSizeToggle';
|
||||||
|
export { Modal } from './Modal';
|
||||||
|
export { ConfirmModal } from './ConfirmModal';
|
||||||
|
|||||||
Reference in New Issue
Block a user