Preserve the local Ant UI work across views/components and add the new home dashboard chart panels before merging latest main.
359 lines
11 KiB
TypeScript
359 lines
11 KiB
TypeScript
import type {
|
|
ActivityDto,
|
|
HomeDashboardDto,
|
|
PipelineBoardDto,
|
|
} from '@mento/shared';
|
|
|
|
/** پیشنمایش داشبورد خانه وقتی API خالی است یا FORCE روشن باشد */
|
|
export const HOME_DASHBOARD_MOCK: HomeDashboardDto = {
|
|
currency: 'IRR',
|
|
kpis: [
|
|
{
|
|
key: 'sales',
|
|
label: 'فروش این ماه',
|
|
value: 186_500_000,
|
|
changePercent: 18.4,
|
|
changeLabel: 'نسبت به ماه قبل',
|
|
},
|
|
{
|
|
key: 'customers',
|
|
label: 'مشتریان جدید',
|
|
value: 42,
|
|
changePercent: 12.5,
|
|
changeLabel: 'نسبت به ماه قبل',
|
|
},
|
|
{
|
|
key: 'open-deals',
|
|
label: 'معاملات باز',
|
|
value: 27,
|
|
changePercent: -4.2,
|
|
changeLabel: 'نسبت به هفته قبل',
|
|
},
|
|
{
|
|
key: 'followups',
|
|
label: 'پیگیری امروز',
|
|
value: 9,
|
|
changePercent: 25,
|
|
changeLabel: 'نسبت به دیروز',
|
|
},
|
|
{
|
|
key: 'win-rate',
|
|
label: 'نرخ برد',
|
|
value: 58.3,
|
|
changePercent: null,
|
|
changeLabel: '۱۲ ماه اخیر',
|
|
},
|
|
{
|
|
key: 'overdue',
|
|
label: 'عقبافتاده',
|
|
value: 6,
|
|
changePercent: null,
|
|
changeLabel: 'فعالیت باز',
|
|
},
|
|
],
|
|
alerts: [
|
|
{
|
|
key: 'stale-deal',
|
|
tone: 'urgent',
|
|
text: 'شرکت نمونه آکمه در مرحله تصمیمگیری است — امروز تماس بگیرید',
|
|
link: '/pipeline',
|
|
},
|
|
{
|
|
key: 'stale-count',
|
|
tone: 'warning',
|
|
text: '۵ معامله بیش از ۵ روز پیگیری نشده',
|
|
link: '/pipeline',
|
|
},
|
|
{
|
|
key: 'overdue-acts',
|
|
tone: 'warning',
|
|
text: '۶ پیگیری عقبافتاده دارید — همین امروز ببندید',
|
|
link: '/activities',
|
|
},
|
|
{
|
|
key: 'target-close',
|
|
tone: 'info',
|
|
text: 'به تارگت ماه نزدیک شدهاید',
|
|
link: '/sales/forecast',
|
|
},
|
|
{
|
|
key: 'big-deal',
|
|
tone: 'info',
|
|
text: 'پیشنهاد ۱۱۲ میلیون تومانی سپهر در انتظار تأیید است',
|
|
link: '/pipeline',
|
|
},
|
|
{
|
|
key: 'win-streak',
|
|
tone: 'success',
|
|
text: 'این هفته ۳ معامله برنده بستهاید',
|
|
link: '/reports',
|
|
},
|
|
{
|
|
key: 'meeting-today',
|
|
tone: 'urgent',
|
|
text: 'جلسه دمو با پارسنوین ساعت ۱۴ — آمادهسازی پیشنهاد',
|
|
link: '/activities',
|
|
},
|
|
{
|
|
key: 'quote-expire',
|
|
tone: 'warning',
|
|
text: 'پیشفاکتور صنایع سپهر تا فردا منقضی میشود',
|
|
link: '/pipeline',
|
|
},
|
|
{
|
|
key: 'new-lead',
|
|
tone: 'info',
|
|
text: '۴ لید جدید از فرم وبسایت بدون مالک ماندهاند',
|
|
link: '/contacts',
|
|
},
|
|
{
|
|
key: 'renewal',
|
|
tone: 'info',
|
|
text: 'قرارداد تمدید سارا محمدی این ماه سررسید میشود',
|
|
link: '/pipeline',
|
|
},
|
|
],
|
|
salesTrend: [
|
|
{ monthKey: '1403-07', monthLabel: 'مهر', amount: 64_000_000 },
|
|
{ monthKey: '1403-08', monthLabel: 'آبان', amount: 71_500_000 },
|
|
{ monthKey: '1403-09', monthLabel: 'آذر', amount: 68_200_000 },
|
|
{ monthKey: '1403-10', monthLabel: 'دی', amount: 79_800_000 },
|
|
{ monthKey: '1403-11', monthLabel: 'بهمن', amount: 88_400_000 },
|
|
{ monthKey: '1403-12', monthLabel: 'اسفند', amount: 95_000_000 },
|
|
{ monthKey: '1404-01', monthLabel: 'فروردین', amount: 82_600_000 },
|
|
{ monthKey: '1404-02', monthLabel: 'اردیبهشت', amount: 92_000_000 },
|
|
{ monthKey: '1404-03', monthLabel: 'خرداد', amount: 118_500_000 },
|
|
{ monthKey: '1404-04', monthLabel: 'تیر', amount: 141_200_000 },
|
|
{ monthKey: '1404-05', monthLabel: 'مرداد', amount: 157_800_000 },
|
|
{ monthKey: '1404-06', monthLabel: 'شهریور', amount: 186_500_000 },
|
|
],
|
|
customerGrowth: [
|
|
{ monthKey: '1403-07', monthLabel: 'مهر', value: 11 },
|
|
{ monthKey: '1403-08', monthLabel: 'آبان', value: 14 },
|
|
{ monthKey: '1403-09', monthLabel: 'آذر', value: 13 },
|
|
{ monthKey: '1403-10', monthLabel: 'دی', value: 16 },
|
|
{ monthKey: '1403-11', monthLabel: 'بهمن', value: 19 },
|
|
{ monthKey: '1403-12', monthLabel: 'اسفند', value: 22 },
|
|
{ monthKey: '1404-01', monthLabel: 'فروردین', value: 15 },
|
|
{ monthKey: '1404-02', monthLabel: 'اردیبهشت', value: 18 },
|
|
{ monthKey: '1404-03', monthLabel: 'خرداد', value: 24 },
|
|
{ monthKey: '1404-04', monthLabel: 'تیر', value: 31 },
|
|
{ monthKey: '1404-05', monthLabel: 'مرداد', value: 28 },
|
|
{ monthKey: '1404-06', monthLabel: 'شهریور', value: 42 },
|
|
],
|
|
sources: [
|
|
{ label: 'فرم لید', count: 38, percent: 32.2 },
|
|
{ label: 'معرفی دوستان', count: 29, percent: 24.6 },
|
|
{ label: 'وبسایت', count: 21, percent: 17.8 },
|
|
{ label: 'اکسل', count: 16, percent: 13.6 },
|
|
{ label: 'تلفن', count: 8, percent: 6.8 },
|
|
{ label: 'سایر', count: 6, percent: 5 },
|
|
],
|
|
topCustomers: [
|
|
{ id: 'mock-c1', name: 'شرکت آکمه', dealCount: 7, rank: 1 },
|
|
{ id: 'mock-c2', name: 'گروه پارسنوین', dealCount: 5, rank: 2 },
|
|
{ id: 'mock-c3', name: 'سارا محمدی', dealCount: 4, rank: 3 },
|
|
{ id: 'mock-c4', name: 'صنایع سپهر', dealCount: 3, rank: 4 },
|
|
{ id: 'mock-c5', name: 'رضا کریمی', dealCount: 3, rank: 5 },
|
|
],
|
|
salesSummary: {
|
|
openCount: 27,
|
|
wonCount: 35,
|
|
lostCount: 25,
|
|
openAmount: 412_000_000,
|
|
wonAmount: 696_000_000,
|
|
lostAmount: 218_000_000,
|
|
winRate: 58.3,
|
|
},
|
|
funnel: [
|
|
{ stageId: 's1', stageName: 'تماس اول', count: 11, amount: 95_000_000 },
|
|
{ stageId: 's2', stageName: 'نیازسنجی', count: 8, amount: 128_000_000 },
|
|
{ stageId: 's3', stageName: 'پیشنهاد', count: 5, amount: 112_000_000 },
|
|
{ stageId: 's4', stageName: 'مذاکره', count: 3, amount: 77_000_000 },
|
|
],
|
|
lostReasons: [
|
|
{ label: 'قیمت', count: 9, percent: 36, amount: 82_000_000 },
|
|
{ label: 'رقیب', count: 7, percent: 28, amount: 61_000_000 },
|
|
{ label: 'زمانبندی', count: 5, percent: 20, amount: 44_000_000 },
|
|
{ label: 'بدون دلیل ثبتشده', count: 4, percent: 16, amount: 31_000_000 },
|
|
],
|
|
activityByType: [
|
|
{ type: 'call', label: 'تماس', count: 14 },
|
|
{ type: 'meeting', label: 'جلسه', count: 7 },
|
|
{ type: 'task', label: 'کار', count: 11 },
|
|
{ type: 'note', label: 'یادداشت', count: 4 },
|
|
],
|
|
};
|
|
|
|
function hoursFromNow(hours: number) {
|
|
const d = new Date();
|
|
d.setHours(d.getHours() + hours);
|
|
return d.toISOString();
|
|
}
|
|
|
|
export const HOME_ACTIVITIES_MOCK = [
|
|
{
|
|
id: 'mock-act-1',
|
|
type: 'call',
|
|
status: 'open',
|
|
priority: 'high',
|
|
title: 'تماس پیگیری پیشنهاد آکمه',
|
|
description: null,
|
|
dueAt: hoursFromNow(3),
|
|
durationMinutes: 30,
|
|
reminderAt: null,
|
|
reminderNotifyApp: false,
|
|
reminderNotifySms: false,
|
|
reminderSentAt: null,
|
|
completedAt: null,
|
|
tags: [],
|
|
person: { id: 'p1', fullName: 'علی رضایی' },
|
|
company: { id: 'c1', name: 'شرکت آکمه' },
|
|
deal: null,
|
|
assignee: null,
|
|
assignees: [],
|
|
createdBy: null,
|
|
createdAt: hoursFromNow(-20),
|
|
updatedAt: hoursFromNow(-2),
|
|
},
|
|
{
|
|
id: 'mock-act-2',
|
|
type: 'meeting',
|
|
status: 'open',
|
|
priority: 'medium',
|
|
title: 'جلسه دمو با پارسنوین',
|
|
description: null,
|
|
dueAt: hoursFromNow(26),
|
|
durationMinutes: 60,
|
|
reminderAt: null,
|
|
reminderNotifyApp: false,
|
|
reminderNotifySms: false,
|
|
reminderSentAt: null,
|
|
completedAt: null,
|
|
tags: [],
|
|
person: { id: 'p2', fullName: 'مریم احمدی' },
|
|
company: { id: 'c2', name: 'گروه پارسنوین' },
|
|
deal: null,
|
|
assignee: null,
|
|
assignees: [],
|
|
createdBy: null,
|
|
createdAt: hoursFromNow(-48),
|
|
updatedAt: hoursFromNow(-10),
|
|
},
|
|
{
|
|
id: 'mock-act-3',
|
|
type: 'task',
|
|
status: 'done',
|
|
priority: 'low',
|
|
title: 'ارسال پیشفاکتور سپهر',
|
|
description: null,
|
|
dueAt: hoursFromNow(-5),
|
|
durationMinutes: null,
|
|
reminderAt: null,
|
|
reminderNotifyApp: false,
|
|
reminderNotifySms: false,
|
|
reminderSentAt: null,
|
|
completedAt: hoursFromNow(-4),
|
|
tags: [],
|
|
person: null,
|
|
company: { id: 'c3', name: 'صنایع سپهر' },
|
|
deal: null,
|
|
assignee: null,
|
|
assignees: [],
|
|
createdBy: null,
|
|
createdAt: hoursFromNow(-72),
|
|
updatedAt: hoursFromNow(-4),
|
|
},
|
|
] as unknown as ActivityDto[];
|
|
|
|
export const HOME_PIPELINE_MOCK = {
|
|
pipeline: {
|
|
id: 'mock-pipe',
|
|
name: 'فروش اصلی',
|
|
isDefault: true,
|
|
createdAt: hoursFromNow(-1000),
|
|
updatedAt: hoursFromNow(-1),
|
|
},
|
|
stages: [
|
|
{
|
|
id: 's1',
|
|
name: 'تماس اول',
|
|
position: 0,
|
|
isWon: false,
|
|
isLost: false,
|
|
totalAmount: 95_000_000,
|
|
deals: [
|
|
{
|
|
id: 'd1',
|
|
title: 'لایسنس سازمانی',
|
|
amount: 45_000_000,
|
|
currency: 'IRR',
|
|
company: { id: 'c1', name: 'شرکت آکمه' },
|
|
person: null,
|
|
updatedAt: hoursFromNow(-30),
|
|
createdAt: hoursFromNow(-200),
|
|
},
|
|
{
|
|
id: 'd2',
|
|
title: 'پکیج پشتیبانی',
|
|
amount: 22_000_000,
|
|
currency: 'IRR',
|
|
company: null,
|
|
person: { id: 'p3', fullName: 'سارا محمدی' },
|
|
updatedAt: hoursFromNow(-8),
|
|
createdAt: hoursFromNow(-90),
|
|
},
|
|
],
|
|
},
|
|
{
|
|
id: 's2',
|
|
name: 'نیازسنجی',
|
|
position: 1,
|
|
isWon: false,
|
|
isLost: false,
|
|
totalAmount: 128_000_000,
|
|
deals: [
|
|
{
|
|
id: 'd3',
|
|
title: 'ارتقای CRM',
|
|
amount: 68_000_000,
|
|
currency: 'IRR',
|
|
company: { id: 'c2', name: 'گروه پارسنوین' },
|
|
person: null,
|
|
updatedAt: hoursFromNow(-12),
|
|
createdAt: hoursFromNow(-140),
|
|
},
|
|
],
|
|
},
|
|
{
|
|
id: 's3',
|
|
name: 'پیشنهاد',
|
|
position: 2,
|
|
isWon: false,
|
|
isLost: false,
|
|
totalAmount: 112_000_000,
|
|
deals: [
|
|
{
|
|
id: 'd4',
|
|
title: 'پروژه یکپارچهسازی',
|
|
amount: 112_000_000,
|
|
currency: 'IRR',
|
|
company: { id: 'c3', name: 'صنایع سپهر' },
|
|
person: null,
|
|
updatedAt: hoursFromNow(-2),
|
|
createdAt: hoursFromNow(-60),
|
|
},
|
|
],
|
|
},
|
|
],
|
|
} as unknown as PipelineBoardDto;
|
|
|
|
export function isHomeDashboardEmpty(data: HomeDashboardDto | null | undefined): boolean {
|
|
if (!data) return true;
|
|
const salesFlat = (data.salesTrend ?? []).every((p) => !p.amount);
|
|
const growthFlat = (data.customerGrowth ?? []).every((p) => !p.value);
|
|
const noSources = !(data.sources ?? []).length;
|
|
const kpisFlat = (data.kpis ?? []).every((k) => !k.value);
|
|
return (salesFlat && growthFlat && noSources) || kpisFlat;
|
|
}
|