fix: keep birthdays and show Shamsi dates

Imported customers keep their birthday, and assistant text plus remaining calendars use the Jalali date.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-10-13 18:04:53 +03:30
co-authored by Cursor
parent 5f4cc47c41
commit cf326d1ed9
34 changed files with 637 additions and 105 deletions
+5 -10
View File
@@ -3,7 +3,7 @@ import { computed, nextTick, onMounted, onUnmounted, ref } from 'vue';
import { useAuthStore } from '@/stores/auth';
import { api } from '@/lib/api';
import { formatMoney } from '@/lib/money';
import { toFaDigits } from '@/lib/jalali';
import { formatJalaliDateTime, rewriteGregorianDates, toFaDigits } from '@/lib/jalali';
import HomeHero from '@/components/dashboard/HomeHero.vue';
import HomeKpiRow from '@/components/dashboard/HomeKpiRow.vue';
import HomeSalesTrend from '@/components/dashboard/HomeSalesTrend.vue';
@@ -52,14 +52,9 @@ const greeting = computed(() => {
function formatWhen(value: string | null) {
if (!value) return '';
try {
return new Intl.DateTimeFormat('fa-IR', {
dateStyle: 'medium',
timeStyle: 'short',
}).format(new Date(value));
} catch {
return value;
}
const d = new Date(value);
if (Number.isNaN(d.getTime())) return value;
return formatJalaliDateTime(d);
}
function formatKpiValue(key: string, value: number) {
@@ -117,7 +112,7 @@ async function submitAsk(preset?: string) {
askSources.value = [];
try {
const { data } = await api.post<AskMentoResponse>('/ai/ask', { question: q });
askAnswer.value = data.answer;
askAnswer.value = rewriteGregorianDates(data.answer);
askSources.value = data.sources ?? [];
await nextTick();
document
+14 -1
View File
@@ -3,7 +3,7 @@ import { computed, onBeforeUnmount, onMounted, ref } from 'vue';
import { useRoute, useRouter } from 'vue-router';
import axios from 'axios';
import { api } from '@/lib/api';
import { formatClockFa, formatJalaliNumeric, parseLocalInput, toFaDigits } from '@/lib/jalali';
import { formatClockFa, formatJalaliNumeric, formatLocalDateInput, parseLocalInput, toFaDigits } from '@/lib/jalali';
import { useAuthStore } from '@/stores/auth';
import { canManageSettings } from '@/lib/roles';
import JalaliDateInput from '@/components/JalaliDateInput.vue';
@@ -69,6 +69,7 @@ const editForm = ref({
region: '',
source: '',
instagram: '',
birthDate: '',
trade: '',
notes: '',
});
@@ -131,6 +132,7 @@ function startEdit() {
region: lead.value.region ?? '',
source: lead.value.source ?? '',
instagram: lead.value.instagram ?? '',
birthDate: lead.value.birthDate ? formatLocalDateInput(new Date(lead.value.birthDate)) : '',
trade: lead.value.trade ?? '',
notes: lead.value.notes ?? '',
};
@@ -153,6 +155,9 @@ async function saveEdit() {
region: editForm.value.region.trim() || null,
source: editForm.value.source.trim() || null,
instagram: editForm.value.instagram.trim() || null,
birthDate: editForm.value.birthDate
? parseLocalInput(editForm.value.birthDate)?.toISOString() ?? null
: null,
trade: editForm.value.trade.trim() || null,
notes: editForm.value.notes.trim() || null,
});
@@ -511,6 +516,10 @@ onBeforeUnmount(() => narrowMq?.removeEventListener('change', syncNarrow));
<dt><UiIcon name="user" :size="14" /> کارشناس</dt>
<dd>{{ lead.assigneeName || '—' }}</dd>
</div>
<div class="cell">
<dt><UiIcon name="calendar" :size="14" /> تاریخ تولد</dt>
<dd>{{ formatDate(lead.birthDate) }}</dd>
</div>
<div class="cell">
<dt><UiIcon name="calendar" :size="14" /> تاریخ ثبت</dt>
<dd>{{ formatDate(lead.createdAt) }}</dd>
@@ -610,6 +619,9 @@ onBeforeUnmount(() => narrowMq?.removeEventListener('change', syncNarrow));
<UiField label="اینستاگرام">
<UiInput v-model="editForm.instagram" dir="ltr" />
</UiField>
<UiField label="تاریخ تولد">
<JalaliDateInput v-model="editForm.birthDate" mode="date" />
</UiField>
<UiField label="صنف">
<UiInput v-model="editForm.trade" />
</UiField>
@@ -635,6 +647,7 @@ onBeforeUnmount(() => narrowMq?.removeEventListener('change', syncNarrow));
<div><dt>منبع</dt><dd>{{ lead.source || '—' }}</dd></div>
<div><dt>وضعیت</dt><dd>{{ lead.stageName }}</dd></div>
<div><dt>کارشناس</dt><dd>{{ lead.assigneeName || '—' }}</dd></div>
<div><dt>تاریخ تولد</dt><dd>{{ formatDate(lead.birthDate) }}</dd></div>
<div><dt>تاریخ ثبت</dt><dd>{{ formatDate(lead.createdAt) }}</dd></div>
<div><dt>آخرین تماس</dt><dd>{{ formatDate(lead.lastContactAt) }}</dd></div>
<div><dt>پیگیری بعدی</dt><dd>{{ formatDate(lead.nextFollowUpAt) }}</dd></div>
+9 -1
View File
@@ -5,7 +5,8 @@ import axios from 'axios';
import { api } from '@/lib/api';
import type { CompanyDto, CreatePersonRequest, PersonDto, PersonMood } from '@mento/shared';
import { PERSON_MOOD_LABELS, PERSON_MOODS } from '@/lib/person-mood';
import { toFaDigits } from '@/lib/jalali';
import { formatLocalDateInput, parseLocalInput, toFaDigits } from '@/lib/jalali';
import JalaliDateInput from '@/components/JalaliDateInput.vue';
import {
filterPhoneContacts,
isContactPickerSupported,
@@ -63,6 +64,7 @@ const mobile = ref('');
const position = ref('');
const website = ref('');
const instagram = ref('');
const birthDate = ref('');
const referrer = ref('');
const addresses = ref<string[]>(['']);
const notes = ref('');
@@ -209,6 +211,7 @@ function fill(p: PersonDto) {
position.value = p.position ?? '';
website.value = p.website ?? '';
instagram.value = p.instagram ?? '';
birthDate.value = p.birthDate ? formatLocalDateInput(new Date(p.birthDate)) : '';
referrer.value = p.referrer ?? '';
addresses.value = p.addresses?.length ? [...p.addresses] : [''];
notes.value = p.notes ?? '';
@@ -285,6 +288,7 @@ function payload(): CreatePersonRequest {
position: position.value || undefined,
website: website.value || undefined,
instagram: instagram.value || undefined,
birthDate: birthDate.value ? parseLocalInput(birthDate.value)?.toISOString() ?? null : null,
referrer: referrer.value || undefined,
addresses: addresses.value.map((a) => a.trim()).filter(Boolean),
notes: notes.value || undefined,
@@ -483,6 +487,10 @@ onMounted(load);
<UiInput id="instagram" v-model="instagram" dir="ltr" placeholder="@username یا لینک" />
</UiField>
<UiField label="تاریخ تولد" for-id="birth-date">
<JalaliDateInput id="birth-date" v-model="birthDate" mode="date" />
</UiField>
<UiField label="معرف" for-id="referrer">
<UiInput id="referrer" v-model="referrer" placeholder="نام معرف" />
</UiField>
+12 -2
View File
@@ -7,6 +7,7 @@ import {
calendarDaysBetween,
formatClockFa,
formatJalaliNumeric,
rewriteGregorianDates,
toFaDigits,
} from '@/lib/jalali';
import {
@@ -141,6 +142,9 @@ const mailHref = computed(() =>
const registeredAt = computed(() =>
person.value?.createdAt ? formatJalaliNumeric(new Date(person.value.createdAt)) : '—',
);
const birthDateLabel = computed(() =>
person.value?.birthDate ? formatJalaliNumeric(new Date(person.value.birthDate)) : '—',
);
const externalCode = computed(() => {
if (person.value?.sourceRef?.trim()) return person.value.sourceRef.trim();
const fields = person.value?.customFields ?? {};
@@ -239,8 +243,8 @@ const smart = computed(() =>
);
const shownSmart = computed(() => ({
text: aiSummary.value?.text || smart.value.text,
cta: aiSummary.value?.cta || smart.value.cta,
text: rewriteGregorianDates(aiSummary.value?.text || smart.value.text),
cta: rewriteGregorianDates(aiSummary.value?.cta || smart.value.cta),
ctaKind: aiSummary.value?.ctaKind || smart.value.ctaKind,
}));
@@ -367,6 +371,7 @@ const infoEmpty = computed(() => {
!p.position &&
!p.website &&
!p.instagram &&
!p.birthDate &&
!p.referrer &&
!(p.addresses?.length) &&
!p.company &&
@@ -687,6 +692,10 @@ watch(() => route.query.tab, readTab, { immediate: true });
<dt>سمت</dt>
<dd>{{ person.position || '—' }}</dd>
</div>
<div class="fact">
<dt>تاریخ تولد</dt>
<dd>{{ birthDateLabel }}</dd>
</div>
<div class="fact">
<dt>تاریخ ثبت</dt>
<dd>{{ registeredAt }}</dd>
@@ -993,6 +1002,7 @@ watch(() => route.query.tab, readTab, { immediate: true });
</div>
<div><dt>شرکت</dt><dd>{{ person.company?.name || '—' }}</dd></div>
<div><dt>مسئول</dt><dd>{{ person.owner?.fullName || '—' }}</dd></div>
<div><dt>تاریخ تولد</dt><dd>{{ birthDateLabel }}</dd></div>
<div><dt>تاریخ ثبت</dt><dd>{{ registeredAt }}</dd></div>
<div>
<dt>باشگاه</dt>
+4 -8
View File
@@ -16,6 +16,7 @@ import type {
PortalTicketDto,
} from '@mento/shared';
import { brand } from '@mento/shared';
import { formatJalaliDateTime } from '@/lib/jalali';
const route = useRoute();
const router = useRouter();
@@ -54,14 +55,9 @@ const statusLabel: Record<string, string> = {
};
function formatWhen(iso: string) {
try {
return new Intl.DateTimeFormat('fa-IR', {
dateStyle: 'medium',
timeStyle: 'short',
}).format(new Date(iso));
} catch {
return iso;
}
const d = new Date(iso);
if (Number.isNaN(d.getTime())) return iso;
return formatJalaliDateTime(d);
}
async function loadOrg() {
+2 -1
View File
@@ -11,6 +11,7 @@ import {
calendarDaysBetween,
formatJalaliDayClock,
formatNextSlot,
rewriteGregorianDates,
formatRelativeContact,
toFaDigits,
} from '@/lib/jalali';
@@ -435,7 +436,7 @@ async function loadAiBrief() {
question:
'وضعیت کاری امروز من را خلاصه کن: فعالیت‌های عقب‌افتاده، معاملات باز و مهم‌ترین پیگیری بعدی.',
});
aiBrief.value = data.answer;
aiBrief.value = rewriteGregorianDates(data.answer);
} catch {
aiBrief.value = '';
}
+4 -8
View File
@@ -3,6 +3,7 @@ import { computed, onMounted, ref, watch } from 'vue';
import { RouterLink, useRoute, useRouter } from 'vue-router';
import axios from 'axios';
import { api } from '@/lib/api';
import { formatJalaliDateTime } from '@/lib/jalali';
import { canViewAllRecords } from '@/lib/roles';
import { useAuthStore } from '@/stores/auth';
import {
@@ -84,14 +85,9 @@ const priorityLabel: Record<TicketPriority, string> = {
};
function formatWhen(iso: string) {
try {
return new Intl.DateTimeFormat('fa-IR', {
dateStyle: 'medium',
timeStyle: 'short',
}).format(new Date(iso));
} catch {
return iso;
}
const d = new Date(iso);
if (Number.isNaN(d.getTime())) return iso;
return formatJalaliDateTime(d);
}
async function loadFaqHints() {
+4 -9
View File
@@ -4,7 +4,7 @@ import TableColumnsMenu from '@/components/TableColumnsMenu.vue';
import { RouterLink, useRoute, useRouter } from 'vue-router';
import axios from 'axios';
import { api } from '@/lib/api';
import { toFaDigits } from '@/lib/jalali';
import { formatJalaliDateTime, toFaDigits } from '@/lib/jalali';
import {
UiButton,
UiPageHeader,
@@ -57,14 +57,9 @@ const statusFilterLabel = computed(() =>
function formatSla(iso: string | null) {
if (!iso) return '';
try {
return new Intl.DateTimeFormat('fa-IR', {
dateStyle: 'short',
timeStyle: 'short',
}).format(new Date(iso));
} catch {
return iso;
}
const d = new Date(iso);
if (Number.isNaN(d.getTime())) return iso;
return formatJalaliDateTime(d);
}
async function load() {
+2 -2
View File
@@ -18,7 +18,7 @@ import {
UiPageHeader,
} from '@/components/ui';
import UiIcon from '@/components/UiIcon.vue';
import { formatJalaliNumeric, toFaDigits } from '@/lib/jalali';
import { formatJalaliNumeric, rewriteGregorianDates, toFaDigits } from '@/lib/jalali';
const loading = ref(false);
const saving = ref(false);
@@ -267,7 +267,7 @@ onMounted(() => {
{{ summaryLabel[c.summaryStatus] ?? c.summaryStatus ?? '—' }}
</span>
<p v-if="c.summary" class="summary-preview">
{{ c.summary.slice(0, 120) }}{{ c.summary.length > 120 ? '…' : '' }}
{{ rewriteGregorianDates(c.summary).slice(0, 120) }}{{ c.summary.length > 120 ? '…' : '' }}
</p>
<p v-if="c.summaryError && c.summaryStatus === 'failed'" class="warn">
{{ c.summaryError }}