fix(supply-quick-add): sticky input at viewport bottom + auto-scroll on add
Some checks are pending
Some checks are pending
Quick-add bar теперь не sticky-внутри-Section, а отдельный flex-sibling
формы — всегда прибит к нижнему краю viewport независимо от высоты
содержимого и overflow-hidden у Section'а:
<form flex flex-col h-full>
<topbar />
<body flex-1 overflow-auto> ← скроллится
<quick-add bar flex-shrink-0> ← всегда виден
</form>
После каждого добавления строки скролл-контейнер тела документа
автоскроллится к низу (smooth, через requestAnimationFrame чтобы
дождаться рендера новой строки) — новая строка всегда появляется
прямо над input'ом и пользователь видит подтверждение скана.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
cad6b32f5e
commit
f9a17ad5c2
|
|
@ -1,4 +1,4 @@
|
|||
import { useState, useEffect, type FormEvent, type ReactNode } from 'react'
|
||||
import { useState, useEffect, useRef, type FormEvent, type ReactNode } from 'react'
|
||||
import { useNavigate, useParams, Link } from 'react-router-dom'
|
||||
import { useQuery, useQueryClient, useMutation } from '@tanstack/react-query'
|
||||
import { ArrowLeft, Plus, Trash2, Save, CheckCircle } from 'lucide-react'
|
||||
|
|
@ -65,6 +65,16 @@ export function SupplyEditPage() {
|
|||
const [form, setForm] = useState<Form>(emptyForm)
|
||||
const [pickerOpen, setPickerOpen] = useState(false)
|
||||
const [error, setError] = useState<string | null>(null)
|
||||
// Скролл-контейнер тела документа: после каждого добавления строки
|
||||
// автоскроллим к низу, чтобы новая строка и input оказались в зоне видимости.
|
||||
const scrollBodyRef = useRef<HTMLDivElement>(null)
|
||||
const scrollToBottom = () => {
|
||||
const el = scrollBodyRef.current
|
||||
if (!el) return
|
||||
requestAnimationFrame(() => {
|
||||
el.scrollTo({ top: el.scrollHeight, behavior: 'smooth' })
|
||||
})
|
||||
}
|
||||
|
||||
const existing = useQuery({
|
||||
queryKey: ['/api/purchases/supplies', id],
|
||||
|
|
@ -211,6 +221,7 @@ export function SupplyEditPage() {
|
|||
...form,
|
||||
lines: form.lines.map((l, ix) => ix === idx ? { ...l, quantity: l.quantity + 1 } : l),
|
||||
})
|
||||
scrollToBottom()
|
||||
return true
|
||||
}
|
||||
const defaultRetail = p.prices?.[0]?.amount ?? null
|
||||
|
|
@ -228,6 +239,7 @@ export function SupplyEditPage() {
|
|||
retailPriceOverride: null,
|
||||
}],
|
||||
})
|
||||
scrollToBottom()
|
||||
return false
|
||||
}
|
||||
const updateLine = (i: number, patch: Partial<LineRow>) =>
|
||||
|
|
@ -273,7 +285,7 @@ export function SupplyEditPage() {
|
|||
</div>
|
||||
|
||||
{/* Scrollable body */}
|
||||
<div className="flex-1 overflow-auto">
|
||||
<div ref={scrollBodyRef} className="flex-1 overflow-auto">
|
||||
<div className="max-w-6xl mx-auto p-3 sm:p-6 space-y-4 sm:space-y-5">
|
||||
{error && (
|
||||
<div className="p-3 rounded-md bg-red-50 text-red-700 text-sm border border-red-200">{error}</div>
|
||||
|
|
@ -448,23 +460,25 @@ export function SupplyEditPage() {
|
|||
)}
|
||||
|
||||
</Section>
|
||||
|
||||
{!isPosted && (
|
||||
// Sticky-bottom вне Section (overflow-hidden родителя ломает sticky):
|
||||
// пользователь может подряд сканировать партию штрихкодов — после
|
||||
// каждого скана строка добавляется в таблицу выше, а input остаётся
|
||||
// прибит к низу скроллируемого тела документа и принимает следующий ввод.
|
||||
<div className="sticky bottom-0 -mx-3 sm:-mx-6 px-3 sm:px-6 py-3 bg-white dark:bg-slate-900 border-t border-slate-200 dark:border-slate-800 z-10 shadow-[0_-4px_12px_rgba(0,0,0,0.04)]">
|
||||
<SupplyLineQuickAdd
|
||||
storeId={form.storeId}
|
||||
onPick={addOrIncrementLine}
|
||||
linesCount={form.lines.length}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Quick-add bar — flex-sibling формы, всегда у нижнего края viewport.
|
||||
* Не sticky внутри scroll-body, чтобы overflow родителей и высота
|
||||
* содержимого не влияли на видимость. После каждого добавления
|
||||
* строки тело документа автоскроллится к низу. */}
|
||||
{!isPosted && (
|
||||
<div className="flex-shrink-0 bg-white dark:bg-slate-900 border-t border-slate-200 dark:border-slate-800 px-3 sm:px-6 py-3 shadow-[0_-4px_12px_rgba(0,0,0,0.04)]">
|
||||
<div className="max-w-6xl mx-auto">
|
||||
<SupplyLineQuickAdd
|
||||
storeId={form.storeId}
|
||||
onPick={addOrIncrementLine}
|
||||
linesCount={form.lines.length}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<ProductPicker open={pickerOpen} onClose={() => setPickerOpen(false)} onPick={addLineFromProduct} />
|
||||
</form>
|
||||
)
|
||||
|
|
|
|||
Loading…
Reference in a new issue