fix(web): calm hero flash, lead detail, and scope sources
Keep the dashboard backdrop out of flow on first paint, clamp long lead notes, scroll the quick-actions rail, return to the lead board, and only list import sources that exist in this environment. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue';
|
||||
import { computed, watch } from 'vue';
|
||||
import { Select as ASelect } from 'ant-design-vue';
|
||||
import type {
|
||||
DataAccessOwnerKind,
|
||||
@@ -10,13 +10,13 @@ import type {
|
||||
} from '@mento/shared';
|
||||
import {
|
||||
DATA_ACCESS_SECTION_LABELS,
|
||||
PERSON_IMPORT_SOURCE_LABELS,
|
||||
PERSON_IMPORT_SOURCES,
|
||||
PERSON_MOOD_LABELS,
|
||||
PERSON_MOODS,
|
||||
sectionHasDataAccessLimits,
|
||||
} from '@mento/shared';
|
||||
export type IdOption = { id: string; name: string };
|
||||
type Choice = { value: string; label: string };
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
@@ -27,6 +27,10 @@ const props = withDefaults(
|
||||
pipelines?: IdOption[];
|
||||
boards?: IdOption[];
|
||||
activityTypes?: IdOption[];
|
||||
/** منبعهایی که در مشتریان همین محیط رکورد دارند */
|
||||
importSources?: Choice[];
|
||||
importSourcesReady?: boolean;
|
||||
importSourcesFailed?: boolean;
|
||||
disabled?: boolean;
|
||||
compact?: boolean;
|
||||
}>(),
|
||||
@@ -37,6 +41,9 @@ const props = withDefaults(
|
||||
pipelines: () => [],
|
||||
boards: () => [],
|
||||
activityTypes: () => [],
|
||||
importSources: () => [],
|
||||
importSourcesReady: false,
|
||||
importSourcesFailed: false,
|
||||
},
|
||||
);
|
||||
|
||||
@@ -211,7 +218,6 @@ type ListKey =
|
||||
| 'boardIds'
|
||||
| 'typeIds';
|
||||
|
||||
type Choice = { value: string; label: string };
|
||||
type PickerMode = 'multiple' | 'tags';
|
||||
type Picker = {
|
||||
key: ListKey;
|
||||
@@ -221,6 +227,7 @@ type Picker = {
|
||||
notFound: string;
|
||||
hint?: string;
|
||||
options: Choice[];
|
||||
loading?: boolean;
|
||||
};
|
||||
|
||||
function namedOptions(items: IdOption[]): Choice[] {
|
||||
@@ -239,18 +246,44 @@ function listValue(key: ListKey): string[] {
|
||||
return ((filter.value[key] as string[] | undefined) ?? []).map(String);
|
||||
}
|
||||
|
||||
const catalogSources = new Set<string>(PERSON_IMPORT_SOURCES);
|
||||
|
||||
/** کلیدهای ثابت کاتالوگ که در این محیط رکورد ندارند را نشان نده؛ متن سفارشی بماند. */
|
||||
function sourcesInThisEnvironment(selected: readonly string[] | undefined): string[] {
|
||||
const list = selected ?? [];
|
||||
if (!props.importSourcesReady) {
|
||||
return list.filter((source) => !catalogSources.has(source));
|
||||
}
|
||||
const present = new Set(props.importSources.map((option) => option.value));
|
||||
return list.filter((source) => present.has(source) || !catalogSources.has(source));
|
||||
}
|
||||
|
||||
const visibleImportSources = computed(() => sourcesInThisEnvironment(filter.value.sources));
|
||||
|
||||
function setList(key: ListKey, value: unknown) {
|
||||
const raw = Array.isArray(value) ? value : value == null || value === '' ? [] : [value];
|
||||
const list = [...new Set(raw.map((item) => String(item).trim()).filter(Boolean))];
|
||||
let list = [...new Set(raw.map((item) => String(item).trim()).filter(Boolean))];
|
||||
if (key === 'sources' && showImportSources.value && props.importSourcesReady) {
|
||||
list = sourcesInThisEnvironment(list);
|
||||
}
|
||||
patchFilter({ [key]: list.length ? list : undefined });
|
||||
}
|
||||
|
||||
const sourceOptions = computed(() =>
|
||||
PERSON_IMPORT_SOURCES.map((s) => ({
|
||||
value: s,
|
||||
label: PERSON_IMPORT_SOURCE_LABELS[s],
|
||||
})),
|
||||
);
|
||||
function pickerValue(picker: Picker): string[] {
|
||||
if (picker.key === 'sources' && showImportSources.value && props.importSourcesReady) {
|
||||
return visibleImportSources.value;
|
||||
}
|
||||
return listValue(picker.key);
|
||||
}
|
||||
|
||||
watch(visibleImportSources, (next) => {
|
||||
if (!showImportSources.value || !props.importSourcesReady) return;
|
||||
const current = filter.value.sources ?? [];
|
||||
if (current.length === next.length && current.every((source, index) => source === next[index])) {
|
||||
return;
|
||||
}
|
||||
patchFilter({ sources: next.length ? next : undefined });
|
||||
});
|
||||
|
||||
const moodOptions: Choice[] = PERSON_MOODS.map((mood) => ({
|
||||
value: mood,
|
||||
@@ -271,14 +304,26 @@ const pickers = computed((): Picker[] => {
|
||||
});
|
||||
}
|
||||
if (showImportSources.value) {
|
||||
const ready = props.importSourcesReady;
|
||||
const failed = props.importSourcesFailed;
|
||||
items.push({
|
||||
key: 'sources',
|
||||
legend: 'منبع',
|
||||
mode: 'tags',
|
||||
placeholder: 'جستجو، انتخاب یا نوشتن منبع جدید…',
|
||||
placeholder: failed
|
||||
? 'فهرست منبعها بارگذاری نشد'
|
||||
: props.importSources.length
|
||||
? 'جستجو، انتخاب یا نوشتن منبع جدید…'
|
||||
: 'منبعی در این محیط نیست',
|
||||
notFound: 'برای منبع جدید Enter بزنید',
|
||||
hint: 'چند منبع را با هم انتخاب کنید. نام تازه را بنویسید و Enter بزنید.',
|
||||
options: withSelected(sourceOptions.value, rule.sources),
|
||||
hint: failed
|
||||
? 'فهرست منبعهای این محیط خوانده نشد؛ انتخاب ذخیرهشده دست نخورده است.'
|
||||
: 'فقط منبعهایی که در مشتریان این محیط هستند. نام تازه را بنویسید و Enter بزنید.',
|
||||
loading: !ready && !failed,
|
||||
options: withSelected(
|
||||
ready ? props.importSources : [],
|
||||
ready ? visibleImportSources.value : (rule.sources ?? []),
|
||||
),
|
||||
});
|
||||
}
|
||||
if (showLeadSources.value) {
|
||||
@@ -430,8 +475,10 @@ const filterSummary = computed(() => {
|
||||
|
||||
<label v-for="picker in pickers" :key="picker.legend" class="pick">
|
||||
<span>{{ picker.legend }}</span>
|
||||
<p v-if="picker.loading" class="muted">در حال بارگذاری منبعها…</p>
|
||||
<a-select
|
||||
:value="listValue(picker.key)"
|
||||
v-else
|
||||
:value="pickerValue(picker)"
|
||||
:mode="picker.mode"
|
||||
allow-clear
|
||||
show-search
|
||||
|
||||
Reference in New Issue
Block a user