feat(tyres): по умолчанию только подходящая резина, «Показать все» разворачивает
Кладовщик у подъёмника выбирал размер из девятнадцати позиций глазами. Теперь экран показывает только подходящее выбранной машине (по подтверждённой применимости), с счётчиком и кнопкой-переключателем на полный список. Если по модели применимость не отмечена, фильтровать нечем — показываются все размеры и объясняется, почему: прятать правильную резину по незнанию хуже, чем показать лишнюю. Плашка на строке различает «подходит» (применимость) и «штатный» (справочник размеров), и предвыбор позиции теперь учитывает оба признака.
This commit is contained in:
@@ -375,6 +375,8 @@ export interface TyreStockRow {
|
||||
by_grade: Partial<Record<TyreGrade, number>>;
|
||||
places: string[];
|
||||
suggested: boolean;
|
||||
/** Подтверждённая применимость к выбранной машине. */
|
||||
fits: boolean;
|
||||
}
|
||||
|
||||
export interface TyreSet {
|
||||
@@ -410,6 +412,8 @@ export interface TyreContext {
|
||||
suggested_size: string | null;
|
||||
open_set: TyreSet | null;
|
||||
stock: TyreStockRow[];
|
||||
/** Известно ли, что подходит машине. false — фильтровать нечем, показываем всё. */
|
||||
fit_known: boolean;
|
||||
}
|
||||
|
||||
export interface TyreClose {
|
||||
|
||||
@@ -179,14 +179,19 @@ function IssueForm({ carId, onDone }: { carId: number; onDone: () => void }) {
|
||||
// Подставляем подсказанный размер, как только пришёл контекст.
|
||||
useEffect(() => {
|
||||
if (!ctx || partId !== null) return;
|
||||
const suggested = ctx.stock.find((s) => s.suggested && s.qty > 0);
|
||||
if (suggested) setPartId(suggested.part_id);
|
||||
const best = ctx.stock.find((s) => (s.fits || s.suggested) && s.qty > 0);
|
||||
if (best) setPartId(best.part_id);
|
||||
}, [ctx, partId]);
|
||||
|
||||
const visibleStock = useMemo(() => {
|
||||
if (!ctx) return [];
|
||||
return showAll ? ctx.stock : ctx.stock.slice(0, 6);
|
||||
}, [ctx, showAll]);
|
||||
// По умолчанию показываем ТОЛЬКО подходящее этой машине — иначе кладовщик
|
||||
// выбирает размер из девятнадцати позиций глазами. Если применимость по
|
||||
// машине не заполнена, фильтровать нечем: прятать правильную резину по
|
||||
// незнанию хуже, чем показать лишнюю (то же правило, что у подбора запчастей).
|
||||
const fitting = useMemo(
|
||||
() => (ctx ? ctx.stock.filter((s) => s.fits || s.suggested) : []),
|
||||
[ctx],
|
||||
);
|
||||
const visibleStock = showAll || !ctx?.fit_known ? (ctx?.stock ?? []) : fitting;
|
||||
|
||||
if (ctxQuery.isLoading) {
|
||||
return <main className="p-4 text-sm text-muted-foreground">Загрузка…</main>;
|
||||
@@ -290,7 +295,20 @@ function IssueForm({ carId, onDone }: { carId: number; onDone: () => void }) {
|
||||
|
||||
{/* Что выдаём */}
|
||||
<section className="p-3 border-b">
|
||||
<h2 className="text-sm font-semibold mb-2">Что выдаём</h2>
|
||||
<div className="flex items-baseline justify-between mb-2">
|
||||
<h2 className="text-sm font-semibold">Что выдаём</h2>
|
||||
{ctx.fit_known && (
|
||||
<span className="text-xs text-muted-foreground">
|
||||
{showAll ? `все размеры (${ctx.stock.length})` : `подходит: ${fitting.length}`}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
{!ctx.fit_known && (
|
||||
<p className="text-xs text-muted-foreground mb-2">
|
||||
Для этой модели не отмечено, какая резина подходит, — показаны все размеры.
|
||||
Отметить можно в карточке позиции на складе.
|
||||
</p>
|
||||
)}
|
||||
<div className="space-y-1">
|
||||
{visibleStock.length === 0 && (
|
||||
<p className="text-sm text-muted-foreground">На складе нет резины.</p>
|
||||
@@ -304,9 +322,16 @@ function IssueForm({ carId, onDone }: { carId: number; onDone: () => void }) {
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
{!showAll && ctx.stock.length > 6 && (
|
||||
<Button variant="ghost" size="sm" className="mt-1" onClick={() => setShowAll(true)}>
|
||||
Показать все размеры ({ctx.stock.length})
|
||||
{ctx.fit_known && (
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className="mt-1"
|
||||
onClick={() => setShowAll((v) => !v)}
|
||||
>
|
||||
{showAll
|
||||
? `Только подходящие (${fitting.length})`
|
||||
: `Показать все размеры (${ctx.stock.length})`}
|
||||
</Button>
|
||||
)}
|
||||
{chosen && chosen.qty < qty && (
|
||||
@@ -400,8 +425,10 @@ function StockRow({
|
||||
<span className="flex-1 min-w-0">
|
||||
<span className="flex items-baseline gap-2 flex-wrap">
|
||||
<span className="font-semibold">{row.size ?? row.name}</span>
|
||||
{row.suggested && (
|
||||
<span className="text-xs rounded px-1.5 py-0.5 bg-primary/10 text-primary">штатный</span>
|
||||
{(row.fits || row.suggested) && (
|
||||
<span className="text-xs rounded px-1.5 py-0.5 bg-primary/10 text-primary">
|
||||
{row.fits ? "подходит" : "штатный"}
|
||||
</span>
|
||||
)}
|
||||
<span className={`text-sm ${row.qty > 0 ? "text-muted-foreground" : "text-destructive"}`}>
|
||||
{row.qty} шт
|
||||
|
||||
Reference in New Issue
Block a user