Skip to content

Commit c55795d

Browse files
sancheslflCopilotFrancisca105
authored
feat: fuzzy search with microfuzz
* feat: fuzzy search with microfuzz * Update frontend/src/components/CompanyOrSpeakerAutocompleteWithDialog.vue Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * fix: moved custom types config file to tsconfig.app.json * refactor: formatcode * refactor: format code * feat: implement types for fuzzy search --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: Francisca Vicente de Almeida <francisca.vicente.de.almeida@tecnico.ulisboa.pt>
1 parent 4ae944f commit c55795d

File tree

5 files changed

+86
-32
lines changed

5 files changed

+86
-32
lines changed

frontend/package-lock.json

Lines changed: 10 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

frontend/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
"format": "prettier . --write"
1818
},
1919
"dependencies": {
20+
"@nozbe/microfuzz": "^1.0.0",
2021
"@pinia/colada": "^0.17.1",
2122
"@tailwindcss/vite": "^4.1.11",
2223
"@vee-validate/zod": "^4.15.1",

frontend/src/components/CompanyOrSpeakerOrMemberAutocompleteWithDialog.vue

Lines changed: 61 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -306,6 +306,7 @@ import {
306306
} from "@/components/ui/alert-dialog";
307307
import type { Company } from "@/dto/companies";
308308
import type { Speaker } from "@/dto/speakers";
309+
import createFuzzySearch, { type FuzzySearcher } from "@nozbe/microfuzz";
309310
import type { Member } from "@/dto/members";
310311
311312
type SelectedItem = Company | Speaker | Member;
@@ -352,10 +353,15 @@ const highlightedIndex = ref(-1);
352353
353354
// Detect platform for keyboard shortcut
354355
const isMac = computed(() => {
355-
return (
356-
typeof navigator !== "undefined" &&
357-
navigator.platform.toUpperCase().indexOf("MAC") >= 0
358-
);
356+
if (typeof navigator === "undefined") return false;
357+
358+
const uaData = navigator.userAgentData;
359+
if (uaData && typeof uaData.platform === "string") {
360+
return uaData.platform === "macOS";
361+
}
362+
363+
const ua = navigator.userAgent || "";
364+
return /\bMacintosh\b|\bMac OS X\b/.test(ua);
359365
});
360366
361367
// Companies query
@@ -382,41 +388,68 @@ const isLoading = computed(
382388
() => companiesLoading.value || speakersLoading.value || membersLoading.value,
383389
);
384390
391+
const companyFuzzy = ref<FuzzySearcher<Company> | null>(null);
392+
watch(
393+
() => companiesData.value?.data,
394+
(list) => {
395+
if (!list || !list.length) {
396+
companyFuzzy.value = null;
397+
} else {
398+
companyFuzzy.value = createFuzzySearch(list, {
399+
// match on multiple fields
400+
getText: (company: Company) => [
401+
company.name,
402+
company.description ?? "",
403+
],
404+
});
405+
}
406+
},
407+
{ immediate: true },
408+
);
409+
410+
const speakerFuzzy = ref<FuzzySearcher<Speaker> | null>(null);
411+
watch(
412+
() => speakersData.value?.data,
413+
(list) => {
414+
if (!list || !list.length) {
415+
speakerFuzzy.value = null;
416+
} else {
417+
speakerFuzzy.value = createFuzzySearch(list, {
418+
getText: (speaker: Speaker) => [
419+
speaker.name,
420+
speaker.companyName ?? "",
421+
],
422+
});
423+
}
424+
},
425+
{ immediate: true },
426+
);
385427
const filteredCompanies = computed(() => {
386-
if (!companiesData.value?.data) return [];
428+
const list = companiesData.value?.data ?? [];
429+
const term = searchTerm.value.trim();
387430
388-
const term = searchTerm.value.toLowerCase();
431+
// Show recent companies when no search term
432+
if (!term) return list.slice(0, 5);
389433
390-
if (!term) {
391-
// Show recent companies when no search term
392-
return companiesData.value.data.slice(0, 5);
393-
}
434+
const fuzzy = companyFuzzy.value;
435+
if (!fuzzy) return [];
394436
395-
return companiesData.value.data
396-
.filter(
397-
(company: Company) =>
398-
company.name.toLowerCase().includes(term) ||
399-
company.description?.toLowerCase().includes(term),
400-
)
437+
return fuzzy(term)
438+
.map((res: { item: Company }) => res.item)
401439
.slice(0, 5); // Limit to 5 results
402440
});
403441
404442
const filteredSpeakers = computed(() => {
405-
if (!speakersData.value?.data) return [];
443+
const list = speakersData.value?.data ?? [];
444+
const term = searchTerm.value.trim();
406445
407-
const term = searchTerm.value.toLowerCase();
446+
if (!term) return list.slice(0, 5);
408447
409-
if (!term) {
410-
// Show recent speakers when no search term
411-
return speakersData.value.data.slice(0, 5);
412-
}
448+
const fuzzy = speakerFuzzy.value;
449+
if (!fuzzy) return [];
413450
414-
return speakersData.value.data
415-
.filter(
416-
(speaker: Speaker) =>
417-
speaker.name.toLowerCase().includes(term) ||
418-
speaker.companyName?.toLowerCase().includes(term),
419-
)
451+
return fuzzy(term)
452+
.map((res: { item: Speaker }) => res.item)
420453
.slice(0, 5); // Limit to 5 results
421454
});
422455

frontend/src/global.d.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
interface NavigatorUAData {
2+
platform?: string;
3+
brands?: Array<{ brand: string; version: string }>;
4+
mobile?: boolean;
5+
}
6+
7+
declare global {
8+
interface Navigator {
9+
userAgentData?: NavigatorUAData;
10+
}
11+
}
12+
13+
export {};

frontend/tsconfig.app.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,5 +16,5 @@
1616
"noFallthroughCasesInSwitch": true,
1717
"noUncheckedSideEffectImports": true
1818
},
19-
"include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.vue"]
19+
"include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.vue", "src/**/*.d.ts"]
2020
}

0 commit comments

Comments
 (0)