feat(leads): edit lead-board columns from the board
New-lead counts follow the first open column instead of its name, and cards no longer show an initials mark. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -4,11 +4,16 @@ import { RouterLink, useRouter } from 'vue-router';
|
||||
import axios from 'axios';
|
||||
import {
|
||||
Alert as AAlert,
|
||||
Avatar as AAvatar,
|
||||
Button as AButton,
|
||||
Card as ACard,
|
||||
Checkbox as ACheckbox,
|
||||
Dropdown as ADropdown,
|
||||
Empty as AEmpty,
|
||||
Flex as AFlex,
|
||||
Input as AInput,
|
||||
Menu as AMenu,
|
||||
MenuItem as AMenuItem,
|
||||
Modal as AModal,
|
||||
Space as ASpace,
|
||||
Spin as ASpin,
|
||||
Tag as ATag,
|
||||
@@ -18,25 +23,43 @@ import {
|
||||
import {
|
||||
CalendarOutlined,
|
||||
HolderOutlined,
|
||||
MoreOutlined,
|
||||
PhoneOutlined,
|
||||
PlusOutlined,
|
||||
ReloadOutlined,
|
||||
UnorderedListOutlined,
|
||||
} from '@ant-design/icons-vue';
|
||||
import { api } from '@/lib/api';
|
||||
import { formatJalaliNumeric, toFaDigits } from '@/lib/jalali';
|
||||
import type { LeadBoardColumnDto, LeadDto } from '@mento/shared';
|
||||
import { canManageSettings } from '@/lib/roles';
|
||||
import { useAuthStore } from '@/stores/auth';
|
||||
import type { LeadBoardColumnDto, LeadDto, LeadStageDto } from '@mento/shared';
|
||||
|
||||
const { Title: ATypographyTitle, Text: ATypographyText } = Typography;
|
||||
const { token } = theme.useToken();
|
||||
const router = useRouter();
|
||||
const auth = useAuthStore();
|
||||
const canManage = computed(() => canManageSettings(auth.user?.role));
|
||||
|
||||
const GAP = 16;
|
||||
const COL_WIDTH = 280;
|
||||
|
||||
const loading = ref(false);
|
||||
const moving = ref(false);
|
||||
const stageSaving = ref(false);
|
||||
const error = ref('');
|
||||
const stageError = ref('');
|
||||
const columns = ref<LeadBoardColumnDto[]>([]);
|
||||
const stages = ref<LeadStageDto[]>([]);
|
||||
const renameTarget = ref<LeadStageDto | null>(null);
|
||||
const renameName = ref('');
|
||||
const addOpen = ref(false);
|
||||
const addName = ref('');
|
||||
const addConverted = ref(false);
|
||||
const addLost = ref(false);
|
||||
const addUnqualified = ref(false);
|
||||
const deleteTarget = ref<LeadStageDto | null>(null);
|
||||
const moveConfirm = ref<{ stageIds: string[]; message: string } | null>(null);
|
||||
const draggingId = ref<string | null>(null);
|
||||
const dropStageId = ref<string | null>(null);
|
||||
/** بعد از درگ، کلیک باز شدن جزئیات را یک فریم نادیده بگیر */
|
||||
@@ -137,14 +160,6 @@ const cardBodyStyle = computed(() => ({
|
||||
margin: 0,
|
||||
}));
|
||||
|
||||
function leadInitials(lead: LeadDto) {
|
||||
const raw = (lead.contactName || lead.title || '?').trim();
|
||||
const parts = raw.split(/\s+/).filter(Boolean);
|
||||
if (!parts.length) return '؟';
|
||||
if (parts.length === 1) return parts[0]!.slice(0, 1);
|
||||
return `${parts[0]!.slice(0, 1)}${parts[parts.length - 1]!.slice(0, 1)}`;
|
||||
}
|
||||
|
||||
function leadSubtitle(lead: LeadDto) {
|
||||
return [lead.contactName, lead.city].filter(Boolean).join(' · ') || '';
|
||||
}
|
||||
@@ -158,22 +173,288 @@ function formatDate(value: string | null) {
|
||||
}
|
||||
}
|
||||
|
||||
function apiMessage(e: unknown, fallback: string) {
|
||||
if (axios.isAxiosError(e)) {
|
||||
const msg = (e.response?.data as { message?: string | string[] } | undefined)?.message;
|
||||
if (Array.isArray(msg)) return msg[0] ?? fallback;
|
||||
if (typeof msg === 'string' && msg) return msg;
|
||||
if (!e.response) return 'اتصال به API برقرار نشد.';
|
||||
}
|
||||
return fallback;
|
||||
}
|
||||
|
||||
function isOpenStage(stage: Pick<LeadStageDto, 'isConverted' | 'isLost' | 'isUnqualified'>) {
|
||||
return !stage.isConverted && !stage.isLost && !stage.isUnqualified;
|
||||
}
|
||||
|
||||
function entryStage(list: LeadStageDto[]) {
|
||||
return list.find(isOpenStage) ?? null;
|
||||
}
|
||||
|
||||
async function load() {
|
||||
loading.value = true;
|
||||
error.value = '';
|
||||
try {
|
||||
const { data } = await api.get<LeadBoardColumnDto[]>('/leads/board');
|
||||
columns.value = data;
|
||||
const [board, stageRows] = await Promise.all([
|
||||
api.get<LeadBoardColumnDto[]>('/leads/board'),
|
||||
api.get<LeadStageDto[]>('/leads/stages'),
|
||||
]);
|
||||
columns.value = board.data;
|
||||
stages.value = stageRows.data;
|
||||
} catch (e) {
|
||||
error.value =
|
||||
axios.isAxiosError(e) && !e.response
|
||||
? 'اتصال به API برقرار نشد.'
|
||||
: 'بارگذاری مسیر لیدها ناموفق بود.';
|
||||
error.value = apiMessage(e, 'بارگذاری مسیر لیدها ناموفق بود.');
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
function visibleIndex(stageId: string) {
|
||||
return stages.value.filter((stage) => stage.showOnBoard).findIndex((stage) => stage.id === stageId);
|
||||
}
|
||||
|
||||
function plannedOrder(stageId: string, dir: -1 | 1) {
|
||||
const visible = stages.value.filter((stage) => stage.showOnBoard);
|
||||
const from = visible.findIndex((stage) => stage.id === stageId);
|
||||
const to = from + dir;
|
||||
if (from < 0 || to < 0 || to >= visible.length) return null;
|
||||
const nextVisible = visible.slice();
|
||||
const [item] = nextVisible.splice(from, 1);
|
||||
if (!item) return null;
|
||||
nextVisible.splice(to, 0, item);
|
||||
const queue = nextVisible.slice();
|
||||
return stages.value.map((stage) => (stage.showOnBoard ? queue.shift()!.id : stage.id));
|
||||
}
|
||||
|
||||
function entryIn(ids: string[]) {
|
||||
const byId = new Map(stages.value.map((stage) => [stage.id, stage]));
|
||||
for (const id of ids) {
|
||||
const stage = byId.get(id);
|
||||
if (stage && isOpenStage(stage)) return stage;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
async function persistOrder(stageIds: string[]) {
|
||||
stageSaving.value = true;
|
||||
error.value = '';
|
||||
try {
|
||||
await api.patch('/leads/stages/reorder', { stageIds });
|
||||
moveConfirm.value = null;
|
||||
await load();
|
||||
} catch (e) {
|
||||
error.value = apiMessage(e, 'جابهجایی ستون ناموفق بود.');
|
||||
} finally {
|
||||
stageSaving.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
function requestMove(stageId: string, dir: -1 | 1) {
|
||||
const next = plannedOrder(stageId, dir);
|
||||
if (!next) return;
|
||||
const before = entryStage(stages.value);
|
||||
const after = entryIn(next);
|
||||
if (before && after && before.id !== after.id) {
|
||||
moveConfirm.value = {
|
||||
stageIds: next,
|
||||
message: `با این جابهجایی، لیدهای تازهٔ فرم و ورود از اکسل بهجای «${before.name}» در ستون «${after.name}» ثبت میشوند. لیدهای فعلی سر جایشان میمانند.`,
|
||||
};
|
||||
return;
|
||||
}
|
||||
void persistOrder(next);
|
||||
}
|
||||
|
||||
function nameTaken(name: string, exceptId?: string) {
|
||||
const trimmed = name.trim();
|
||||
return stages.value.some((stage) => stage.id !== exceptId && stage.name === trimmed);
|
||||
}
|
||||
|
||||
function openRename(stage: LeadStageDto) {
|
||||
stageError.value = '';
|
||||
renameName.value = stage.name;
|
||||
renameTarget.value = stage;
|
||||
}
|
||||
|
||||
function openAdd() {
|
||||
stageError.value = '';
|
||||
addName.value = '';
|
||||
addConverted.value = false;
|
||||
addLost.value = false;
|
||||
addUnqualified.value = false;
|
||||
addOpen.value = true;
|
||||
}
|
||||
|
||||
function openDelete(stage: LeadStageDto) {
|
||||
stageError.value = '';
|
||||
deleteTarget.value = stage;
|
||||
}
|
||||
|
||||
function onColumnMenu(stageId: string, key: string) {
|
||||
const stage = stages.value.find((item) => item.id === stageId);
|
||||
if (!stage) return;
|
||||
if (key === 'rename') openRename(stage);
|
||||
else if (key === 'right') requestMove(stageId, -1);
|
||||
else if (key === 'left') requestMove(stageId, 1);
|
||||
else if (key === 'delete') openDelete(stage);
|
||||
}
|
||||
|
||||
const renameNotes = computed(() => {
|
||||
const stage = renameTarget.value;
|
||||
if (!stage) return [];
|
||||
const lines = [
|
||||
'اسم جدید در فهرست لیدها، صفحهٔ جزئیات، فرمساز و فیلتر پیامک هم دیده میشود. لیدهای این ستون سر جایشان میمانند.',
|
||||
];
|
||||
if (entryStage(stages.value)?.id === stage.id) {
|
||||
lines.push('شمارش لیدهای تازه به اسم ستون وابسته نیست و با این تغییر نام بههم نمیریزد.');
|
||||
}
|
||||
if (stage.isConverted) lines.push('علامت تبدیلشده روی همین ستون میماند.');
|
||||
if (stage.isLost) lines.push('علامت از دسترفته روی همین ستون میماند و آمار همان را میشمارد.');
|
||||
if (stage.isUnqualified) lines.push('علامت نامرتبط روی همین ستون میماند.');
|
||||
return lines;
|
||||
});
|
||||
|
||||
const addNotes = computed(() => {
|
||||
const lines = [
|
||||
'ستون در انتهای مسیر اضافه میشود. لیدهای ستونهای دیگر جابهجا نمیشوند.',
|
||||
];
|
||||
const flags = [addConverted.value, addLost.value, addUnqualified.value].filter(Boolean).length;
|
||||
if (flags > 1) {
|
||||
lines.push(
|
||||
'یک ستون را با یک علامت بسازید. اگر تبدیلشده، از دسترفته و نامرتبط با هم باشند، فرم و آمار هر کدام رفتار جدا دارند.',
|
||||
);
|
||||
} else if (!flags) {
|
||||
lines.push('بدون علامت، این ستون مرحلهٔ عادی است و مقصد تبدیل به مشتری یا آمار از دسترفته نمیشود.');
|
||||
}
|
||||
if (addConverted.value && stages.value.some((stage) => stage.isConverted)) {
|
||||
lines.push(
|
||||
'ستون تبدیلشدهٔ دیگری هم هست. تبدیل به مشتری به اولین ستون تبدیلشده در ترتیب مسیر میرود.',
|
||||
);
|
||||
}
|
||||
if (addLost.value && stages.value.some((stage) => stage.isLost)) {
|
||||
lines.push('ستون از دسترفتهٔ دیگری هم هست. آمار، لیدهای هر دو ستون را میشمارد.');
|
||||
}
|
||||
return lines;
|
||||
});
|
||||
|
||||
const deleteInfo = computed(() => {
|
||||
const stage = deleteTarget.value;
|
||||
if (!stage) return { blocked: '', lines: [] as string[] };
|
||||
const total = columns.value.find((col) => col.stage.id === stage.id)?.total ?? stage.leadCount ?? 0;
|
||||
if (total > 0) {
|
||||
return {
|
||||
blocked: `ستون «${stage.name}» ${toFaDigits(total)} لید دارد و حذف نمیشود. اول لیدها را به ستون دیگری ببرید.`,
|
||||
lines: [] as string[],
|
||||
};
|
||||
}
|
||||
const others = stages.value.filter((item) => item.id !== stage.id);
|
||||
const lines = [
|
||||
'این ستون از فهرست لیدها، جزئیات، فرمساز و فیلتر پیامک هم برداشته میشود.',
|
||||
];
|
||||
if (stage.isConverted && !others.some((item) => item.isConverted)) {
|
||||
lines.push(
|
||||
'این تنها ستون تبدیلشده است. بعد از حذف، تبدیل به مشتری هنوز انجام میشود ولی لید به ستون تبدیلشده منتقل نمیشود.',
|
||||
);
|
||||
}
|
||||
if (stage.isLost && !others.some((item) => item.isLost)) {
|
||||
lines.push('این تنها ستون از دسترفته است. بعد از حذف، آمار لیدهای از دسترفته دیگر ستونی برای شمردن ندارد.');
|
||||
}
|
||||
if (stage.isUnqualified && !others.some((item) => item.isUnqualified)) {
|
||||
lines.push('این تنها ستون نامرتبط است. بعد از حذف، مسیر دیگر ستون مشخصی برای لید نامرتبط ندارد.');
|
||||
}
|
||||
if (entryStage(stages.value)?.id === stage.id) {
|
||||
const next = others.find(isOpenStage);
|
||||
lines.push(
|
||||
next
|
||||
? `لیدهای تازهٔ فرم و ورود از اکسل بهجای این ستون در «${next.name}» ثبت میشوند.`
|
||||
: 'ستون باز دیگری نمیماند. لید تازهٔ فرم در اولین ستون باقیمانده ثبت میشود.',
|
||||
);
|
||||
}
|
||||
lines.push('اگر فرمی این ستون را مرحلهٔ اولیه کرده باشد، لید تازه به اولین ستون باز میرود.');
|
||||
return { blocked: '', lines };
|
||||
});
|
||||
|
||||
async function saveRename() {
|
||||
const stage = renameTarget.value;
|
||||
const name = renameName.value.trim();
|
||||
if (!stage || stageSaving.value) return;
|
||||
if (name.length < 1) {
|
||||
stageError.value = 'نام ستون لازم است.';
|
||||
return;
|
||||
}
|
||||
if (name.length > 80) {
|
||||
stageError.value = 'نام ستون حداکثر ۸۰ کاراکتر است.';
|
||||
return;
|
||||
}
|
||||
if (nameTaken(name, stage.id)) {
|
||||
stageError.value = 'ستونی با این نام وجود دارد.';
|
||||
return;
|
||||
}
|
||||
if (name === stage.name) {
|
||||
renameTarget.value = null;
|
||||
return;
|
||||
}
|
||||
stageSaving.value = true;
|
||||
stageError.value = '';
|
||||
try {
|
||||
await api.patch(`/leads/stages/${stage.id}`, { name });
|
||||
renameTarget.value = null;
|
||||
await load();
|
||||
} catch (e) {
|
||||
stageError.value = apiMessage(e, 'تغییر نام ستون ناموفق بود.');
|
||||
} finally {
|
||||
stageSaving.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function saveAdd() {
|
||||
const name = addName.value.trim();
|
||||
if (stageSaving.value) return;
|
||||
if (!name) {
|
||||
stageError.value = 'نام ستون لازم است.';
|
||||
return;
|
||||
}
|
||||
if (name.length > 80) {
|
||||
stageError.value = 'نام ستون حداکثر ۸۰ کاراکتر است.';
|
||||
return;
|
||||
}
|
||||
if (nameTaken(name)) {
|
||||
stageError.value = 'ستونی با این نام وجود دارد.';
|
||||
return;
|
||||
}
|
||||
stageSaving.value = true;
|
||||
stageError.value = '';
|
||||
try {
|
||||
await api.post('/leads/stages', {
|
||||
name,
|
||||
showOnBoard: true,
|
||||
isConverted: addConverted.value,
|
||||
isLost: addLost.value,
|
||||
isUnqualified: addUnqualified.value,
|
||||
});
|
||||
addOpen.value = false;
|
||||
await load();
|
||||
} catch (e) {
|
||||
stageError.value = apiMessage(e, 'افزودن ستون ناموفق بود.');
|
||||
} finally {
|
||||
stageSaving.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function confirmDelete() {
|
||||
const stage = deleteTarget.value;
|
||||
if (!stage || deleteInfo.value.blocked || stageSaving.value) return;
|
||||
stageSaving.value = true;
|
||||
stageError.value = '';
|
||||
try {
|
||||
await api.delete(`/leads/stages/${stage.id}`);
|
||||
deleteTarget.value = null;
|
||||
await load();
|
||||
} catch (e) {
|
||||
stageError.value = apiMessage(e, 'حذف ستون ناموفق بود.');
|
||||
} finally {
|
||||
stageSaving.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
function openLead(lead: LeadDto) {
|
||||
if (suppressCardClick || draggingId.value || moving.value) return;
|
||||
void router.push({ name: 'lead-detail', params: { id: lead.id } });
|
||||
@@ -223,11 +504,16 @@ async function onDrop(stageId: string, event: DragEvent) {
|
||||
|
||||
columns.value = columns.value.map((c) => {
|
||||
if (c.stage.id === fromCol.stage.id) {
|
||||
return { ...c, leads: c.leads.filter((l) => l.id !== leadId) };
|
||||
return {
|
||||
...c,
|
||||
total: Math.max(0, c.total - 1),
|
||||
leads: c.leads.filter((l) => l.id !== leadId),
|
||||
};
|
||||
}
|
||||
if (c.stage.id === stageId) {
|
||||
return {
|
||||
...c,
|
||||
total: c.total + 1,
|
||||
leads: [{ ...lead, stageId, stageName: c.stage.name }, ...c.leads],
|
||||
};
|
||||
}
|
||||
@@ -257,7 +543,7 @@ onMounted(load);
|
||||
مسیر لیدها
|
||||
</a-typography-title>
|
||||
<a-typography-text type="secondary">
|
||||
کشیدن و رها کردن برای تغییر وضعیت
|
||||
کشیدن کارت برای تغییر وضعیت. از منوی هر ستون میتوانید نام، ترتیب و حذف را عوض کنید.
|
||||
</a-typography-text>
|
||||
</div>
|
||||
<a-space :size="GAP">
|
||||
@@ -298,9 +584,14 @@ onMounted(load);
|
||||
|
||||
<a-empty
|
||||
v-if="!loading && !columns.length"
|
||||
description="مرحلهای برای نمایش در مسیر لیدها نیست. در تنظیمات مراحل لید، «نمایش روی مسیر» را فعال کنید."
|
||||
description="مرحلهای برای نمایش در مسیر لیدها نیست."
|
||||
:style="{ margin: 'auto' }"
|
||||
/>
|
||||
>
|
||||
<a-button v-if="canManage" type="primary" @click="openAdd">
|
||||
<template #icon><PlusOutlined /></template>
|
||||
افزودن ستون
|
||||
</a-button>
|
||||
</a-empty>
|
||||
|
||||
<div
|
||||
v-else-if="columns.length"
|
||||
@@ -328,9 +619,31 @@ onMounted(load);
|
||||
/>
|
||||
<a-typography-text strong>{{ col.stage.name }}</a-typography-text>
|
||||
</a-space>
|
||||
<a-typography-text type="secondary">
|
||||
{{ toFaDigits(col.leads.length) }}
|
||||
</a-typography-text>
|
||||
<a-flex align="center" :gap="4">
|
||||
<a-typography-text type="secondary">
|
||||
{{ toFaDigits(col.total) }}
|
||||
</a-typography-text>
|
||||
<a-dropdown v-if="canManage" :trigger="['click']">
|
||||
<a-button type="text" aria-label="منوی ستون" @click.stop>
|
||||
<template #icon><MoreOutlined /></template>
|
||||
</a-button>
|
||||
<template #overlay>
|
||||
<a-menu @click="({ key }) => onColumnMenu(col.stage.id, String(key))">
|
||||
<a-menu-item key="rename">تغییر نام</a-menu-item>
|
||||
<a-menu-item key="right" :disabled="visibleIndex(col.stage.id) <= 0">
|
||||
انتقال به راست
|
||||
</a-menu-item>
|
||||
<a-menu-item
|
||||
key="left"
|
||||
:disabled="visibleIndex(col.stage.id) < 0 || visibleIndex(col.stage.id) >= stages.filter((stage) => stage.showOnBoard).length - 1"
|
||||
>
|
||||
انتقال به چپ
|
||||
</a-menu-item>
|
||||
<a-menu-item key="delete" danger>حذف ستون</a-menu-item>
|
||||
</a-menu>
|
||||
</template>
|
||||
</a-dropdown>
|
||||
</a-flex>
|
||||
</div>
|
||||
|
||||
<div :style="columnBodyStyle">
|
||||
@@ -376,16 +689,6 @@ onMounted(load);
|
||||
>
|
||||
<HolderOutlined />
|
||||
</span>
|
||||
<a-avatar
|
||||
:style="{
|
||||
background: token.colorPrimaryBg,
|
||||
color: token.colorPrimary,
|
||||
flexShrink: 0,
|
||||
fontWeight: 700,
|
||||
}"
|
||||
>
|
||||
{{ leadInitials(lead) }}
|
||||
</a-avatar>
|
||||
<div :style="{ flex: 1, minWidth: 0 }">
|
||||
<a-typography-text
|
||||
strong
|
||||
@@ -501,8 +804,110 @@ onMounted(load);
|
||||
</a-typography-text>
|
||||
</div>
|
||||
</div>
|
||||
<a-flex
|
||||
v-if="canManage"
|
||||
vertical
|
||||
justify="center"
|
||||
align="center"
|
||||
:gap="8"
|
||||
:style="{
|
||||
flex: `0 0 ${COL_WIDTH}px`,
|
||||
width: `${COL_WIDTH}px`,
|
||||
alignSelf: 'stretch',
|
||||
border: `1px dashed ${token.colorBorder}`,
|
||||
borderRadius: `${token.borderRadiusLG}px`,
|
||||
background: token.colorFillAlter,
|
||||
}"
|
||||
>
|
||||
<a-button type="primary" :disabled="stageSaving" @click="openAdd">
|
||||
<template #icon><PlusOutlined /></template>
|
||||
افزودن ستون
|
||||
</a-button>
|
||||
</a-flex>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<a-modal
|
||||
:open="!!renameTarget"
|
||||
title="تغییر نام ستون"
|
||||
ok-text="ذخیره"
|
||||
cancel-text="انصراف"
|
||||
:confirm-loading="stageSaving"
|
||||
@ok="saveRename"
|
||||
@cancel="renameTarget = null"
|
||||
>
|
||||
<a-flex vertical :gap="12">
|
||||
<a-alert
|
||||
v-for="line in renameNotes"
|
||||
:key="line"
|
||||
type="info"
|
||||
show-icon
|
||||
:message="line"
|
||||
/>
|
||||
<a-alert v-if="stageError" type="error" show-icon :message="stageError" />
|
||||
<a-input v-model:value="renameName" :maxlength="80" placeholder="نام ستون" @press-enter="saveRename" />
|
||||
</a-flex>
|
||||
</a-modal>
|
||||
|
||||
<a-modal
|
||||
:open="addOpen"
|
||||
title="افزودن ستون"
|
||||
ok-text="افزودن"
|
||||
cancel-text="انصراف"
|
||||
:confirm-loading="stageSaving"
|
||||
@ok="saveAdd"
|
||||
@cancel="addOpen = false"
|
||||
>
|
||||
<a-flex vertical :gap="12">
|
||||
<a-alert
|
||||
v-for="(line, index) in addNotes"
|
||||
:key="index"
|
||||
:type="index === 0 ? 'info' : 'warning'"
|
||||
show-icon
|
||||
:message="line"
|
||||
/>
|
||||
<a-alert v-if="stageError" type="error" show-icon :message="stageError" />
|
||||
<a-input v-model:value="addName" :maxlength="80" placeholder="مثلاً تماس اولیه" @press-enter="saveAdd" />
|
||||
<a-checkbox v-model:checked="addConverted">علامت تبدیلشده</a-checkbox>
|
||||
<a-checkbox v-model:checked="addLost">علامت از دسترفته</a-checkbox>
|
||||
<a-checkbox v-model:checked="addUnqualified">علامت نامرتبط</a-checkbox>
|
||||
</a-flex>
|
||||
</a-modal>
|
||||
|
||||
<a-modal
|
||||
:open="!!deleteTarget"
|
||||
:title="deleteInfo.blocked ? 'حذف ستون ممکن نیست' : 'حذف ستون'"
|
||||
:ok-text="deleteInfo.blocked ? 'متوجه شدم' : 'حذف ستون'"
|
||||
:ok-button-props="deleteInfo.blocked ? undefined : { danger: true }"
|
||||
cancel-text="انصراف"
|
||||
:confirm-loading="stageSaving"
|
||||
@ok="deleteInfo.blocked ? (deleteTarget = null) : confirmDelete()"
|
||||
@cancel="deleteTarget = null"
|
||||
>
|
||||
<a-flex vertical :gap="12">
|
||||
<a-alert v-if="deleteInfo.blocked" type="error" show-icon :message="deleteInfo.blocked" />
|
||||
<a-alert
|
||||
v-for="line in deleteInfo.lines"
|
||||
:key="line"
|
||||
type="warning"
|
||||
show-icon
|
||||
:message="line"
|
||||
/>
|
||||
<a-alert v-if="stageError" type="error" show-icon :message="stageError" />
|
||||
</a-flex>
|
||||
</a-modal>
|
||||
|
||||
<a-modal
|
||||
:open="!!moveConfirm"
|
||||
title="جابهجایی ستون"
|
||||
ok-text="جابهجا شود"
|
||||
cancel-text="انصراف"
|
||||
:confirm-loading="stageSaving"
|
||||
@ok="moveConfirm && persistOrder(moveConfirm.stageIds)"
|
||||
@cancel="moveConfirm = null"
|
||||
>
|
||||
<a-alert v-if="moveConfirm" type="warning" show-icon :message="moveConfirm.message" />
|
||||
</a-modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user