diff --git a/src/webparts/ctFormsPortal/forms/ct/components/Step3ComparisonTable/AddExtraRow.tsx b/src/webparts/ctFormsPortal/forms/ct/components/Step3ComparisonTable/AddExtraRow.tsx
index 47361fa..14a83a0 100644
--- a/src/webparts/ctFormsPortal/forms/ct/components/Step3ComparisonTable/AddExtraRow.tsx
+++ b/src/webparts/ctFormsPortal/forms/ct/components/Step3ComparisonTable/AddExtraRow.tsx
@@ -59,6 +59,8 @@ export function AddExtraRow({ matrix, isRestrictedView, colSpan }: Props): JSX.E
{suppliersWithOffer.map(s => (
|
))}
+ {/* Под колонкой «+ Добавить поставщика» (видна там же, где эта строка, — не в режиме просмотра). */}
+ |
);
}
diff --git a/src/webparts/ctFormsPortal/forms/ct/components/Step3ComparisonTable/AddSupplierDialog.tsx b/src/webparts/ctFormsPortal/forms/ct/components/Step3ComparisonTable/AddSupplierDialog.tsx
new file mode 100644
index 0000000..933faf0
--- /dev/null
+++ b/src/webparts/ctFormsPortal/forms/ct/components/Step3ComparisonTable/AddSupplierDialog.tsx
@@ -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 = ({ 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 (
+
+ );
+};
diff --git a/src/webparts/ctFormsPortal/forms/ct/components/Step3ComparisonTable/AddSupplierToolbar.tsx b/src/webparts/ctFormsPortal/forms/ct/components/Step3ComparisonTable/AddSupplierToolbar.tsx
new file mode 100644
index 0000000..b0ee3d7
--- /dev/null
+++ b/src/webparts/ctFormsPortal/forms/ct/components/Step3ComparisonTable/AddSupplierToolbar.tsx
@@ -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 = ({
+ 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 (
+
+
+
Потенциальные поставщики
+
+ Добавьте поставщика, у которого нет загруженного КП, — вручную или из базы поставщиков
+
+
+
+
+ { if (e.key === 'Enter') handleAdd(); }}
+ />
+
+
+
+
или
+
+
+ {isNameMissing && Введите наименование поставщика}
+
+ );
+};
diff --git a/src/webparts/ctFormsPortal/forms/ct/components/Step3ComparisonTable/ComparisonMatrix.tsx b/src/webparts/ctFormsPortal/forms/ct/components/Step3ComparisonTable/ComparisonMatrix.tsx
index d9cfa79..648b995 100644
--- a/src/webparts/ctFormsPortal/forms/ct/components/Step3ComparisonTable/ComparisonMatrix.tsx
+++ b/src/webparts/ctFormsPortal/forms/ct/components/Step3ComparisonTable/ComparisonMatrix.tsx
@@ -19,12 +19,14 @@ interface Props {
isRestrictedView: boolean;
adata: UseAdataCheckResult;
recommendedId?: string; // победитель формулы 80% цена / 20% условия оплаты (useRecommendedPick)
+ // Кнопка «+ Добавить поставщика» в последней колонке шапки.
+ onAddSupplierClick: () => void;
}
// Таблица сравнения целиком: шапка (фикс-колонки + заголовки поставщиков) + строки позиций
// (MatrixRowView/ExtraPositionRow) + итоговые строки + строка добавления (AddExtraRow).
// Перенесено 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 {
isReadOnly, suppliersWithOffer, supplierCurrencies, selectedSupplierId,
supplierStatuses, nameColWidth, regularPositions, extraPositionsRows, allPositions, computedOffers,
@@ -37,7 +39,12 @@ export function ComparisonMatrix({ matrix, currencyRates, isRestrictedView, adat
// Как в проверке перед сохранением (getStep3SaveError): выбор на исключённом — не победитель.
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),
// а row2 прибит sticky сразу под ней. Измеряем реальную высоту и отдаём в CSS-переменную,
@@ -142,6 +149,13 @@ export function ComparisonMatrix({ matrix, currencyRates, isRestrictedView, adat
);
})}
+ {showAddColumn && (
+
+
+ |
+ )}
{/* Row 2: sub-headers — 4 cols for KZT, 6 cols for foreign currency */}
@@ -206,6 +220,7 @@ export function ComparisonMatrix({ matrix, currencyRates, isRestrictedView, adat
);
})}
+ {showAddColumn && | }
{/* Доп. расходы positions — single row each (no bundle support needed) */}
@@ -242,6 +257,7 @@ export function ComparisonMatrix({ matrix, currencyRates, isRestrictedView, adat
);
})}
+ {showAddColumn && | }
)}
@@ -265,6 +281,7 @@ export function ComparisonMatrix({ matrix, currencyRates, isRestrictedView, adat
);
})}
+ {showAddColumn && | }
{/* Inline add-position row */}
diff --git a/src/webparts/ctFormsPortal/forms/ct/components/Step3ComparisonTable/ExtraPositionRow.tsx b/src/webparts/ctFormsPortal/forms/ct/components/Step3ComparisonTable/ExtraPositionRow.tsx
index 0eb1c98..2180a4f 100644
--- a/src/webparts/ctFormsPortal/forms/ct/components/Step3ComparisonTable/ExtraPositionRow.tsx
+++ b/src/webparts/ctFormsPortal/forms/ct/components/Step3ComparisonTable/ExtraPositionRow.tsx
@@ -234,6 +234,8 @@ export function ExtraPositionRow({ pos, posIndex, regularPositionsCount, matrix,
);
})}
+ {/* Под колонкой «+ Добавить поставщика». */}
+ {!isReadOnly && | }
);
}
diff --git a/src/webparts/ctFormsPortal/forms/ct/components/Step3ComparisonTable/MatrixRowView.tsx b/src/webparts/ctFormsPortal/forms/ct/components/Step3ComparisonTable/MatrixRowView.tsx
index 3a57132..a2301e7 100644
--- a/src/webparts/ctFormsPortal/forms/ct/components/Step3ComparisonTable/MatrixRowView.tsx
+++ b/src/webparts/ctFormsPortal/forms/ct/components/Step3ComparisonTable/MatrixRowView.tsx
@@ -218,6 +218,8 @@ export function MatrixRowView({ pos, posIndex, matrix, currencyRates, isRestrict
);
})}
+ {/* Под колонкой «+ Добавить поставщика»: одна ячейка на позицию, включая строки комплекта. */}
+ {rowIdx === 0 && !isReadOnly && | }
);
}
diff --git a/src/webparts/ctFormsPortal/forms/ct/components/Step3ComparisonTable/Step3ComparisonTable.module.scss b/src/webparts/ctFormsPortal/forms/ct/components/Step3ComparisonTable/Step3ComparisonTable.module.scss
index 41e967f..0a4cf1e 100644
--- a/src/webparts/ctFormsPortal/forms/ct/components/Step3ComparisonTable/Step3ComparisonTable.module.scss
+++ b/src/webparts/ctFormsPortal/forms/ct/components/Step3ComparisonTable/Step3ComparisonTable.module.scss
@@ -866,20 +866,6 @@ $delivery-km-min-width: 76px;
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 {
display: flex;
align-items: flex-end;
@@ -1369,3 +1355,94 @@ $delivery-km-min-width: 76px;
border-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;
+}
diff --git a/src/webparts/ctFormsPortal/forms/ct/components/Step3ComparisonTable/Step3ComparisonTable.tsx b/src/webparts/ctFormsPortal/forms/ct/components/Step3ComparisonTable/Step3ComparisonTable.tsx
index 5b44902..3144c8a 100644
--- a/src/webparts/ctFormsPortal/forms/ct/components/Step3ComparisonTable/Step3ComparisonTable.tsx
+++ b/src/webparts/ctFormsPortal/forms/ct/components/Step3ComparisonTable/Step3ComparisonTable.tsx
@@ -1,6 +1,6 @@
import * as React 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 { CtService } from '../../services/CtService';
import { VendorDatabaseService, IVendorRecord } from '../../services/VendorDatabaseService';
@@ -19,6 +19,8 @@ import { useAdataCheck } from './useAdataCheck';
import { useRecommendedPick } from './useRecommendedPick';
import { VendorPickerDialog } from './VendorPickerDialog';
import { ExcludedSuppliersList } from './ExcludedSuppliersList';
+import { AddSupplierToolbar } from './AddSupplierToolbar';
+import { AddSupplierDialog } from './AddSupplierDialog';
import { SaveDraftButton } from '../SaveDraftButton/SaveDraftButton';
import { withoutPlaceholders } from '../utils/placeholderKpFile';
@@ -58,7 +60,7 @@ export const Step3ComparisonTable: React.FC = ({
const matrix = useStep3Matrix({ service, formData, isReadOnly: !canEdit, onSave, onNext, onStepComplete });
const {
isReadOnly, newSupplierName, newSupplierCurrency, setNewSupplierName, setNewSupplierCurrency,
- handleAddSupplier, handleAddSupplierFromDatabase, translateUnits, setTranslateUnits, isSaving, saveError, setSaveError,
+ addManualSupplier, handleAddSupplier, handleAddSupplierFromDatabase, translateUnits, setTranslateUnits, isSaving, saveError, setSaveError,
validateBeforeSave, handleSave, draftSavedAt,
extraPositions, suppliersWithOffer, columnDisplayNames, allPositions, computedOffers, selectedSupplierId,
excludedColumns, handleRestoreSupplier,
@@ -70,6 +72,8 @@ export const Step3ComparisonTable: React.FC = ({
// Модалка выбора поставщика из «База данных поставщиков» (см. VendorPickerDialog).
const [showVendorPicker, setShowVendorPicker] = useState(false);
+ // Окно кнопки «+ Добавить поставщика» в шапке таблицы.
+ const [showAddSupplierDialog, setShowAddSupplierDialog] = useState(false);
const vendorDbService = useMemo(() => new VendorDatabaseService(service.webPartContext), [service]);
const handleAddVendorFromDatabase = (vendor: IVendorRecord): void => {
const id = handleAddSupplierFromDatabase(vendor);
@@ -117,35 +121,17 @@ export const Step3ComparisonTable: React.FC = ({
return (
- {/* Toolbar: add supplier — always visible above the table */}
+ {/* Добавление потенциального поставщика: блок над таблицей + кнопка в шапке таблицы. */}
{!isReadOnly && (
-
-
- { if (e.key === 'Enter') handleAddSupplier(); }}
- />
-
-
-
-
-
+
setShowVendorPicker(true)}
+ />
)}
{/* Тумблер пересчёта единиц измерения поставщика в ед. изм. заявки */}
@@ -185,7 +171,14 @@ export const Step3ComparisonTable: React.FC = ({
{recPick.pickError && {recPick.pickError}}
-
+ setShowAddSupplierDialog(true)}
+ />
@@ -269,6 +262,15 @@ export const Step3ComparisonTable: React.FC = ({
+ {showAddSupplierDialog && (
+ setShowVendorPicker(true)}
+ onClose={() => setShowAddSupplierDialog(false)}
+ />
+ )}
+
{showVendorPicker && (
{
- if (!newSupplierName.trim()) return;
+ // Поставщик, добавленный на Шаге 3 вручную (без КП). Общая точка для обеих кнопок добавления:
+ // блока над таблицей и колонки «+ Добавить поставщика» в самой таблице.
+ const addManualSupplier = (name: string, currency: string): void => {
+ if (isReadOnly || !name.trim()) return;
const id = `extra_sup_${++extraIdCounter}`;
- const newEntry = { id, name: newSupplierName.trim(), currency: newSupplierCurrency };
- const updated = [...extraSuppliers, newEntry];
+ const updated = [...extraSuppliers, { id, name: name.trim(), currency }];
setExtraSuppliers(updated);
setSupplierStatuses(prev => ({ ...prev, [id]: '' }));
+ onSave({ step3ExtraSuppliers: updated });
+ };
+
+ // Блок над таблицей: добавить из своих полей и очистить их.
+ const handleAddSupplier = (): void => {
+ if (!newSupplierName.trim()) return;
+ addManualSupplier(newSupplierName, newSupplierCurrency);
setNewSupplierName('');
setNewSupplierCurrency('KZT');
- onSave({ step3ExtraSuppliers: updated });
};
// Добавление поставщика из VendorPickerDialog («База данных поставщиков»): та же механика
@@ -1000,6 +1007,7 @@ export function useStep3Matrix(args: UseStep3MatrixArgs) {
handleRemoveCustomCriteria,
handleNameColResizeMouseDown,
handleStatusChange,
+ addManualSupplier,
handleAddSupplier,
handleAddSupplierFromDatabase,
handleAddPosition,