Track person import source and previous names; add source report and edit UX.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -197,6 +197,7 @@ async function importSelected() {
|
||||
mobile: row.mobile,
|
||||
phone: row.phone,
|
||||
email: row.email,
|
||||
importSource: 'phone',
|
||||
};
|
||||
await api.post<PersonDto>('/people', body);
|
||||
}
|
||||
|
||||
@@ -33,6 +33,9 @@ const teams = ref<TeamDto[]>([]);
|
||||
const newName = ref('');
|
||||
const newDesc = ref('');
|
||||
const editingId = ref<string | null>(null);
|
||||
const editingMetaId = ref<string | null>(null);
|
||||
const editName = ref('');
|
||||
const editDesc = ref('');
|
||||
const memberDraft = ref<Record<string, TeamMemberRole>>({});
|
||||
|
||||
const editableUsers = computed(() =>
|
||||
@@ -83,6 +86,7 @@ async function createTeam() {
|
||||
|
||||
function startEdit(team: TeamDto) {
|
||||
editingId.value = team.id;
|
||||
editingMetaId.value = null;
|
||||
const draft: Record<string, TeamMemberRole> = {};
|
||||
for (const m of team.members) {
|
||||
draft[m.userId] = m.role;
|
||||
@@ -90,6 +94,38 @@ function startEdit(team: TeamDto) {
|
||||
memberDraft.value = draft;
|
||||
}
|
||||
|
||||
function startMetaEdit(team: TeamDto) {
|
||||
editingMetaId.value = team.id;
|
||||
editingId.value = null;
|
||||
editName.value = team.name;
|
||||
editDesc.value = team.description || '';
|
||||
}
|
||||
|
||||
async function saveMeta() {
|
||||
if (!editingMetaId.value) return;
|
||||
if (!editName.value.trim()) {
|
||||
error.value = 'نام تیم را وارد کنید.';
|
||||
return;
|
||||
}
|
||||
saving.value = true;
|
||||
error.value = '';
|
||||
try {
|
||||
await api.patch(`/teams/${editingMetaId.value}`, {
|
||||
name: editName.value.trim(),
|
||||
description: editDesc.value.trim(),
|
||||
});
|
||||
ok.value = 'اطلاعات تیم ذخیره شد.';
|
||||
editingMetaId.value = null;
|
||||
await load();
|
||||
} catch (e) {
|
||||
error.value = axios.isAxiosError(e)
|
||||
? String((e.response?.data as { message?: string })?.message ?? 'ذخیره تیم ناموفق بود.')
|
||||
: 'ذخیره تیم ناموفق بود.';
|
||||
} finally {
|
||||
saving.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
function toggleMember(userId: string) {
|
||||
if (memberDraft.value[userId]) {
|
||||
const next = { ...memberDraft.value };
|
||||
@@ -194,6 +230,7 @@ onMounted(() => {
|
||||
</p>
|
||||
</div>
|
||||
<div v-if="canEdit" class="actions">
|
||||
<UiButton variant="secondary" @click="startMetaEdit(t)">ویرایش</UiButton>
|
||||
<UiButton variant="secondary" @click="startEdit(t)">اعضا</UiButton>
|
||||
<UiButton variant="secondary" @click="removeTeam(t.id)">حذف</UiButton>
|
||||
</div>
|
||||
@@ -208,6 +245,21 @@ onMounted(() => {
|
||||
}}
|
||||
</p>
|
||||
|
||||
<div v-if="editingMetaId === t.id" class="editor">
|
||||
<div class="row">
|
||||
<UiField label="نام تیم">
|
||||
<UiInput v-model="editName" />
|
||||
</UiField>
|
||||
<UiField label="توضیح">
|
||||
<UiInput v-model="editDesc" />
|
||||
</UiField>
|
||||
</div>
|
||||
<div class="actions">
|
||||
<UiButton :loading="saving" @click="saveMeta">ذخیره</UiButton>
|
||||
<UiButton variant="secondary" @click="editingMetaId = null">انصراف</UiButton>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="editingId === t.id" class="editor">
|
||||
<p class="hint">اعضا را انتخاب کنید؛ سرپرست همان عضو تیم است.</p>
|
||||
<label v-for="u in users.filter((x) => x.isActive !== false)" :key="u.id" class="person">
|
||||
|
||||
@@ -25,9 +25,15 @@ import type {
|
||||
ImportContactsResult,
|
||||
PaginatedResponse,
|
||||
PersonDto,
|
||||
PersonImportSource,
|
||||
PersonImportSourceStatDto,
|
||||
PersonMood,
|
||||
} from '@mento/shared';
|
||||
import { brand } from '@mento/shared';
|
||||
import {
|
||||
brand,
|
||||
PERSON_IMPORT_SOURCE_LABELS,
|
||||
PERSON_IMPORT_SOURCES,
|
||||
} from '@mento/shared';
|
||||
import { PERSON_MOOD_LABELS, PERSON_MOODS } from '@/lib/person-mood';
|
||||
|
||||
type Tab = 'people' | 'companies';
|
||||
@@ -50,6 +56,12 @@ const moodFilter = computed<PersonMood | 'all'>(() => {
|
||||
const value = typeof route.query.mood === 'string' ? route.query.mood : 'all';
|
||||
return PERSON_MOODS.includes(value as PersonMood) ? (value as PersonMood) : 'all';
|
||||
});
|
||||
const sourceFilter = computed<PersonImportSource | 'all'>(() => {
|
||||
const value = typeof route.query.source === 'string' ? route.query.source : 'all';
|
||||
return PERSON_IMPORT_SOURCES.includes(value as PersonImportSource)
|
||||
? (value as PersonImportSource)
|
||||
: 'all';
|
||||
});
|
||||
const q = ref(typeof route.query.q === 'string' ? route.query.q : '');
|
||||
const loading = ref(false);
|
||||
const busy = ref(false);
|
||||
@@ -59,6 +71,7 @@ const error = ref('');
|
||||
const importMsg = ref('');
|
||||
const people = ref<PaginatedResponse<PersonDto> | null>(null);
|
||||
const companies = ref<PaginatedResponse<CompanyDto> | null>(null);
|
||||
const sourceStats = ref<PersonImportSourceStatDto[]>([]);
|
||||
const menuId = ref<string | null>(null);
|
||||
const sortOpen = ref(false);
|
||||
const filterOpen = ref(false);
|
||||
@@ -73,6 +86,7 @@ const peopleSavedFilters = computed(() => ({
|
||||
q: q.value.trim() || undefined,
|
||||
sort: sort.value === 'name' ? undefined : sort.value,
|
||||
mood: moodFilter.value === 'all' ? undefined : moodFilter.value,
|
||||
source: sourceFilter.value === 'all' ? undefined : sourceFilter.value,
|
||||
}));
|
||||
|
||||
function applyPeopleSavedFilters(filters: Record<string, unknown>) {
|
||||
@@ -84,15 +98,28 @@ function applyPeopleSavedFilters(filters: Record<string, unknown>) {
|
||||
PERSON_MOODS.includes(filters.mood as PersonMood)
|
||||
? (filters.mood as PersonMood)
|
||||
: 'all';
|
||||
const nextSource =
|
||||
typeof filters.source === 'string' &&
|
||||
PERSON_IMPORT_SOURCES.includes(filters.source as PersonImportSource)
|
||||
? (filters.source as PersonImportSource)
|
||||
: 'all';
|
||||
patchQuery({
|
||||
q: nextQ.trim() || undefined,
|
||||
sort: nextSort === 'name' ? undefined : nextSort,
|
||||
mood: nextMood === 'all' ? undefined : nextMood,
|
||||
source: nextSource === 'all' ? undefined : nextSource,
|
||||
page: undefined,
|
||||
tab: undefined,
|
||||
});
|
||||
}
|
||||
|
||||
function sourceLabel(source: string) {
|
||||
if (source in PERSON_IMPORT_SOURCE_LABELS) {
|
||||
return PERSON_IMPORT_SOURCE_LABELS[source as PersonImportSource];
|
||||
}
|
||||
return source || 'دستی';
|
||||
}
|
||||
|
||||
function moodOf(person: PersonDto): PersonMood {
|
||||
return person.mood || 'newcomer';
|
||||
}
|
||||
@@ -103,6 +130,7 @@ function moodLabel(mood: PersonMood) {
|
||||
|
||||
/** کد شخص در نرمافزار مبدأ (دیدار و مشابه) */
|
||||
function externalCode(person: PersonDto): string {
|
||||
if (person.sourceRef?.trim()) return person.sourceRef.trim();
|
||||
const fields = person.customFields ?? {};
|
||||
for (const key of ['didarId', 'customerCode'] as const) {
|
||||
const raw = fields[key];
|
||||
@@ -137,16 +165,24 @@ async function load() {
|
||||
closePopups();
|
||||
try {
|
||||
if (tab.value === 'people') {
|
||||
const { data } = await api.get<PaginatedResponse<PersonDto>>('/people', {
|
||||
params: {
|
||||
q: q.value || undefined,
|
||||
page: page.value,
|
||||
limit: PAGE_SIZE,
|
||||
sort: sort.value,
|
||||
mood: moodFilter.value === 'all' ? undefined : moodFilter.value,
|
||||
},
|
||||
});
|
||||
people.value = data;
|
||||
const [peopleRes, statsRes] = await Promise.all([
|
||||
api.get<PaginatedResponse<PersonDto>>('/people', {
|
||||
params: {
|
||||
q: q.value || undefined,
|
||||
page: page.value,
|
||||
limit: PAGE_SIZE,
|
||||
sort: sort.value,
|
||||
mood: moodFilter.value === 'all' ? undefined : moodFilter.value,
|
||||
importSource:
|
||||
sourceFilter.value === 'all' ? undefined : sourceFilter.value,
|
||||
},
|
||||
}),
|
||||
api.get<PersonImportSourceStatDto[]>('/people/source-stats').catch(() => ({
|
||||
data: [] as PersonImportSourceStatDto[],
|
||||
})),
|
||||
]);
|
||||
people.value = peopleRes.data;
|
||||
sourceStats.value = statsRes.data;
|
||||
} else {
|
||||
const { data } = await api.get<PaginatedResponse<CompanyDto>>('/companies', {
|
||||
params: { q: q.value || undefined, page: page.value, limit: PAGE_SIZE },
|
||||
@@ -194,6 +230,13 @@ function setMood(next: PersonMood | 'all') {
|
||||
patchQuery({ mood: next === 'all' ? undefined : next, page: undefined });
|
||||
}
|
||||
|
||||
function setSource(next: PersonImportSource | 'all') {
|
||||
patchQuery({
|
||||
source: next === 'all' ? undefined : next,
|
||||
page: undefined,
|
||||
});
|
||||
}
|
||||
|
||||
function setPage(next: number) {
|
||||
if (next < 1 || next > pageCount.value) return;
|
||||
patchQuery({ page: next === 1 ? undefined : String(next) });
|
||||
@@ -422,6 +465,28 @@ onMounted(load);
|
||||
@apply="applyPeopleSavedFilters"
|
||||
/>
|
||||
|
||||
<div v-if="tab === 'people' && sourceStats.length" class="source-stats">
|
||||
<button
|
||||
type="button"
|
||||
class="source-chip"
|
||||
:class="{ on: sourceFilter === 'all' }"
|
||||
@click="setSource('all')"
|
||||
>
|
||||
همه منابع
|
||||
</button>
|
||||
<button
|
||||
v-for="row in sourceStats"
|
||||
:key="row.source"
|
||||
type="button"
|
||||
class="source-chip"
|
||||
:class="{ on: sourceFilter === row.source }"
|
||||
@click="setSource(row.source as PersonImportSource)"
|
||||
>
|
||||
{{ row.label }}
|
||||
<em>{{ toFaDigits(row.count) }}</em>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<p v-if="importMsg" class="ok">{{ importMsg }}</p>
|
||||
<p v-if="error" class="error">{{ error }}</p>
|
||||
<p v-else-if="loading" class="muted">در حال بارگذاری…</p>
|
||||
@@ -460,8 +525,12 @@ onMounted(load);
|
||||
<p class="role">
|
||||
{{ [p.position, p.company?.name].filter(Boolean).join(' • ') || 'بدون سمت' }}
|
||||
</p>
|
||||
<p v-if="externalCode(p)" class="line">
|
||||
<span>کد مبدأ: {{ externalCodeLabel(p) }}</span>
|
||||
<p class="line">
|
||||
<span>منبع: {{ sourceLabel(p.importSource || 'manual') }}</span>
|
||||
<span v-if="externalCode(p)"> · کد: {{ externalCodeLabel(p) }}</span>
|
||||
</p>
|
||||
<p v-if="p.previousFullName" class="line muted">
|
||||
نام قبلی: {{ p.previousFullName }}
|
||||
</p>
|
||||
<p v-if="p.mobile || p.phone" class="line" dir="ltr">
|
||||
<UiIcon name="phone" :size="13" />
|
||||
@@ -482,6 +551,7 @@ onMounted(load);
|
||||
<tr>
|
||||
<th>وضعیت</th>
|
||||
<th>نام شخص</th>
|
||||
<th>منبع</th>
|
||||
<th>کد مبدأ</th>
|
||||
<th>سمت</th>
|
||||
<th>شرکت</th>
|
||||
@@ -504,7 +574,11 @@ onMounted(load);
|
||||
<RouterLink class="name" :to="{ name: 'person-detail', params: { id: p.id } }">
|
||||
{{ p.fullName }}
|
||||
</RouterLink>
|
||||
<div v-if="p.previousFullName" class="muted tiny">
|
||||
قبلی: {{ p.previousFullName }}
|
||||
</div>
|
||||
</td>
|
||||
<td>{{ sourceLabel(p.importSource || 'manual') }}</td>
|
||||
<td dir="ltr">{{ externalCodeLabel(p) }}</td>
|
||||
<td>{{ p.position || '—' }}</td>
|
||||
<td>
|
||||
@@ -752,6 +826,37 @@ h1 {
|
||||
color: #94a3b8;
|
||||
}
|
||||
|
||||
.source-stats {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.4rem;
|
||||
margin-bottom: 0.75rem;
|
||||
}
|
||||
.source-chip {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.35rem;
|
||||
border: 1px solid var(--color-border, #ddd);
|
||||
background: var(--color-surface, #fff);
|
||||
border-radius: 999px;
|
||||
padding: 0.25rem 0.7rem;
|
||||
font-size: 0.82rem;
|
||||
cursor: pointer;
|
||||
}
|
||||
.source-chip.on {
|
||||
border-color: var(--color-primary, #2563eb);
|
||||
color: var(--color-primary, #2563eb);
|
||||
background: color-mix(in srgb, var(--color-primary, #2563eb) 10%, transparent);
|
||||
}
|
||||
.source-chip em {
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
}
|
||||
.tiny {
|
||||
font-size: 0.75rem;
|
||||
margin-top: 0.15rem;
|
||||
}
|
||||
|
||||
.toolbar {
|
||||
display: flex;
|
||||
gap: 0.55rem;
|
||||
|
||||
@@ -47,6 +47,19 @@ const callFollowUp = ref('');
|
||||
const noteBody = ref('');
|
||||
const followUpAt = ref('');
|
||||
const followUpBody = ref('');
|
||||
const editing = ref(false);
|
||||
const editForm = ref({
|
||||
title: '',
|
||||
contactName: '',
|
||||
mobile: '',
|
||||
phone: '',
|
||||
city: '',
|
||||
region: '',
|
||||
source: '',
|
||||
instagram: '',
|
||||
trade: '',
|
||||
notes: '',
|
||||
});
|
||||
|
||||
function formatDate(value: string | null | undefined) {
|
||||
if (!value) return '—';
|
||||
@@ -88,6 +101,53 @@ async function load() {
|
||||
}
|
||||
}
|
||||
|
||||
function startEdit() {
|
||||
if (!lead.value) return;
|
||||
editing.value = true;
|
||||
editForm.value = {
|
||||
title: lead.value.title,
|
||||
contactName: lead.value.contactName ?? '',
|
||||
mobile: lead.value.mobile,
|
||||
phone: lead.value.phone ?? '',
|
||||
city: lead.value.city ?? '',
|
||||
region: lead.value.region ?? '',
|
||||
source: lead.value.source ?? '',
|
||||
instagram: lead.value.instagram ?? '',
|
||||
trade: lead.value.trade ?? '',
|
||||
notes: lead.value.notes ?? '',
|
||||
};
|
||||
}
|
||||
|
||||
async function saveEdit() {
|
||||
if (!editForm.value.title.trim() || !editForm.value.mobile.trim()) {
|
||||
error.value = 'عنوان و موبایل الزامی است.';
|
||||
return;
|
||||
}
|
||||
busy.value = true;
|
||||
error.value = '';
|
||||
try {
|
||||
await api.patch(`/leads/${id.value}`, {
|
||||
title: editForm.value.title.trim(),
|
||||
contactName: editForm.value.contactName.trim() || null,
|
||||
mobile: editForm.value.mobile.trim(),
|
||||
phone: editForm.value.phone.trim() || null,
|
||||
city: editForm.value.city.trim() || null,
|
||||
region: editForm.value.region.trim() || null,
|
||||
source: editForm.value.source.trim() || null,
|
||||
instagram: editForm.value.instagram.trim() || null,
|
||||
trade: editForm.value.trade.trim() || null,
|
||||
notes: editForm.value.notes.trim() || null,
|
||||
});
|
||||
okMsg.value = 'لید ویرایش شد.';
|
||||
editing.value = false;
|
||||
await load();
|
||||
} catch (e) {
|
||||
error.value = apiError(e, 'ویرایش لید ناموفق بود.');
|
||||
} finally {
|
||||
busy.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function changeStage() {
|
||||
if (!stageId.value || stageId.value === lead.value?.stageId) return;
|
||||
busy.value = true;
|
||||
@@ -226,6 +286,15 @@ onMounted(load);
|
||||
:back-to="{ name: 'leads' }"
|
||||
>
|
||||
<template #actions>
|
||||
<UiButton
|
||||
v-if="lead && !editing"
|
||||
variant="secondary"
|
||||
size="sm"
|
||||
:disabled="busy"
|
||||
@click="startEdit"
|
||||
>
|
||||
ویرایش
|
||||
</UiButton>
|
||||
<UiButton
|
||||
v-if="lead && !lead.convertedPersonId"
|
||||
variant="primary"
|
||||
@@ -255,7 +324,45 @@ onMounted(load);
|
||||
<div class="layout">
|
||||
<div class="main">
|
||||
<UiCard title="اطلاعات لید">
|
||||
<dl class="meta">
|
||||
<div v-if="editing" class="edit-grid">
|
||||
<UiField label="عنوان" required>
|
||||
<UiInput v-model="editForm.title" />
|
||||
</UiField>
|
||||
<UiField label="مالک/مخاطب">
|
||||
<UiInput v-model="editForm.contactName" />
|
||||
</UiField>
|
||||
<UiField label="موبایل" required>
|
||||
<UiInput v-model="editForm.mobile" dir="ltr" />
|
||||
</UiField>
|
||||
<UiField label="تلفن">
|
||||
<UiInput v-model="editForm.phone" dir="ltr" />
|
||||
</UiField>
|
||||
<UiField label="شهر">
|
||||
<UiInput v-model="editForm.city" />
|
||||
</UiField>
|
||||
<UiField label="منطقه">
|
||||
<UiInput v-model="editForm.region" />
|
||||
</UiField>
|
||||
<UiField label="منبع">
|
||||
<UiInput v-model="editForm.source" />
|
||||
</UiField>
|
||||
<UiField label="اینستاگرام">
|
||||
<UiInput v-model="editForm.instagram" dir="ltr" />
|
||||
</UiField>
|
||||
<UiField label="صنف">
|
||||
<UiInput v-model="editForm.trade" />
|
||||
</UiField>
|
||||
<UiField label="یادداشت" class="full">
|
||||
<UiInput v-model="editForm.notes" />
|
||||
</UiField>
|
||||
<div class="edit-actions full">
|
||||
<UiButton size="sm" :loading="busy" @click="saveEdit">ذخیره</UiButton>
|
||||
<UiButton size="sm" variant="ghost" :disabled="busy" @click="editing = false">
|
||||
انصراف
|
||||
</UiButton>
|
||||
</div>
|
||||
</div>
|
||||
<dl v-else class="meta">
|
||||
<div><dt>نام</dt><dd>{{ lead.title }}</dd></div>
|
||||
<div><dt>مالک/مخاطب</dt><dd>{{ lead.contactName || '—' }}</dd></div>
|
||||
<div><dt>موبایل</dt><dd dir="ltr">{{ lead.mobile }}</dd></div>
|
||||
@@ -384,6 +491,20 @@ onMounted(load);
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.edit-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
gap: 0.75rem 1rem;
|
||||
}
|
||||
.edit-grid .full {
|
||||
grid-column: 1 / -1;
|
||||
}
|
||||
.edit-actions {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.meta {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
|
||||
@@ -138,6 +138,7 @@ const registeredAt = computed(() =>
|
||||
person.value?.createdAt ? formatJalaliNumeric(new Date(person.value.createdAt)) : '—',
|
||||
);
|
||||
const externalCode = computed(() => {
|
||||
if (person.value?.sourceRef?.trim()) return person.value.sourceRef.trim();
|
||||
const fields = person.value?.customFields ?? {};
|
||||
for (const key of ['didarId', 'customerCode'] as const) {
|
||||
const raw = fields[key];
|
||||
@@ -146,6 +147,19 @@ const externalCode = computed(() => {
|
||||
}
|
||||
return '';
|
||||
});
|
||||
const importSourceLabel = computed(() => {
|
||||
const src = person.value?.importSource || 'manual';
|
||||
const map: Record<string, string> = {
|
||||
manual: 'دستی',
|
||||
tahesab: 'تهحساب',
|
||||
didar: 'دیدار',
|
||||
excel: 'اکسل',
|
||||
phone: 'مخاطبین گوشی',
|
||||
lead_form: 'فرم لید',
|
||||
other: 'سایر',
|
||||
};
|
||||
return map[src] || src;
|
||||
});
|
||||
|
||||
const lastCallAt = computed(() => {
|
||||
const calls = activities.value
|
||||
@@ -630,7 +644,9 @@ watch(() => route.query.tab, readTab, { immediate: true });
|
||||
</div>
|
||||
<dl class="facts">
|
||||
<div><dt>سمت</dt><dd>{{ person.position || '—' }}</dd></div>
|
||||
<div><dt>منبع ورود</dt><dd>{{ importSourceLabel }}</dd></div>
|
||||
<div><dt>کد مبدأ</dt><dd dir="ltr">{{ externalCode ? toFaDigits(externalCode) : '—' }}</dd></div>
|
||||
<div><dt>نام قبلی</dt><dd>{{ person.previousFullName || '—' }}</dd></div>
|
||||
<div><dt>وبسایت</dt><dd>{{ person.website || '—' }}</dd></div>
|
||||
<div><dt>اینستاگرام</dt><dd dir="ltr">{{ person.instagram || '—' }}</dd></div>
|
||||
<div><dt>معرف</dt><dd>{{ person.referrer || '—' }}</dd></div>
|
||||
@@ -916,7 +932,9 @@ watch(() => route.query.tab, readTab, { immediate: true });
|
||||
<div><dt>تلفن</dt><dd>{{ person.phone ? toFaDigits(person.phone) : '—' }}</dd></div>
|
||||
<div><dt>ایمیل</dt><dd>{{ person.email || '—' }}</dd></div>
|
||||
<div><dt>سمت</dt><dd>{{ person.position || '—' }}</dd></div>
|
||||
<div><dt>منبع ورود</dt><dd>{{ importSourceLabel }}</dd></div>
|
||||
<div><dt>کد مبدأ</dt><dd dir="ltr">{{ externalCode ? toFaDigits(externalCode) : '—' }}</dd></div>
|
||||
<div><dt>نام قبلی</dt><dd>{{ person.previousFullName || '—' }}</dd></div>
|
||||
<div><dt>وبسایت</dt><dd>{{ person.website || '—' }}</dd></div>
|
||||
<div><dt>اینستاگرام</dt><dd dir="ltr">{{ person.instagram || '—' }}</dd></div>
|
||||
<div><dt>معرف</dt><dd>{{ person.referrer || '—' }}</dd></div>
|
||||
|
||||
Reference in New Issue
Block a user