feat(ui): replace native confirm() with DaisyUI modal composable (F7.2)
Create useConfirm composable (promise-based, singleton state) and ConfirmModal component. Replace all 10 confirm()/window.confirm() calls across 9 pages and 1 composable. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
41
app/components/common/ConfirmModal.vue
Normal file
41
app/components/common/ConfirmModal.vue
Normal file
@@ -0,0 +1,41 @@
|
||||
<template>
|
||||
<teleport to="body">
|
||||
<div
|
||||
v-if="confirmState.open"
|
||||
class="fixed inset-0 z-[1200] flex items-center justify-center bg-black/60 backdrop-blur-sm"
|
||||
@click.self="handleCancel"
|
||||
>
|
||||
<div class="bg-base-100 rounded-box shadow-xl w-full max-w-md mx-4 p-6 space-y-4">
|
||||
<h3 class="font-bold text-lg">
|
||||
{{ confirmState.title }}
|
||||
</h3>
|
||||
|
||||
<p class="whitespace-pre-line text-base-content/80">
|
||||
{{ confirmState.message }}
|
||||
</p>
|
||||
|
||||
<div class="flex justify-end gap-2 pt-2">
|
||||
<button
|
||||
class="btn btn-ghost btn-sm"
|
||||
@click="handleCancel"
|
||||
>
|
||||
{{ confirmState.cancelText }}
|
||||
</button>
|
||||
<button
|
||||
class="btn btn-sm"
|
||||
:class="confirmState.dangerous ? 'btn-error' : 'btn-primary'"
|
||||
@click="handleConfirm"
|
||||
>
|
||||
{{ confirmState.confirmText }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</teleport>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useConfirm } from '~/composables/useConfirm'
|
||||
|
||||
const { confirmState, handleConfirm, handleCancel } = useConfirm()
|
||||
</script>
|
||||
73
app/composables/useConfirm.ts
Normal file
73
app/composables/useConfirm.ts
Normal file
@@ -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<ConfirmState>({
|
||||
open: false,
|
||||
title: '',
|
||||
message: '',
|
||||
confirmText: 'Supprimer',
|
||||
cancelText: 'Annuler',
|
||||
dangerous: true,
|
||||
resolve: null,
|
||||
})
|
||||
|
||||
function confirm(options: ConfirmOptions): Promise<boolean> {
|
||||
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,
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -327,7 +327,8 @@ const handleDeleteComponent = async (component: Record<string, any>) => {
|
||||
)
|
||||
}
|
||||
|
||||
const confirmed = window.confirm(confirmLines.join('\n\n'))
|
||||
const { confirm } = useConfirm()
|
||||
const confirmed = await confirm({ message: confirmLines.join('\n\n') })
|
||||
if (!confirmed) {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -189,7 +189,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, onMounted, ref, watch } from 'vue'
|
||||
import { computed, onMounted, ref } from 'vue'
|
||||
import { usePieces } from '~/composables/usePieces'
|
||||
import { usePieceTypes } from '~/composables/usePieceTypes'
|
||||
import { useToast } from '~/composables/useToast'
|
||||
@@ -442,7 +442,8 @@ const handleDeletePiece = async (piece: Record<string, any>) => {
|
||||
)
|
||||
}
|
||||
|
||||
const confirmed = window.confirm(confirmLines.join('\n\n'))
|
||||
const { confirm } = useConfirm()
|
||||
const confirmed = await confirm({ message: confirmLines.join('\n\n') })
|
||||
if (!confirmed) {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -382,10 +382,12 @@ const reload = async () => {
|
||||
await loadProducts({ force: true })
|
||||
}
|
||||
|
||||
const { confirm } = useConfirm()
|
||||
|
||||
const confirmDelete = async (product: Record<string, any>) => {
|
||||
const confirmed = window.confirm(
|
||||
`Voulez-vous vraiment supprimer le produit "${product.name}" ?\n\nCette action est irréversible.`,
|
||||
)
|
||||
const confirmed = await confirm({
|
||||
message: `Voulez-vous vraiment supprimer le produit "${product.name}" ?\n\nCette action est irréversible.`,
|
||||
})
|
||||
if (!confirmed) {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -169,8 +169,10 @@ const create = async () => {
|
||||
}
|
||||
}
|
||||
|
||||
const { confirm } = useConfirm()
|
||||
|
||||
const remove = async (profileId) => {
|
||||
if (!confirm('Supprimer ce profil ?')) { return }
|
||||
if (!await confirm({ message: 'Supprimer ce profil ?' })) { return }
|
||||
deleting.value = profileId
|
||||
try {
|
||||
await deleteProfile(profileId)
|
||||
|
||||
Reference in New Issue
Block a user