feat: send one SMS from a person or lead and keep the dashboard inside the phone frame

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-10-13 21:05:17 +03:30
co-authored by Cursor
parent 13ee70764b
commit 42f0573a5f
13 changed files with 733 additions and 113 deletions
+6
View File
@@ -228,7 +228,13 @@ onUnmounted(() => {
<style scoped>
.dashboard {
display: grid;
grid-template-columns: minmax(0, 1fr);
gap: 1.15rem;
min-width: 0;
}
.dashboard > * {
min-width: 0;
}
.mid-grid {
+38 -7
View File
@@ -19,6 +19,7 @@ import {
UiTextarea,
} from '@/components/ui';
import RelatedTasksPanel from '@/components/routines/RelatedTasksPanel.vue';
import SmsComposeModal from '@/components/SmsComposeModal.vue';
import type {
ActivityDto,
ConvertLeadResult,
@@ -57,6 +58,7 @@ const followUpAt = ref('');
const followUpBody = ref('');
const editing = ref(false);
const moreOpen = ref(false);
const smsOpen = ref(false);
const copied = ref(false);
const narrow = ref(typeof window !== 'undefined' && window.matchMedia('(max-width: 900px)').matches);
let narrowMq: MediaQueryList | null = null;
@@ -371,6 +373,15 @@ onBeforeUnmount(() => narrowMq?.removeEventListener('change', syncNarrow));
:back-to="{ name: 'leads' }"
>
<template #actions>
<UiButton
v-if="lead && !editing"
variant="secondary"
size="sm"
:disabled="busy || !lead.mobile"
@click="smsOpen = true"
>
پیامک
</UiButton>
<UiButton
v-if="lead && !editing"
variant="secondary"
@@ -444,10 +455,17 @@ onBeforeUnmount(() => narrowMq?.removeEventListener('change', syncNarrow));
<UiIcon name="phone" :size="16" />
تماس
</a>
<button class="m-btn" type="button" @click="smsOpen = true">
<UiIcon name="chat" :size="16" />
پیامک
</button>
<button class="m-btn" type="button" @click="startEdit">
<UiIcon name="pencil" :size="16" />
ویرایش
</button>
<button class="m-btn icon" type="button" aria-label="بیشتر" @click="moreOpen = true">
<UiIcon name="dots" :size="18" />
</button>
<button
v-if="!lead.convertedPersonId"
class="m-btn solid"
@@ -458,9 +476,6 @@ onBeforeUnmount(() => narrowMq?.removeEventListener('change', syncNarrow));
<UiIcon name="user-plus" :size="16" />
تبدیل به مشتری
</button>
<button class="m-btn icon" type="button" aria-label="بیشتر" @click="moreOpen = true">
<UiIcon name="dots" :size="18" />
</button>
</div>
</article>
@@ -704,6 +719,11 @@ onBeforeUnmount(() => narrowMq?.removeEventListener('change', syncNarrow));
<aside class="side">
<button class="sheet-close" type="button" @click="moreOpen = false">بستن</button>
<UiCard title="پیامک">
<UiButton size="sm" :disabled="!lead.mobile" @click="smsOpen = true; moreOpen = false">
ارسال پیامک
</UiButton>
</UiCard>
<UiCard title="تغییر وضعیت">
<UiField label="وضعیت">
<UiSelect v-model="stageId">
@@ -764,6 +784,16 @@ onBeforeUnmount(() => narrowMq?.removeEventListener('change', syncNarrow));
</UiCard>
</aside>
</div>
<SmsComposeModal
:open="smsOpen"
title="ارسال پیامک"
:endpoint="`/leads/${lead.id}/sms`"
:name="lead.contactName || lead.title"
:phone="lead.mobile || lead.phone || ''"
:record-title="lead.title"
:company="lead.contactName ? lead.title : ''"
@close="smsOpen = false"
/>
</template>
</section>
</template>
@@ -1082,14 +1112,15 @@ onBeforeUnmount(() => narrowMq?.removeEventListener('change', syncNarrow));
color: #94a3b8;
}
.m-actions {
.m-actions,
.m-actions.converted {
display: grid;
grid-template-columns: auto auto minmax(0, 1fr) 36px;
grid-template-columns: repeat(3, minmax(0, 1fr)) 36px;
gap: 0.35rem;
}
.m-actions.converted {
grid-template-columns: 1fr 1fr auto;
.m-actions .solid {
grid-column: 1 / -1;
}
.m-btn {
+31
View File
@@ -19,6 +19,7 @@ import UiIcon from '@/components/UiIcon.vue';
import CustomerMoodFace from '@/components/CustomerMoodFace.vue';
import MentoAiAvatar from '@/components/brand/MentoAiAvatar.vue';
import { brand } from '@mento/shared';
import SmsComposeModal from '@/components/SmsComposeModal.vue';
import MentoMemoryPanel from '@/components/MentoMemoryPanel.vue';
import AskMentoPanel from '@/components/AskMentoPanel.vue';
import ProfileEmptyState from '@/components/person-profile/ProfileEmptyState.vue';
@@ -104,6 +105,7 @@ const cards = ref<ProjectCardDto[]>([]);
const tahesabMoshtariCode = ref<number | null>(null);
const moreOpen = ref(false);
const smsOpen = ref(false);
const mobMoreOpen = ref(false);
const filterOpen = ref(false);
const historyFilter = ref<HistoryFilter>('all');
@@ -117,10 +119,12 @@ const moreItems = computed<ProfileMoreItem[]>(() => [
{ key: 'quote', label: 'پیش‌فاکتور جدید', to: { name: 'quote-new', query: { personId: id.value } } },
{ key: 'ticket', label: 'تیکت جدید', to: { name: 'ticket-new', query: { personId: id.value } } },
{ key: 'note', label: 'یادداشت جدید', to: { name: 'notes', query: { personId: id.value } } },
{ key: 'sms', label: 'ارسال پیامک' },
{ key: 'delete', label: 'حذف شخص', danger: true },
]);
function onMoreSelect(key: string) {
if (key === 'sms') smsOpen.value = true;
if (key === 'delete') void removePerson();
}
@@ -676,6 +680,10 @@ watch(() => route.query.tab, readTab, { immediate: true });
<UiIcon name="phone" :size="15" />
{{ phoneDisplay }}
</a>
<button class="contact-pill sms" type="button" :disabled="!phoneNumber" @click="smsOpen = true">
<UiIcon name="chat" :size="15" />
پیامک
</button>
<a v-if="person.email" class="contact-pill" :href="mailHref || undefined" dir="ltr">
<UiIcon name="mail" :size="15" />
{{ person.email }}
@@ -1033,6 +1041,18 @@ watch(() => route.query.tab, readTab, { immediate: true });
</div>
</template>
<SmsComposeModal
v-if="person"
:open="smsOpen"
title="ارسال پیامک"
:endpoint="`/people/${id}/sms`"
:name="fullName"
:phone="phoneNumber"
:record-title="fullName"
:company="person.company?.name ?? ''"
@close="smsOpen = false"
/>
<div v-if="person" class="dock">
<button class="fab-item call" type="button" :disabled="!telHref" @click="goCall">
<span class="fab-circle">
@@ -1275,6 +1295,17 @@ watch(() => route.query.tab, readTab, { immediate: true });
background: #f0fdfa;
}
button.contact-pill {
font: inherit;
font-weight: 700;
cursor: pointer;
}
button.contact-pill:disabled {
opacity: 0.55;
cursor: default;
}
.facts-grid {
margin: 0;
display: grid;