diff --git a/app/components/common/ConfirmModal.vue b/app/components/common/ConfirmModal.vue new file mode 100644 index 0000000..4702bda --- /dev/null +++ b/app/components/common/ConfirmModal.vue @@ -0,0 +1,41 @@ + + + + + + {{ confirmState.title }} + + + + {{ confirmState.message }} + + + + + {{ confirmState.cancelText }} + + + {{ confirmState.confirmText }} + + + + + + + + diff --git a/app/composables/useConfirm.ts b/app/composables/useConfirm.ts new file mode 100644 index 0000000..a623509 --- /dev/null +++ b/app/composables/useConfirm.ts @@ -0,0 +1,73 @@ +/** + * Promise-based confirmation dialog composable. + * + * Usage: + * const { confirm, confirmState } = useConfirm() + * const ok = await confirm({ message: 'Supprimer ?' }) + * if (ok) { ... } + * + * The ConfirmModal component reads `confirmState` to render the dialog. + */ + +import { reactive } from 'vue' + +export interface ConfirmOptions { + title?: string + message: string + confirmText?: string + cancelText?: string + dangerous?: boolean +} + +export interface ConfirmState { + open: boolean + title: string + message: string + confirmText: string + cancelText: string + dangerous: boolean + resolve: ((value: boolean) => void) | null +} + +const state = reactive({ + open: false, + title: '', + message: '', + confirmText: 'Supprimer', + cancelText: 'Annuler', + dangerous: true, + resolve: null, +}) + +function confirm(options: ConfirmOptions): Promise { + return new Promise((resolve) => { + state.title = options.title ?? 'Confirmation' + state.message = options.message + state.confirmText = options.confirmText ?? 'Supprimer' + state.cancelText = options.cancelText ?? 'Annuler' + state.dangerous = options.dangerous ?? true + state.resolve = resolve + state.open = true + }) +} + +function handleConfirm() { + state.resolve?.(true) + state.open = false + state.resolve = null +} + +function handleCancel() { + state.resolve?.(false) + state.open = false + state.resolve = null +} + +export function useConfirm() { + return { + confirm, + confirmState: state, + handleConfirm, + handleCancel, + } +} diff --git a/app/composables/useSiteManagement.ts b/app/composables/useSiteManagement.ts index 8cf0d53..3e5047a 100644 --- a/app/composables/useSiteManagement.ts +++ b/app/composables/useSiteManagement.ts @@ -3,6 +3,7 @@ import { navigateTo, useRoute } from '#imports' import { useSites } from '~/composables/useSites' import { useToast } from '~/composables/useToast' import { useDocuments } from '~/composables/useDocuments' +import { useConfirm } from '~/composables/useConfirm' import { getFileIcon } from '~/utils/fileIcons' import { canPreviewDocument } from '~/utils/documentPreview' @@ -41,6 +42,7 @@ export function useSiteManagement() { const { showError, showSuccess } = useToast() const { sites, loading, loadSites, createSite, updateSite, deleteSite } = useSites() const { uploadDocuments, deleteDocument, loadDocumentsBySite } = useDocuments() + const { confirm: confirmDialog } = useConfirm() const showAddSiteModal = ref(false) const showEditSiteModal = ref(false) @@ -132,7 +134,8 @@ export function useSiteManagement() { if (index !== -1) { sites.value[index] = { ...sites.value[index], - ...updated + ...updated, + id, } } } @@ -251,9 +254,9 @@ export function useSiteManagement() { const confirmDeleteSite = async (site: SiteWithDocuments) => { if ( - !confirm( - `Êtes-vous sûr de vouloir supprimer le site "${site.name}" ? Cette action est irréversible.` - ) + !await confirmDialog({ + message: `Êtes-vous sûr de vouloir supprimer le site "${site.name}" ? Cette action est irréversible.`, + }) ) { return } diff --git a/app/pages/component-catalog.vue b/app/pages/component-catalog.vue index a80f86c..7e4fe51 100644 --- a/app/pages/component-catalog.vue +++ b/app/pages/component-catalog.vue @@ -327,7 +327,8 @@ const handleDeleteComponent = async (component: Record) => { ) } - const confirmed = window.confirm(confirmLines.join('\n\n')) + const { confirm } = useConfirm() + const confirmed = await confirm({ message: confirmLines.join('\n\n') }) if (!confirmed) { return } diff --git a/app/pages/constructeurs.vue b/app/pages/constructeurs.vue index ef62fff..5239785 100644 --- a/app/pages/constructeurs.vue +++ b/app/pages/constructeurs.vue @@ -127,7 +127,7 @@ import { formatPhone } from '~/utils/formatters/phone' import IconLucidePlus from '~icons/lucide/plus' const { constructeurs, loading, searchConstructeurs, createConstructeur, updateConstructeur, deleteConstructeur, loadConstructeurs } = useConstructeurs() -const { showError, showSuccess } = useToast() +const { showError } = useToast() const searchTerm = ref('') const sortKey = usePersistedValue('constructeurs-sort', 'name') @@ -211,8 +211,10 @@ const saveConstructeur = async () => { } } +const { confirm } = useConfirm() + const confirmDelete = async (constructeur) => { - if (!confirm(`Supprimer le fournisseur "${constructeur.name}" ?`)) { return } + if (!await confirm({ message: `Supprimer le fournisseur "${constructeur.name}" ?` })) { return } const result = await deleteConstructeur(constructeur.id) if (!result.success && result.error) { showError(result.error) diff --git a/app/pages/index.vue b/app/pages/index.vue index 8c1eae2..24eec9f 100644 --- a/app/pages/index.vue +++ b/app/pages/index.vue @@ -705,13 +705,15 @@ const editMachine = (machine) => { navigateTo(`/machine/${machine.id}?edit=true`) } +const { confirm: confirmDialog } = useConfirm() + const confirmDeleteMachine = async (machine) => { const { showError, showSuccess } = useToast() if ( - confirm( - `Êtes-vous sûr de vouloir supprimer la machine "${machine.name}" ? Cette action est irréversible.` - ) + await confirmDialog({ + message: `Êtes-vous sûr de vouloir supprimer la machine "${machine.name}" ? Cette action est irréversible.`, + }) ) { try { const result = await deleteMachine(machine.id) diff --git a/app/pages/machine-skeleton/index.vue b/app/pages/machine-skeleton/index.vue index 36059a6..5b8282b 100644 --- a/app/pages/machine-skeleton/index.vue +++ b/app/pages/machine-skeleton/index.vue @@ -108,7 +108,7 @@ import IconLucidePackage from "~icons/lucide/package"; import IconLucideLayoutGrid from "~icons/lucide/layout-grid"; import IconLucideBox from "~icons/lucide/box"; -const { machineTypes, loading, loadMachineTypes, deleteMachineType } = +const { machineTypes, loadMachineTypes, deleteMachineType } = useMachineTypesApi(); const categories = ref([ @@ -131,13 +131,15 @@ const filteredTypes = computed(() => { ); }); +const { confirm: confirmDialog } = useConfirm(); + const confirmDeleteType = async (type) => { const { showError, showSuccess } = useToast(); if ( - confirm( - `Êtes-vous sûr de vouloir supprimer le type "${type.name}" ? Cette action est irréversible.` - ) + await confirmDialog({ + message: `Êtes-vous sûr de vouloir supprimer le type "${type.name}" ? Cette action est irréversible.`, + }) ) { try { const result = await deleteMachineType(type.id); diff --git a/app/pages/machines/index.vue b/app/pages/machines/index.vue index 08de066..b2cfe75 100644 --- a/app/pages/machines/index.vue +++ b/app/pages/machines/index.vue @@ -202,10 +202,12 @@ const editMachine = (machine) => { navigateTo(`/machine/${machine.id}?edit=true`) } +const { confirm: confirmDialog } = useConfirm() + const confirmDeleteMachine = async (machine) => { const { showError, showSuccess } = toast - if (confirm(`Êtes-vous sûr de vouloir supprimer la machine "${machine.name}" ? Cette action est irréversible.`)) { + if (await confirmDialog({ message: `Êtes-vous sûr de vouloir supprimer la machine "${machine.name}" ? Cette action est irréversible.` })) { try { const result = await deleteMachine(machine.id) if (result.success) { diff --git a/app/pages/pieces-catalog.vue b/app/pages/pieces-catalog.vue index 08f07d0..a718913 100644 --- a/app/pages/pieces-catalog.vue +++ b/app/pages/pieces-catalog.vue @@ -189,7 +189,7 @@
+ {{ confirmState.message }} +