feat: keep deletions restorable and confirm a pipeline's deals before it is removed

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-10-13 22:23:09 +03:30
co-authored by Cursor
parent c04582072d
commit 2cc81bb596
17 changed files with 1158 additions and 6 deletions
@@ -20,6 +20,9 @@ const pipelines = ref<PipelineListItem[]>([]);
const name = ref('');
const mergeOpen = ref(false);
const mergeSourceId = ref<string | null>(null);
const deleteTarget = ref<PipelineListItem | null>(null);
const deleteCountReady = ref(false);
const deleting = ref(false);
const canMerge = computed(() => pipelines.value.length >= 2);
@@ -67,6 +70,61 @@ async function createPipeline() {
}
}
function deletePrompt(pipeline: PipelineListItem) {
if (!deleteCountReady.value) return 'در حال شمارش معاملات این مسیر…';
const count = pipeline.dealCount ?? 0;
const name = pipeline.name;
if (count > 0) {
return `مسیر «${name}» شامل ${toFaDigits(count)} معامله است. با حذف، این معاملات هم پاک می‌شوند. از حذف مطمئن هستید؟`;
}
return `مسیر «${name}» معامله‌ای ندارد. از حذف آن مطمئن هستید؟`;
}
async function openDelete(pipeline: PipelineListItem) {
deleteTarget.value = pipeline;
deleteCountReady.value = pipeline.dealCount != null;
if (deleteCountReady.value) return;
try {
const { data } = await api.get<{ dealCount: number }>(
`/pipelines/${pipeline.id}/merge-preview`,
);
if (deleteTarget.value?.id !== pipeline.id) return;
deleteTarget.value = { ...pipeline, dealCount: data.dealCount };
} catch (e) {
if (deleteTarget.value?.id === pipeline.id) {
error.value = apiError(e, 'شمارش معاملات این مسیر ناموفق بود.');
deleteTarget.value = null;
}
return;
} finally {
if (deleteTarget.value?.id === pipeline.id) deleteCountReady.value = true;
}
}
function closeDelete() {
if (deleting.value) return;
deleteTarget.value = null;
deleteCountReady.value = false;
}
async function confirmDelete() {
const pipeline = deleteTarget.value;
if (!pipeline || deleting.value) return;
deleting.value = true;
error.value = '';
okMsg.value = '';
try {
await api.delete(`/pipelines/${pipeline.id}`);
okMsg.value = 'کاریز حذف شد.';
deleteTarget.value = null;
await load();
} catch (e) {
error.value = apiError(e, 'حذف کاریز ناموفق بود.');
} finally {
deleting.value = false;
}
}
function openMerge(pipeline: PipelineListItem) {
if (!canMerge.value) return;
mergeSourceId.value = pipeline.id;
@@ -142,6 +200,9 @@ onMounted(load);
>
ادغام
</UiButton>
<UiButton size="sm" variant="danger" @click="openDelete(pipeline)">
حذف
</UiButton>
</div>
</div>
</UiCard>
@@ -153,6 +214,25 @@ onMounted(load);
description="اولین مسیر فروش را اضافه کنید."
/>
<UiModal
:open="!!deleteTarget"
title="حذف کاریز"
@close="closeDelete"
>
<p v-if="deleteTarget" class="confirm">{{ deletePrompt(deleteTarget) }}</p>
<template #footer>
<UiButton variant="ghost" :disabled="deleting" @click="closeDelete">انصراف</UiButton>
<UiButton
variant="danger"
:loading="deleting"
:disabled="!deleteCountReady"
@click="confirmDelete"
>
حذف کاریز
</UiButton>
</template>
</UiModal>
<UiModal :open="mergeOpen" title="ادغام کاریز" @close="closeMerge">
<PipelineMergeWizard
:open="mergeOpen"
@@ -242,6 +322,12 @@ onMounted(load);
font-size: 0.85rem;
}
.confirm {
margin: 0;
line-height: 1.8;
color: #0f172a;
}
.ok {
color: #0f766e;
margin: 0;