From b56c499b458f233e6d394c620d7e69394be999e9 Mon Sep 17 00:00:00 2001 From: nns <278048682+nurdotnet@users.noreply.github.com> Date: Sun, 26 Apr 2026 02:33:57 +0500 Subject: [PATCH] fix(supply-quick-add): dropdown opens upward + show only N results + create-new at bottom MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit UX как в сторонняя системае: - Dropdown открывается ВВЕРХ от input'а (anchor по input.top, fixed bottom). Max-height 60vh — не перекрывает шапку, внутри overflow-y. Раньше выпадал вниз и при добавлении строк визуально не оставалось места. - Показываем первые 10 матчей (VISIBLE_LIMIT=10), под ними ссылка «Ещё N товаров» которая раскрывает полный список (limit=50 на сервере). На запрос приходит до 50 — этого достаточно для подавляющего большинства поисков. - Под списком (через тонкий разделитель) — пункт «+ Создать новый товар: «{q}»» / «Создать товар со штрихкодом «…»». Всегда последний визуально, ближайший к input'у — самый удобный для быстрого клика. Стрелки ↑↓ работают по видимой части (1..10 или весь список после expand). Enter подбирает подсвеченный из видимой части. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../src/components/SupplyLineQuickAdd.tsx | 54 ++++++++++++++----- 1 file changed, 41 insertions(+), 13 deletions(-) diff --git a/src/food-market.web/src/components/SupplyLineQuickAdd.tsx b/src/food-market.web/src/components/SupplyLineQuickAdd.tsx index 91667c4..187ef29 100644 --- a/src/food-market.web/src/components/SupplyLineQuickAdd.tsx +++ b/src/food-market.web/src/components/SupplyLineQuickAdd.tsx @@ -54,6 +54,10 @@ export function SupplyLineQuickAdd({ storeId, disabled, onPick }: Props) { const [open, setOpen] = useState(false) const [highlight, setHighlight] = useState(0) const [loading, setLoading] = useState(false) + // По умолчанию показываем первые 10 матчей. Ссылка «Ещё N товаров» + // расширяет dropdown до полного списка (limit=50 на запрос). + const VISIBLE_LIMIT = 10 + const [showAll, setShowAll] = useState(false) const [hint, setHint] = useState(null) const [createPrefill, setCreatePrefill] = useState(null) const wrapRef = useRef(null) const dropdownRef = useRef(null) - const [dropdownPos, setDropdownPos] = useState<{ top: number; left: number; width: number } | null>(null) + const [dropdownPos, setDropdownPos] = useState<{ bottom: number; left: number; width: number } | null>(null) - // Считаем позицию dropdown'а по rect input'а — без этого - // он рендерится через portal в body и будет в углу. + // Считаем позицию dropdown'а по rect input'а. Dropdown открывается ВВЕРХ + // от input'а (МС-стиль): фиксируем `bottom` относительно низа viewport, + // вычисленный из input.top, и max-height 60vh — расти вверх в пределах + // экрана. Без этого portal в body рендерил бы попап в углу. const recomputePos = () => { const el = inputRef.current if (!el) return const r = el.getBoundingClientRect() - setDropdownPos({ top: r.bottom + 4, left: r.left, width: r.width }) + setDropdownPos({ bottom: window.innerHeight - r.top + 4, left: r.left, width: r.width }) } useLayoutEffect(() => { if (!open) return @@ -104,8 +110,9 @@ export function SupplyLineQuickAdd({ storeId, disabled, onPick }: Props) { if (q.length === 0) { setItems([]); setOpen(false); return } const ac = new AbortController() setLoading(true) + setShowAll(false) api.get('/api/catalog/products/quick-search', { - params: { search: q, storeId: storeId || undefined, limit: 20 }, + params: { search: q, storeId: storeId || undefined, limit: 50 }, signal: ac.signal, }).then((res) => { setItems(res.data) @@ -200,9 +207,10 @@ export function SupplyLineQuickAdd({ storeId, disabled, onPick }: Props) { const onKeyDown = async (e: KeyboardEvent) => { if (e.key === 'Escape') { setOpen(false); return } if (e.key === 'Tab') { setOpen(false); return } + const visibleLen = (showAll ? items.length : Math.min(items.length, VISIBLE_LIMIT)) if (e.key === 'ArrowDown') { e.preventDefault() - setHighlight((h) => Math.min(h + 1, items.length - 1)) + setHighlight((h) => Math.min(h + 1, Math.max(0, visibleLen - 1))) return } if (e.key === 'ArrowUp') { @@ -216,9 +224,10 @@ export function SupplyLineQuickAdd({ storeId, disabled, onPick }: Props) { if (q.length === 0) return // Сначала пробуем сканер-флоу — точный barcode lookup if (await tryScanByBarcode(q)) return - // Иначе берём подсвеченный пункт из dropdown - if (items.length > 0) { - const target = items[highlight] ?? items[0] + // Иначе берём подсвеченный пункт из видимой части dropdown'а + const visible = showAll ? items : items.slice(0, VISIBLE_LIMIT) + if (visible.length > 0) { + const target = visible[highlight] ?? visible[0] await addById(target.id) return } @@ -269,16 +278,16 @@ export function SupplyLineQuickAdd({ storeId, disabled, onPick }: Props) { {open && (query.trim().length > 0 || items.length > 0) && dropdownPos && createPortal(
{loading && items.length === 0 ? (
Ищу…
) : items.length === 0 ? (
Ничего не найдено
) : ( -
    - {items.map((it, i) => ( +
      + {(showAll ? items : items.slice(0, VISIBLE_LIMIT)).map((it, i) => (
    • +
+ )} {showCreateRow && (