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:
@@ -3,6 +3,7 @@ import { ref } from 'vue';
|
||||
import { RouterLink } from 'vue-router';
|
||||
import axios from 'axios';
|
||||
import { api } from '@/lib/api';
|
||||
import { rewriteGregorianDates } from '@/lib/jalali';
|
||||
import UiIcon from '@/components/UiIcon.vue';
|
||||
import { brand, type AskMentoResponse, type AskMentoSourceDto } from '@mento/shared';
|
||||
|
||||
@@ -43,7 +44,7 @@ async function submit() {
|
||||
personId: props.personId || undefined,
|
||||
chatContext: props.chatContext?.trim() || undefined,
|
||||
});
|
||||
result.value = data;
|
||||
result.value = { ...data, answer: rewriteGregorianDates(data.answer) };
|
||||
} catch (e) {
|
||||
if (axios.isAxiosError(e)) {
|
||||
const msg = (e.response?.data as { message?: string | string[] })?.message;
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
import { computed, onMounted, ref, watch } from 'vue';
|
||||
import { RouterLink } from 'vue-router';
|
||||
import { api } from '@/lib/api';
|
||||
import { formatJalaliDateTime } from '@/lib/jalali';
|
||||
import VoiceToTextButton from '@/components/VoiceToTextButton.vue';
|
||||
import { appendSpokenText } from '@/lib/spoken-text';
|
||||
import type {
|
||||
@@ -63,14 +64,9 @@ function noteTypeLabel(value?: string | null) {
|
||||
}
|
||||
|
||||
function formatTime(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 load() {
|
||||
|
||||
@@ -3,6 +3,7 @@ import { computed, onMounted, ref, watch } from 'vue';
|
||||
import axios from 'axios';
|
||||
import { api } from '@/lib/api';
|
||||
import { brand, type MemoryItemDto, type PaginatedResponse } from '@mento/shared';
|
||||
import { formatJalaliDateTime } from '@/lib/jalali';
|
||||
|
||||
const props = defineProps<{
|
||||
personId: string;
|
||||
@@ -97,14 +98,9 @@ async function removeItem(id: string) {
|
||||
}
|
||||
|
||||
function formatWhen(value: string) {
|
||||
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);
|
||||
}
|
||||
|
||||
const hasItems = computed(() => items.value.length > 0);
|
||||
|
||||
@@ -4,6 +4,7 @@ import axios from 'axios';
|
||||
import { api } from '@/lib/api';
|
||||
import JalaliDateInput from '@/components/JalaliDateInput.vue';
|
||||
import { brand, type NoteAiPayloadDto, type NoteAiSuggestionDto } from '@mento/shared';
|
||||
import { rewriteGregorianDates } from '@/lib/jalali';
|
||||
|
||||
const props = defineProps<{
|
||||
noteId: string;
|
||||
@@ -142,7 +143,7 @@ async function dismiss() {
|
||||
|
||||
function formatPayloadSummary(value: NoteAiPayloadDto | null) {
|
||||
if (!value?.summary) return '';
|
||||
return value.summary;
|
||||
return rewriteGregorianDates(value.summary);
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ import axios from 'axios';
|
||||
import UiIcon from '@/components/UiIcon.vue';
|
||||
import VoiceToTextButton from '@/components/VoiceToTextButton.vue';
|
||||
import JalaliDateInput from '@/components/JalaliDateInput.vue';
|
||||
import { formatLocalDateTimeInput } from '@/lib/jalali';
|
||||
import { formatLocalDateTimeInput, rewriteGregorianDates } from '@/lib/jalali';
|
||||
import { UiButton, UiModal } from '@/components/ui';
|
||||
import { api } from '@/lib/api';
|
||||
import { brand, type ActivityType, type AskMentoSourceDto, type PersonDto, type VoiceCommandDraft } from '@mento/shared';
|
||||
@@ -150,7 +150,7 @@ async function parseCommand(text: string, fallbackToAsk: boolean) {
|
||||
}
|
||||
voiceError.value = looksLikeAction(text)
|
||||
? 'دستور را کامل نفهمیدم. نام و مشخصات را واضحتر بگویید تا قبل از ساخت، تأیید کنید.'
|
||||
: data.summary;
|
||||
: rewriteGregorianDates(data.summary);
|
||||
return;
|
||||
}
|
||||
draft.value = data;
|
||||
@@ -328,7 +328,7 @@ watch(
|
||||
:title="draft?.intent === 'create_person' ? 'تأیید ساخت مشتری' : 'تأیید ثبت فعالیت'"
|
||||
@close="cancelDraft"
|
||||
>
|
||||
<p v-if="draft" class="modal-lead">{{ draft.summary }}</p>
|
||||
<p v-if="draft" class="modal-lead">{{ rewriteGregorianDates(draft.summary) }}</p>
|
||||
<p class="edit-hint">قبل از تأیید میتوانید متن را اصلاح کنید.</p>
|
||||
<div v-if="draft?.person" class="modal-fields">
|
||||
<label>نام <input v-model="draft.person.firstName" @input="onPersonNameEdit" /></label>
|
||||
@@ -372,7 +372,7 @@ watch(
|
||||
</UiModal>
|
||||
|
||||
<UiModal :open="answerOpen && Boolean(answer)" title="پاسخ دستیار" @close="closeAnswer">
|
||||
<p class="modal-lead answer-body">{{ answer }}</p>
|
||||
<p class="modal-lead answer-body">{{ rewriteGregorianDates(answer || '') }}</p>
|
||||
<div v-if="sources?.length" class="sources">
|
||||
<RouterLink
|
||||
v-for="source in sources"
|
||||
|
||||
@@ -3,7 +3,7 @@ import { computed, onMounted, onUnmounted, ref, watch } from 'vue';
|
||||
import { RouterLink, RouterView, useRoute, useRouter } from 'vue-router';
|
||||
import { useAuthStore } from '@/stores/auth';
|
||||
import { api } from '@/lib/api';
|
||||
import { formatJalaliShort, toFaDigits } from '@/lib/jalali';
|
||||
import { formatJalaliDateTime, formatJalaliShort, toFaDigits } from '@/lib/jalali';
|
||||
import UiIcon from '@/components/UiIcon.vue';
|
||||
import MentoLogo from '@/components/brand/MentoLogo.vue';
|
||||
import QuickCreate from '@/components/QuickCreate.vue';
|
||||
@@ -350,14 +350,9 @@ function toggleNotif() {
|
||||
}
|
||||
|
||||
function formatTime(iso: string) {
|
||||
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);
|
||||
}
|
||||
|
||||
function submitSearch() {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { GamificationEvent, GamificationTxDto } from '@mento/shared';
|
||||
import { GAMIFICATION_EVENTS } from '@mento/shared';
|
||||
import { toFaDigits } from '@/lib/jalali';
|
||||
import { formatJalaliDateTime, formatJalaliDay, toFaDigits } from '@/lib/jalali';
|
||||
|
||||
export { apiError } from '@/lib/routines';
|
||||
|
||||
@@ -81,15 +81,13 @@ export function timeAgo(iso: string): string {
|
||||
if (hours < 24) return `${toFaDigits(hours)} ساعت پیش`;
|
||||
const days = Math.floor(hours / 24);
|
||||
if (days < 7) return `${toFaDigits(days)} روز پیش`;
|
||||
return new Intl.DateTimeFormat('fa-IR', { dateStyle: 'medium' }).format(d);
|
||||
return formatJalaliDay(d);
|
||||
}
|
||||
|
||||
export function formatDateTime(iso: string): 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);
|
||||
}
|
||||
|
||||
export const REWARD_ICONS = ['gift', 'star', 'trophy', 'medal', 'crown', 'calendar-sun', 'bag', 'target', 'fire'] as const;
|
||||
|
||||
@@ -204,6 +204,11 @@ export function formatJalaliDayClock(d: Date): string {
|
||||
return `${toFaDigits(jd)} ${jalaliMonthName(jm)} ${formatClockFa(d)}`;
|
||||
}
|
||||
|
||||
/** مثل: ۸ شهریور ۱۴۰۵، ۱۶:۳۰ */
|
||||
export function formatJalaliDateTime(d: Date): string {
|
||||
return `${formatJalaliDay(d)}، ${formatClockFa(d)}`;
|
||||
}
|
||||
|
||||
export function calendarDaysBetween(from: Date, to: Date): number {
|
||||
return Math.round(
|
||||
(startOfLocalDay(to).getTime() - startOfLocalDay(from).getTime()) / 86_400_000,
|
||||
@@ -270,3 +275,93 @@ export function formatJalaliNumeric(d: Date): string {
|
||||
const { jy, jm, jd } = dateToJalali(d);
|
||||
return `${toFaDigits(jy)}/${toFaDigits(String(jm).padStart(2, '0'))}/${toFaDigits(String(jd).padStart(2, '0'))}`;
|
||||
}
|
||||
|
||||
const GREG_MONTHS: Array<[string, number]> = [
|
||||
['ژانویه', 1],
|
||||
['january', 1],
|
||||
['فوریه', 2],
|
||||
['february', 2],
|
||||
['مارس', 3],
|
||||
['march', 3],
|
||||
['آوریل', 4],
|
||||
['april', 4],
|
||||
['ژوئن', 6],
|
||||
['june', 6],
|
||||
['ژوئیه', 7],
|
||||
['جولای', 7],
|
||||
['july', 7],
|
||||
['آگوست', 8],
|
||||
['آگست', 8],
|
||||
['اوت', 8],
|
||||
['august', 8],
|
||||
['سپتامبر', 9],
|
||||
['september', 9],
|
||||
['اکتبر', 10],
|
||||
['october', 10],
|
||||
['نوامبر', 11],
|
||||
['november', 11],
|
||||
['دسامبر', 12],
|
||||
['december', 12],
|
||||
['مه', 5],
|
||||
['می', 5],
|
||||
['may', 5],
|
||||
];
|
||||
|
||||
function toAsciiDigits(value: string) {
|
||||
return value
|
||||
.replace(/[۰-۹]/g, (d) => String('۰۱۲۳۴۵۶۷۸۹'.indexOf(d)))
|
||||
.replace(/[٠-٩]/g, (d) => String('٠١٢٣٤٥٦٧٨٩'.indexOf(d)));
|
||||
}
|
||||
|
||||
function gregMonthNumber(name: string): number | null {
|
||||
const key = name.trim().toLowerCase();
|
||||
return GREG_MONTHS.find(([label]) => label === key)?.[1] ?? null;
|
||||
}
|
||||
|
||||
function inferGregorianYear(month: number, day: number, now = new Date()): number {
|
||||
const gy = now.getFullYear();
|
||||
const gm = now.getMonth() + 1;
|
||||
const gd = now.getDate();
|
||||
if (month > gm || (month === gm && day > gd)) return gy - 1;
|
||||
return gy;
|
||||
}
|
||||
|
||||
/** «21 سپتامبر» و تاریخ میلادی عددی را در متن نمایشی به شمسی تبدیل میکند. */
|
||||
export function rewriteGregorianDates(text: string, now = new Date()): string {
|
||||
const monthAlt = GREG_MONTHS.map(([name]) => name).join('|');
|
||||
const dayMonth = new RegExp(
|
||||
`(^|[^\\p{L}\\d])([0-9۰-۹]{1,2})\\s*(${monthAlt})(?![\\p{L}\\u200c])(?:\\s+([0-9۰-۹]{4}))?`,
|
||||
'giu',
|
||||
);
|
||||
const monthDay = new RegExp(
|
||||
`(^|[^\\p{L}\\d])(${monthAlt})(?![\\p{L}\\u200c])\\s+([0-9۰-۹]{1,2})(?:\\s*,?\\s*([0-9۰-۹]{4}))?`,
|
||||
'giu',
|
||||
);
|
||||
const iso = /\b((?:19|20)\d{2})-(\d{2})-(\d{2})\b/g;
|
||||
|
||||
const stamp = (year: number, month: number, day: number) => {
|
||||
if (month < 1 || month > 12 || day < 1 || day > 31 || year < 1700 || year > 2500) return null;
|
||||
const j = toJalali(year, month, day);
|
||||
return `${toFaDigits(j.jy)}/${toFaDigits(String(j.jm).padStart(2, '0'))}/${toFaDigits(String(j.jd).padStart(2, '0'))}`;
|
||||
};
|
||||
const fromParts = (dayRaw: string, monthName: string, yearRaw?: string) => {
|
||||
const day = Number(toAsciiDigits(dayRaw));
|
||||
const month = gregMonthNumber(monthName);
|
||||
if (!month) return null;
|
||||
const year = yearRaw ? Number(toAsciiDigits(yearRaw)) : inferGregorianYear(month, day, now);
|
||||
return stamp(year, month, day);
|
||||
};
|
||||
|
||||
let out = text.replace(dayMonth, (full, lead: string, day: string, month: string, year?: string) => {
|
||||
const next = fromParts(day, month, year);
|
||||
return next ? `${lead}${next}` : full;
|
||||
});
|
||||
out = out.replace(monthDay, (full, lead: string, month: string, day: string, year?: string) => {
|
||||
const next = fromParts(day, month, year);
|
||||
return next ? `${lead}${next}` : full;
|
||||
});
|
||||
out = out.replace(iso, (full, year: string, month: string, day: string) => {
|
||||
return stamp(Number(year), Number(month), Number(day)) ?? full;
|
||||
});
|
||||
return out;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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 = '';
|
||||
}
|
||||
|
||||
@@ -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,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() {
|
||||
|
||||
@@ -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 }}
|
||||
|
||||
Reference in New Issue
Block a user