Adds fuzzy search and enhances services UI interactions
Implements advanced service search using Fuse.js Improves category expand/collapse animations Adds interactive search functionality for service list Enhances user experience by enabling quick service filtering and smooth UI interactions
This commit is contained in:
parent
4cf30e1f27
commit
fad5e46dfb
4 changed files with 141 additions and 3 deletions
12
PlanTempus.Application/package-lock.json
generated
12
PlanTempus.Application/package-lock.json
generated
|
|
@ -4,6 +4,9 @@
|
|||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"dependencies": {
|
||||
"fuse.js": "^7.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"esbuild": "^0.27.2",
|
||||
"purgecss": "^6.0.0"
|
||||
|
|
@ -654,6 +657,15 @@
|
|||
"url": "https://github.com/sponsors/isaacs"
|
||||
}
|
||||
},
|
||||
"node_modules/fuse.js": {
|
||||
"version": "7.1.0",
|
||||
"resolved": "https://registry.npmjs.org/fuse.js/-/fuse.js-7.1.0.tgz",
|
||||
"integrity": "sha512-trLf4SzuuUxfusZADLINj+dE8clK1frKdmqiJNb1Es75fmI5oY6X2mxLVUciLLjxqw/xr72Dhy+lER6dGd02FQ==",
|
||||
"license": "Apache-2.0",
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/glob": {
|
||||
"version": "10.5.0",
|
||||
"resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz",
|
||||
|
|
|
|||
|
|
@ -6,5 +6,8 @@
|
|||
},
|
||||
"scripts": {
|
||||
"analyze-css": "node analyze-css.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"fuse.js": "^7.1.0"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -37,10 +37,16 @@ swp-search-input {
|
|||
border: none;
|
||||
background: transparent;
|
||||
outline: none;
|
||||
box-shadow: none;
|
||||
font-size: var(--font-size-base);
|
||||
color: var(--color-text);
|
||||
width: 100%;
|
||||
|
||||
&:focus {
|
||||
outline: none;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
&::placeholder {
|
||||
color: var(--color-text-tertiary);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,15 +1,133 @@
|
|||
/**
|
||||
* Services Controller
|
||||
* Handles category collapse/expand animations
|
||||
* Handles category collapse/expand animations and fuzzy search
|
||||
*/
|
||||
|
||||
import Fuse from 'fuse.js';
|
||||
|
||||
interface ServiceItem {
|
||||
id: string;
|
||||
name: string;
|
||||
element: HTMLElement;
|
||||
categoryRow: HTMLElement;
|
||||
}
|
||||
|
||||
export class ServicesController {
|
||||
private fuse: Fuse<ServiceItem> | null = null;
|
||||
private services: ServiceItem[] = [];
|
||||
|
||||
constructor() {
|
||||
this.init();
|
||||
}
|
||||
|
||||
private init(): void {
|
||||
this.setupCategoryToggle();
|
||||
this.setupSearch();
|
||||
}
|
||||
|
||||
private setupSearch(): void {
|
||||
const searchInput = document.querySelector('swp-search-input input') as HTMLInputElement;
|
||||
if (!searchInput) return;
|
||||
|
||||
// Build service index from DOM
|
||||
this.buildServiceIndex();
|
||||
|
||||
// Initialize Fuse.js
|
||||
this.fuse = new Fuse(this.services, {
|
||||
keys: ['name'],
|
||||
threshold: 0.3,
|
||||
minMatchCharLength: 2
|
||||
});
|
||||
|
||||
// Listen for input
|
||||
searchInput.addEventListener('input', (e) => {
|
||||
const query = (e.target as HTMLInputElement).value.trim();
|
||||
this.filterServices(query);
|
||||
});
|
||||
}
|
||||
|
||||
private buildServiceIndex(): void {
|
||||
const serviceRows = document.querySelectorAll('swp-card.services-list swp-data-table-row');
|
||||
|
||||
serviceRows.forEach((row) => {
|
||||
const element = row as HTMLElement;
|
||||
const id = element.getAttribute('data-service-detail') || '';
|
||||
const nameCell = element.querySelector('swp-data-table-cell:first-child');
|
||||
const name = nameCell?.textContent?.trim() || '';
|
||||
|
||||
// Find the category row for this service
|
||||
let categoryRow: HTMLElement | null = null;
|
||||
let sibling = element.previousElementSibling;
|
||||
while (sibling) {
|
||||
if (sibling.tagName.toLowerCase() === 'swp-category-row') {
|
||||
categoryRow = sibling as HTMLElement;
|
||||
break;
|
||||
}
|
||||
sibling = sibling.previousElementSibling;
|
||||
}
|
||||
|
||||
if (categoryRow) {
|
||||
this.services.push({
|
||||
id,
|
||||
name,
|
||||
element,
|
||||
categoryRow
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private filterServices(query: string): void {
|
||||
if (!query || query.length < 2) {
|
||||
// Show all services and categories
|
||||
this.showAll();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.fuse) return;
|
||||
|
||||
// Get matching services
|
||||
const results = this.fuse.search(query);
|
||||
const matchingIds = new Set(results.map(r => r.item.id));
|
||||
|
||||
// Track which categories have visible services
|
||||
const visibleCategories = new Set<HTMLElement>();
|
||||
|
||||
// Show/hide services
|
||||
this.services.forEach(service => {
|
||||
if (matchingIds.has(service.id)) {
|
||||
service.element.style.display = 'grid';
|
||||
visibleCategories.add(service.categoryRow);
|
||||
} else {
|
||||
service.element.style.display = 'none';
|
||||
}
|
||||
});
|
||||
|
||||
// Show/hide categories based on whether they have visible services
|
||||
const allCategoryRows = document.querySelectorAll('swp-card.services-list swp-category-row');
|
||||
allCategoryRows.forEach((row) => {
|
||||
const categoryRow = row as HTMLElement;
|
||||
if (visibleCategories.has(categoryRow)) {
|
||||
categoryRow.style.display = 'grid';
|
||||
// Ensure category is expanded when filtering
|
||||
categoryRow.setAttribute('data-expanded', 'true');
|
||||
} else {
|
||||
categoryRow.style.display = 'none';
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private showAll(): void {
|
||||
// Show all services
|
||||
this.services.forEach(service => {
|
||||
service.element.style.display = 'grid';
|
||||
});
|
||||
|
||||
// Show all categories
|
||||
const allCategoryRows = document.querySelectorAll('swp-card.services-list swp-category-row');
|
||||
allCategoryRows.forEach((row) => {
|
||||
(row as HTMLElement).style.display = 'grid';
|
||||
});
|
||||
}
|
||||
|
||||
private setupCategoryToggle(): void {
|
||||
|
|
@ -18,7 +136,6 @@ export class ServicesController {
|
|||
if (!categoryRow) return;
|
||||
|
||||
const isExpanded = categoryRow.getAttribute('data-expanded') !== 'false';
|
||||
const categoryId = categoryRow.getAttribute('data-category');
|
||||
|
||||
// Find all service rows belonging to this category
|
||||
const serviceRows = this.getServiceRowsForCategory(categoryRow);
|
||||
|
|
@ -77,7 +194,7 @@ export class ServicesController {
|
|||
}
|
||||
|
||||
private expandRows(rows: HTMLElement[]): void {
|
||||
rows.forEach((row, index) => {
|
||||
rows.forEach((row) => {
|
||||
// First make visible but with 0 height
|
||||
row.style.display = 'grid';
|
||||
row.style.height = '0';
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue