Unified search algorithm across all lists; per-section record visibility (contacts, deals, activities, leads, finance, tickets, notes, projects)
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, nextTick, onMounted, onUnmounted, ref, watch } from 'vue';
|
||||
import { matchesSearch } from '@/lib/search';
|
||||
import { useRouter } from 'vue-router';
|
||||
import axios from 'axios';
|
||||
import { api } from '@/lib/api';
|
||||
@@ -181,9 +182,9 @@ const canPostInActive = computed(() => {
|
||||
});
|
||||
|
||||
const filteredPeers = computed(() => {
|
||||
const q = peerPickerQuery.value.trim().toLowerCase();
|
||||
const q = peerPickerQuery.value.trim();
|
||||
if (!q) return peers.value;
|
||||
return peers.value.filter((p) => p.fullName.toLowerCase().includes(q));
|
||||
return peers.value.filter((p) => matchesSearch(q, [p.fullName]));
|
||||
});
|
||||
|
||||
const selectedGroupPeers = computed(() =>
|
||||
@@ -251,34 +252,32 @@ const sortedRooms = computed(() =>
|
||||
);
|
||||
|
||||
const filteredRooms = computed(() => {
|
||||
const q = roomListQuery.value.trim().toLowerCase();
|
||||
const q = roomListQuery.value.trim();
|
||||
return sortedRooms.value.filter((room) => {
|
||||
if (roomListTab.value === 'dm' && room.kind !== 'dm') return false;
|
||||
if (roomListTab.value === 'group' && room.kind !== 'group') return false;
|
||||
if (roomListTab.value === 'channel' && room.kind !== 'channel') return false;
|
||||
if (!q) return true;
|
||||
const title = roomTitle(room).toLowerCase();
|
||||
const preview = (room.lastMessagePreview || '').toLowerCase();
|
||||
return title.includes(q) || preview.includes(q);
|
||||
return matchesSearch(q, [roomTitle(room), room.lastMessagePreview]);
|
||||
});
|
||||
});
|
||||
|
||||
const mentionCandidates = computed(() => {
|
||||
const state = mentionState.value;
|
||||
if (!state) return [] as ConversationPeerDto[];
|
||||
const q = state.query.trim().toLowerCase();
|
||||
const q = state.query.trim();
|
||||
return peers.value
|
||||
.filter((p) => !q || p.fullName.toLowerCase().includes(q))
|
||||
.filter((p) => !q || matchesSearch(q, [p.fullName]))
|
||||
.slice(0, 8);
|
||||
});
|
||||
|
||||
const chatSearchHits = computed(() => {
|
||||
const q = chatSearchQuery.value.trim().toLowerCase();
|
||||
const q = chatSearchQuery.value.trim();
|
||||
if (!q || !chatSearchOpen.value) return [] as ChatMessage[];
|
||||
return messages.value.filter((m) => {
|
||||
if (m.deleted) return false;
|
||||
if (m.type === 'text') return (m.content || '').toLowerCase().includes(q);
|
||||
return previewLabelForMessage(m).toLowerCase().includes(q);
|
||||
if (m.type === 'text') return matchesSearch(q, [m.content]);
|
||||
return matchesSearch(q, [previewLabelForMessage(m)]);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -764,9 +763,13 @@ async function searchCrmLinks() {
|
||||
} else {
|
||||
const { data } = await api.get<PipelineBoardDto>('/pipelines/board');
|
||||
const deals = (data.stages || []).flatMap((s) => s.deals || []);
|
||||
const ql = q.toLowerCase();
|
||||
crmLinkResults.value = deals
|
||||
.filter((d) => !ql || (d.title || '').toLowerCase().includes(ql) || d.id.includes(q))
|
||||
.filter(
|
||||
(d) =>
|
||||
!q.trim() ||
|
||||
d.id.includes(q) ||
|
||||
matchesSearch(q, [d.title, d.person?.fullName, d.company?.name]),
|
||||
)
|
||||
.slice(0, 20)
|
||||
.map((d) => ({ id: d.id, title: d.title || 'معامله' }));
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, onMounted, ref, watch } from 'vue';
|
||||
import { matchesSearch } from '@/lib/search';
|
||||
import { RouterLink, useRoute, useRouter } from 'vue-router';
|
||||
import axios from 'axios';
|
||||
import { api } from '@/lib/api';
|
||||
@@ -219,13 +220,10 @@ const currencySelectOptions = computed(() => {
|
||||
});
|
||||
|
||||
const filteredProducts = computed(() => {
|
||||
const q = productSearch.value.trim().toLowerCase();
|
||||
const q = productSearch.value.trim();
|
||||
if (!q) return products.value;
|
||||
return products.value.filter(
|
||||
(p) =>
|
||||
p.name.toLowerCase().includes(q) ||
|
||||
(p.sku && p.sku.toLowerCase().includes(q)) ||
|
||||
(p.description && p.description.toLowerCase().includes(q)),
|
||||
return products.value.filter((p) =>
|
||||
matchesSearch(q, [p.name, p.sku, p.description, p.category]),
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, onMounted, ref, watch } from 'vue';
|
||||
import { matchesSearch } from '@/lib/search';
|
||||
import TableColumnsMenu from '@/components/TableColumnsMenu.vue';
|
||||
import { PAGE_SIZE_OPTIONS, usePageSize } from '@/lib/pageSize';
|
||||
import { RouterLink, useRoute, useRouter } from 'vue-router';
|
||||
@@ -204,13 +205,9 @@ const filtered = computed(() => {
|
||||
if (Number.isFinite(amax) && amountMax.value !== '') {
|
||||
list = list.filter((r) => Math.abs(r.amount) <= amax);
|
||||
}
|
||||
const query = q.value.trim().toLowerCase();
|
||||
if (query) {
|
||||
if (q.value.trim()) {
|
||||
list = list.filter((r) =>
|
||||
[r.number, r.customerName, r.personName, r.statusLabel, String(r.amount)]
|
||||
.join(' ')
|
||||
.toLowerCase()
|
||||
.includes(query),
|
||||
matchesSearch(q.value, [r.number, r.customerName, r.personName, r.statusLabel, String(r.amount)]),
|
||||
);
|
||||
}
|
||||
return list;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, nextTick, onMounted, onUnmounted, ref, watch } from 'vue';
|
||||
import { matchesSearch } from '@/lib/search';
|
||||
import TableColumnsMenu from '@/components/TableColumnsMenu.vue';
|
||||
import axios from 'axios';
|
||||
import { api } from '@/lib/api';
|
||||
@@ -79,7 +80,7 @@ const visibleItems = computed(() => {
|
||||
if (statusFilter.value === 'active' && !row.isActive) return false;
|
||||
if (statusFilter.value === 'inactive' && row.isActive) return false;
|
||||
if (!q) return true;
|
||||
return `${row.name} ${row.description ?? ''} ${row.body}`.includes(q);
|
||||
return matchesSearch(q, [row.name, row.description, row.body]);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, onMounted, ref, watch } from 'vue';
|
||||
import { matchesSearch } from '@/lib/search';
|
||||
import TableColumnsMenu from '@/components/TableColumnsMenu.vue';
|
||||
import { PAGE_SIZE_OPTIONS, usePageSize } from '@/lib/pageSize';
|
||||
import { useRouter } from 'vue-router';
|
||||
@@ -59,15 +60,8 @@ const categories = computed(() => {
|
||||
|
||||
const filtered = computed(() => {
|
||||
let list = [...products.value];
|
||||
const query = q.value.trim().toLowerCase();
|
||||
if (query) {
|
||||
list = list.filter((p) => {
|
||||
const hay = [p.name, p.sku, p.description, p.category]
|
||||
.filter(Boolean)
|
||||
.join(' ')
|
||||
.toLowerCase();
|
||||
return hay.includes(query);
|
||||
});
|
||||
if (q.value.trim()) {
|
||||
list = list.filter((p) => matchesSearch(q.value, [p.name, p.sku, p.description, p.category]));
|
||||
}
|
||||
if (category.value) {
|
||||
list = list.filter((p) => p.category === category.value);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, onMounted, ref, watch } from 'vue';
|
||||
import { matchesSearch } from '@/lib/search';
|
||||
import TableColumnsMenu from '@/components/TableColumnsMenu.vue';
|
||||
import { RouterLink } from 'vue-router';
|
||||
import axios from 'axios';
|
||||
@@ -93,9 +94,8 @@ const filteredGoals = computed(() => {
|
||||
if (statusFilter.value !== 'all') {
|
||||
list = list.filter((g) => g.status === statusFilter.value);
|
||||
}
|
||||
const query = q.value.trim().toLowerCase();
|
||||
if (query) {
|
||||
list = list.filter((g) => g.title.toLowerCase().includes(query));
|
||||
if (q.value.trim()) {
|
||||
list = list.filter((g) => matchesSearch(q.value, [g.title]));
|
||||
}
|
||||
return list;
|
||||
});
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, onMounted, onUnmounted, ref, watch } from 'vue';
|
||||
import { matchesSearch } from '@/lib/search';
|
||||
import TableColumnsMenu from '@/components/TableColumnsMenu.vue';
|
||||
import { useRoute, useRouter } from 'vue-router';
|
||||
import axios from 'axios';
|
||||
@@ -123,13 +124,7 @@ const filteredUsers = computed(() => {
|
||||
const q = query.value.trim();
|
||||
let list = users.value;
|
||||
if (q) {
|
||||
const needle = q.toLowerCase();
|
||||
list = list.filter(
|
||||
(u) =>
|
||||
u.fullName.toLowerCase().includes(needle) ||
|
||||
(u.mobile ?? '').includes(needle) ||
|
||||
(u.orgRoleName ?? '').includes(q),
|
||||
);
|
||||
list = list.filter((u) => matchesSearch(q, [u.fullName, u.mobile, u.email, u.orgRoleName]));
|
||||
}
|
||||
return [...list].sort((a, b) => {
|
||||
const at = a.lastLoginAt ? new Date(a.lastLoginAt).getTime() : 0;
|
||||
|
||||
Reference in New Issue
Block a user