Fix Leads row menu overflow and add edit/delete actions.
Use a teleported dropdown so the menu stays above the table, with edit modal and archive delete.
This commit is contained in:
@@ -11,6 +11,7 @@ import { useAuthStore } from '@/stores/auth';
|
||||
import { canManageSettings } from '@/lib/roles';
|
||||
import {
|
||||
UiButton,
|
||||
UiDropdownMenu,
|
||||
UiEmptyState,
|
||||
UiModal,
|
||||
UiPageHeader,
|
||||
@@ -69,6 +70,7 @@ const importJob = ref<LeadImportJobDto | null>(null);
|
||||
const createOpen = ref(false);
|
||||
const createBusy = ref(false);
|
||||
const createError = ref('');
|
||||
const editingLeadId = ref<string | null>(null);
|
||||
const createForm = ref({
|
||||
title: '',
|
||||
contactName: '',
|
||||
@@ -359,7 +361,12 @@ async function load() {
|
||||
}
|
||||
}
|
||||
|
||||
const createModalTitle = computed(() =>
|
||||
editingLeadId.value ? 'ویرایش لید' : 'ثبت لید جدید',
|
||||
);
|
||||
|
||||
function openCreate() {
|
||||
editingLeadId.value = null;
|
||||
createError.value = '';
|
||||
createForm.value = {
|
||||
title: '',
|
||||
@@ -376,6 +383,42 @@ function openCreate() {
|
||||
createOpen.value = true;
|
||||
}
|
||||
|
||||
function openEdit(lead: LeadDto) {
|
||||
rowMenuId.value = null;
|
||||
editingLeadId.value = lead.id;
|
||||
createError.value = '';
|
||||
createForm.value = {
|
||||
title: lead.title,
|
||||
contactName: lead.contactName ?? '',
|
||||
mobile: lead.mobile,
|
||||
city: lead.city ?? '',
|
||||
region: lead.region ?? '',
|
||||
source: lead.source ?? '',
|
||||
stageId: lead.stageId,
|
||||
assigneeId: lead.assigneeId ?? '',
|
||||
};
|
||||
closePick();
|
||||
closeSource();
|
||||
createOpen.value = true;
|
||||
}
|
||||
|
||||
async function deleteLead(lead: LeadDto) {
|
||||
rowMenuId.value = null;
|
||||
if (!confirm(`لید «${lead.title}» حذف (بایگانی) شود؟`)) return;
|
||||
busy.value = true;
|
||||
error.value = '';
|
||||
try {
|
||||
await api.post('/leads/archive', { leadIds: [lead.id] });
|
||||
okMsg.value = 'لید حذف شد.';
|
||||
selected.value = new Set([...selected.value].filter((id) => id !== lead.id));
|
||||
await load();
|
||||
} catch {
|
||||
error.value = 'حذف لید ناموفق بود.';
|
||||
} finally {
|
||||
busy.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
function applyContactDraft(draft: PhoneContactDraft) {
|
||||
if (draft.mobile) {
|
||||
createForm.value.mobile = draft.mobile;
|
||||
@@ -490,26 +533,36 @@ async function submitCreate() {
|
||||
return;
|
||||
}
|
||||
createBusy.value = true;
|
||||
const payload = {
|
||||
title: createForm.value.title.trim(),
|
||||
mobile: createForm.value.mobile.trim(),
|
||||
contactName: createForm.value.contactName.trim() || null,
|
||||
city: createForm.value.city.trim() || null,
|
||||
region: createForm.value.region.trim() || null,
|
||||
source: createForm.value.source.trim() || null,
|
||||
stageId: createForm.value.stageId || undefined,
|
||||
assigneeId: createForm.value.assigneeId || null,
|
||||
};
|
||||
try {
|
||||
const { data: created } = await api.post<LeadDto>('/leads', {
|
||||
title: createForm.value.title.trim(),
|
||||
mobile: createForm.value.mobile.trim(),
|
||||
contactName: createForm.value.contactName.trim() || null,
|
||||
city: createForm.value.city.trim() || null,
|
||||
region: createForm.value.region.trim() || null,
|
||||
source: createForm.value.source.trim() || null,
|
||||
stageId: createForm.value.stageId || undefined,
|
||||
assigneeId: createForm.value.assigneeId || null,
|
||||
});
|
||||
createOpen.value = false;
|
||||
okMsg.value = 'لید جدید ثبت شد.';
|
||||
await load();
|
||||
void router.push({ name: 'lead-detail', params: { id: created.id } });
|
||||
if (editingLeadId.value) {
|
||||
await api.patch(`/leads/${editingLeadId.value}`, payload);
|
||||
createOpen.value = false;
|
||||
okMsg.value = 'لید بهروز شد.';
|
||||
await load();
|
||||
} else {
|
||||
const { data: created } = await api.post<LeadDto>('/leads', payload);
|
||||
createOpen.value = false;
|
||||
okMsg.value = 'لید جدید ثبت شد.';
|
||||
await load();
|
||||
void router.push({ name: 'lead-detail', params: { id: created.id } });
|
||||
}
|
||||
} catch (e) {
|
||||
createError.value =
|
||||
axios.isAxiosError(e) && (e.response?.data as { message?: string })?.message
|
||||
? String((e.response?.data as { message?: string }).message)
|
||||
: 'ثبت لید ناموفق بود.';
|
||||
: editingLeadId.value
|
||||
? 'ویرایش لید ناموفق بود.'
|
||||
: 'ثبت لید ناموفق بود.';
|
||||
} finally {
|
||||
createBusy.value = false;
|
||||
}
|
||||
@@ -977,24 +1030,28 @@ onBeforeUnmount(stopImportPoll);
|
||||
</td>
|
||||
<td class="actions-col" @click.stop>
|
||||
<div class="row-actions">
|
||||
<div class="pop">
|
||||
<button
|
||||
type="button"
|
||||
class="act-btn"
|
||||
title="بیشتر"
|
||||
@click="rowMenuId = rowMenuId === lead.id ? null : lead.id"
|
||||
>
|
||||
<UiIcon name="dots-v" :size="16" />
|
||||
<UiDropdownMenu
|
||||
:open="rowMenuId === lead.id"
|
||||
align="end"
|
||||
@update:open="(v) => (rowMenuId = v ? lead.id : null)"
|
||||
>
|
||||
<template #trigger="{ toggle }">
|
||||
<button
|
||||
type="button"
|
||||
class="act-btn"
|
||||
title="بیشتر"
|
||||
@click.stop="toggle"
|
||||
>
|
||||
<UiIcon name="dots-v" :size="16" />
|
||||
</button>
|
||||
</template>
|
||||
<button type="button" @click="goDetail(lead.id)">مشاهده</button>
|
||||
<button type="button" @click="openEdit(lead)">ویرایش</button>
|
||||
<a :href="`tel:${lead.mobile}`">تماس</a>
|
||||
<button type="button" class="danger-item" @click="deleteLead(lead)">
|
||||
حذف
|
||||
</button>
|
||||
<ul v-if="rowMenuId === lead.id" class="menu row-menu">
|
||||
<li>
|
||||
<button type="button" @click="goDetail(lead.id)">مشاهده</button>
|
||||
</li>
|
||||
<li>
|
||||
<a :href="`tel:${lead.mobile}`">تماس</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</UiDropdownMenu>
|
||||
<button
|
||||
type="button"
|
||||
class="act-btn call"
|
||||
@@ -1076,7 +1133,7 @@ onBeforeUnmount(stopImportPoll);
|
||||
</template>
|
||||
</UiEmptyState>
|
||||
|
||||
<UiModal :open="createOpen" title="ثبت لید جدید" @close="createOpen = false">
|
||||
<UiModal :open="createOpen" :title="createModalTitle" @close="createOpen = false">
|
||||
<form class="create-form" @submit.prevent="submitCreate">
|
||||
<label>
|
||||
نام / شرکت
|
||||
@@ -1201,7 +1258,15 @@ onBeforeUnmount(stopImportPoll);
|
||||
انصراف
|
||||
</UiButton>
|
||||
<UiButton variant="primary" :disabled="createBusy" @click="submitCreate">
|
||||
{{ createBusy ? 'در حال ثبت…' : 'ثبت لید' }}
|
||||
{{
|
||||
createBusy
|
||||
? editingLeadId
|
||||
? 'در حال ذخیره…'
|
||||
: 'در حال ثبت…'
|
||||
: editingLeadId
|
||||
? 'ذخیره تغییرات'
|
||||
: 'ثبت لید'
|
||||
}}
|
||||
</UiButton>
|
||||
</template>
|
||||
</UiModal>
|
||||
@@ -1766,6 +1831,11 @@ onBeforeUnmount(stopImportPoll);
|
||||
background: #d1fae5;
|
||||
}
|
||||
|
||||
:deep(.ui-dd-panel) .danger-item,
|
||||
.danger-item {
|
||||
color: #b91c1c !important;
|
||||
}
|
||||
|
||||
.pager {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
|
||||
Reference in New Issue
Block a user