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