- Plant Clock "Job cards" tab: scan a vehicle QR to start a job, scan again to finish with odometer (or no meter), kind of work and a note - /running/job-cards table with period, work, vehicle and mechanic filters - Remove /running/operators and /running/mechanics; mechanic badge PDF moves to Employees Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
28 lines
2 KiB
TypeScript
28 lines
2 KiB
TypeScript
'use client';
|
|
import { useState } from 'react';
|
|
|
|
type Option = { id: string; label: string; sub?: string };
|
|
|
|
/** Type to filter, tap to choose. A native <select> cannot be searched on a phone. */
|
|
export function SearchPicker({ id, options, value, onChange, placeholder }: { id: string; options: Option[]; value: string; onChange: (id: string) => void; placeholder: string }) {
|
|
const chosen = options.find(o => o.id === value);
|
|
const [query, setQuery] = useState('');
|
|
const [open, setOpen] = useState(false);
|
|
const needle = query.trim().toLowerCase();
|
|
const matches = needle ? options.filter(o => `${o.label} ${o.sub ?? ''}`.toLowerCase().includes(needle)) : options;
|
|
return <div className="relative mt-1">
|
|
<input id={id} className="plant-clock-input" autoComplete="off" role="combobox" aria-expanded={open} aria-controls={`${id}-list`} placeholder={placeholder}
|
|
value={open ? query : chosen ? `${chosen.label}${chosen.sub ? ` · ${chosen.sub}` : ''}` : ''}
|
|
onFocus={() => { setQuery(''); setOpen(true); }}
|
|
onChange={e => { setQuery(e.target.value); setOpen(true); if (value) onChange(''); }}
|
|
onBlur={() => setTimeout(() => setOpen(false), 150)}
|
|
onKeyDown={e => { if (e.key === 'Enter') { e.preventDefault(); if (matches[0]) { onChange(matches[0].id); setOpen(false); } } if (e.key === 'Escape') setOpen(false); }}/>
|
|
{open && <ul id={`${id}-list`} role="listbox" className="absolute z-30 mt-1 w-full max-h-64 overflow-y-auto rounded-xl border border-border bg-surface shadow-lg">
|
|
{matches.length === 0 ? <li className="px-3 py-3 text-sm text-text-muted">Nothing matches “{query}”.</li>
|
|
: matches.map(o => <li key={o.id} role="option" aria-selected={o.id === value}>
|
|
<button type="button" className="w-full min-h-[44px] px-3 text-left text-sm hover:bg-bg"
|
|
onMouseDown={e => e.preventDefault()} onClick={() => { onChange(o.id); setOpen(false); }}>
|
|
<span className="font-semibold">{o.label}</span>{o.sub ? ` · ${o.sub}` : ''}</button></li>)}
|
|
</ul>}
|
|
</div>;
|
|
}
|