feat(ct): заметное добавление потенциального поставщика на Шаге 3 (#12)
Блок добавления не замечали: поле без подписи и серая заблокированная кнопка читались как «не работает», а сам блок стоял в зоне настроек. Теперь у блока заголовок и подсказка, кнопка «Добавить» не блокируется (с пустым названием подсказывает, что ввести), «Из базы поставщиков» рядом. И вторая точка входа там, где ищут, — колонка «+ Добавить поставщика» в шапке таблицы с окном «вручную или из базы». Добавление вручную — одна функция на обе кнопки. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -59,6 +59,8 @@ export function AddExtraRow({ matrix, isRestrictedView, colSpan }: Props): JSX.E
|
|||||||
{suppliersWithOffer.map(s => (
|
{suppliersWithOffer.map(s => (
|
||||||
<td key={s.id} colSpan={getSubCols(supplierCurrencies[s.id] ?? 'KZT', isRestrictedView)} />
|
<td key={s.id} colSpan={getSubCols(supplierCurrencies[s.id] ?? 'KZT', isRestrictedView)} />
|
||||||
))}
|
))}
|
||||||
|
{/* Под колонкой «+ Добавить поставщика» (видна там же, где эта строка, — не в режиме просмотра). */}
|
||||||
|
<td className={styles.addSupplierFillerCell} />
|
||||||
</tr>
|
</tr>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
+69
@@ -0,0 +1,69 @@
|
|||||||
|
import * as React from 'react';
|
||||||
|
import { useState } from 'react';
|
||||||
|
import { Button, Dialog, Icon, TextInput } from '@tnsu/ui-kit';
|
||||||
|
import styles from './Step3ComparisonTable.module.scss';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
currencyOptions: string[];
|
||||||
|
onAddManual: (name: string, currency: string) => void;
|
||||||
|
onOpenDatabase: () => void;
|
||||||
|
onClose: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const DEFAULT_CURRENCY = 'KZT';
|
||||||
|
|
||||||
|
// Окно кнопки «+ Добавить поставщика» в шапке таблицы: вручную (название и валюта) или из базы
|
||||||
|
// поставщиков. Добавление — те же функции, что у блока над таблицей.
|
||||||
|
export const AddSupplierDialog: React.FC<Props> = ({ currencyOptions, onAddManual, onOpenDatabase, onClose }) => {
|
||||||
|
const [name, setName] = useState('');
|
||||||
|
const [currency, setCurrency] = useState(DEFAULT_CURRENCY);
|
||||||
|
const canAdd = name.trim().length > 0;
|
||||||
|
|
||||||
|
const handleAdd = (): void => {
|
||||||
|
if (!canAdd) return;
|
||||||
|
onAddManual(name, currency);
|
||||||
|
onClose();
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Dialog
|
||||||
|
open
|
||||||
|
title="Добавить потенциального поставщика"
|
||||||
|
subText="Поставщик, у которого нет загруженного КП: цены вносятся в таблицу вручную."
|
||||||
|
onDismiss={onClose}
|
||||||
|
footer={<Button variant="secondary" onClick={onClose}>Отмена</Button>}
|
||||||
|
>
|
||||||
|
<div className={styles.addSupplierDialogBody}>
|
||||||
|
<div className={styles.addSupplierRow}>
|
||||||
|
<div className={styles.addInput}>
|
||||||
|
<TextInput
|
||||||
|
value={name}
|
||||||
|
onChange={setName}
|
||||||
|
placeholder="Наименование поставщика"
|
||||||
|
ariaLabel="Наименование поставщика"
|
||||||
|
autoFocus
|
||||||
|
onKeyDown={e => { if (e.key === 'Enter') handleAdd(); }}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<select
|
||||||
|
className={styles.currencySelect}
|
||||||
|
value={currency}
|
||||||
|
onChange={e => setCurrency(e.target.value)}
|
||||||
|
aria-label="Валюта поставщика"
|
||||||
|
>
|
||||||
|
{currencyOptions.map(c => <option key={c} value={c}>{c}</option>)}
|
||||||
|
</select>
|
||||||
|
<Button variant="accent" onClick={handleAdd} disabled={!canAdd}>
|
||||||
|
<Icon name="plus" size="sm" />
|
||||||
|
Добавить
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
<div className={styles.addSupplierDivider}>или</div>
|
||||||
|
<Button variant="secondary" onClick={() => { onClose(); onOpenDatabase(); }}>
|
||||||
|
<Icon name="building" size="sm" />
|
||||||
|
Выбрать из базы поставщиков
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</Dialog>
|
||||||
|
);
|
||||||
|
};
|
||||||
+82
@@ -0,0 +1,82 @@
|
|||||||
|
import * as React from 'react';
|
||||||
|
import { useState } from 'react';
|
||||||
|
import { Button, Icon, TextInput } from '@tnsu/ui-kit';
|
||||||
|
import styles from './Step3ComparisonTable.module.scss';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
name: string;
|
||||||
|
currency: string;
|
||||||
|
currencyOptions: string[];
|
||||||
|
onNameChange: (value: string) => void;
|
||||||
|
onCurrencyChange: (value: string) => void;
|
||||||
|
onAdd: () => void;
|
||||||
|
onOpenDatabase: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const NAME_INPUT_ID = 'step3-new-supplier-name';
|
||||||
|
|
||||||
|
// Блок «Потенциальные поставщики» над таблицей. Раньше это были поле, выпадашка и серая кнопка без
|
||||||
|
// подписи — их не замечали: заблокированная кнопка читалась как «не работает». Теперь у блока есть
|
||||||
|
// заголовок, кнопка не блокируется: с пустым названием подсказывает, что ввести.
|
||||||
|
export const AddSupplierToolbar: React.FC<Props> = ({
|
||||||
|
name, currency, currencyOptions, onNameChange, onCurrencyChange, onAdd, onOpenDatabase,
|
||||||
|
}) => {
|
||||||
|
const [isNameMissing, setIsNameMissing] = useState(false);
|
||||||
|
|
||||||
|
const handleAdd = (): void => {
|
||||||
|
if (!name.trim()) {
|
||||||
|
setIsNameMissing(true);
|
||||||
|
// Поле — в ui-kit, ref он не отдаёт; id задаём сами.
|
||||||
|
document.getElementById(NAME_INPUT_ID)?.focus();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setIsNameMissing(false);
|
||||||
|
onAdd();
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleNameChange = (value: string): void => {
|
||||||
|
if (value.trim()) setIsNameMissing(false);
|
||||||
|
onNameChange(value);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<section className={styles.addSupplierBlock}>
|
||||||
|
<div className={styles.addSupplierHeading}>
|
||||||
|
<h4 className={styles.addSupplierTitle}>Потенциальные поставщики</h4>
|
||||||
|
<span className={styles.addSupplierHint}>
|
||||||
|
Добавьте поставщика, у которого нет загруженного КП, — вручную или из базы поставщиков
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div className={styles.addSupplierRow}>
|
||||||
|
<div className={styles.addInput}>
|
||||||
|
<TextInput
|
||||||
|
id={NAME_INPUT_ID}
|
||||||
|
placeholder="Наименование поставщика"
|
||||||
|
value={name}
|
||||||
|
onChange={handleNameChange}
|
||||||
|
ariaLabel="Наименование нового потенциального поставщика"
|
||||||
|
onKeyDown={e => { if (e.key === 'Enter') handleAdd(); }}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<select
|
||||||
|
className={styles.currencySelect}
|
||||||
|
value={currency}
|
||||||
|
onChange={e => onCurrencyChange(e.target.value)}
|
||||||
|
aria-label="Валюта нового потенциального поставщика"
|
||||||
|
>
|
||||||
|
{currencyOptions.map(c => <option key={c} value={c}>{c}</option>)}
|
||||||
|
</select>
|
||||||
|
<Button variant="accent" onClick={handleAdd}>
|
||||||
|
<Icon name="plus" size="sm" />
|
||||||
|
Добавить
|
||||||
|
</Button>
|
||||||
|
<span className={styles.addSupplierOr}>или</span>
|
||||||
|
<Button variant="secondary" onClick={onOpenDatabase}>
|
||||||
|
<Icon name="building" size="sm" />
|
||||||
|
Из базы поставщиков
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
{isNameMissing && <span className={styles.addSupplierError}>Введите наименование поставщика</span>}
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
};
|
||||||
+19
-2
@@ -19,12 +19,14 @@ interface Props {
|
|||||||
isRestrictedView: boolean;
|
isRestrictedView: boolean;
|
||||||
adata: UseAdataCheckResult;
|
adata: UseAdataCheckResult;
|
||||||
recommendedId?: string; // победитель формулы 80% цена / 20% условия оплаты (useRecommendedPick)
|
recommendedId?: string; // победитель формулы 80% цена / 20% условия оплаты (useRecommendedPick)
|
||||||
|
// Кнопка «+ Добавить поставщика» в последней колонке шапки.
|
||||||
|
onAddSupplierClick: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Таблица сравнения целиком: шапка (фикс-колонки + заголовки поставщиков) + строки позиций
|
// Таблица сравнения целиком: шапка (фикс-колонки + заголовки поставщиков) + строки позиций
|
||||||
// (MatrixRowView/ExtraPositionRow) + итоговые строки + строка добавления (AddExtraRow).
|
// (MatrixRowView/ExtraPositionRow) + итоговые строки + строка добавления (AddExtraRow).
|
||||||
// Перенесено 1:1 из Step3ComparisonTable (§стайл-гайд: чистая вёрстка, логика — в useStep3Matrix).
|
// Перенесено 1:1 из Step3ComparisonTable (§стайл-гайд: чистая вёрстка, логика — в useStep3Matrix).
|
||||||
export function ComparisonMatrix({ matrix, currencyRates, isRestrictedView, adata, recommendedId }: Props): JSX.Element {
|
export function ComparisonMatrix({ matrix, currencyRates, isRestrictedView, adata, recommendedId, onAddSupplierClick }: Props): JSX.Element {
|
||||||
const {
|
const {
|
||||||
isReadOnly, suppliersWithOffer, supplierCurrencies, selectedSupplierId,
|
isReadOnly, suppliersWithOffer, supplierCurrencies, selectedSupplierId,
|
||||||
supplierStatuses, nameColWidth, regularPositions, extraPositionsRows, allPositions, computedOffers,
|
supplierStatuses, nameColWidth, regularPositions, extraPositionsRows, allPositions, computedOffers,
|
||||||
@@ -37,7 +39,12 @@ export function ComparisonMatrix({ matrix, currencyRates, isRestrictedView, adat
|
|||||||
// Как в проверке перед сохранением (getStep3SaveError): выбор на исключённом — не победитель.
|
// Как в проверке перед сохранением (getStep3SaveError): выбор на исключённом — не победитель.
|
||||||
const hasWinner = suppliersWithOffer.some(s => s.id === selectedSupplierId);
|
const hasWinner = suppliersWithOffer.some(s => s.id === selectedSupplierId);
|
||||||
|
|
||||||
const totalColSpan = FIXED_COLS + suppliersWithOffer.reduce((sum, s) => sum + getSubCols(supplierCurrencies[s.id] ?? 'KZT', isRestrictedView), 0);
|
// Последняя колонка «+ Добавить поставщика» — там, где ищут, как добавить колонку. Во всех строках
|
||||||
|
// под ней пустая ячейка (addSupplierFillerCell), иначе строки таблицы были бы разной длины.
|
||||||
|
const showAddColumn = !isReadOnly;
|
||||||
|
const totalColSpan = FIXED_COLS
|
||||||
|
+ suppliersWithOffer.reduce((sum, s) => sum + getSubCols(supplierCurrencies[s.id] ?? 'KZT', isRestrictedView), 0)
|
||||||
|
+ (showAddColumn ? 1 : 0);
|
||||||
|
|
||||||
// Высота row1 (шапка поставщика) переменная (теги валют, выбор поставщика, состояние Adata),
|
// Высота row1 (шапка поставщика) переменная (теги валют, выбор поставщика, состояние Adata),
|
||||||
// а row2 прибит sticky сразу под ней. Измеряем реальную высоту и отдаём в CSS-переменную,
|
// а row2 прибит sticky сразу под ней. Измеряем реальную высоту и отдаём в CSS-переменную,
|
||||||
@@ -142,6 +149,13 @@ export function ComparisonMatrix({ matrix, currencyRates, isRestrictedView, adat
|
|||||||
</th>
|
</th>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
{showAddColumn && (
|
||||||
|
<th rowSpan={2} className={styles.addSupplierHeaderCell}>
|
||||||
|
<button className={styles.addSupplierHeaderBtn} onClick={onAddSupplierClick} title="Добавить потенциального поставщика">
|
||||||
|
+ Добавить поставщика
|
||||||
|
</button>
|
||||||
|
</th>
|
||||||
|
)}
|
||||||
</tr>
|
</tr>
|
||||||
|
|
||||||
{/* Row 2: sub-headers — 4 cols for KZT, 6 cols for foreign currency */}
|
{/* Row 2: sub-headers — 4 cols for KZT, 6 cols for foreign currency */}
|
||||||
@@ -206,6 +220,7 @@ export function ComparisonMatrix({ matrix, currencyRates, isRestrictedView, adat
|
|||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
{showAddColumn && <td className={styles.addSupplierFillerCell} />}
|
||||||
</tr>
|
</tr>
|
||||||
|
|
||||||
{/* Доп. расходы positions — single row each (no bundle support needed) */}
|
{/* Доп. расходы positions — single row each (no bundle support needed) */}
|
||||||
@@ -242,6 +257,7 @@ export function ComparisonMatrix({ matrix, currencyRates, isRestrictedView, adat
|
|||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
{showAddColumn && <td className={styles.addSupplierFillerCell} />}
|
||||||
</tr>
|
</tr>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
@@ -265,6 +281,7 @@ export function ComparisonMatrix({ matrix, currencyRates, isRestrictedView, adat
|
|||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
{showAddColumn && <td className={styles.addSupplierFillerCell} />}
|
||||||
</tr>
|
</tr>
|
||||||
|
|
||||||
{/* Inline add-position row */}
|
{/* Inline add-position row */}
|
||||||
|
|||||||
+2
@@ -234,6 +234,8 @@ export function ExtraPositionRow({ pos, posIndex, regularPositionsCount, matrix,
|
|||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
{/* Под колонкой «+ Добавить поставщика». */}
|
||||||
|
{!isReadOnly && <td className={styles.addSupplierFillerCell} />}
|
||||||
</tr>
|
</tr>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -218,6 +218,8 @@ export function MatrixRowView({ pos, posIndex, matrix, currencyRates, isRestrict
|
|||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
{/* Под колонкой «+ Добавить поставщика»: одна ячейка на позицию, включая строки комплекта. */}
|
||||||
|
{rowIdx === 0 && !isReadOnly && <td className={styles.addSupplierFillerCell} rowSpan={posRowSpan} />}
|
||||||
</tr>
|
</tr>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
+91
-14
@@ -866,20 +866,6 @@ $delivery-km-min-width: 76px;
|
|||||||
color: $text;
|
color: $text;
|
||||||
}
|
}
|
||||||
|
|
||||||
.supplierToolbar {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 8px;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
padding: 4px 0 8px;
|
|
||||||
max-width: 30%;
|
|
||||||
|
|
||||||
> :first-child {
|
|
||||||
flex: 1 1 0;
|
|
||||||
min-width: 160px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.addRow {
|
.addRow {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: flex-end;
|
align-items: flex-end;
|
||||||
@@ -1369,3 +1355,94 @@ $delivery-km-min-width: 76px;
|
|||||||
border-color: $danger;
|
border-color: $danger;
|
||||||
color: $danger;
|
color: $danger;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ─── Добавление потенциального поставщика ───────────────────────────────────
|
||||||
|
// Блок над таблицей (AddSupplierToolbar): раньше без заголовка и с серой кнопкой — его не замечали.
|
||||||
|
|
||||||
|
.addSupplierBlock {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 8px;
|
||||||
|
padding: 12px 16px;
|
||||||
|
border: 1px solid $accent-border;
|
||||||
|
border-radius: 4px;
|
||||||
|
background: $accent-bg;
|
||||||
|
}
|
||||||
|
|
||||||
|
.addSupplierHeading {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
align-items: baseline;
|
||||||
|
gap: 4px 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.addSupplierTitle {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: $accent-dark;
|
||||||
|
}
|
||||||
|
|
||||||
|
.addSupplierHint {
|
||||||
|
font-size: 12px;
|
||||||
|
color: $muted;
|
||||||
|
}
|
||||||
|
|
||||||
|
.addSupplierRow {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.addSupplierOr {
|
||||||
|
font-size: 12px;
|
||||||
|
color: $muted;
|
||||||
|
}
|
||||||
|
|
||||||
|
.addSupplierError {
|
||||||
|
font-size: 12px;
|
||||||
|
color: $danger;
|
||||||
|
}
|
||||||
|
|
||||||
|
.addSupplierDialogBody {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-start;
|
||||||
|
gap: 12px;
|
||||||
|
|
||||||
|
> .addSupplierRow { align-self: stretch; }
|
||||||
|
}
|
||||||
|
|
||||||
|
.addSupplierDivider {
|
||||||
|
font-size: 12px;
|
||||||
|
color: $muted;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Последняя колонка таблицы «+ Добавить поставщика» — там, где ищут, как добавить колонку.
|
||||||
|
.addSupplierHeaderCell {
|
||||||
|
min-width: 140px;
|
||||||
|
vertical-align: middle;
|
||||||
|
text-align: center;
|
||||||
|
background: $head-bg;
|
||||||
|
border-left: 2px dashed $accent-border !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.addSupplierHeaderBtn {
|
||||||
|
padding: 8px 12px;
|
||||||
|
border: 1px dashed $accent;
|
||||||
|
border-radius: 4px;
|
||||||
|
background: $surface;
|
||||||
|
color: $accent;
|
||||||
|
font-family: inherit;
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 600;
|
||||||
|
white-space: nowrap;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
&:hover { background: $accent-bg; }
|
||||||
|
}
|
||||||
|
|
||||||
|
.addSupplierFillerCell {
|
||||||
|
border-left: 2px dashed $accent-border !important;
|
||||||
|
}
|
||||||
|
|||||||
+33
-31
@@ -1,6 +1,6 @@
|
|||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import { useMemo, useState } from 'react';
|
import { useMemo, useState } from 'react';
|
||||||
import { Button, Alert, TextInput, Toggle, Spinner, Dialog, Icon } from '@tnsu/ui-kit';
|
import { Button, Alert, Toggle, Spinner, Dialog, Icon } from '@tnsu/ui-kit';
|
||||||
import { FormStep } from '../types/domain';
|
import { FormStep } from '../types/domain';
|
||||||
import { CtService } from '../../services/CtService';
|
import { CtService } from '../../services/CtService';
|
||||||
import { VendorDatabaseService, IVendorRecord } from '../../services/VendorDatabaseService';
|
import { VendorDatabaseService, IVendorRecord } from '../../services/VendorDatabaseService';
|
||||||
@@ -19,6 +19,8 @@ import { useAdataCheck } from './useAdataCheck';
|
|||||||
import { useRecommendedPick } from './useRecommendedPick';
|
import { useRecommendedPick } from './useRecommendedPick';
|
||||||
import { VendorPickerDialog } from './VendorPickerDialog';
|
import { VendorPickerDialog } from './VendorPickerDialog';
|
||||||
import { ExcludedSuppliersList } from './ExcludedSuppliersList';
|
import { ExcludedSuppliersList } from './ExcludedSuppliersList';
|
||||||
|
import { AddSupplierToolbar } from './AddSupplierToolbar';
|
||||||
|
import { AddSupplierDialog } from './AddSupplierDialog';
|
||||||
import { SaveDraftButton } from '../SaveDraftButton/SaveDraftButton';
|
import { SaveDraftButton } from '../SaveDraftButton/SaveDraftButton';
|
||||||
import { withoutPlaceholders } from '../utils/placeholderKpFile';
|
import { withoutPlaceholders } from '../utils/placeholderKpFile';
|
||||||
|
|
||||||
@@ -58,7 +60,7 @@ export const Step3ComparisonTable: React.FC<Props> = ({
|
|||||||
const matrix = useStep3Matrix({ service, formData, isReadOnly: !canEdit, onSave, onNext, onStepComplete });
|
const matrix = useStep3Matrix({ service, formData, isReadOnly: !canEdit, onSave, onNext, onStepComplete });
|
||||||
const {
|
const {
|
||||||
isReadOnly, newSupplierName, newSupplierCurrency, setNewSupplierName, setNewSupplierCurrency,
|
isReadOnly, newSupplierName, newSupplierCurrency, setNewSupplierName, setNewSupplierCurrency,
|
||||||
handleAddSupplier, handleAddSupplierFromDatabase, translateUnits, setTranslateUnits, isSaving, saveError, setSaveError,
|
addManualSupplier, handleAddSupplier, handleAddSupplierFromDatabase, translateUnits, setTranslateUnits, isSaving, saveError, setSaveError,
|
||||||
validateBeforeSave, handleSave, draftSavedAt,
|
validateBeforeSave, handleSave, draftSavedAt,
|
||||||
extraPositions, suppliersWithOffer, columnDisplayNames, allPositions, computedOffers, selectedSupplierId,
|
extraPositions, suppliersWithOffer, columnDisplayNames, allPositions, computedOffers, selectedSupplierId,
|
||||||
excludedColumns, handleRestoreSupplier,
|
excludedColumns, handleRestoreSupplier,
|
||||||
@@ -70,6 +72,8 @@ export const Step3ComparisonTable: React.FC<Props> = ({
|
|||||||
|
|
||||||
// Модалка выбора поставщика из «База данных поставщиков» (см. VendorPickerDialog).
|
// Модалка выбора поставщика из «База данных поставщиков» (см. VendorPickerDialog).
|
||||||
const [showVendorPicker, setShowVendorPicker] = useState(false);
|
const [showVendorPicker, setShowVendorPicker] = useState(false);
|
||||||
|
// Окно кнопки «+ Добавить поставщика» в шапке таблицы.
|
||||||
|
const [showAddSupplierDialog, setShowAddSupplierDialog] = useState(false);
|
||||||
const vendorDbService = useMemo(() => new VendorDatabaseService(service.webPartContext), [service]);
|
const vendorDbService = useMemo(() => new VendorDatabaseService(service.webPartContext), [service]);
|
||||||
const handleAddVendorFromDatabase = (vendor: IVendorRecord): void => {
|
const handleAddVendorFromDatabase = (vendor: IVendorRecord): void => {
|
||||||
const id = handleAddSupplierFromDatabase(vendor);
|
const id = handleAddSupplierFromDatabase(vendor);
|
||||||
@@ -117,35 +121,17 @@ export const Step3ComparisonTable: React.FC<Props> = ({
|
|||||||
return (
|
return (
|
||||||
<div className={styles.root}>
|
<div className={styles.root}>
|
||||||
|
|
||||||
{/* Toolbar: add supplier — always visible above the table */}
|
{/* Добавление потенциального поставщика: блок над таблицей + кнопка в шапке таблицы. */}
|
||||||
{!isReadOnly && (
|
{!isReadOnly && (
|
||||||
<div className={styles.supplierToolbar}>
|
<AddSupplierToolbar
|
||||||
<div className={styles.addInput}>
|
name={newSupplierName}
|
||||||
<TextInput
|
currency={newSupplierCurrency}
|
||||||
placeholder="Наименование потенциального поставщика"
|
currencyOptions={currencyOptions}
|
||||||
value={newSupplierName}
|
onNameChange={setNewSupplierName}
|
||||||
onChange={setNewSupplierName}
|
onCurrencyChange={setNewSupplierCurrency}
|
||||||
ariaLabel="Наименование нового потенциального поставщика"
|
onAdd={handleAddSupplier}
|
||||||
onKeyDown={e => { if (e.key === 'Enter') handleAddSupplier(); }}
|
onOpenDatabase={() => setShowVendorPicker(true)}
|
||||||
/>
|
/>
|
||||||
</div>
|
|
||||||
<select
|
|
||||||
className={styles.currencySelect}
|
|
||||||
value={newSupplierCurrency}
|
|
||||||
onChange={e => setNewSupplierCurrency(e.target.value)}
|
|
||||||
aria-label="Валюта нового потенциального поставщика"
|
|
||||||
>
|
|
||||||
{currencyOptions.map(c => <option key={c} value={c}>{c}</option>)}
|
|
||||||
</select>
|
|
||||||
<Button variant="secondary" onClick={handleAddSupplier} disabled={!newSupplierName.trim()}>
|
|
||||||
<Icon name="plus" size="sm" />
|
|
||||||
Добавить потенциального поставщика
|
|
||||||
</Button>
|
|
||||||
<Button variant="secondary" onClick={() => setShowVendorPicker(true)}>
|
|
||||||
<Icon name="building" size="sm" />
|
|
||||||
Добавить из базы поставщиков
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Тумблер пересчёта единиц измерения поставщика в ед. изм. заявки */}
|
{/* Тумблер пересчёта единиц измерения поставщика в ед. изм. заявки */}
|
||||||
@@ -185,7 +171,14 @@ export const Step3ComparisonTable: React.FC<Props> = ({
|
|||||||
|
|
||||||
{recPick.pickError && <Alert variant="err">{recPick.pickError}</Alert>}
|
{recPick.pickError && <Alert variant="err">{recPick.pickError}</Alert>}
|
||||||
|
|
||||||
<ComparisonMatrix matrix={matrix} currencyRates={currencyRates} isRestrictedView={isRestrictedView} adata={adata} recommendedId={recPick.recommendedId} />
|
<ComparisonMatrix
|
||||||
|
matrix={matrix}
|
||||||
|
currencyRates={currencyRates}
|
||||||
|
isRestrictedView={isRestrictedView}
|
||||||
|
adata={adata}
|
||||||
|
recommendedId={recPick.recommendedId}
|
||||||
|
onAddSupplierClick={() => setShowAddSupplierDialog(true)}
|
||||||
|
/>
|
||||||
|
|
||||||
<CriteriaTable matrix={matrix} />
|
<CriteriaTable matrix={matrix} />
|
||||||
|
|
||||||
@@ -269,6 +262,15 @@ export const Step3ComparisonTable: React.FC<Props> = ({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{showAddSupplierDialog && (
|
||||||
|
<AddSupplierDialog
|
||||||
|
currencyOptions={currencyOptions}
|
||||||
|
onAddManual={addManualSupplier}
|
||||||
|
onOpenDatabase={() => setShowVendorPicker(true)}
|
||||||
|
onClose={() => setShowAddSupplierDialog(false)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
{showVendorPicker && (
|
{showVendorPicker && (
|
||||||
<VendorPickerDialog
|
<VendorPickerDialog
|
||||||
vendorDbService={vendorDbService}
|
vendorDbService={vendorDbService}
|
||||||
|
|||||||
+13
-5
@@ -587,16 +587,23 @@ export function useStep3Matrix(args: UseStep3MatrixArgs) {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleAddSupplier = (): void => {
|
// Поставщик, добавленный на Шаге 3 вручную (без КП). Общая точка для обеих кнопок добавления:
|
||||||
if (!newSupplierName.trim()) return;
|
// блока над таблицей и колонки «+ Добавить поставщика» в самой таблице.
|
||||||
|
const addManualSupplier = (name: string, currency: string): void => {
|
||||||
|
if (isReadOnly || !name.trim()) return;
|
||||||
const id = `extra_sup_${++extraIdCounter}`;
|
const id = `extra_sup_${++extraIdCounter}`;
|
||||||
const newEntry = { id, name: newSupplierName.trim(), currency: newSupplierCurrency };
|
const updated = [...extraSuppliers, { id, name: name.trim(), currency }];
|
||||||
const updated = [...extraSuppliers, newEntry];
|
|
||||||
setExtraSuppliers(updated);
|
setExtraSuppliers(updated);
|
||||||
setSupplierStatuses(prev => ({ ...prev, [id]: '' }));
|
setSupplierStatuses(prev => ({ ...prev, [id]: '' }));
|
||||||
|
onSave({ step3ExtraSuppliers: updated });
|
||||||
|
};
|
||||||
|
|
||||||
|
// Блок над таблицей: добавить из своих полей и очистить их.
|
||||||
|
const handleAddSupplier = (): void => {
|
||||||
|
if (!newSupplierName.trim()) return;
|
||||||
|
addManualSupplier(newSupplierName, newSupplierCurrency);
|
||||||
setNewSupplierName('');
|
setNewSupplierName('');
|
||||||
setNewSupplierCurrency('KZT');
|
setNewSupplierCurrency('KZT');
|
||||||
onSave({ step3ExtraSuppliers: updated });
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Добавление поставщика из VendorPickerDialog («База данных поставщиков»): та же механика
|
// Добавление поставщика из VendorPickerDialog («База данных поставщиков»): та же механика
|
||||||
@@ -1000,6 +1007,7 @@ export function useStep3Matrix(args: UseStep3MatrixArgs) {
|
|||||||
handleRemoveCustomCriteria,
|
handleRemoveCustomCriteria,
|
||||||
handleNameColResizeMouseDown,
|
handleNameColResizeMouseDown,
|
||||||
handleStatusChange,
|
handleStatusChange,
|
||||||
|
addManualSupplier,
|
||||||
handleAddSupplier,
|
handleAddSupplier,
|
||||||
handleAddSupplierFromDatabase,
|
handleAddSupplierFromDatabase,
|
||||||
handleAddPosition,
|
handleAddPosition,
|
||||||
|
|||||||
Reference in New Issue
Block a user