diff --git a/apps/web/src/components/SavedFiltersBar.vue b/apps/web/src/components/SavedFiltersBar.vue index ab2aa50..0f6a2b1 100644 --- a/apps/web/src/components/SavedFiltersBar.vue +++ b/apps/web/src/components/SavedFiltersBar.vue @@ -39,6 +39,8 @@ const busy = ref(false); const saveOpen = ref(false); const saveName = ref(''); const saveShared = ref(false); +/** عکس فیلترهای اعمال‌شده در لحظهٔ باز شدن مودال ذخیره */ +const saveFilters = ref>({}); const activeView = computed( () => views.value.find((v) => v.id === activeId.value) ?? null, @@ -51,6 +53,41 @@ const selectOptions = computed(() => })), ); +const saveFilterCount = computed(() => Object.keys(saveFilters.value).length); + +const saveFilterSummary = computed(() => { + const parts: string[] = []; + const f = saveFilters.value; + if (typeof f.q === 'string' && f.q) parts.push(`جستجو: ${f.q}`); + if (typeof f.group === 'string' && f.group) { + parts.push(`گروه (${f.group.split(',').filter(Boolean).length})`); + } + if (typeof f.source === 'string' && f.source) { + parts.push(`منبع (${f.source.split(',').filter(Boolean).length})`); + } + if (typeof f.mood === 'string' && f.mood) { + parts.push(`وضعیت (${f.mood.split(',').filter(Boolean).length})`); + } + if (typeof f.disc === 'string' && f.disc) parts.push(`DISC: ${f.disc}`); + if (typeof f.sort === 'string' && f.sort) parts.push(`مرتب‌سازی: ${f.sort}`); + if (typeof f.viewMode === 'string' && f.viewMode) parts.push(`نمایش: ${f.viewMode}`); + if (typeof f.pipelineId === 'string' && f.pipelineId) parts.push('مسیر فروش'); + if (f.amountMin != null && f.amountMin !== '') parts.push(`حداقل مبلغ`); + if (f.amountMax != null && f.amountMax !== '') parts.push(`حداکثر مبلغ`); + return parts.length ? parts.join(' · ') : 'هیچ فیلتری اعمال نشده'; +}); + +/** فقط کلیدهای پر؛ برای JSON و جلوگیری از ذخیرهٔ خالی */ +function compactFilters(raw: Record | null | undefined): Record { + const out: Record = {}; + for (const [key, value] of Object.entries(raw ?? {})) { + if (value == null || value === '') continue; + if (Array.isArray(value) && value.length === 0) continue; + out[key] = value; + } + return out; +} + async function loadViews() { loading.value = true; try { @@ -71,10 +108,19 @@ function onPick(id: unknown) { activeId.value = next || undefined; if (!next) return; const view = views.value.find((v) => v.id === next); - if (view) emit('apply', view.filters ?? {}); + if (!view) return; + // کپی ساده تا مصرف‌کننده همان آبجکت ری‌اکتیو را دستکاری نکند + emit('apply', compactFilters(view.filters ?? {})); } function openSave() { + saveFilters.value = compactFilters(props.currentFilters); + if (!Object.keys(saveFilters.value).length) { + message.warning( + 'ابتدا فیلترها را از دکمهٔ «فیلتر» اعمال کنید (یا جستجو بزنید)، بعد ذخیره کنید.', + ); + return; + } saveName.value = activeView.value?.name ?? ''; saveShared.value = activeView.value?.isShared ?? false; saveOpen.value = true; @@ -83,12 +129,17 @@ function openSave() { async function saveView() { const name = saveName.value.trim(); if (name.length < 2 || busy.value) return; + const filters = compactFilters(saveFilters.value); + if (!Object.keys(filters).length) { + message.warning('چیزی برای ذخیره نیست؛ اول فیلتر را روی صفحه اعمال کنید.'); + return; + } busy.value = true; try { const body = { name, scope: props.scope, - filters: props.currentFilters, + filters, isShared: saveShared.value, }; if (activeId.value) { @@ -160,11 +211,11 @@ onMounted(() => { :disabled="loading" :loading="loading" :options="selectOptions" - :placeholder="compact ? 'فیلتر ذخیره‌شده' : '— انتخاب —'" + :placeholder="compact ? 'فیلتر ذخیره‌شده' : 'فیلتر ذخیره‌شده را انتخاب کنید'" :style="{ flex: 1, minWidth: 0, width: '100%' }" @update:value="onPick" /> - + ذخیره @@ -193,9 +244,14 @@ onMounted(() => { + +

+ {{ saveFilterSummary }} +

+
اشتراک با سازمان diff --git a/apps/web/src/views/ContactsView.vue b/apps/web/src/views/ContactsView.vue index 6d8068f..ca4b3d4 100644 --- a/apps/web/src/views/ContactsView.vue +++ b/apps/web/src/views/ContactsView.vue @@ -174,32 +174,58 @@ function onPageSize(size: number) { if (page.value === 1) void load(); } -const peopleSavedFilters = computed(() => ({ - q: q.value.trim() || undefined, - sort: sort.value === 'name' ? undefined : sort.value, - mood: csv(moodFilters.value), - source: csv(sourceFilters.value), - disc: discFilter.value || undefined, - group: csv(groupFilters.value), -})); +const peopleSavedFilters = computed(() => { + const out: Record = {}; + const nextQ = q.value.trim(); + if (nextQ) out.q = nextQ; + if (sort.value !== 'name') out.sort = sort.value; + const mood = csv(moodFilters.value); + if (mood) out.mood = mood; + const source = csv(sourceFilters.value); + if (source) out.source = source; + if (discFilter.value) out.disc = discFilter.value; + const group = csv(groupFilters.value); + if (group) out.group = group; + return out; +}); -function savedText(raw: unknown): string | undefined { - return typeof raw === 'string' && raw.trim() ? raw.trim() : undefined; +/** مقدار ذخیره‌شده: رشته یا آرایه → رشتهٔ کاما‌جدا برای URL */ +function savedCsv(raw: unknown): string | undefined { + if (Array.isArray(raw)) { + const list = raw.map((v) => String(v).trim()).filter(Boolean); + return list.length ? list.join(',') : undefined; + } + if (typeof raw === 'string' && raw.trim()) return raw.trim(); + return undefined; } function applyPeopleSavedFilters(filters: Record) { const nextQ = typeof filters.q === 'string' ? filters.q : ''; q.value = nextQ; - const nextSort = filters.sort === 'newest' ? 'newest' : 'name'; - patchQuery({ - q: nextQ.trim() || undefined, - sort: nextSort === 'name' ? undefined : nextSort, - mood: savedText(filters.mood), - source: savedText(filters.source), - disc: savedText(filters.disc), - group: savedText(filters.group), - page: undefined, - tab: undefined, + // کلیدهای فیلتر را از نو می‌سازیم تا فیلتر قبلی روی صفحه نماند + const query: Record = {}; + for (const [key, value] of Object.entries(route.query)) { + if (['q', 'sort', 'mood', 'source', 'disc', 'group', 'page', 'tab'].includes(key)) continue; + if (value == null || value === '') continue; + if (Array.isArray(value)) { + const list = value.filter((v): v is string => typeof v === 'string' && v !== ''); + if (list.length) query[key] = list; + } else if (typeof value === 'string') { + query[key] = value; + } + } + if (nextQ.trim()) query.q = nextQ.trim(); + if (filters.sort === 'newest') query.sort = 'newest'; + const mood = savedCsv(filters.mood); + if (mood) query.mood = mood; + const source = savedCsv(filters.source); + if (source) query.source = source; + const disc = savedCsv(filters.disc); + if (disc) query.disc = disc; + const group = savedCsv(filters.group); + if (group) query.group = group; + void router.replace({ name: 'contacts', query }).then(() => { + void load(); }); }