2026-09-27 17:05:11 +03:30
|
|
|
|
<script setup lang="ts">
|
2026-10-13 14:45:29 +03:30
|
|
|
|
import { computed, onBeforeUnmount, onMounted, ref } from 'vue';
|
2026-09-27 17:05:11 +03:30
|
|
|
|
import { useRoute, useRouter } from 'vue-router';
|
|
|
|
|
|
import axios from 'axios';
|
|
|
|
|
|
import { api } from '@/lib/api';
|
2026-10-13 18:04:53 +03:30
|
|
|
|
import { formatClockFa, formatJalaliNumeric, formatLocalDateInput, parseLocalInput, toFaDigits } from '@/lib/jalali';
|
2026-09-27 17:05:11 +03:30
|
|
|
|
import { useAuthStore } from '@/stores/auth';
|
|
|
|
|
|
import { canManageSettings } from '@/lib/roles';
|
2026-10-13 14:45:29 +03:30
|
|
|
|
import JalaliDateInput from '@/components/JalaliDateInput.vue';
|
|
|
|
|
|
import UiIcon from '@/components/UiIcon.vue';
|
2026-09-27 17:05:11 +03:30
|
|
|
|
import {
|
|
|
|
|
|
UiButton,
|
|
|
|
|
|
UiCard,
|
|
|
|
|
|
UiEmptyState,
|
|
|
|
|
|
UiField,
|
|
|
|
|
|
UiInput,
|
|
|
|
|
|
UiPageHeader,
|
|
|
|
|
|
UiSelect,
|
2026-10-13 14:45:29 +03:30
|
|
|
|
UiTextarea,
|
2026-09-27 17:05:11 +03:30
|
|
|
|
} from '@/components/ui';
|
2026-10-06 19:54:38 +03:30
|
|
|
|
import RelatedTasksPanel from '@/components/routines/RelatedTasksPanel.vue';
|
2026-10-13 21:05:17 +03:30
|
|
|
|
import SmsComposeModal from '@/components/SmsComposeModal.vue';
|
2026-09-27 17:05:11 +03:30
|
|
|
|
import type {
|
2026-10-11 14:12:36 +03:30
|
|
|
|
ActivityDto,
|
2026-09-27 17:05:11 +03:30
|
|
|
|
ConvertLeadResult,
|
|
|
|
|
|
LeadDto,
|
|
|
|
|
|
LeadEventDto,
|
|
|
|
|
|
LeadStageDto,
|
|
|
|
|
|
OrgUserDto,
|
2026-10-11 14:12:36 +03:30
|
|
|
|
PaginatedResponse,
|
2026-09-27 17:05:11 +03:30
|
|
|
|
} from '@mento/shared';
|
2026-10-11 14:12:36 +03:30
|
|
|
|
import { ACTIVITY_TYPE_LABELS } from '@mento/shared';
|
2026-09-27 17:05:11 +03:30
|
|
|
|
|
|
|
|
|
|
type LeadDetail = LeadDto & { events: LeadEventDto[] };
|
|
|
|
|
|
|
|
|
|
|
|
const route = useRoute();
|
|
|
|
|
|
const router = useRouter();
|
|
|
|
|
|
const auth = useAuthStore();
|
|
|
|
|
|
const isAdmin = computed(() => canManageSettings(auth.user?.role));
|
|
|
|
|
|
|
|
|
|
|
|
const id = computed(() => String(route.params.id));
|
|
|
|
|
|
const loading = ref(false);
|
|
|
|
|
|
const busy = ref(false);
|
|
|
|
|
|
const error = ref('');
|
|
|
|
|
|
const okMsg = ref('');
|
|
|
|
|
|
const lead = ref<LeadDetail | null>(null);
|
|
|
|
|
|
const stages = ref<LeadStageDto[]>([]);
|
|
|
|
|
|
const users = ref<OrgUserDto[]>([]);
|
2026-10-11 14:12:36 +03:30
|
|
|
|
const activities = ref<ActivityDto[]>([]);
|
2026-09-27 17:05:11 +03:30
|
|
|
|
|
|
|
|
|
|
const stageId = ref('');
|
|
|
|
|
|
const assigneeId = ref('');
|
|
|
|
|
|
const callOutcome = ref('');
|
|
|
|
|
|
const callBody = ref('');
|
|
|
|
|
|
const callFollowUp = ref('');
|
|
|
|
|
|
const noteBody = ref('');
|
|
|
|
|
|
const followUpAt = ref('');
|
|
|
|
|
|
const followUpBody = ref('');
|
2026-10-04 12:01:21 +03:30
|
|
|
|
const editing = ref(false);
|
2026-10-13 14:45:29 +03:30
|
|
|
|
const moreOpen = ref(false);
|
2026-10-13 21:05:17 +03:30
|
|
|
|
const smsOpen = ref(false);
|
2026-10-13 14:45:29 +03:30
|
|
|
|
const copied = ref(false);
|
|
|
|
|
|
const narrow = ref(typeof window !== 'undefined' && window.matchMedia('(max-width: 900px)').matches);
|
|
|
|
|
|
let narrowMq: MediaQueryList | null = null;
|
2026-10-04 12:01:21 +03:30
|
|
|
|
const editForm = ref({
|
|
|
|
|
|
title: '',
|
|
|
|
|
|
contactName: '',
|
|
|
|
|
|
mobile: '',
|
|
|
|
|
|
phone: '',
|
|
|
|
|
|
city: '',
|
|
|
|
|
|
region: '',
|
|
|
|
|
|
source: '',
|
|
|
|
|
|
instagram: '',
|
2026-10-13 18:04:53 +03:30
|
|
|
|
birthDate: '',
|
2026-10-04 12:01:21 +03:30
|
|
|
|
trade: '',
|
|
|
|
|
|
notes: '',
|
|
|
|
|
|
});
|
2026-09-27 17:05:11 +03:30
|
|
|
|
|
|
|
|
|
|
function formatDate(value: string | null | undefined) {
|
|
|
|
|
|
if (!value) return '—';
|
|
|
|
|
|
try {
|
|
|
|
|
|
return formatJalaliNumeric(new Date(value));
|
|
|
|
|
|
} catch {
|
|
|
|
|
|
return value;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function apiError(e: unknown, fallback: string) {
|
|
|
|
|
|
if (axios.isAxiosError(e)) {
|
|
|
|
|
|
const msg = (e.response?.data as { message?: string | string[] })?.message;
|
|
|
|
|
|
if (Array.isArray(msg)) return msg.join('، ');
|
|
|
|
|
|
if (msg) return msg;
|
|
|
|
|
|
if (!e.response) return 'اتصال به API برقرار نشد.';
|
|
|
|
|
|
}
|
|
|
|
|
|
return fallback;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async function load() {
|
|
|
|
|
|
loading.value = true;
|
|
|
|
|
|
error.value = '';
|
|
|
|
|
|
try {
|
2026-10-11 14:12:36 +03:30
|
|
|
|
const [leadRes, stagesRes, usersRes, actRes] = await Promise.all([
|
2026-09-27 17:05:11 +03:30
|
|
|
|
api.get<LeadDetail>(`/leads/${id.value}`),
|
|
|
|
|
|
api.get<LeadStageDto[]>('/leads/stages'),
|
|
|
|
|
|
api.get<OrgUserDto[]>('/users').catch(() => ({ data: [] as OrgUserDto[] })),
|
2026-10-11 14:12:36 +03:30
|
|
|
|
api
|
|
|
|
|
|
.get<PaginatedResponse<ActivityDto>>('/activities', {
|
|
|
|
|
|
params: { filter: 'all', leadId: id.value, limit: 30 },
|
|
|
|
|
|
})
|
|
|
|
|
|
.catch(() => null),
|
2026-09-27 17:05:11 +03:30
|
|
|
|
]);
|
|
|
|
|
|
lead.value = leadRes.data;
|
2026-10-11 14:12:36 +03:30
|
|
|
|
activities.value = actRes?.data.items ?? [];
|
2026-09-27 17:05:11 +03:30
|
|
|
|
stages.value = stagesRes.data;
|
|
|
|
|
|
users.value = usersRes.data.filter((u) => u.isActive !== false);
|
|
|
|
|
|
stageId.value = leadRes.data.stageId;
|
|
|
|
|
|
assigneeId.value = leadRes.data.assigneeId ?? '';
|
|
|
|
|
|
} catch (e) {
|
|
|
|
|
|
error.value = apiError(e, 'بارگذاری لید ناموفق بود.');
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
loading.value = false;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-10-04 12:01:21 +03:30
|
|
|
|
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 ?? '',
|
2026-10-13 18:04:53 +03:30
|
|
|
|
birthDate: lead.value.birthDate ? formatLocalDateInput(new Date(lead.value.birthDate)) : '',
|
2026-10-04 12:01:21 +03:30
|
|
|
|
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,
|
2026-10-13 18:04:53 +03:30
|
|
|
|
birthDate: editForm.value.birthDate
|
|
|
|
|
|
? parseLocalInput(editForm.value.birthDate)?.toISOString() ?? null
|
|
|
|
|
|
: null,
|
2026-10-04 12:01:21 +03:30
|
|
|
|
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;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-09-27 17:05:11 +03:30
|
|
|
|
async function changeStage() {
|
|
|
|
|
|
if (!stageId.value || stageId.value === lead.value?.stageId) return;
|
|
|
|
|
|
busy.value = true;
|
|
|
|
|
|
error.value = '';
|
|
|
|
|
|
try {
|
|
|
|
|
|
await api.patch(`/leads/${id.value}/move`, { stageId: stageId.value });
|
|
|
|
|
|
okMsg.value = 'وضعیت بهروز شد.';
|
|
|
|
|
|
await load();
|
|
|
|
|
|
} catch (e) {
|
|
|
|
|
|
error.value = apiError(e, 'تغییر وضعیت ناموفق بود.');
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
busy.value = false;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async function assignLead() {
|
|
|
|
|
|
if (!assigneeId.value) return;
|
|
|
|
|
|
busy.value = true;
|
|
|
|
|
|
error.value = '';
|
|
|
|
|
|
try {
|
|
|
|
|
|
await api.post('/leads/assign', {
|
|
|
|
|
|
leadIds: [id.value],
|
|
|
|
|
|
assigneeId: assigneeId.value,
|
|
|
|
|
|
});
|
|
|
|
|
|
okMsg.value = 'کارشناس تخصیص داده شد.';
|
|
|
|
|
|
await load();
|
|
|
|
|
|
} catch (e) {
|
|
|
|
|
|
error.value = apiError(e, 'تخصیص ناموفق بود.');
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
busy.value = false;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-10-13 14:45:29 +03:30
|
|
|
|
function localToIso(value: string): string | undefined {
|
|
|
|
|
|
const parsed = parseLocalInput(value);
|
|
|
|
|
|
if (!parsed || Number.isNaN(parsed.getTime())) return undefined;
|
|
|
|
|
|
return parsed.toISOString();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-09-27 17:05:11 +03:30
|
|
|
|
async function logCall() {
|
|
|
|
|
|
busy.value = true;
|
|
|
|
|
|
error.value = '';
|
|
|
|
|
|
try {
|
|
|
|
|
|
await api.post(`/leads/${id.value}/call`, {
|
|
|
|
|
|
outcome: callOutcome.value || undefined,
|
|
|
|
|
|
body: callBody.value || undefined,
|
2026-10-13 14:45:29 +03:30
|
|
|
|
nextFollowUpAt: callFollowUp.value ? localToIso(callFollowUp.value) : undefined,
|
2026-09-27 17:05:11 +03:30
|
|
|
|
});
|
|
|
|
|
|
callOutcome.value = '';
|
|
|
|
|
|
callBody.value = '';
|
|
|
|
|
|
callFollowUp.value = '';
|
|
|
|
|
|
okMsg.value = 'تماس ثبت شد.';
|
|
|
|
|
|
await load();
|
|
|
|
|
|
} catch (e) {
|
|
|
|
|
|
error.value = apiError(e, 'ثبت تماس ناموفق بود.');
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
busy.value = false;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async function addNote() {
|
|
|
|
|
|
if (!noteBody.value.trim()) return;
|
|
|
|
|
|
busy.value = true;
|
|
|
|
|
|
error.value = '';
|
|
|
|
|
|
try {
|
|
|
|
|
|
await api.post(`/leads/${id.value}/notes`, { body: noteBody.value.trim() });
|
|
|
|
|
|
noteBody.value = '';
|
|
|
|
|
|
okMsg.value = 'یادداشت ثبت شد.';
|
|
|
|
|
|
await load();
|
|
|
|
|
|
} catch (e) {
|
|
|
|
|
|
error.value = apiError(e, 'ثبت یادداشت ناموفق بود.');
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
busy.value = false;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async function setFollowUp() {
|
2026-10-13 14:45:29 +03:30
|
|
|
|
const nextFollowUpAt = localToIso(followUpAt.value);
|
|
|
|
|
|
if (!nextFollowUpAt) return;
|
2026-09-27 17:05:11 +03:30
|
|
|
|
busy.value = true;
|
|
|
|
|
|
error.value = '';
|
|
|
|
|
|
try {
|
|
|
|
|
|
await api.post(`/leads/${id.value}/follow-up`, {
|
2026-10-13 14:45:29 +03:30
|
|
|
|
nextFollowUpAt,
|
2026-09-27 17:05:11 +03:30
|
|
|
|
body: followUpBody.value || undefined,
|
|
|
|
|
|
});
|
|
|
|
|
|
followUpAt.value = '';
|
|
|
|
|
|
followUpBody.value = '';
|
|
|
|
|
|
okMsg.value = 'پیگیری تنظیم شد.';
|
|
|
|
|
|
await load();
|
|
|
|
|
|
} catch (e) {
|
|
|
|
|
|
error.value = apiError(e, 'تنظیم پیگیری ناموفق بود.');
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
busy.value = false;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async function convertLead() {
|
|
|
|
|
|
if (!confirm('این لید به مشتری تبدیل شود؟')) return;
|
|
|
|
|
|
busy.value = true;
|
|
|
|
|
|
error.value = '';
|
|
|
|
|
|
try {
|
|
|
|
|
|
const { data } = await api.post<ConvertLeadResult>(`/leads/${id.value}/convert`, {
|
|
|
|
|
|
createIfMissing: true,
|
|
|
|
|
|
});
|
|
|
|
|
|
okMsg.value = data.createdPerson
|
|
|
|
|
|
? 'لید تبدیل و شخص جدید ساخته شد.'
|
|
|
|
|
|
: 'لید به مشتری موجود متصل شد.';
|
|
|
|
|
|
await load();
|
|
|
|
|
|
if (data.personId) {
|
|
|
|
|
|
void router.push({ name: 'person-detail', params: { id: data.personId } });
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (e) {
|
|
|
|
|
|
error.value = apiError(e, 'تبدیل به مشتری ناموفق بود.');
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
busy.value = false;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async function archiveLead() {
|
|
|
|
|
|
if (!confirm('این لید بایگانی شود؟')) return;
|
|
|
|
|
|
busy.value = true;
|
|
|
|
|
|
error.value = '';
|
|
|
|
|
|
try {
|
|
|
|
|
|
await api.post('/leads/archive', { leadIds: [id.value] });
|
|
|
|
|
|
okMsg.value = 'لید بایگانی شد.';
|
|
|
|
|
|
void router.push({ name: 'leads' });
|
|
|
|
|
|
} catch (e) {
|
|
|
|
|
|
error.value = apiError(e, 'بایگانی ناموفق بود.');
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
busy.value = false;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-10-13 14:45:29 +03:30
|
|
|
|
const telHref = computed(() => (lead.value?.mobile ? `tel:${lead.value.mobile}` : undefined));
|
|
|
|
|
|
|
|
|
|
|
|
const feed = computed(() => {
|
|
|
|
|
|
const rows: Array<{ key: string; title: string; by: string; at: string }> = [];
|
|
|
|
|
|
for (const ev of lead.value?.events ?? []) {
|
|
|
|
|
|
rows.push({
|
|
|
|
|
|
key: `ev-${ev.id}`,
|
|
|
|
|
|
title: ev.title,
|
|
|
|
|
|
by: ev.body?.trim() || 'توسط سیستم',
|
|
|
|
|
|
at: ev.createdAt,
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
for (const a of activities.value) {
|
|
|
|
|
|
const who = a.createdBy?.fullName || a.assignee?.fullName;
|
|
|
|
|
|
rows.push({
|
|
|
|
|
|
key: `act-${a.id}`,
|
|
|
|
|
|
title: a.title,
|
|
|
|
|
|
by: who ? `توسط ${who}` : 'توسط سیستم',
|
|
|
|
|
|
at: a.createdAt,
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
return rows.sort((a, b) => +new Date(b.at) - +new Date(a.at)).slice(0, 4);
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
function feedParts(iso: string) {
|
|
|
|
|
|
const d = new Date(iso);
|
|
|
|
|
|
if (Number.isNaN(d.getTime())) return { day: '—', time: '' };
|
|
|
|
|
|
return { day: formatJalaliNumeric(d), time: formatClockFa(d) };
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async function copyMobile() {
|
|
|
|
|
|
const value = lead.value?.mobile;
|
|
|
|
|
|
if (!value) return;
|
|
|
|
|
|
try {
|
|
|
|
|
|
await navigator.clipboard.writeText(value);
|
|
|
|
|
|
copied.value = true;
|
|
|
|
|
|
window.setTimeout(() => {
|
|
|
|
|
|
copied.value = false;
|
|
|
|
|
|
}, 1400);
|
|
|
|
|
|
} catch {
|
|
|
|
|
|
copied.value = false;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function syncNarrow() {
|
|
|
|
|
|
narrow.value = window.matchMedia('(max-width: 900px)').matches;
|
|
|
|
|
|
if (!narrow.value) moreOpen.value = false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
onMounted(() => {
|
|
|
|
|
|
narrowMq = window.matchMedia('(max-width: 900px)');
|
|
|
|
|
|
narrowMq.addEventListener('change', syncNarrow);
|
|
|
|
|
|
void load();
|
|
|
|
|
|
});
|
|
|
|
|
|
onBeforeUnmount(() => narrowMq?.removeEventListener('change', syncNarrow));
|
2026-09-27 17:05:11 +03:30
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
|
|
<template>
|
2026-10-13 14:45:29 +03:30
|
|
|
|
<section class="page" :class="{ editing, 'more-open': moreOpen }">
|
|
|
|
|
|
<div class="desk-only">
|
2026-09-27 17:05:11 +03:30
|
|
|
|
<UiPageHeader
|
|
|
|
|
|
:title="lead?.title || 'جزئیات لید'"
|
|
|
|
|
|
:lead="lead ? `موبایل ${lead.mobile}` : 'در حال بارگذاری…'"
|
|
|
|
|
|
:back-to="{ name: 'leads' }"
|
|
|
|
|
|
>
|
|
|
|
|
|
<template #actions>
|
2026-10-13 21:05:17 +03:30
|
|
|
|
<UiButton
|
|
|
|
|
|
v-if="lead && !editing"
|
|
|
|
|
|
variant="secondary"
|
|
|
|
|
|
size="sm"
|
|
|
|
|
|
:disabled="busy || !lead.mobile"
|
|
|
|
|
|
@click="smsOpen = true"
|
|
|
|
|
|
>
|
|
|
|
|
|
پیامک
|
|
|
|
|
|
</UiButton>
|
2026-10-04 12:01:21 +03:30
|
|
|
|
<UiButton
|
|
|
|
|
|
v-if="lead && !editing"
|
|
|
|
|
|
variant="secondary"
|
|
|
|
|
|
size="sm"
|
|
|
|
|
|
:disabled="busy"
|
|
|
|
|
|
@click="startEdit"
|
|
|
|
|
|
>
|
|
|
|
|
|
ویرایش
|
|
|
|
|
|
</UiButton>
|
2026-09-27 17:05:11 +03:30
|
|
|
|
<UiButton
|
|
|
|
|
|
v-if="lead && !lead.convertedPersonId"
|
|
|
|
|
|
variant="primary"
|
|
|
|
|
|
size="sm"
|
|
|
|
|
|
:disabled="busy"
|
|
|
|
|
|
@click="convertLead"
|
|
|
|
|
|
>
|
|
|
|
|
|
تبدیل به مشتری
|
|
|
|
|
|
</UiButton>
|
|
|
|
|
|
<UiButton
|
|
|
|
|
|
v-if="lead && !lead.archivedAt"
|
|
|
|
|
|
variant="ghost"
|
|
|
|
|
|
size="sm"
|
|
|
|
|
|
:disabled="busy"
|
|
|
|
|
|
@click="archiveLead"
|
|
|
|
|
|
>
|
|
|
|
|
|
بایگانی
|
|
|
|
|
|
</UiButton>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
</UiPageHeader>
|
2026-10-13 14:45:29 +03:30
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<button
|
|
|
|
|
|
v-if="moreOpen"
|
|
|
|
|
|
class="sheet-back"
|
|
|
|
|
|
type="button"
|
|
|
|
|
|
aria-label="بستن"
|
|
|
|
|
|
@click="moreOpen = false"
|
|
|
|
|
|
/>
|
2026-09-27 17:05:11 +03:30
|
|
|
|
|
|
|
|
|
|
<p v-if="okMsg" class="ok">{{ okMsg }}</p>
|
|
|
|
|
|
<p v-if="error" class="error">{{ error }}</p>
|
|
|
|
|
|
<p v-else-if="loading" class="muted">در حال بارگذاری…</p>
|
|
|
|
|
|
|
|
|
|
|
|
<template v-else-if="lead">
|
2026-10-13 14:45:29 +03:30
|
|
|
|
<div v-if="narrow" class="mob">
|
|
|
|
|
|
<article class="m-hero">
|
|
|
|
|
|
<div class="m-top">
|
|
|
|
|
|
<span class="m-mark" aria-hidden="true">
|
|
|
|
|
|
<UiIcon name="bag" :size="22" />
|
|
|
|
|
|
</span>
|
|
|
|
|
|
<div class="m-id">
|
|
|
|
|
|
<div class="m-name-row">
|
|
|
|
|
|
<h2>{{ lead.title }}</h2>
|
|
|
|
|
|
<span class="m-pill"><i />{{ lead.stageName }}</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<button class="m-phone" type="button" @click="copyMobile">
|
|
|
|
|
|
<span dir="ltr">{{ toFaDigits(lead.mobile) }}</span>
|
|
|
|
|
|
<svg viewBox="0 0 24 24" width="15" height="15" fill="none" stroke="currentColor" stroke-width="1.8" aria-hidden="true">
|
|
|
|
|
|
<rect x="8" y="8" width="11" height="11" rx="2" />
|
|
|
|
|
|
<path d="M5 15V6a2 2 0 0 1 2-2h9" />
|
|
|
|
|
|
</svg>
|
|
|
|
|
|
<em v-if="copied">کپی شد</em>
|
|
|
|
|
|
</button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<RouterLink class="m-back" :to="{ name: 'leads' }" aria-label="بازگشت">
|
|
|
|
|
|
<UiIcon name="chevron-left" :size="20" />
|
|
|
|
|
|
</RouterLink>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="m-actions" :class="{ converted: !!lead.convertedPersonId }">
|
|
|
|
|
|
<a class="m-btn mint" :href="telHref">
|
|
|
|
|
|
<UiIcon name="phone" :size="16" />
|
|
|
|
|
|
تماس
|
|
|
|
|
|
</a>
|
2026-10-13 21:05:17 +03:30
|
|
|
|
<button class="m-btn" type="button" @click="smsOpen = true">
|
|
|
|
|
|
<UiIcon name="chat" :size="16" />
|
|
|
|
|
|
پیامک
|
|
|
|
|
|
</button>
|
2026-10-13 14:45:29 +03:30
|
|
|
|
<button class="m-btn" type="button" @click="startEdit">
|
|
|
|
|
|
<UiIcon name="pencil" :size="16" />
|
|
|
|
|
|
ویرایش
|
|
|
|
|
|
</button>
|
2026-10-13 21:05:17 +03:30
|
|
|
|
<button class="m-btn icon" type="button" aria-label="بیشتر" @click="moreOpen = true">
|
|
|
|
|
|
<UiIcon name="dots" :size="18" />
|
|
|
|
|
|
</button>
|
2026-10-13 14:45:29 +03:30
|
|
|
|
<button
|
|
|
|
|
|
v-if="!lead.convertedPersonId"
|
|
|
|
|
|
class="m-btn solid"
|
|
|
|
|
|
type="button"
|
|
|
|
|
|
:disabled="busy"
|
|
|
|
|
|
@click="convertLead"
|
|
|
|
|
|
>
|
|
|
|
|
|
<UiIcon name="user-plus" :size="16" />
|
|
|
|
|
|
تبدیل به مشتری
|
|
|
|
|
|
</button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</article>
|
|
|
|
|
|
|
|
|
|
|
|
<article v-if="!editing" class="m-card">
|
|
|
|
|
|
<header class="m-card-h">
|
|
|
|
|
|
<h3>اطلاعات لید</h3>
|
|
|
|
|
|
<button class="m-ico" type="button" aria-label="ویرایش اطلاعات" @click="startEdit">
|
|
|
|
|
|
<UiIcon name="document" :size="16" />
|
|
|
|
|
|
</button>
|
|
|
|
|
|
</header>
|
|
|
|
|
|
<dl class="m-grid">
|
|
|
|
|
|
<div class="cell">
|
|
|
|
|
|
<dt><UiIcon name="user" :size="14" /> نام</dt>
|
|
|
|
|
|
<dd>{{ lead.title }}</dd>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="cell">
|
|
|
|
|
|
<dt><UiIcon name="users" :size="14" /> مالک / مخاطب</dt>
|
|
|
|
|
|
<dd>{{ lead.contactName || '—' }}</dd>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="cell hot">
|
|
|
|
|
|
<dt><UiIcon name="phone" :size="14" /> موبایل</dt>
|
|
|
|
|
|
<dd dir="ltr">{{ toFaDigits(lead.mobile) }}</dd>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="cell">
|
|
|
|
|
|
<dt><UiIcon name="phone" :size="14" /> تلفن</dt>
|
|
|
|
|
|
<dd dir="ltr">{{ lead.phone ? toFaDigits(lead.phone) : '—' }}</dd>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="cell">
|
|
|
|
|
|
<dt><UiIcon name="pin" :size="14" /> شهر</dt>
|
|
|
|
|
|
<dd>{{ lead.city || '—' }}</dd>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="cell">
|
|
|
|
|
|
<dt><UiIcon name="grid" :size="14" /> منطقه</dt>
|
|
|
|
|
|
<dd>{{ lead.region || '—' }}</dd>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="cell">
|
|
|
|
|
|
<dt><UiIcon name="bag" :size="14" /> صنف</dt>
|
|
|
|
|
|
<dd>{{ lead.trade || '—' }}</dd>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="cell">
|
|
|
|
|
|
<dt><UiIcon name="camera" :size="14" /> اینستاگرام</dt>
|
|
|
|
|
|
<dd dir="ltr">{{ lead.instagram || '—' }}</dd>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="cell">
|
|
|
|
|
|
<dt><UiIcon name="target" :size="14" /> منبع</dt>
|
|
|
|
|
|
<dd>{{ lead.source || '—' }}</dd>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="cell">
|
|
|
|
|
|
<dt><UiIcon name="flag" :size="14" /> وضعیت</dt>
|
|
|
|
|
|
<dd><span class="m-pill"><i />{{ lead.stageName }}</span></dd>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="cell">
|
|
|
|
|
|
<dt><UiIcon name="user" :size="14" /> کارشناس</dt>
|
|
|
|
|
|
<dd>{{ lead.assigneeName || '—' }}</dd>
|
|
|
|
|
|
</div>
|
2026-10-13 18:04:53 +03:30
|
|
|
|
<div class="cell">
|
|
|
|
|
|
<dt><UiIcon name="calendar" :size="14" /> تاریخ تولد</dt>
|
|
|
|
|
|
<dd>{{ formatDate(lead.birthDate) }}</dd>
|
|
|
|
|
|
</div>
|
2026-10-13 14:45:29 +03:30
|
|
|
|
<div class="cell">
|
|
|
|
|
|
<dt><UiIcon name="calendar" :size="14" /> تاریخ ثبت</dt>
|
|
|
|
|
|
<dd>{{ formatDate(lead.createdAt) }}</dd>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="cell">
|
|
|
|
|
|
<dt><UiIcon name="chart" :size="14" /> روز در مرحله</dt>
|
|
|
|
|
|
<dd>{{ toFaDigits(lead.daysInStage) }}</dd>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="cell">
|
|
|
|
|
|
<dt><UiIcon name="clock" :size="14" /> پیگیری بعدی</dt>
|
|
|
|
|
|
<dd>{{ formatDate(lead.nextFollowUpAt) }}</dd>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div v-if="lead.addresses?.length" class="cell wide">
|
|
|
|
|
|
<dt><UiIcon name="pin" :size="14" /> آدرسها</dt>
|
|
|
|
|
|
<dd>
|
|
|
|
|
|
<p v-for="(addr, i) in lead.addresses" :key="i" class="addr-line">{{ addr }}</p>
|
|
|
|
|
|
</dd>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</dl>
|
|
|
|
|
|
</article>
|
|
|
|
|
|
|
|
|
|
|
|
<article v-if="!editing" class="m-card">
|
|
|
|
|
|
<header class="m-card-h">
|
|
|
|
|
|
<h3>یادداشت</h3>
|
|
|
|
|
|
<button class="m-ico" type="button" aria-label="ویرایش یادداشت" @click="startEdit">
|
|
|
|
|
|
<UiIcon name="pencil" :size="16" />
|
|
|
|
|
|
</button>
|
|
|
|
|
|
</header>
|
|
|
|
|
|
<p class="m-note">{{ lead.notes?.trim() || 'یادداشتی ثبت نشده است.' }}</p>
|
|
|
|
|
|
</article>
|
|
|
|
|
|
|
|
|
|
|
|
<RelatedTasksPanel
|
|
|
|
|
|
v-if="!editing"
|
|
|
|
|
|
class="fancy"
|
|
|
|
|
|
:entity="{ type: 'lead', id: lead.id, label: lead.title }"
|
|
|
|
|
|
empty-title="هنوز کاری برای این لید ثبت نشده."
|
|
|
|
|
|
empty-hint="برای پیگیری بهتر، یک تسک ایجاد کنید."
|
|
|
|
|
|
/>
|
|
|
|
|
|
|
|
|
|
|
|
<article v-if="!editing" class="m-card">
|
|
|
|
|
|
<header class="m-card-h">
|
|
|
|
|
|
<h3>
|
|
|
|
|
|
<span class="m-ico static"><UiIcon name="clock" :size="16" /></span>
|
|
|
|
|
|
فعالیتها
|
|
|
|
|
|
</h3>
|
|
|
|
|
|
<RouterLink class="m-add" :to="{ name: 'activity-new' }">
|
|
|
|
|
|
ایجاد فعالیت
|
|
|
|
|
|
<UiIcon name="plus" :size="14" />
|
|
|
|
|
|
</RouterLink>
|
|
|
|
|
|
</header>
|
|
|
|
|
|
<ol v-if="feed.length" class="m-feed">
|
|
|
|
|
|
<li v-for="row in feed" :key="row.key">
|
|
|
|
|
|
<span class="m-feed-ic"><UiIcon name="document" :size="16" /></span>
|
|
|
|
|
|
<div class="m-feed-body">
|
|
|
|
|
|
<strong>{{ row.title }}</strong>
|
|
|
|
|
|
<small>{{ row.by }}</small>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<time>
|
|
|
|
|
|
<b>{{ feedParts(row.at).day }}</b>
|
|
|
|
|
|
<span>{{ feedParts(row.at).time }}</span>
|
|
|
|
|
|
</time>
|
|
|
|
|
|
</li>
|
|
|
|
|
|
</ol>
|
|
|
|
|
|
<p v-else class="m-note">هنوز فعالیتی ثبت نشده است.</p>
|
|
|
|
|
|
<RouterLink class="m-all" :to="{ name: 'activities' }">
|
|
|
|
|
|
مشاهده همه فعالیتها
|
|
|
|
|
|
<UiIcon name="arrow-left" :size="14" />
|
|
|
|
|
|
</RouterLink>
|
|
|
|
|
|
</article>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
2026-09-27 17:05:11 +03:30
|
|
|
|
<div class="layout">
|
|
|
|
|
|
<div class="main">
|
2026-10-13 14:45:29 +03:30
|
|
|
|
<UiCard class="info-card" title="اطلاعات لید">
|
2026-10-04 12:01:21 +03:30
|
|
|
|
<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>
|
2026-10-13 18:04:53 +03:30
|
|
|
|
<UiField label="تاریخ تولد">
|
|
|
|
|
|
<JalaliDateInput v-model="editForm.birthDate" mode="date" />
|
|
|
|
|
|
</UiField>
|
2026-10-04 12:01:21 +03:30
|
|
|
|
<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">
|
2026-09-27 17:05:11 +03:30
|
|
|
|
<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>
|
|
|
|
|
|
<div><dt>تلفن</dt><dd dir="ltr">{{ lead.phone || '—' }}</dd></div>
|
|
|
|
|
|
<div><dt>شهر</dt><dd>{{ lead.city || '—' }}</dd></div>
|
|
|
|
|
|
<div><dt>منطقه</dt><dd>{{ lead.region || '—' }}</dd></div>
|
2026-10-03 12:29:11 +03:30
|
|
|
|
<div><dt>اینستاگرام</dt><dd dir="ltr">{{ lead.instagram || '—' }}</dd></div>
|
2026-09-27 17:05:11 +03:30
|
|
|
|
<div><dt>صنف</dt><dd>{{ lead.trade || '—' }}</dd></div>
|
|
|
|
|
|
<div><dt>منبع</dt><dd>{{ lead.source || '—' }}</dd></div>
|
|
|
|
|
|
<div><dt>وضعیت</dt><dd>{{ lead.stageName }}</dd></div>
|
|
|
|
|
|
<div><dt>کارشناس</dt><dd>{{ lead.assigneeName || '—' }}</dd></div>
|
2026-10-13 18:04:53 +03:30
|
|
|
|
<div><dt>تاریخ تولد</dt><dd>{{ formatDate(lead.birthDate) }}</dd></div>
|
2026-10-03 12:29:11 +03:30
|
|
|
|
<div><dt>تاریخ ثبت</dt><dd>{{ formatDate(lead.createdAt) }}</dd></div>
|
2026-09-27 17:05:11 +03:30
|
|
|
|
<div><dt>آخرین تماس</dt><dd>{{ formatDate(lead.lastContactAt) }}</dd></div>
|
|
|
|
|
|
<div><dt>پیگیری بعدی</dt><dd>{{ formatDate(lead.nextFollowUpAt) }}</dd></div>
|
|
|
|
|
|
<div><dt>روز در مرحله</dt><dd>{{ toFaDigits(lead.daysInStage) }}</dd></div>
|
2026-10-03 12:29:11 +03:30
|
|
|
|
<div v-if="lead.addresses?.length" class="full">
|
|
|
|
|
|
<dt>آدرسها</dt>
|
|
|
|
|
|
<dd>
|
|
|
|
|
|
<p v-for="(addr, i) in lead.addresses" :key="i" class="addr-line">{{ addr }}</p>
|
|
|
|
|
|
</dd>
|
|
|
|
|
|
</div>
|
2026-09-27 17:05:11 +03:30
|
|
|
|
<div v-if="lead.notes" class="full">
|
|
|
|
|
|
<dt>یادداشت</dt>
|
|
|
|
|
|
<dd>{{ lead.notes }}</dd>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</dl>
|
|
|
|
|
|
</UiCard>
|
|
|
|
|
|
|
2026-10-13 14:45:29 +03:30
|
|
|
|
<RelatedTasksPanel
|
|
|
|
|
|
v-if="!narrow"
|
|
|
|
|
|
:entity="{ type: 'lead', id: lead.id, label: lead.title }"
|
|
|
|
|
|
/>
|
2026-10-06 19:54:38 +03:30
|
|
|
|
|
2026-10-13 14:45:29 +03:30
|
|
|
|
<UiCard v-if="activities.length" class="desk-only" title="فعالیتهای مرتبط">
|
2026-10-11 14:12:36 +03:30
|
|
|
|
<ul class="lead-acts">
|
|
|
|
|
|
<li v-for="a in activities" :key="a.id">
|
|
|
|
|
|
<RouterLink :to="{ name: 'activity-detail', params: { id: a.id } }">
|
|
|
|
|
|
<strong>{{ a.title }}</strong>
|
|
|
|
|
|
</RouterLink>
|
|
|
|
|
|
<span class="muted">
|
|
|
|
|
|
{{ a.typeDef?.name || ACTIVITY_TYPE_LABELS[a.type] }}
|
|
|
|
|
|
· {{ a.assignee?.fullName || 'بدون مسئول' }}
|
|
|
|
|
|
· {{ formatDate(a.dueAt) }}
|
|
|
|
|
|
· {{ a.status === 'done' ? 'انجام شد' : a.status === 'canceled' ? 'لغو شده' : 'باز' }}
|
|
|
|
|
|
</span>
|
|
|
|
|
|
</li>
|
|
|
|
|
|
</ul>
|
|
|
|
|
|
</UiCard>
|
|
|
|
|
|
|
2026-10-13 14:45:29 +03:30
|
|
|
|
<UiCard class="desk-only" title="جدول زمانی">
|
2026-09-27 17:05:11 +03:30
|
|
|
|
<ul v-if="lead.events.length" class="timeline">
|
|
|
|
|
|
<li v-for="ev in lead.events" :key="ev.id">
|
|
|
|
|
|
<strong>{{ ev.title }}</strong>
|
|
|
|
|
|
<span class="muted">{{ formatDate(ev.createdAt) }}</span>
|
|
|
|
|
|
<p v-if="ev.body">{{ ev.body }}</p>
|
|
|
|
|
|
</li>
|
|
|
|
|
|
</ul>
|
|
|
|
|
|
<UiEmptyState
|
|
|
|
|
|
v-else
|
|
|
|
|
|
title="رویدادی ثبت نشده."
|
|
|
|
|
|
description="تماس، یادداشت یا پیگیری اضافه کنید."
|
|
|
|
|
|
/>
|
|
|
|
|
|
</UiCard>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<aside class="side">
|
2026-10-13 14:45:29 +03:30
|
|
|
|
<button class="sheet-close" type="button" @click="moreOpen = false">بستن</button>
|
2026-10-13 21:05:17 +03:30
|
|
|
|
<UiCard title="پیامک">
|
|
|
|
|
|
<UiButton size="sm" :disabled="!lead.mobile" @click="smsOpen = true; moreOpen = false">
|
|
|
|
|
|
ارسال پیامک
|
|
|
|
|
|
</UiButton>
|
|
|
|
|
|
</UiCard>
|
2026-09-27 17:05:11 +03:30
|
|
|
|
<UiCard title="تغییر وضعیت">
|
|
|
|
|
|
<UiField label="وضعیت">
|
|
|
|
|
|
<UiSelect v-model="stageId">
|
|
|
|
|
|
<option v-for="s in stages" :key="s.id" :value="s.id">
|
|
|
|
|
|
{{ s.name }}
|
|
|
|
|
|
</option>
|
|
|
|
|
|
</UiSelect>
|
|
|
|
|
|
</UiField>
|
|
|
|
|
|
<UiButton size="sm" :disabled="busy" @click="changeStage">اعمال</UiButton>
|
|
|
|
|
|
</UiCard>
|
|
|
|
|
|
|
|
|
|
|
|
<UiCard v-if="isAdmin" title="تخصیص کارشناس">
|
|
|
|
|
|
<UiField label="کارشناس">
|
|
|
|
|
|
<UiSelect v-model="assigneeId">
|
|
|
|
|
|
<option value="">انتخاب…</option>
|
|
|
|
|
|
<option v-for="u in users" :key="u.id" :value="u.id">
|
|
|
|
|
|
{{ u.fullName }}
|
|
|
|
|
|
</option>
|
|
|
|
|
|
</UiSelect>
|
|
|
|
|
|
</UiField>
|
|
|
|
|
|
<UiButton size="sm" :disabled="busy || !assigneeId" @click="assignLead">
|
|
|
|
|
|
تخصیص
|
|
|
|
|
|
</UiButton>
|
|
|
|
|
|
</UiCard>
|
|
|
|
|
|
|
|
|
|
|
|
<UiCard title="ثبت تماس">
|
|
|
|
|
|
<UiField label="نتیجه">
|
|
|
|
|
|
<UiInput v-model="callOutcome" placeholder="مثلاً پاسخ داد / اشغال" />
|
|
|
|
|
|
</UiField>
|
|
|
|
|
|
<UiField label="توضیح">
|
2026-10-13 14:45:29 +03:30
|
|
|
|
<UiTextarea v-model="callBody" :rows="4" placeholder="خلاصه مکالمه" />
|
2026-09-27 17:05:11 +03:30
|
|
|
|
</UiField>
|
|
|
|
|
|
<UiField label="پیگیری بعدی">
|
2026-10-13 14:45:29 +03:30
|
|
|
|
<JalaliDateInput v-model="callFollowUp" mode="datetime" />
|
2026-09-27 17:05:11 +03:30
|
|
|
|
</UiField>
|
|
|
|
|
|
<UiButton size="sm" :disabled="busy" @click="logCall">ثبت تماس</UiButton>
|
|
|
|
|
|
</UiCard>
|
|
|
|
|
|
|
|
|
|
|
|
<UiCard title="یادداشت">
|
|
|
|
|
|
<UiField label="متن">
|
|
|
|
|
|
<UiInput v-model="noteBody" placeholder="یادداشت…" />
|
|
|
|
|
|
</UiField>
|
|
|
|
|
|
<UiButton size="sm" :disabled="busy || !noteBody.trim()" @click="addNote">
|
|
|
|
|
|
ثبت یادداشت
|
|
|
|
|
|
</UiButton>
|
|
|
|
|
|
</UiCard>
|
|
|
|
|
|
|
|
|
|
|
|
<UiCard title="پیگیری بعدی">
|
|
|
|
|
|
<UiField label="زمان">
|
2026-10-13 14:45:29 +03:30
|
|
|
|
<JalaliDateInput v-model="followUpAt" mode="datetime" />
|
2026-09-27 17:05:11 +03:30
|
|
|
|
</UiField>
|
|
|
|
|
|
<UiField label="توضیح">
|
|
|
|
|
|
<UiInput v-model="followUpBody" placeholder="اختیاری" />
|
|
|
|
|
|
</UiField>
|
|
|
|
|
|
<UiButton size="sm" :disabled="busy || !followUpAt" @click="setFollowUp">
|
|
|
|
|
|
ذخیره پیگیری
|
|
|
|
|
|
</UiButton>
|
|
|
|
|
|
</UiCard>
|
|
|
|
|
|
</aside>
|
|
|
|
|
|
</div>
|
2026-10-13 21:05:17 +03:30
|
|
|
|
<SmsComposeModal
|
|
|
|
|
|
:open="smsOpen"
|
|
|
|
|
|
title="ارسال پیامک"
|
|
|
|
|
|
:endpoint="`/leads/${lead.id}/sms`"
|
2026-10-17 21:23:07 +03:30
|
|
|
|
:history-endpoint="`/leads/${lead.id}/sms-history`"
|
2026-10-13 21:05:17 +03:30
|
|
|
|
:name="lead.contactName || lead.title"
|
|
|
|
|
|
:phone="lead.mobile || lead.phone || ''"
|
|
|
|
|
|
:record-title="lead.title"
|
|
|
|
|
|
:company="lead.contactName ? lead.title : ''"
|
|
|
|
|
|
@close="smsOpen = false"
|
|
|
|
|
|
/>
|
2026-09-27 17:05:11 +03:30
|
|
|
|
</template>
|
|
|
|
|
|
</section>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
|
|
<style scoped>
|
|
|
|
|
|
.page {
|
|
|
|
|
|
display: grid;
|
|
|
|
|
|
gap: 1rem;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.layout {
|
|
|
|
|
|
display: grid;
|
|
|
|
|
|
gap: 1rem;
|
|
|
|
|
|
grid-template-columns: minmax(0, 1fr) minmax(16rem, 20rem);
|
|
|
|
|
|
align-items: start;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.main,
|
|
|
|
|
|
.side {
|
|
|
|
|
|
display: grid;
|
|
|
|
|
|
gap: 1rem;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-10-04 12:01:21 +03:30
|
|
|
|
.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;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-09-27 17:05:11 +03:30
|
|
|
|
.meta {
|
|
|
|
|
|
display: grid;
|
|
|
|
|
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
|
|
|
|
gap: 0.75rem 1rem;
|
|
|
|
|
|
margin: 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.meta .full {
|
|
|
|
|
|
grid-column: 1 / -1;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.meta dt {
|
|
|
|
|
|
font-size: 0.75rem;
|
|
|
|
|
|
color: var(--muted);
|
|
|
|
|
|
margin-bottom: 0.15rem;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.meta dd {
|
|
|
|
|
|
margin: 0;
|
|
|
|
|
|
font-weight: 500;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-10-03 12:29:11 +03:30
|
|
|
|
.addr-line {
|
|
|
|
|
|
margin: 0 0 0.25rem;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.addr-line:last-child {
|
|
|
|
|
|
margin-bottom: 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-10-11 14:12:36 +03:30
|
|
|
|
.lead-acts {
|
|
|
|
|
|
list-style: none;
|
|
|
|
|
|
margin: 0;
|
|
|
|
|
|
padding: 0;
|
|
|
|
|
|
display: grid;
|
|
|
|
|
|
gap: 0.6rem;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.lead-acts li {
|
|
|
|
|
|
display: grid;
|
|
|
|
|
|
gap: 0.15rem;
|
|
|
|
|
|
padding-bottom: 0.55rem;
|
|
|
|
|
|
border-bottom: 1px solid #f1f5f9;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.lead-acts li:last-child {
|
|
|
|
|
|
border-bottom: 0;
|
|
|
|
|
|
padding-bottom: 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.lead-acts a {
|
|
|
|
|
|
color: inherit;
|
|
|
|
|
|
text-decoration: none;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.lead-acts a:hover strong {
|
|
|
|
|
|
color: var(--accent, #0f766e);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.lead-acts .muted {
|
|
|
|
|
|
font-size: 0.78rem;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-09-27 17:05:11 +03:30
|
|
|
|
.timeline {
|
|
|
|
|
|
list-style: none;
|
|
|
|
|
|
margin: 0;
|
|
|
|
|
|
padding: 0;
|
|
|
|
|
|
display: grid;
|
|
|
|
|
|
gap: 0.85rem;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.timeline li {
|
|
|
|
|
|
display: grid;
|
|
|
|
|
|
gap: 0.2rem;
|
|
|
|
|
|
padding-bottom: 0.85rem;
|
|
|
|
|
|
border-bottom: 1px solid var(--line);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.timeline li:last-child {
|
|
|
|
|
|
border-bottom: 0;
|
|
|
|
|
|
padding-bottom: 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.timeline p {
|
|
|
|
|
|
margin: 0;
|
|
|
|
|
|
color: var(--ink);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.native {
|
|
|
|
|
|
width: 100%;
|
|
|
|
|
|
min-height: var(--control-h);
|
|
|
|
|
|
border: 1px solid var(--line);
|
|
|
|
|
|
border-radius: var(--radius);
|
|
|
|
|
|
padding: 0 0.65rem;
|
|
|
|
|
|
font: inherit;
|
|
|
|
|
|
background: #fff;
|
|
|
|
|
|
box-sizing: border-box;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.side :deep(.ui-card) {
|
|
|
|
|
|
display: grid;
|
|
|
|
|
|
gap: 0.65rem;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.muted {
|
|
|
|
|
|
color: var(--muted);
|
|
|
|
|
|
font-size: 0.85rem;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.ok {
|
|
|
|
|
|
color: #0f766e;
|
|
|
|
|
|
margin: 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.error {
|
|
|
|
|
|
color: #b91c1c;
|
|
|
|
|
|
margin: 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-10-13 14:45:29 +03:30
|
|
|
|
.sheet-back,
|
|
|
|
|
|
.sheet-close,
|
|
|
|
|
|
.mob {
|
|
|
|
|
|
display: none;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-09-27 17:05:11 +03:30
|
|
|
|
@media (max-width: 900px) {
|
2026-10-13 14:45:29 +03:30
|
|
|
|
.desk-only,
|
|
|
|
|
|
:deep(.desk-only) {
|
|
|
|
|
|
display: none !important;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-09-27 17:05:11 +03:30
|
|
|
|
.layout {
|
2026-10-13 14:45:29 +03:30
|
|
|
|
display: none;
|
2026-09-27 17:05:11 +03:30
|
|
|
|
grid-template-columns: 1fr;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-10-13 14:45:29 +03:30
|
|
|
|
.editing .layout,
|
|
|
|
|
|
.more-open .layout {
|
|
|
|
|
|
display: grid;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.more-open:not(.editing) .main {
|
|
|
|
|
|
display: none;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
:deep(.info-card) {
|
|
|
|
|
|
display: none;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.editing :deep(.info-card) {
|
|
|
|
|
|
display: block;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.meta,
|
|
|
|
|
|
.edit-grid {
|
2026-09-27 17:05:11 +03:30
|
|
|
|
grid-template-columns: 1fr;
|
|
|
|
|
|
}
|
2026-10-13 14:45:29 +03:30
|
|
|
|
|
|
|
|
|
|
.mob {
|
|
|
|
|
|
display: grid;
|
|
|
|
|
|
gap: 0.75rem;
|
|
|
|
|
|
width: 100%;
|
|
|
|
|
|
min-width: 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.m-hero,
|
|
|
|
|
|
.m-card,
|
|
|
|
|
|
.m-top,
|
|
|
|
|
|
.m-id,
|
|
|
|
|
|
.m-actions,
|
|
|
|
|
|
.m-grid,
|
|
|
|
|
|
.m-name-row,
|
|
|
|
|
|
.mob :deep(.related) {
|
|
|
|
|
|
min-width: 0;
|
|
|
|
|
|
max-width: 100%;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.m-hero,
|
|
|
|
|
|
.m-card,
|
|
|
|
|
|
.mob :deep(.related) {
|
|
|
|
|
|
background: #fff;
|
|
|
|
|
|
border: 1px solid #e7eef2;
|
|
|
|
|
|
border-radius: 18px;
|
|
|
|
|
|
box-shadow: 0 8px 24px rgba(15, 23, 42, 0.04);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.m-hero {
|
|
|
|
|
|
display: grid;
|
|
|
|
|
|
gap: 0.85rem;
|
|
|
|
|
|
padding: 0.95rem 0.9rem 0.85rem;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.m-top {
|
|
|
|
|
|
display: grid;
|
|
|
|
|
|
grid-template-columns: auto minmax(0, 1fr) auto;
|
|
|
|
|
|
gap: 0.7rem;
|
|
|
|
|
|
align-items: start;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.m-mark {
|
|
|
|
|
|
width: 52px;
|
|
|
|
|
|
height: 52px;
|
|
|
|
|
|
border-radius: 16px;
|
|
|
|
|
|
display: grid;
|
|
|
|
|
|
place-items: center;
|
|
|
|
|
|
background: #e7f6f1;
|
|
|
|
|
|
color: #0f766e;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.m-id {
|
|
|
|
|
|
min-width: 0;
|
|
|
|
|
|
display: grid;
|
|
|
|
|
|
gap: 0.35rem;
|
|
|
|
|
|
padding-top: 0.1rem;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.m-name-row {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
flex-wrap: wrap;
|
|
|
|
|
|
gap: 0.4rem;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.m-name-row h2 {
|
|
|
|
|
|
margin: 0;
|
|
|
|
|
|
min-width: 0;
|
|
|
|
|
|
font-size: 1.15rem;
|
|
|
|
|
|
font-weight: 800;
|
|
|
|
|
|
line-height: 1.35;
|
|
|
|
|
|
color: #0f172a;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.m-pill {
|
|
|
|
|
|
display: inline-flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
gap: 0.3rem;
|
|
|
|
|
|
padding: 0.12rem 0.55rem;
|
|
|
|
|
|
border-radius: 999px;
|
|
|
|
|
|
background: #e7f6f1;
|
|
|
|
|
|
color: #0f766e;
|
|
|
|
|
|
font-size: 0.72rem;
|
|
|
|
|
|
font-weight: 700;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.m-pill i {
|
|
|
|
|
|
width: 6px;
|
|
|
|
|
|
height: 6px;
|
|
|
|
|
|
border-radius: 999px;
|
|
|
|
|
|
background: #14b8a6;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.m-phone {
|
|
|
|
|
|
justify-self: start;
|
|
|
|
|
|
width: auto;
|
|
|
|
|
|
display: inline-flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
gap: 0.35rem;
|
|
|
|
|
|
border: 0;
|
|
|
|
|
|
background: none;
|
|
|
|
|
|
padding: 0;
|
|
|
|
|
|
color: #64748b;
|
|
|
|
|
|
font: inherit;
|
|
|
|
|
|
font-size: 0.86rem;
|
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.m-phone em {
|
|
|
|
|
|
font-style: normal;
|
|
|
|
|
|
color: #0f766e;
|
|
|
|
|
|
font-size: 0.72rem;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.m-back {
|
|
|
|
|
|
width: 32px;
|
|
|
|
|
|
height: 32px;
|
|
|
|
|
|
display: grid;
|
|
|
|
|
|
place-items: center;
|
|
|
|
|
|
color: #94a3b8;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-10-13 21:05:17 +03:30
|
|
|
|
.m-actions,
|
|
|
|
|
|
.m-actions.converted {
|
2026-10-13 14:45:29 +03:30
|
|
|
|
display: grid;
|
2026-10-13 21:05:17 +03:30
|
|
|
|
grid-template-columns: repeat(3, minmax(0, 1fr)) 36px;
|
2026-10-13 14:45:29 +03:30
|
|
|
|
gap: 0.35rem;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-10-13 21:05:17 +03:30
|
|
|
|
.m-actions .solid {
|
|
|
|
|
|
grid-column: 1 / -1;
|
2026-10-13 14:45:29 +03:30
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.m-btn {
|
|
|
|
|
|
min-width: 0;
|
|
|
|
|
|
min-height: 40px;
|
|
|
|
|
|
border: 0;
|
|
|
|
|
|
border-radius: 12px;
|
|
|
|
|
|
background: #f3f5f7;
|
|
|
|
|
|
color: #334155;
|
|
|
|
|
|
display: inline-flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
justify-content: center;
|
|
|
|
|
|
gap: 0.3rem;
|
|
|
|
|
|
padding: 0 0.45rem;
|
|
|
|
|
|
font: inherit;
|
|
|
|
|
|
font-size: 0.74rem;
|
|
|
|
|
|
font-weight: 700;
|
|
|
|
|
|
white-space: nowrap;
|
|
|
|
|
|
text-decoration: none;
|
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.m-btn.mint {
|
|
|
|
|
|
background: #e7f6f1;
|
|
|
|
|
|
color: #0f766e;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.m-btn.solid {
|
|
|
|
|
|
background: #12856a;
|
|
|
|
|
|
color: #fff;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.m-btn.solid:disabled {
|
|
|
|
|
|
opacity: 0.6;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.m-btn.icon {
|
|
|
|
|
|
width: 40px;
|
|
|
|
|
|
padding: 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.m-card {
|
|
|
|
|
|
padding: 0.9rem 0.85rem 0.85rem;
|
|
|
|
|
|
display: grid;
|
|
|
|
|
|
grid-template-columns: minmax(0, 1fr);
|
|
|
|
|
|
gap: 0.75rem;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.m-card-h {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
justify-content: space-between;
|
|
|
|
|
|
gap: 0.5rem;
|
|
|
|
|
|
min-width: 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.m-card-h h3 {
|
|
|
|
|
|
margin: 0;
|
|
|
|
|
|
display: inline-flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
gap: 0.4rem;
|
|
|
|
|
|
font-size: 0.98rem;
|
|
|
|
|
|
font-weight: 800;
|
|
|
|
|
|
color: #0f172a;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.m-ico {
|
|
|
|
|
|
width: 32px;
|
|
|
|
|
|
height: 32px;
|
|
|
|
|
|
border: 0;
|
|
|
|
|
|
border-radius: 10px;
|
|
|
|
|
|
background: #e7f6f1;
|
|
|
|
|
|
color: #0f766e;
|
|
|
|
|
|
display: grid;
|
|
|
|
|
|
place-items: center;
|
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.m-ico.static {
|
|
|
|
|
|
cursor: default;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.m-grid {
|
|
|
|
|
|
display: grid;
|
|
|
|
|
|
grid-template-columns: 1fr 1fr;
|
|
|
|
|
|
gap: 0.45rem;
|
|
|
|
|
|
margin: 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.cell {
|
|
|
|
|
|
min-width: 0;
|
|
|
|
|
|
border: 1px solid #eef2f5;
|
|
|
|
|
|
border-radius: 12px;
|
|
|
|
|
|
padding: 0.55rem 0.6rem 0.6rem;
|
|
|
|
|
|
background: #fff;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.cell.wide {
|
|
|
|
|
|
grid-column: 1 / -1;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.cell.hot {
|
|
|
|
|
|
background: #e8f7f1;
|
|
|
|
|
|
border-color: #d7f0e6;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.cell.hot dt,
|
|
|
|
|
|
.cell.hot :deep(.ico) {
|
|
|
|
|
|
color: #0f766e;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.cell dt {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
gap: 0.28rem;
|
|
|
|
|
|
margin: 0 0 0.28rem;
|
|
|
|
|
|
color: #94a3b8;
|
|
|
|
|
|
font-size: 0.7rem;
|
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.cell dd {
|
|
|
|
|
|
margin: 0;
|
|
|
|
|
|
color: #0f172a;
|
|
|
|
|
|
font-size: 0.84rem;
|
|
|
|
|
|
font-weight: 700;
|
|
|
|
|
|
overflow-wrap: anywhere;
|
|
|
|
|
|
text-align: start;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.cell dd[dir='ltr'] {
|
|
|
|
|
|
text-align: right;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.m-note {
|
|
|
|
|
|
margin: 0;
|
|
|
|
|
|
min-width: 0;
|
|
|
|
|
|
color: #334155;
|
|
|
|
|
|
font-size: 0.86rem;
|
|
|
|
|
|
line-height: 1.7;
|
|
|
|
|
|
overflow-wrap: anywhere;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.m-add {
|
|
|
|
|
|
display: inline-flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
gap: 0.2rem;
|
|
|
|
|
|
border-radius: 999px;
|
|
|
|
|
|
background: #e7f6f1;
|
|
|
|
|
|
color: #0f766e;
|
|
|
|
|
|
text-decoration: none;
|
|
|
|
|
|
font-size: 0.75rem;
|
|
|
|
|
|
font-weight: 700;
|
|
|
|
|
|
padding: 0.35rem 0.7rem;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.m-feed {
|
|
|
|
|
|
list-style: none;
|
|
|
|
|
|
margin: 0;
|
|
|
|
|
|
padding: 0;
|
|
|
|
|
|
display: grid;
|
|
|
|
|
|
gap: 0.7rem;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.m-feed li {
|
|
|
|
|
|
display: grid;
|
|
|
|
|
|
grid-template-columns: auto minmax(0, 1fr) auto;
|
|
|
|
|
|
gap: 0.6rem;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.m-feed-ic {
|
|
|
|
|
|
width: 36px;
|
|
|
|
|
|
height: 36px;
|
|
|
|
|
|
border-radius: 12px;
|
|
|
|
|
|
background: #e7f6f1;
|
|
|
|
|
|
color: #0f766e;
|
|
|
|
|
|
display: grid;
|
|
|
|
|
|
place-items: center;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.m-feed-body {
|
|
|
|
|
|
min-width: 0;
|
|
|
|
|
|
display: grid;
|
|
|
|
|
|
gap: 0.1rem;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.m-feed-body strong {
|
|
|
|
|
|
overflow-wrap: anywhere;
|
|
|
|
|
|
font-size: 0.84rem;
|
|
|
|
|
|
font-weight: 800;
|
|
|
|
|
|
color: #0f172a;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.m-feed-body small {
|
|
|
|
|
|
color: #94a3b8;
|
|
|
|
|
|
font-size: 0.72rem;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.m-feed time {
|
|
|
|
|
|
display: grid;
|
|
|
|
|
|
justify-items: end;
|
|
|
|
|
|
gap: 0.1rem;
|
|
|
|
|
|
color: #64748b;
|
|
|
|
|
|
font-size: 0.72rem;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.m-feed time b {
|
|
|
|
|
|
font-weight: 700;
|
|
|
|
|
|
color: #334155;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.m-all {
|
|
|
|
|
|
display: inline-flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
justify-content: center;
|
|
|
|
|
|
gap: 0.25rem;
|
|
|
|
|
|
color: #64748b;
|
|
|
|
|
|
text-decoration: none;
|
|
|
|
|
|
font-size: 0.78rem;
|
|
|
|
|
|
font-weight: 700;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.side {
|
|
|
|
|
|
display: none;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.sheet-back {
|
|
|
|
|
|
display: block;
|
|
|
|
|
|
position: fixed;
|
|
|
|
|
|
inset: 0;
|
|
|
|
|
|
z-index: 60;
|
|
|
|
|
|
border: 0;
|
|
|
|
|
|
background: rgba(15, 23, 42, 0.35);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.more-open .side {
|
|
|
|
|
|
display: grid;
|
|
|
|
|
|
position: fixed;
|
|
|
|
|
|
z-index: 70;
|
|
|
|
|
|
inset-inline: 0;
|
|
|
|
|
|
bottom: 0;
|
|
|
|
|
|
max-height: min(78vh, 640px);
|
|
|
|
|
|
overflow: auto;
|
|
|
|
|
|
gap: 0.75rem;
|
|
|
|
|
|
margin: 0;
|
|
|
|
|
|
padding: 0.85rem 0.85rem calc(0.9rem + env(safe-area-inset-bottom));
|
|
|
|
|
|
background: #f8fafc;
|
|
|
|
|
|
border-radius: 18px 18px 0 0;
|
|
|
|
|
|
box-shadow: 0 -12px 40px rgba(15, 23, 42, 0.18);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.sheet-close {
|
|
|
|
|
|
display: inline-flex;
|
|
|
|
|
|
justify-self: start;
|
|
|
|
|
|
border: 0;
|
|
|
|
|
|
background: #fff;
|
|
|
|
|
|
border-radius: 999px;
|
|
|
|
|
|
padding: 0.35rem 0.8rem;
|
|
|
|
|
|
font: inherit;
|
|
|
|
|
|
font-weight: 700;
|
|
|
|
|
|
color: #334155;
|
|
|
|
|
|
}
|
2026-09-27 17:05:11 +03:30
|
|
|
|
}
|
|
|
|
|
|
</style>
|