feat(badges): search filter polish and result limit stepper

- Replace show_hidden checkbox with visibility_filter select (Default /
  Show Hidden / Show Disabled+Hidden) — collapses two orphaned boolean
  fields (show_hidden, show_not_enabled) into one purpose-built value;
  wires disabled-badge filter through to both IDB and API paths
- Add max-results stepper (edit mode only): steps of 25 up to 250,
  steps of 100 up to 2550; tier-capped (trusted=250, manager=2550);
  stepper uses pure reactivity — no handle_search_trigger() call needed
- Fix fallback liveQuery (SCENARIO 2): was hardcoded .limit(50);
  now reads qry_result_limit in outer $derived.by so Svelte tracks it
  and stepper updates the no-text browse list immediately
- Fix Search button disabled state: replace pointer-events-none +
  class:opacity-50 with HTML disabled attribute + disabled:cursor-not-allowed
  so hover cursor reflects disabled state correctly
- Global placeholder fix (app.css): add italic + opacity-0.6 rule for
  .input/.textarea ::placeholder in light mode; add italic to dark rule —
  prevents placeholder text from reading as typed content

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Scott Idem
2026-06-04 18:33:59 -04:00
parent b45a27481a
commit 9d904446d4
5 changed files with 110 additions and 32 deletions

View File

@@ -101,6 +101,9 @@ $effect(() => {
let lq__event_badge_obj_li = $derived.by(() => {
const ids = event_badge_id_li;
const event_id = $events_slct?.event_id;
// Read in outer scope so Svelte tracks it — liveQuery async callbacks are not tracked.
// In edit mode the stepper controls the limit; otherwise fall back to 50.
const fallback_limit = $ae_loc.edit_mode ? badges_loc.current.qry_result_limit : 50;
return liveQuery(async () => {
// SCENARIO 1: Specific IDs provided (Search Results)
@@ -128,7 +131,7 @@ let lq__event_badge_obj_li = $derived.by(() => {
return await db_events.badge
.where('event_id')
.equals(event_id)
.limit(50)
.limit(fallback_limit)
.sortBy('given_name');
}
@@ -169,10 +172,13 @@ let search_params = $derived({
printed: badges_loc.current.qry_printed_status,
aff: (badges_loc.current.qry_affiliations ?? '').toLowerCase().trim(),
sort: badges_loc.current.qry_sort_order,
show_hidden: badges_loc.current.show_hidden,
visibility_filter: badges_loc.current.visibility_filter,
event_id: $events_slct?.event_id,
remote_first: badges_loc.current.qry__remote_first,
result_limit: effective_search_limits.result_limit,
// In edit mode the stepper overrides the server-configured tier limit.
result_limit: $ae_loc.edit_mode
? badges_loc.current.qry_result_limit
: effective_search_limits.result_limit,
min_chars: effective_search_limits.min_chars
});
@@ -219,7 +225,9 @@ async function handle_search_refresh(params: any) {
const result_limit = params.result_limit;
const min_chars = params.min_chars;
const show_hidden = params.show_hidden;
const visibility_filter = params.visibility_filter as 'default' | 'show_hidden' | 'show_all';
const show_hidden = visibility_filter !== 'default';
const show_all = visibility_filter === 'show_all';
// Defense-in-depth: enforce min_chars even if the search component lets one through.
// Exception: if the user has set a non-default filter or sort, that is explicit intent —
@@ -243,8 +251,9 @@ async function handle_search_refresh(params: any) {
.where('event_id')
.equals(event_id)
.filter((badge) => {
// Exclude hidden badges unless show_hidden is active
// Exclude hidden/disabled badges unless the visibility filter allows them
if (!show_hidden && badge.hide) return false;
if (!show_all && badge.enable === false) return false;
if (type_code && badge.badge_type_code !== type_code)
return false;
@@ -423,6 +432,7 @@ async function handle_search_refresh(params: any) {
type_code: type_code || null,
printed_status: printed_status,
affiliations_qry_str: aff_str || null,
enabled: show_all ? 'all' : 'enabled',
hidden: show_hidden ? 'all' : 'not_hidden',
order_by_li: order_by_li,
limit: result_limit,