Merge pull request 'RBAC : enforcement des permissions granulaires + suppression client/prospect' (#20) from feat/rbac-enforcement into develop
Auto Tag Develop / tag (push) Successful in 13s
Auto Tag Develop / tag (push) Successful in 13s
Reviewed-on: #20
This commit was merged in pull request #20.
This commit is contained in:
@@ -24,7 +24,9 @@
|
||||
"updated": "Client mis à jour avec succès.",
|
||||
"deleted": "Client supprimé avec succès.",
|
||||
"addClient": "Ajouter un client",
|
||||
"editClient": "Modifier un client"
|
||||
"editClient": "Modifier un client",
|
||||
"deleteConfirmTitle": "Supprimer le client",
|
||||
"deleteConfirmMessage": "Êtes-vous sûr de vouloir supprimer le client « {name} » ? Cette action est irréversible."
|
||||
},
|
||||
"projects": {
|
||||
"title": "Projets",
|
||||
@@ -908,6 +910,8 @@
|
||||
"editProspect": "Modifier un prospect",
|
||||
"convert": "Convertir en client",
|
||||
"alreadyConverted": "Déjà converti en client",
|
||||
"deleteConfirmTitle": "Supprimer le prospect",
|
||||
"deleteConfirmMessage": "Êtes-vous sûr de vouloir supprimer le prospect « {name} » ? Cette action est irréversible.",
|
||||
"fields": {
|
||||
"name": "Nom",
|
||||
"company": "Société",
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
<template>
|
||||
<Teleport v-if="modelValue" to="body">
|
||||
<Transition name="modal" appear>
|
||||
<div class="fixed inset-0 z-50 flex items-center justify-center">
|
||||
<div class="absolute inset-0 bg-black/30" @click="cancel" />
|
||||
<div class="relative z-10 w-full max-w-md rounded-lg bg-white p-6 shadow-xl">
|
||||
<h3 class="text-lg font-bold text-neutral-900">{{ title }}</h3>
|
||||
<p class="mt-3 text-sm text-neutral-600">
|
||||
{{ message }}
|
||||
</p>
|
||||
<div class="mt-6 flex justify-end gap-3">
|
||||
<MalioButton
|
||||
variant="tertiary"
|
||||
:label="$t('common.cancel')"
|
||||
button-class="w-auto px-4"
|
||||
@click="cancel"
|
||||
/>
|
||||
<MalioButton
|
||||
variant="danger"
|
||||
:label="$t('common.delete')"
|
||||
button-class="w-auto px-4"
|
||||
@click="$emit('confirm')"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Transition>
|
||||
</Teleport>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
defineProps<{
|
||||
modelValue: boolean
|
||||
title: string
|
||||
message: string
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'update:modelValue', value: boolean): void
|
||||
(e: 'confirm'): void
|
||||
}>()
|
||||
|
||||
function cancel() {
|
||||
emit('update:modelValue', false)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.modal-enter-active,
|
||||
.modal-leave-active {
|
||||
transition: opacity 0.2s ease;
|
||||
}
|
||||
|
||||
.modal-enter-from,
|
||||
.modal-leave-to {
|
||||
opacity: 0;
|
||||
}
|
||||
</style>
|
||||
@@ -31,6 +31,17 @@
|
||||
<template #cell-phone="{ item }">
|
||||
{{ (item as Client).phone ?? '—' }}
|
||||
</template>
|
||||
<template #cell-actions="{ item }">
|
||||
<div class="flex justify-end" @click.stop>
|
||||
<MalioButtonIcon
|
||||
icon="mdi:trash-can-outline"
|
||||
:aria-label="$t('common.delete')"
|
||||
button-class="!bg-red-100 !text-red-700"
|
||||
:icon-size="18"
|
||||
@click="askDeleteClient(item as Client)"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
</MalioDataTable>
|
||||
</div>
|
||||
</template>
|
||||
@@ -75,20 +86,23 @@
|
||||
{{ (item as ProspectRow).phone ?? '—' }}
|
||||
</template>
|
||||
<template #cell-actions="{ item }">
|
||||
<div
|
||||
v-if="!(item as ProspectRow).convertedClient"
|
||||
class="flex justify-end"
|
||||
@click.stop
|
||||
>
|
||||
<div class="flex justify-end gap-2" @click.stop>
|
||||
<MalioButtonIcon
|
||||
v-if="!(item as ProspectRow).convertedClient"
|
||||
icon="mdi:account-convert"
|
||||
:aria-label="$t('prospects.convert')"
|
||||
button-class="!bg-green-100 !text-green-700"
|
||||
:icon-size="18"
|
||||
@click="convertProspect(item as ProspectRow)"
|
||||
/>
|
||||
<MalioButtonIcon
|
||||
icon="mdi:trash-can-outline"
|
||||
:aria-label="$t('common.delete')"
|
||||
button-class="!bg-red-100 !text-red-700"
|
||||
:icon-size="18"
|
||||
@click="askDeleteProspect(item as ProspectRow)"
|
||||
/>
|
||||
</div>
|
||||
<span v-else class="text-neutral-300">—</span>
|
||||
</template>
|
||||
</MalioDataTable>
|
||||
</div>
|
||||
@@ -105,6 +119,13 @@
|
||||
:prospect="selectedProspect"
|
||||
@saved="onProspectSaved"
|
||||
/>
|
||||
|
||||
<ConfirmDeleteModal
|
||||
v-model="deleteModalOpen"
|
||||
:title="deleteModalTitle"
|
||||
:message="deleteModalMessage"
|
||||
@confirm="confirmDelete"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -139,6 +160,7 @@ const clientColumns = [
|
||||
{ key: 'name', label: t('prospects.fields.name') },
|
||||
{ key: 'email', label: t('prospects.fields.email') },
|
||||
{ key: 'phone', label: t('prospects.fields.phone') },
|
||||
{ key: 'actions', label: '' },
|
||||
]
|
||||
|
||||
async function loadClients() {
|
||||
@@ -225,6 +247,54 @@ async function onProspectSaved() {
|
||||
await Promise.all([loadProspects(), loadClients()])
|
||||
}
|
||||
|
||||
// --- Suppression (clients & prospects) ---
|
||||
type DeleteTarget =
|
||||
| { type: 'client'; item: Client }
|
||||
| { type: 'prospect'; item: Prospect }
|
||||
|
||||
const deleteModalOpen = ref(false)
|
||||
const deleteTarget = ref<DeleteTarget | null>(null)
|
||||
|
||||
const deleteModalTitle = computed(() =>
|
||||
deleteTarget.value?.type === 'prospect'
|
||||
? t('prospects.deleteConfirmTitle')
|
||||
: t('clients.deleteConfirmTitle'),
|
||||
)
|
||||
|
||||
const deleteModalMessage = computed(() => {
|
||||
if (!deleteTarget.value) return ''
|
||||
const name = deleteTarget.value.item.name
|
||||
return deleteTarget.value.type === 'prospect'
|
||||
? t('prospects.deleteConfirmMessage', { name })
|
||||
: t('clients.deleteConfirmMessage', { name })
|
||||
})
|
||||
|
||||
function askDeleteClient(item: Client) {
|
||||
deleteTarget.value = { type: 'client', item }
|
||||
deleteModalOpen.value = true
|
||||
}
|
||||
|
||||
function askDeleteProspect(item: Prospect) {
|
||||
deleteTarget.value = { type: 'prospect', item }
|
||||
deleteModalOpen.value = true
|
||||
}
|
||||
|
||||
async function confirmDelete() {
|
||||
const target = deleteTarget.value
|
||||
if (!target) return
|
||||
|
||||
if (target.type === 'client') {
|
||||
await clientService.remove(target.item.id)
|
||||
await loadClients()
|
||||
} else {
|
||||
await prospectService.remove(target.item.id)
|
||||
await loadProspects()
|
||||
}
|
||||
|
||||
deleteModalOpen.value = false
|
||||
deleteTarget.value = null
|
||||
}
|
||||
|
||||
watch(statusFilter, loadProspects)
|
||||
|
||||
onMounted(async () => {
|
||||
|
||||
Reference in New Issue
Block a user