feat: add autocomplete dropdown support for filter fields
- Add fetchFieldValues helper function for API calls
- Add fetchAuthorValues for author autocomplete
- Add fetchGenreValues for genre autocomplete
- Add fetchSeriesValues for series autocomplete
- Add fetchLanguageValues for language autocomplete
- Functions use native DOM manipulation to populate datalist elements
- No Alpine.js reactive state (simple pattern, not reactive)
- Functions registered as methods in Alpine.data("bookshelf") component
- Triggers on input with 300ms debounce after 2 characters minimum
- Updates include count in option text (e.g., "Asimov, Isaac (47)")
Uses /api/media-items/search with field-specific params (author=value, genre=value, etc.).
This commit is contained in:
@@ -227,6 +227,87 @@ Alpine.data("bookshelf", () => ({
|
|||||||
showToast("Error saving filter", "error");
|
showToast("Error saving filter", "error");
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// Autocomplete helper function
|
||||||
|
async fetchFieldValues(
|
||||||
|
field: string,
|
||||||
|
search: string,
|
||||||
|
datalistId: string,
|
||||||
|
): Promise<void> {
|
||||||
|
const token = localStorage.getItem("token");
|
||||||
|
if (!token) {
|
||||||
|
console.error("Not authenticated");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (search.length < 2) return;
|
||||||
|
|
||||||
|
const currentLibraryId = (
|
||||||
|
document.getElementById("library-select") as HTMLSelectElement
|
||||||
|
)?.value;
|
||||||
|
if (!currentLibraryId) {
|
||||||
|
console.error("No library selected");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(
|
||||||
|
`/api/media-items/search?${field}=${encodeURIComponent(
|
||||||
|
search,
|
||||||
|
)}&library_id=${currentLibraryId}&limit=50`,
|
||||||
|
{
|
||||||
|
headers: { Authorization: `Bearer ${token}` },
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
console.error("Failed to fetch field values");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
|
||||||
|
// Update datalist via DOM manipulation
|
||||||
|
const datalist = document.getElementById(datalistId);
|
||||||
|
if (!datalist) {
|
||||||
|
console.error(`Datalist ${datalistId} not found`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear existing options
|
||||||
|
datalist.innerHTML = "";
|
||||||
|
|
||||||
|
// Add new options
|
||||||
|
data.results.forEach((item: { value: string; count: number }) => {
|
||||||
|
const option = document.createElement("option");
|
||||||
|
option.value = item.value;
|
||||||
|
option.textContent = `${item.value} (${item.count})`;
|
||||||
|
datalist.appendChild(option);
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error fetching field values:", error);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// Fetch author values for autocomplete
|
||||||
|
async fetchAuthorValues(input: HTMLInputElement): Promise<void> {
|
||||||
|
await this.fetchFieldValues("author", input.value, "author-datalist");
|
||||||
|
},
|
||||||
|
|
||||||
|
// Fetch genre values for autocomplete
|
||||||
|
async fetchGenreValues(input: HTMLInputElement): Promise<void> {
|
||||||
|
await this.fetchFieldValues("genre", input.value, "genre-datalist");
|
||||||
|
},
|
||||||
|
|
||||||
|
// Fetch series values for autocomplete
|
||||||
|
async fetchSeriesValues(input: HTMLInputElement): Promise<void> {
|
||||||
|
await this.fetchFieldValues("series", input.value, "series-datalist");
|
||||||
|
},
|
||||||
|
|
||||||
|
// Fetch language values for autocomplete
|
||||||
|
async fetchLanguageValues(input: HTMLInputElement): Promise<void> {
|
||||||
|
await this.fetchFieldValues("language", input.value, "language-datalist");
|
||||||
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
// Standalone function - initialize the bookshelf (NOT async, like dashboard.ts)
|
// Standalone function - initialize the bookshelf (NOT async, like dashboard.ts)
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user