fix(ServiceRequest): строка сметы/объёмов работ не влезала в узкий Drawer

Форма открывается в боковом Drawer, а не на всю страницу — его ширина не
связана с viewport, поэтому брейкпоинты CollapseSection/FormGrid (следят
за шириной окна) не спасают. Фикс-колонки строки (ед.изм/кол-во/цена/
сумма) при этом не сжимались (grid-колонки по умолчанию не сжимаются
своего содержимого) и вылезали за рамку карточки.

Обернул строки в горизонтально скроллящийся контейнер (тот же приём, что
раньше был у плоской таблицы — .tableWrap в AnnexesSection) вместо попытки
сжать поля до нечитаемости.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
gorbenko.pavel
2026-09-01 16:28:21 +05:00
parent 0b9bb80b1f
commit 97fb71036b
2 changed files with 72 additions and 55 deletions
@@ -54,12 +54,27 @@
}
}
// Строка = наименование / ед.изм / кол-во / цена / сумма (+ кнопка удаления).
// Строка целиком в фикс-колонках (ед.изм/кол-во/цена/сумма) физически не
// влезает в узкую панель — форма открывается в Drawer сбоку, а не на всю
// страницу, и его ширина не связана с шириной окна браузера (в отличие от
// брейкпоинтов CollapseSection/FormGrid, которые следят за viewport, а не за
// шириной своего контейнера). Вместо того чтобы сжимать поля до
// нечитаемости, даём строке её естественную минимальную ширину и скроллим
// горизонтально контейнером — тот же приём, что раньше был у плоской таблицы
// (см. AnnexesSection .tableWrap), а не «резиновая» вёрстка, которая сама себя
// сжать до бесконечности не умеет (grid-колонки по умолчанию не сжимаются
// уже своего содержимого).
.rowsWrap {
width: 100%;
overflow-x: auto;
}
.row {
display: grid;
grid-template-columns: 1fr 96px 110px 140px 140px 32px;
grid-template-columns: minmax(160px, 1fr) 84px 96px 128px 128px 30px;
gap: $sp-2;
align-items: center;
min-width: 640px;
& + & {
margin-top: $sp-2;
@@ -44,61 +44,63 @@ const CostGroup: React.FC<IGroupProps> = ({
)}
</div>
<div className={styles.row} role="row">
<span className={styles.colHead}>Наименование позиции</span>
<span className={styles.colHead}>Ед. изм.</span>
<span className={styles.colHead}>Количество</span>
<span className={styles.colHead}>Цена</span>
<span className={styles.colHead}>Сумма</span>
{!readOnly && <span className={styles.colHead} aria-hidden />}
</div>
<div className={styles.rowsWrap}>
<div className={styles.row} role="row">
<span className={styles.colHead}>Наименование позиции</span>
<span className={styles.colHead}>Ед. изм.</span>
<span className={styles.colHead}>Количество</span>
<span className={styles.colHead}>Цена</span>
<span className={styles.colHead}>Сумма</span>
{!readOnly && <span className={styles.colHead} aria-hidden />}
</div>
{group.items.length === 0 ? (
<div className={styles.empty}>Позиции не добавлены</div>
) : (
group.items.map((item, i) =>
readOnly ? (
<div className={styles.row} key={i} role="row">
<span className={styles.cellStatic}>{item.description || '—'}</span>
<span className={styles.cellStatic}>{item.unit || '—'}</span>
<span className={styles.cellStatic}>{item.qty || '—'}</span>
<span className={styles.cellStatic}>{item.rate || '—'}</span>
<span className={styles.cellAmount}>{formatAmount(rowAmount(item))}</span>
</div>
) : (
<div className={styles.row} key={i} role="row">
<TextInput
value={item.description}
onChange={(v) => onChangeItem(i, { description: v })}
placeholder="Наименование"
ariaLabel="Наименование позиции"
/>
<TextInput
value={item.unit}
onChange={(v) => onChangeItem(i, { unit: v })}
placeholder="шт."
ariaLabel="Единица измерения"
/>
<TextInput
value={item.qty}
onChange={(v) => onChangeItem(i, { qty: v })}
placeholder="0"
ariaLabel="Количество"
/>
<TextInput
value={item.rate}
onChange={(v) => onChangeItem(i, { rate: v })}
placeholder="0"
ariaLabel="Цена за единицу"
/>
<span className={styles.cellAmount}>{item.qty || item.rate ? formatAmount(rowAmount(item)) : 'авто'}</span>
<button type="button" className={styles.removeItem} title="Удалить позицию" onClick={() => onRemoveItem(i)}>
<Icon name="trash" size="sm" />
</button>
</div>
{group.items.length === 0 ? (
<div className={styles.empty}>Позиции не добавлены</div>
) : (
group.items.map((item, i) =>
readOnly ? (
<div className={styles.row} key={i} role="row">
<span className={styles.cellStatic}>{item.description || '—'}</span>
<span className={styles.cellStatic}>{item.unit || '—'}</span>
<span className={styles.cellStatic}>{item.qty || '—'}</span>
<span className={styles.cellStatic}>{item.rate || '—'}</span>
<span className={styles.cellAmount}>{formatAmount(rowAmount(item))}</span>
</div>
) : (
<div className={styles.row} key={i} role="row">
<TextInput
value={item.description}
onChange={(v) => onChangeItem(i, { description: v })}
placeholder="Наименование"
ariaLabel="Наименование позиции"
/>
<TextInput
value={item.unit}
onChange={(v) => onChangeItem(i, { unit: v })}
placeholder="шт."
ariaLabel="Единица измерения"
/>
<TextInput
value={item.qty}
onChange={(v) => onChangeItem(i, { qty: v })}
placeholder="0"
ariaLabel="Количество"
/>
<TextInput
value={item.rate}
onChange={(v) => onChangeItem(i, { rate: v })}
placeholder="0"
ariaLabel="Цена за единицу"
/>
<span className={styles.cellAmount}>{item.qty || item.rate ? formatAmount(rowAmount(item)) : 'авто'}</span>
<button type="button" className={styles.removeItem} title="Удалить позицию" onClick={() => onRemoveItem(i)}>
<Icon name="trash" size="sm" />
</button>
</div>
)
)
)
)}
)}
</div>
<div className={styles.groupFoot}>
{!readOnly && (