diff --git a/app/web/admin.css b/app/web/admin.css
index 0b21f17..a7a833b 100644
--- a/app/web/admin.css
+++ b/app/web/admin.css
@@ -275,6 +275,25 @@
flex-wrap: wrap;
}
+ .config-head-actions {
+ display: flex;
+ gap: 0.5rem;
+ flex-wrap: wrap;
+ align-items: center;
+ }
+
+ .btn-icon-only {
+ width: 40px;
+ min-width: 40px;
+ height: 40px;
+ min-height: 40px;
+ padding: 0;
+ display: inline-grid;
+ place-items: center;
+ font-size: 1.06rem;
+ line-height: 1;
+ }
+
.muted {
color: var(--muted);
margin: 0.45rem 0 0;
@@ -1229,18 +1248,56 @@
background: transparent;
}
- .config-actions-row {
- display: flex;
- justify-content: flex-end;
- align-items: center;
- gap: 0.5rem;
- margin-bottom: 0.5rem;
- }
-
.config-panel-flat .config-content .table-wrap table {
min-width: 640px;
}
+ .config-controls-bar {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ gap: 0.6rem;
+ flex-wrap: wrap;
+ }
+
+ .config-controls-summary {
+ color: var(--muted);
+ font-size: 0.86rem;
+ }
+
+ .config-controls-actions {
+ display: inline-flex;
+ align-items: center;
+ gap: 0.45rem;
+ flex-wrap: wrap;
+ margin-left: auto;
+ }
+
+ .config-control-btn {
+ width: 40px;
+ min-width: 40px;
+ height: 40px;
+ min-height: 40px;
+ padding: 0;
+ display: inline-grid;
+ place-items: center;
+ font-size: 1.05rem;
+ line-height: 1;
+ }
+
+ .config-control-btn-loadall {
+ width: auto;
+ min-width: 58px;
+ padding: 0 0.62rem;
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ gap: 0.33rem;
+ font-size: 0.9rem;
+ font-weight: 700;
+ letter-spacing: 0.01em;
+ }
+
.block {
border: 1px solid var(--line);
border-radius: 12px;
@@ -2918,6 +2975,13 @@
.filter-action {
margin-left: 0;
}
+ .config-controls-summary {
+ width: 100%;
+ }
+ .config-controls-actions {
+ width: 100%;
+ margin-left: 0;
+ }
.topbar {
flex-direction: column;
align-items: flex-start;
diff --git a/app/web/admin.jsx b/app/web/admin.jsx
index ec16e06..baedbf6 100644
--- a/app/web/admin.jsx
+++ b/app/web/admin.jsx
@@ -153,7 +153,7 @@ const NEW_REQUEST_CLIENT_OPTION = "__new_client__";
);
}
- function FilterToolbar({ filters, onOpen, onRemove, onEdit, getChipLabel }) {
+ function FilterToolbar({ filters, onOpen, onRemove, onEdit, getChipLabel, hideAction = false }) {
return (
@@ -190,11 +190,13 @@ const NEW_REQUEST_CLIENT_OPTION = "__new_client__";
Фильтры не заданы
)}
-
-
-
+ {!hideAction ? (
+
+
+
+ ) : null}
);
}
diff --git a/app/web/admin/features/config/ConfigSection.jsx b/app/web/admin/features/config/ConfigSection.jsx
index 48664e5..a2f1787 100644
--- a/app/web/admin/features/config/ConfigSection.jsx
+++ b/app/web/admin/features/config/ConfigSection.jsx
@@ -1,4 +1,4 @@
-import { KNOWN_CONFIG_TABLE_KEYS, OPERATOR_LABELS, TABLE_SERVER_CONFIG } from "../../shared/constants.js";
+import { KNOWN_CONFIG_TABLE_KEYS, OPERATOR_LABELS, PAGE_SIZE, TABLE_SERVER_CONFIG } from "../../shared/constants.js";
import { boolLabel, fmtDate, listPreview, normalizeReferenceMeta, roleLabel, statusKindLabel, statusLabel } from "../../shared/utils.js";
function fmtBalance(value) {
@@ -57,7 +57,6 @@ export function ConfigSection(props) {
loadAllRows,
FilterToolbarComponent,
DataTableComponent,
- TablePagerComponent,
StatusLineComponent,
IconButtonComponent,
UserAvatarComponent,
@@ -65,10 +64,24 @@ export function ConfigSection(props) {
const FilterToolbar = FilterToolbarComponent;
const DataTable = DataTableComponent;
- const TablePager = TablePagerComponent;
const StatusLine = StatusLineComponent;
const IconButton = IconButtonComponent;
const UserAvatar = UserAvatarComponent;
+ const canOpenFilter = Boolean(configActiveKey);
+ const canRefresh = Boolean(configActiveKey);
+ const canCreateRecord = Boolean(canCreateInConfig && configActiveKey);
+ const canLoadAllRows = Boolean(
+ configActiveKey &&
+ activeConfigTableState.total > 0 &&
+ !activeConfigTableState.showAll &&
+ activeConfigTableState.rows.length < activeConfigTableState.total
+ );
+ const canLoadPrev = Boolean(configActiveKey && !activeConfigTableState.showAll && activeConfigTableState.offset > 0);
+ const canLoadNext = Boolean(
+ configActiveKey &&
+ !activeConfigTableState.showAll &&
+ activeConfigTableState.offset + PAGE_SIZE < activeConfigTableState.total
+ );
return (
<>
@@ -83,32 +96,33 @@ export function ConfigSection(props) {
) : null}
-
+
{configActiveKey === "otp_sessions" ? (
) : null}
-
- {canCreateInConfig && configActiveKey ? (
-
- openCreateRecordModal(configActiveKey)}>
- Добавить
-
-
- ) : null}
openFilterModal(configActiveKey)}
onRemove={(index) => removeFilterChip(configActiveKey, index)}
onEdit={(index) => openFilterEditModal(configActiveKey, index)}
+ hideAction
getChipLabel={(clause) => {
const fieldDef = getFieldDef(configActiveKey, clause.field);
return (
@@ -584,12 +598,66 @@ export function ConfigSection(props) {
)}
/>
) : null}
- loadPrevPage(configActiveKey)}
- onNext={() => loadNextPage(configActiveKey)}
- onLoadAll={() => loadAllRows(configActiveKey)}
- />
+
+
+ {activeConfigTableState.showAll
+ ? "Всего: " + activeConfigTableState.total + " • показаны все записи"
+ : "Всего: " + activeConfigTableState.total + " • смещение: " + activeConfigTableState.offset}
+
+
+ loadAllRows(configActiveKey)}
+ disabled={!canLoadAllRows}
+ title={"Загрузить все " + activeConfigTableState.total}
+ aria-label={"Загрузить все " + activeConfigTableState.total}
+ >
+ ⤓
+ {activeConfigTableState.total}
+
+ loadCurrentConfigTable(true)}
+ disabled={!canRefresh}
+ title="Обновить"
+ aria-label="Обновить"
+ >
+ ↻
+
+ openCreateRecordModal(configActiveKey)}
+ disabled={!canCreateRecord}
+ title="Добавить"
+ aria-label="Добавить"
+ >
+ +
+
+ loadPrevPage(configActiveKey)}
+ disabled={!canLoadPrev}
+ title="Назад"
+ aria-label="Назад"
+ >
+ ←
+
+ loadNextPage(configActiveKey)}
+ disabled={!canLoadNext}
+ title="Вперед"
+ aria-label="Вперед"
+ >
+ →
+
+
+