Task 13: Implement fuzzy search with fuse.js
- Installed fuse.js for fuzzy search functionality - Created src/lib/search.ts with buildSearchIndex and groupResultsBySection functions - Search index includes all consultations, medications, problems, investigations, and documents - Updated ClinicalSidebar to use fuse.js instead of simple filter - Search results grouped by section (Experience, Skills, Achievements, Projects, Education) - Section headers show icon and count - Each result shows title and highlight text (truncated) - Clicking a result navigates to the section and expands the matching item - Minimum 2 characters required for search - Top 10 results displayed - Clean dropdown styling with hover states - Integrates with AccessibilityContext to set expandedItem Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,106 @@
|
||||
import Fuse, { type FuseResult } from 'fuse.js'
|
||||
import type { ViewId } from '@/types/pmr'
|
||||
|
||||
// Import all data sources
|
||||
import { consultations } from '@/data/consultations'
|
||||
import { medications } from '@/data/medications'
|
||||
import { problems } from '@/data/problems'
|
||||
import { investigations } from '@/data/investigations'
|
||||
import { documents } from '@/data/documents'
|
||||
|
||||
export interface SearchResult {
|
||||
id: string
|
||||
title: string
|
||||
section: ViewId
|
||||
sectionLabel: string
|
||||
highlight: string
|
||||
score?: number
|
||||
}
|
||||
|
||||
// Build a unified search index from all PMR content
|
||||
export function buildSearchIndex(): Fuse<SearchResult> {
|
||||
const searchableItems: SearchResult[] = []
|
||||
|
||||
// Index consultations (Experience)
|
||||
consultations.forEach(consultation => {
|
||||
searchableItems.push({
|
||||
id: consultation.id,
|
||||
title: consultation.role,
|
||||
section: 'consultations',
|
||||
sectionLabel: 'Experience',
|
||||
highlight: `${consultation.role} at ${consultation.organization} — ${consultation.history}`,
|
||||
})
|
||||
})
|
||||
|
||||
// Index medications (Skills)
|
||||
medications.forEach(medication => {
|
||||
searchableItems.push({
|
||||
id: medication.id,
|
||||
title: medication.name,
|
||||
section: 'medications',
|
||||
sectionLabel: 'Skills',
|
||||
highlight: `${medication.name} — ${medication.frequency} use since ${medication.startYear}`,
|
||||
})
|
||||
})
|
||||
|
||||
// Index problems (Achievements)
|
||||
problems.forEach(problem => {
|
||||
searchableItems.push({
|
||||
id: problem.id,
|
||||
title: problem.description,
|
||||
section: 'problems',
|
||||
sectionLabel: 'Achievements',
|
||||
highlight: `[${problem.code}] ${problem.description} — ${problem.narrative}`,
|
||||
})
|
||||
})
|
||||
|
||||
// Index investigations (Projects)
|
||||
investigations.forEach(investigation => {
|
||||
searchableItems.push({
|
||||
id: investigation.id,
|
||||
title: investigation.name,
|
||||
section: 'investigations',
|
||||
sectionLabel: 'Projects',
|
||||
highlight: `${investigation.name} — ${investigation.methodology}`,
|
||||
})
|
||||
})
|
||||
|
||||
// Index documents (Education)
|
||||
documents.forEach(document => {
|
||||
searchableItems.push({
|
||||
id: document.id,
|
||||
title: document.title,
|
||||
section: 'documents',
|
||||
sectionLabel: 'Education',
|
||||
highlight: `${document.title} from ${document.source} (${document.date})`,
|
||||
})
|
||||
})
|
||||
|
||||
// Fuse.js configuration for fuzzy search
|
||||
const fuseOptions = {
|
||||
keys: [
|
||||
{ name: 'title', weight: 2 }, // Primary match on title
|
||||
{ name: 'highlight', weight: 1 }, // Secondary match on full text
|
||||
],
|
||||
threshold: 0.3, // 0 = exact match, 1 = match anything
|
||||
includeScore: true,
|
||||
minMatchCharLength: 2,
|
||||
}
|
||||
|
||||
return new Fuse(searchableItems, fuseOptions)
|
||||
}
|
||||
|
||||
// Group search results by section
|
||||
export function groupResultsBySection(results: FuseResult<SearchResult>[]): Map<string, FuseResult<SearchResult>[]> {
|
||||
const grouped = new Map<string, FuseResult<SearchResult>[]>()
|
||||
|
||||
results.forEach(result => {
|
||||
const sectionLabel = result.item.sectionLabel
|
||||
if (!grouped.has(sectionLabel)) {
|
||||
grouped.set(sectionLabel, [])
|
||||
}
|
||||
grouped.get(sectionLabel)!.push(result)
|
||||
})
|
||||
|
||||
return grouped
|
||||
}
|
||||
Reference in New Issue
Block a user