feat: add DISC profiles and a sales copilot as an org module
Help reps capture a customer's likely behavior style and get negotiation guidance, with the feature gated from super admin. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -48,8 +48,12 @@ import type {
|
||||
TicketDto,
|
||||
} from '@mento/shared';
|
||||
import { PERSON_MOOD_LABELS } from '@/lib/person-mood';
|
||||
import { canUseBehavior } from '@/lib/behavior-access';
|
||||
import DiscBadge from '@/components/behavior/DiscBadge.vue';
|
||||
import DiscPanel from '@/components/behavior/DiscPanel.vue';
|
||||
import SalesCopilotPanel from '@/components/behavior/SalesCopilotPanel.vue';
|
||||
|
||||
type TabKey = 'activities' | 'tasks' | 'deals' | 'cards' | 'assets' | 'info';
|
||||
type TabKey = 'activities' | 'tasks' | 'deals' | 'cards' | 'assets' | 'info' | 'disc' | 'copilot';
|
||||
type TimelineTone = 'call' | 'quote' | 'note' | 'meeting' | 'task';
|
||||
type HistoryFilter = 'all' | TimelineTone;
|
||||
|
||||
@@ -71,14 +75,25 @@ type AttachmentRow = {
|
||||
kind: 'pdf' | 'doc' | 'img' | 'file';
|
||||
};
|
||||
|
||||
const TABS: Array<{ key: TabKey; label: string }> = [
|
||||
{ key: 'activities', label: 'فعالیتها' },
|
||||
{ key: 'tasks', label: 'کارهای مرتبط' },
|
||||
{ key: 'deals', label: 'معاملات' },
|
||||
{ key: 'cards', label: 'کارتها' },
|
||||
{ key: 'assets', label: 'دارایی' },
|
||||
{ key: 'info', label: 'اطلاعات' },
|
||||
];
|
||||
const auth = useAuthStore();
|
||||
const canBehavior = computed(() => canUseBehavior(auth.user));
|
||||
const TABS = computed<Array<{ key: TabKey; label: string }>>(() => {
|
||||
const items: Array<{ key: TabKey; label: string }> = [
|
||||
{ key: 'activities', label: 'فعالیتها' },
|
||||
{ key: 'tasks', label: 'کارهای مرتبط' },
|
||||
{ key: 'deals', label: 'معاملات' },
|
||||
{ key: 'cards', label: 'کارتها' },
|
||||
{ key: 'assets', label: 'دارایی' },
|
||||
{ key: 'info', label: 'اطلاعات' },
|
||||
];
|
||||
if (canBehavior.value) {
|
||||
items.push(
|
||||
{ key: 'disc', label: 'تحلیل رفتاری DISC' },
|
||||
{ key: 'copilot', label: 'دستیار هوشمند فروش' },
|
||||
);
|
||||
}
|
||||
return items;
|
||||
});
|
||||
|
||||
const HISTORY_FILTERS: Array<{ key: HistoryFilter; label: string }> = [
|
||||
{ key: 'all', label: 'همه' },
|
||||
@@ -182,7 +197,6 @@ const previousSourceLabel = computed(() => {
|
||||
return SOURCE_LABELS[src] || src;
|
||||
});
|
||||
|
||||
const auth = useAuthStore();
|
||||
const priorArchiveOn = computed(() => Boolean(auth.user?.priorArchive));
|
||||
const priorLens = computed(() => auth.user?.priorArchive?.lens ?? null);
|
||||
|
||||
@@ -451,8 +465,14 @@ function readTab() {
|
||||
q === 'deals' ||
|
||||
q === 'cards' ||
|
||||
q === 'assets' ||
|
||||
q === 'info'
|
||||
q === 'info' ||
|
||||
q === 'disc' ||
|
||||
q === 'copilot'
|
||||
) {
|
||||
if ((q === 'disc' || q === 'copilot') && !canBehavior.value) {
|
||||
tab.value = 'activities';
|
||||
return;
|
||||
}
|
||||
tab.value = q;
|
||||
} else {
|
||||
tab.value = 'activities';
|
||||
@@ -696,6 +716,7 @@ watch(() => route.query.tab, readTab, { immediate: true });
|
||||
<h2>{{ fullName }}</h2>
|
||||
<span class="mood-under" :class="mood">{{ moodLabel }}</span>
|
||||
<span v-if="vip" class="vip-mark">VIP</span>
|
||||
<DiscBadge v-if="canBehavior && person.discReliable" :label="person.discLabel" />
|
||||
</div>
|
||||
<p v-if="subtitle" class="sub">{{ subtitle }}</p>
|
||||
<div class="meta-chips">
|
||||
@@ -787,6 +808,7 @@ watch(() => route.query.tab, readTab, { immediate: true });
|
||||
>
|
||||
</h2>
|
||||
<p v-if="subtitle" class="sub">{{ subtitle }}</p>
|
||||
<DiscBadge v-if="canBehavior && person.discReliable" :label="person.discLabel" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="contact-rows">
|
||||
@@ -1018,6 +1040,23 @@ watch(() => route.query.tab, readTab, { immediate: true });
|
||||
<PersonAssetsPanel :person-id="id" @link-changed="onTahesabLinkChanged" />
|
||||
</div>
|
||||
|
||||
<div v-if="canBehavior && tab === 'disc'" class="panel">
|
||||
<DiscPanel :person-id="id" @saved="load" />
|
||||
</div>
|
||||
|
||||
<div v-if="canBehavior && tab === 'copilot'" class="panel">
|
||||
<SalesCopilotPanel
|
||||
:person-id="id"
|
||||
:deal-id="typeof route.query.dealId === 'string' ? route.query.dealId : null"
|
||||
:customer-name="fullName"
|
||||
:disc-label="person.discReliable ? person.discLabel : null"
|
||||
:stage-name="null"
|
||||
:phone="phoneNumber"
|
||||
:sms-endpoint="`/people/${id}/sms`"
|
||||
:sms-history-endpoint="`/people/${id}/sms-history`"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div v-show="tab === 'info'" class="panel info-panel" :class="{ 'is-empty': infoEmpty }">
|
||||
<article class="smart">
|
||||
<header class="smart-head">
|
||||
|
||||
Reference in New Issue
Block a user