feat: config-driven реестр на реальных данных SP

This commit is contained in:
Тимур Городецкий
2026-08-17 11:23:57 +05:00
commit 31ef6fdbb9
99 changed files with 33739 additions and 0 deletions
@@ -0,0 +1,15 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
var tslib_1 = require("tslib");
var React = tslib_1.__importStar(require("react"));
require("@tnsu/ui-kit/styles.css");
var ui_kit_1 = require("@tnsu/ui-kit");
var CtFormsPortal_module_scss_1 = tslib_1.__importDefault(require("./CtFormsPortal.module.scss"));
var Dashboard_1 = require("./Dashboard");
// Корень веб-парта: спрайт иконок (один раз) + дашборд-реестр.
// Импорт CtFormsPortal.module.scss обязателен — в нём :global full-width правила канвы.
var CtFormsPortal = function (props) { return (React.createElement("div", { className: CtFormsPortal_module_scss_1.default.root },
React.createElement(ui_kit_1.IconSprite, null),
React.createElement(Dashboard_1.Dashboard, { context: props.context }))); };
exports.default = CtFormsPortal;
//# sourceMappingURL=CtFormsPortal.js.map
@@ -0,0 +1 @@
{"version":3,"file":"CtFormsPortal.js","sourceRoot":"","sources":["../../../../src/webparts/ctFormsPortal/components/CtFormsPortal.tsx"],"names":[],"mappings":";;;AAAA,mDAA+B;AAC/B,mCAAiC;AACjC,uCAA0C;AAC1C,kGAAiD;AAEjD,yCAAwC;AAExC,+DAA+D;AAC/D,wFAAwF;AACxF,IAAM,aAAa,GAAkC,UAAC,KAAK,IAAK,OAAA,CAC9D,6BAAK,SAAS,EAAE,mCAAM,CAAC,IAAI;IACzB,oBAAC,mBAAU,OAAG;IACd,oBAAC,qBAAS,IAAC,OAAO,EAAE,KAAK,CAAC,OAAO,GAAI,CACjC,CACP,EAL+D,CAK/D,CAAC;AAEF,kBAAe,aAAa,CAAC","sourcesContent":["import * as React from 'react';\nimport '@tnsu/ui-kit/styles.css';\nimport { IconSprite } from '@tnsu/ui-kit';\nimport styles from './CtFormsPortal.module.scss';\nimport type { ICtFormsPortalProps } from './ICtFormsPortalProps';\nimport { Dashboard } from './Dashboard';\n\n// Корень веб-парта: спрайт иконок (один раз) + дашборд-реестр.\n// Импорт CtFormsPortal.module.scss обязателен — в нём :global full-width правила канвы.\nconst CtFormsPortal: React.FC<ICtFormsPortalProps> = (props) => (\n <div className={styles.root}>\n <IconSprite />\n <Dashboard context={props.context} />\n </div>\n);\n\nexport default CtFormsPortal;\n"]}
@@ -0,0 +1,11 @@
:global #workbenchPageContent,
:global #spPageCanvasContent,
:global .SPCanvas-canvas,
:global .CanvasComponent.LCS .CanvasZone,
:global .CanvasZone {
max-width: 100% !important;
}
.root {
width: 100%;
}
@@ -0,0 +1,106 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.Dashboard = void 0;
var tslib_1 = require("tslib");
var React = tslib_1.__importStar(require("react"));
var react_1 = require("react");
var Dashboard_module_scss_1 = tslib_1.__importDefault(require("./Dashboard.module.scss"));
var ui_kit_1 = require("@tnsu/ui-kit");
var config_1 = require("./config");
var useDocumentDashboard_1 = require("./registry/useDocumentDashboard");
// Иконка сайдбара по типу (kit-иконки; Fluent-иконки из конфигов тут не используем).
var TYPE_ICON = { ct: 'clipboard', oid: 'chart', kps: 'building' };
// Цвет статуса DashboardRow → вариант нашего Badge (error → danger).
var VARIANT_MAP = {
neutral: 'neutral',
info: 'info',
success: 'success',
error: 'danger'
};
function formatDate(iso) {
if (!iso)
return '';
var d = new Date(iso);
var dd = d.getDate() < 10 ? "0".concat(d.getDate()) : "".concat(d.getDate());
var mm = d.getMonth() + 1 < 10 ? "0".concat(d.getMonth() + 1) : "".concat(d.getMonth() + 1);
return "".concat(dd, ".").concat(mm, ".").concat(d.getFullYear());
}
// Дашборд-реестр (config-driven, данные из SharePoint). Оболочка и элементы — из @tnsu/ui-kit.
var Dashboard = function (_a) {
var _b;
var context = _a.context;
var _c = (0, useDocumentDashboard_1.useDocumentDashboard)(context, config_1.DOCUMENT_TYPES), myDocs = _c.myDocs, approvedDocs = _c.approvedDocs, pendingDocs = _c.pendingDocs, isLoading = _c.isLoading, error = _c.error;
var _d = (0, react_1.useState)(config_1.DOCUMENT_TYPES[0].typeKey), activeType = _d[0], setActiveType = _d[1];
var _e = (0, react_1.useState)('pending'), queue = _e[0], setQueue = _e[1];
var _f = (0, react_1.useState)(false), collapsed = _f[0], setCollapsed = _f[1];
var _g = (0, react_1.useState)(false), mobileOpen = _g[0], setMobileOpen = _g[1];
var _h = (0, react_1.useState)(''), search = _h[0], setSearch = _h[1];
var _j = (0, react_1.useState)(''), status = _j[0], setStatus = _j[1];
var cfg = config_1.DOCUMENT_TYPES.filter(function (t) { return t.typeKey === activeType; })[0];
var byType = function (arr) { return arr.filter(function (r) { return r.typeKey === activeType; }); };
var pendingForType = byType(pendingDocs);
var mineForType = byType(myDocs);
var approvedForType = byType(approvedDocs);
var sidebarItems = config_1.DOCUMENT_TYPES.map(function (t) {
var _a;
return ({
id: t.typeKey,
title: t.typeLabel,
icon: (_a = TYPE_ICON[t.typeKey]) !== null && _a !== void 0 ? _a : 'document',
count: pendingDocs.filter(function (r) { return r.typeKey === t.typeKey; }).length
});
});
var tabs = [
{ id: 'pending', label: 'На согласовании у меня', count: pendingForType.length },
{ id: 'mine', label: 'Инициализированные мной', count: mineForType.length },
{ id: 'approved', label: 'Согласованные мной', count: approvedForType.length }
];
var activeRows = queue === 'pending' ? pendingForType : queue === 'mine' ? mineForType : approvedForType;
var statusOptions = Array.from(new Set(activeRows.map(function (r) { return r.statusLabel; }))).map(function (s) { return ({
value: s,
label: s
}); });
var rows = activeRows
.filter(function (r) { var _a; return !search || "".concat(r.title, " ").concat((_a = r.subtitle) !== null && _a !== void 0 ? _a : '').toLowerCase().indexOf(search.toLowerCase()) !== -1; })
.filter(function (r) { return !status || r.statusLabel === status; });
var renderCell = function (row, key) {
var _a;
if (key === 'status') {
return (React.createElement(ui_kit_1.Badge, { variant: VARIANT_MAP[row.statusVariant], size: "sm" }, row.statusLabel));
}
if (key === 'created')
return formatDate(row.createdDate);
return String((_a = row[key]) !== null && _a !== void 0 ? _a : '');
};
var columns = cfg.columns.map(function (c) { return ({
key: c.key,
title: c.label,
width: c.minWidth ? "".concat(c.minWidth, "px") : undefined,
render: function (row) { return renderCell(row, c.key); }
}); });
var selectType = function (id) {
setActiveType(String(id));
setQueue('pending');
setSearch('');
setStatus('');
};
return (React.createElement(ui_kit_1.AppLayout, { sidebar: React.createElement(ui_kit_1.Sidebar, { items: sidebarItems, activeId: activeType, onChange: selectType, brand: "\u0414\u043E\u0433\u043E\u0432\u043E\u0440\u0430", collapsed: collapsed, onToggleCollapsed: function () { return setCollapsed(function (c) { return !c; }); }, mobileOpen: mobileOpen, onMobileClose: function () { return setMobileOpen(false); } }), mobileToggle: React.createElement(ui_kit_1.SidebarToggle, { onClick: function () { return setMobileOpen(true); } }) },
React.createElement("div", { className: Dashboard_module_scss_1.default.head },
React.createElement("h2", { className: Dashboard_module_scss_1.default.title }, cfg.typeLabel),
cfg.createNewUrl && (React.createElement(ui_kit_1.Button, { variant: "accent", onClick: function () { return window.open(cfg.createNewUrl, '_blank'); } }, (_b = cfg.createNewLabel) !== null && _b !== void 0 ? _b : 'Создать'))),
React.createElement("div", { className: Dashboard_module_scss_1.default.toolbar },
React.createElement("div", { className: Dashboard_module_scss_1.default.search },
React.createElement(ui_kit_1.TextInput, { value: search, onChange: setSearch, placeholder: "\u041F\u043E\u0438\u0441\u043A \u043F\u043E \u043D\u043E\u043C\u0435\u0440\u0443 \u0438\u043B\u0438 \u043F\u0440\u043E\u0435\u043A\u0442\u0443" })),
React.createElement("div", { className: Dashboard_module_scss_1.default.filter },
React.createElement(ui_kit_1.Select, { value: status, onChange: setStatus, options: statusOptions, placeholder: "\u0412\u0441\u0435 \u0441\u0442\u0430\u0442\u0443\u0441\u044B" }))),
React.createElement(ui_kit_1.Tabs, { tabs: tabs, activeId: queue, onChange: function (id) { return setQueue(id); } }),
error && (React.createElement("div", { className: Dashboard_module_scss_1.default.state },
React.createElement(ui_kit_1.Alert, { variant: "err" },
"\u041D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C \u0437\u0430\u0433\u0440\u0443\u0437\u0438\u0442\u044C \u0434\u0430\u043D\u043D\u044B\u0435: ",
error))),
!error && isLoading && React.createElement("div", { className: Dashboard_module_scss_1.default.state }, "\u0417\u0430\u0433\u0440\u0443\u0437\u043A\u0430\u2026"),
!error && !isLoading && (React.createElement("div", { className: Dashboard_module_scss_1.default.tableWrap },
React.createElement(ui_kit_1.DataTable, { columns: columns, rows: rows, rowKey: function (r) { return "".concat(r.typeKey, "-").concat(r.id); }, onRowClick: function (r) { return window.open(cfg.buildLinkUrl(r), '_blank'); }, emptyText: "\u041D\u0435\u0442 \u0434\u043E\u043A\u0443\u043C\u0435\u043D\u0442\u043E\u0432" })))));
};
exports.Dashboard = Dashboard;
//# sourceMappingURL=Dashboard.js.map
File diff suppressed because one or more lines are too long
@@ -0,0 +1,54 @@
.head {
display: flex;
align-items: center;
justify-content: space-between;
gap: 16px;
flex-wrap: wrap;
margin-bottom: 16px;
}
.title {
margin: 0;
font-family: "Lora", Georgia, serif;
font-size: 22px;
font-weight: 600;
color: #0f2440;
}
.toolbar {
display: flex;
align-items: center;
gap: 12px;
flex-wrap: wrap;
}
.search {
width: 280px;
max-width: 100%;
}
.filter {
width: 200px;
}
.tableWrap {
margin-top: 16px;
background: #fff;
border: 1px solid #e5e7eb;
border-radius: 10px;
box-shadow: 0 1px 2px rgba(15, 36, 64, 0.04), 0 1px 3px rgba(15, 36, 64, 0.06);
overflow: hidden;
}
.state {
margin-top: 16px;
color: #6b7280;
font-size: 13px;
}
@media (max-width: 640px) {
.search,
.filter {
width: 100%;
}
}
@@ -0,0 +1,3 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
//# sourceMappingURL=ICtFormsPortalProps.js.map
@@ -0,0 +1 @@
{"version":3,"file":"ICtFormsPortalProps.js","sourceRoot":"","sources":["../../../../src/webparts/ctFormsPortal/components/ICtFormsPortalProps.ts"],"names":[],"mappings":"","sourcesContent":["import { WebPartContext } from '@microsoft/sp-webpart-base';\n\nexport interface ICtFormsPortalProps {\n description: string;\n isDarkTheme: boolean;\n environmentMessage: string;\n hasTeamsContext: boolean;\n userDisplayName: string;\n context: WebPartContext;\n}\n"]}
@@ -0,0 +1,79 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.createCtDocumentConfig = createCtDocumentConfig;
var STATUS_LABEL = {
'Черновик': 'Сформирована',
'На согласовании': 'На согласовании',
'Согласовано': 'Согласована',
'Отклонено': 'Отклонена'
};
var STATUS_VARIANT = {
'Черновик': 'neutral',
'На согласовании': 'info',
'Согласовано': 'success',
'Отклонено': 'error'
};
function createCtDocumentConfig() {
return {
typeKey: 'ct',
typeLabel: 'СТ ОМТС',
icon: 'ShoppingCart',
// Списки СТ ОМТС — на сайте СЭД. Явно, чтобы портал не зависел от места размещения.
siteUrl: 'https://tnsukz.sharepoint.com/sites/sed',
myDocsListName: 'Сравнительная таблица',
buildMyDocsFilter: function (userId) { return "AuthorId eq ".concat(userId); },
myDocsSelect: 'Id,Title,number,project,status,Created',
pendingApproval: {
approvalListName: 'Согласование СТ',
buildPendingFilter: function (userId) { return "aproover/Id eq ".concat(userId, " and status eq '\u041D\u0430 \u0441\u043E\u0433\u043B\u0430\u0441\u043E\u0432\u0430\u043D\u0438\u0438'"); },
approvalSelect: 'Id,CTId,status,aproover/Id',
approvalExpand: 'aproover',
parentIdField: 'CTId',
buildParentQuery: function (parentIds) { return ({
listName: 'Сравнительная таблица',
filter: parentIds.map(function (id) { return "Id eq ".concat(id); }).join(' or '),
select: 'Id,Title,number,project,status,Created'
}); }
},
approvedByMe: {
approvalListName: 'Согласование СТ',
buildPendingFilter: function (userId) { return "aproover/Id eq ".concat(userId, " and status eq '\u0421\u043E\u0433\u043B\u0430\u0441\u043E\u0432\u0430\u043D\u043E'"); },
approvalSelect: 'Id,CTId,status,aproover/Id',
approvalExpand: 'aproover',
parentIdField: 'CTId',
buildParentQuery: function (parentIds) { return ({
listName: 'Сравнительная таблица',
filter: parentIds.map(function (id) { return "Id eq ".concat(id); }).join(' or '),
select: 'Id,Title,number,project,status,Created'
}); }
},
buildLinkUrl: function (row) {
var base = 'https://tnsukz.sharepoint.com/sites/sed/SitePages/Сравнительная-таблица.aspx';
var param = row.status === 'Черновик' ? 'ctId' : 'ctid';
return "".concat(base, "?").concat(param, "=").concat(row.id);
},
createNewUrl: 'https://tnsukz.sharepoint.com/sites/sed/SitePages/Сравнительная-таблица.aspx',
createNewLabel: 'Создать новую форму СТ',
mapItem: function (raw) {
var _a, _b, _c, _d, _e;
var spStatus = String((_a = raw['status']) !== null && _a !== void 0 ? _a : '');
return {
typeKey: 'ct',
id: String(raw['Id']),
title: "\u0421\u0422 \u2116".concat((_b = raw['number']) !== null && _b !== void 0 ? _b : raw['Title']),
subtitle: String((_c = raw['project']) !== null && _c !== void 0 ? _c : ''),
status: spStatus,
statusLabel: (_d = STATUS_LABEL[spStatus]) !== null && _d !== void 0 ? _d : spStatus,
statusVariant: (_e = STATUS_VARIANT[spStatus]) !== null && _e !== void 0 ? _e : 'neutral',
createdDate: raw['Created'] ? String(raw['Created']) : undefined
};
},
columns: [
{ key: 'title', label: 'Номер СТ', minWidth: 120 },
{ key: 'subtitle', label: 'Проект', minWidth: 180 },
{ key: 'status', label: 'Статус', minWidth: 130 },
{ key: 'created', label: 'Создана', minWidth: 110 }
]
};
}
//# sourceMappingURL=ctDocumentConfig.js.map
File diff suppressed because one or more lines are too long
@@ -0,0 +1,13 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.DOCUMENT_TYPES = void 0;
var ctDocumentConfig_1 = require("./ctDocumentConfig");
var oidDocumentConfig_1 = require("./oidDocumentConfig");
var kpsDocumentConfig_1 = require("./kpsDocumentConfig");
// Все типы документов портала. Добавить тип = добавить конфиг сюда.
exports.DOCUMENT_TYPES = [
(0, ctDocumentConfig_1.createCtDocumentConfig)(),
(0, oidDocumentConfig_1.createOidDocumentConfig)(),
(0, kpsDocumentConfig_1.createKpsDocumentConfig)()
];
//# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../../src/webparts/ctFormsPortal/components/config/index.ts"],"names":[],"mappings":";;;AACA,uDAA4D;AAC5D,yDAA8D;AAC9D,yDAA8D;AAE9D,oEAAoE;AACvD,QAAA,cAAc,GAAyB;IAClD,IAAA,yCAAsB,GAAE;IACxB,IAAA,2CAAuB,GAAE;IACzB,IAAA,2CAAuB,GAAE;CAC1B,CAAC","sourcesContent":["import { DocumentTypeConfig } from '../registry/documentRegistry';\nimport { createCtDocumentConfig } from './ctDocumentConfig';\nimport { createOidDocumentConfig } from './oidDocumentConfig';\nimport { createKpsDocumentConfig } from './kpsDocumentConfig';\n\n// Все типы документов портала. Добавить тип = добавить конфиг сюда.\nexport const DOCUMENT_TYPES: DocumentTypeConfig[] = [\n createCtDocumentConfig(),\n createOidDocumentConfig(),\n createKpsDocumentConfig()\n];\n"]}
@@ -0,0 +1,80 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.createKpsDocumentConfig = createKpsDocumentConfig;
var KPS_SITE_URL = 'https://tnsukz.sharepoint.com/sites/kps';
var KPS_FORM_PAGE = "".concat(KPS_SITE_URL, "/SitePages/\u0421\u0440\u0430\u0432\u043D\u0438\u0442\u0435\u043B\u044C\u043D\u0430\u044F-\u0442\u0430\u0431\u043B\u0438\u0446\u0430.aspx");
var STATUS_LABEL = {
'Черновик': 'Сформирована',
'На согласовании': 'На согласовании',
'Согласовано': 'Согласована',
'Отклонено': 'Отклонена'
};
var STATUS_VARIANT = {
'Черновик': 'neutral',
'На согласовании': 'info',
'Согласовано': 'success',
'Отклонено': 'error'
};
function createKpsDocumentConfig() {
return {
typeKey: 'kps',
typeLabel: 'СТ КПС',
icon: 'Processing',
// Списки КПС — на отдельном сайт-коллекшене /sites/kps.
siteUrl: KPS_SITE_URL,
myDocsListName: 'Сравнительная таблица',
buildMyDocsFilter: function (userId) { return "AuthorId eq ".concat(userId); },
myDocsSelect: 'Id,Title,number,project,status,Created',
pendingApproval: {
approvalListName: 'Согласование СТ',
buildPendingFilter: function (userId) { return "aproover/Id eq ".concat(userId, " and status eq '\u041D\u0430 \u0441\u043E\u0433\u043B\u0430\u0441\u043E\u0432\u0430\u043D\u0438\u0438'"); },
approvalSelect: 'Id,CTId,status,aproover/Id',
approvalExpand: 'aproover',
parentIdField: 'CTId',
buildParentQuery: function (parentIds) { return ({
listName: 'Сравнительная таблица',
filter: parentIds.map(function (id) { return "Id eq ".concat(id); }).join(' or '),
select: 'Id,Title,number,project,status,Created'
}); }
},
approvedByMe: {
approvalListName: 'Согласование СТ',
buildPendingFilter: function (userId) { return "aproover/Id eq ".concat(userId, " and status eq '\u0421\u043E\u0433\u043B\u0430\u0441\u043E\u0432\u0430\u043D\u043E'"); },
approvalSelect: 'Id,CTId,status,aproover/Id',
approvalExpand: 'aproover',
parentIdField: 'CTId',
buildParentQuery: function (parentIds) { return ({
listName: 'Сравнительная таблица',
filter: parentIds.map(function (id) { return "Id eq ".concat(id); }).join(' or '),
select: 'Id,Title,number,project,status,Created'
}); }
},
buildLinkUrl: function (row) {
var param = row.status === 'Черновик' ? 'ctId' : 'ctid';
return "".concat(KPS_FORM_PAGE, "?").concat(param, "=").concat(row.id);
},
createNewUrl: KPS_FORM_PAGE,
createNewLabel: 'Создать новую форму КПС',
mapItem: function (raw) {
var _a, _b, _c, _d, _e;
var spStatus = String((_a = raw['status']) !== null && _a !== void 0 ? _a : '');
return {
typeKey: 'kps',
id: String(raw['Id']),
title: "\u0421\u0422 \u2116".concat((_b = raw['number']) !== null && _b !== void 0 ? _b : raw['Title']),
subtitle: String((_c = raw['project']) !== null && _c !== void 0 ? _c : ''),
status: spStatus,
statusLabel: (_d = STATUS_LABEL[spStatus]) !== null && _d !== void 0 ? _d : spStatus,
statusVariant: (_e = STATUS_VARIANT[spStatus]) !== null && _e !== void 0 ? _e : 'neutral',
createdDate: raw['Created'] ? String(raw['Created']) : undefined
};
},
columns: [
{ key: 'title', label: 'Номер СТ', minWidth: 120 },
{ key: 'subtitle', label: 'Проект', minWidth: 180 },
{ key: 'status', label: 'Статус', minWidth: 130 },
{ key: 'created', label: 'Создана', minWidth: 110 }
]
};
}
//# sourceMappingURL=kpsDocumentConfig.js.map
File diff suppressed because one or more lines are too long
@@ -0,0 +1,21 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.MOCK_ROWS = exports.FORM_TYPES = void 0;
// Типы форм (позже иконки заменим на профильные, когда добавим их в ui-kit).
exports.FORM_TYPES = [
{ typeKey: 'ct', typeLabel: 'СТ ОМТС', icon: 'clipboard', createLabel: 'Создать новую форму СТ' },
{ typeKey: 'oid', typeLabel: 'СТ ОИД', icon: 'chart', createLabel: 'Создать новую форму ОИД' },
{ typeKey: 'kps', typeLabel: 'СТ КПС', icon: 'building', createLabel: 'Создать новую форму КПС' }
];
// Моки реестра (пока без SharePoint) — чтобы увидеть каркас.
exports.MOCK_ROWS = [
{ id: '53', typeKey: 'ct', queue: 'pending', number: 'СТ №53', project: 'Строительство 5-этажного жилого дома в п.Топар', status: 'На согласовании', statusVariant: 'info', created: '04.06.2026' },
{ id: '52', typeKey: 'ct', queue: 'mine', number: 'СТ №52', project: 'Офис TANSU', status: 'Сформирована', statusVariant: 'neutral', created: '02.06.2026' },
{ id: '48', typeKey: 'ct', queue: 'mine', number: 'СТ №48', project: 'Склад в Караганде', status: 'На согласовании', statusVariant: 'info', created: '28.05.2026' },
{ id: '40', typeKey: 'ct', queue: 'approved', number: 'СТ №40', project: 'Строительство 5-этажного жилого дома в п.Топар', status: 'Согласована', statusVariant: 'success', created: '20.05.2026' },
{ id: '21', typeKey: 'oid', queue: 'mine', number: 'ОИД №21', project: 'Реконструкция кровли', status: 'Сформирована', statusVariant: 'neutral', created: '01.06.2026' },
{ id: '19', typeKey: 'oid', queue: 'approved', number: 'ОИД №19', project: 'Благоустройство территории', status: 'Согласована', statusVariant: 'success', created: '15.05.2026' },
{ id: '17', typeKey: 'oid', queue: 'mine', number: 'ОИД №17', project: 'Отделочные работы', status: 'Отклонена', statusVariant: 'danger', created: '10.05.2026' },
{ id: '9', typeKey: 'kps', queue: 'mine', number: 'КПС №9', project: 'Поставка материалов', status: 'На согласовании', statusVariant: 'info', created: '03.06.2026' }
];
//# sourceMappingURL=mock.js.map
@@ -0,0 +1 @@
{"version":3,"file":"mock.js","sourceRoot":"","sources":["../../../../../src/webparts/ctFormsPortal/components/config/mock.ts"],"names":[],"mappings":";;;AAEA,6EAA6E;AAChE,QAAA,UAAU,GAAsB;IAC3C,EAAE,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,SAAS,EAAE,IAAI,EAAE,WAAW,EAAE,WAAW,EAAE,wBAAwB,EAAE;IACjG,EAAE,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,QAAQ,EAAE,IAAI,EAAE,OAAO,EAAE,WAAW,EAAE,yBAAyB,EAAE;IAC9F,EAAE,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,QAAQ,EAAE,IAAI,EAAE,UAAU,EAAE,WAAW,EAAE,yBAAyB,EAAE;CAClG,CAAC;AAEF,6DAA6D;AAChD,QAAA,SAAS,GAAmB;IACvC,EAAE,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,OAAO,EAAE,gDAAgD,EAAE,MAAM,EAAE,iBAAiB,EAAE,aAAa,EAAE,MAAM,EAAE,OAAO,EAAE,YAAY,EAAE;IACnM,EAAE,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,OAAO,EAAE,YAAY,EAAE,MAAM,EAAE,cAAc,EAAE,aAAa,EAAE,SAAS,EAAE,OAAO,EAAE,YAAY,EAAE;IAC5J,EAAE,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,OAAO,EAAE,mBAAmB,EAAE,MAAM,EAAE,iBAAiB,EAAE,aAAa,EAAE,MAAM,EAAE,OAAO,EAAE,YAAY,EAAE;IACnK,EAAE,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,UAAU,EAAE,MAAM,EAAE,QAAQ,EAAE,OAAO,EAAE,gDAAgD,EAAE,MAAM,EAAE,aAAa,EAAE,aAAa,EAAE,SAAS,EAAE,OAAO,EAAE,YAAY,EAAE;IAEnM,EAAE,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,OAAO,EAAE,sBAAsB,EAAE,MAAM,EAAE,cAAc,EAAE,aAAa,EAAE,SAAS,EAAE,OAAO,EAAE,YAAY,EAAE;IACxK,EAAE,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,UAAU,EAAE,MAAM,EAAE,SAAS,EAAE,OAAO,EAAE,4BAA4B,EAAE,MAAM,EAAE,aAAa,EAAE,aAAa,EAAE,SAAS,EAAE,OAAO,EAAE,YAAY,EAAE;IACjL,EAAE,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,OAAO,EAAE,mBAAmB,EAAE,MAAM,EAAE,WAAW,EAAE,aAAa,EAAE,QAAQ,EAAE,OAAO,EAAE,YAAY,EAAE;IAEjK,EAAE,EAAE,EAAE,GAAG,EAAE,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,OAAO,EAAE,qBAAqB,EAAE,MAAM,EAAE,iBAAiB,EAAE,aAAa,EAAE,MAAM,EAAE,OAAO,EAAE,YAAY,EAAE;CACtK,CAAC","sourcesContent":["import { IFormTypeConfig, IRegistryRow } from './types';\n\n// Типы форм (позже иконки заменим на профильные, когда добавим их в ui-kit).\nexport const FORM_TYPES: IFormTypeConfig[] = [\n { typeKey: 'ct', typeLabel: 'СТ ОМТС', icon: 'clipboard', createLabel: 'Создать новую форму СТ' },\n { typeKey: 'oid', typeLabel: 'СТ ОИД', icon: 'chart', createLabel: 'Создать новую форму ОИД' },\n { typeKey: 'kps', typeLabel: 'СТ КПС', icon: 'building', createLabel: 'Создать новую форму КПС' }\n];\n\n// Моки реестра (пока без SharePoint) — чтобы увидеть каркас.\nexport const MOCK_ROWS: IRegistryRow[] = [\n { id: '53', typeKey: 'ct', queue: 'pending', number: 'СТ №53', project: 'Строительство 5-этажного жилого дома в п.Топар', status: 'На согласовании', statusVariant: 'info', created: '04.06.2026' },\n { id: '52', typeKey: 'ct', queue: 'mine', number: 'СТ №52', project: 'Офис TANSU', status: 'Сформирована', statusVariant: 'neutral', created: '02.06.2026' },\n { id: '48', typeKey: 'ct', queue: 'mine', number: 'СТ №48', project: 'Склад в Караганде', status: 'На согласовании', statusVariant: 'info', created: '28.05.2026' },\n { id: '40', typeKey: 'ct', queue: 'approved', number: 'СТ №40', project: 'Строительство 5-этажного жилого дома в п.Топар', status: 'Согласована', statusVariant: 'success', created: '20.05.2026' },\n\n { id: '21', typeKey: 'oid', queue: 'mine', number: 'ОИД №21', project: 'Реконструкция кровли', status: 'Сформирована', statusVariant: 'neutral', created: '01.06.2026' },\n { id: '19', typeKey: 'oid', queue: 'approved', number: 'ОИД №19', project: 'Благоустройство территории', status: 'Согласована', statusVariant: 'success', created: '15.05.2026' },\n { id: '17', typeKey: 'oid', queue: 'mine', number: 'ОИД №17', project: 'Отделочные работы', status: 'Отклонена', statusVariant: 'danger', created: '10.05.2026' },\n\n { id: '9', typeKey: 'kps', queue: 'mine', number: 'КПС №9', project: 'Поставка материалов', status: 'На согласовании', statusVariant: 'info', created: '03.06.2026' }\n];\n"]}
@@ -0,0 +1,85 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.createOidDocumentConfig = createOidDocumentConfig;
var STATUS_LABEL = {
'Черновик': 'Сформирована',
'formed': 'Сформирована',
'На согласовании': 'На согласовании',
'on_approval': 'На согласовании',
'Согласовано': 'Согласована',
'approved': 'Согласована',
'Отклонено': 'Отклонена'
};
var STATUS_VARIANT = {
'Черновик': 'neutral',
'formed': 'neutral',
'На согласовании': 'info',
'on_approval': 'info',
'Согласовано': 'success',
'approved': 'success',
'Отклонено': 'error'
};
function createOidDocumentConfig() {
return {
typeKey: 'oid',
typeLabel: 'СТ ОИД',
icon: 'CRMServices',
// Списки СТ ОИД — на сайте СЭД. Явно, чтобы портал не зависел от места размещения.
siteUrl: 'https://tnsukz.sharepoint.com/sites/sed',
myDocsListName: 'Сравнительная таблица ОИД',
buildMyDocsFilter: function (userId) { return "AuthorId eq ".concat(userId); },
myDocsSelect: 'Id,Title,number,project,status,Created',
pendingApproval: {
approvalListName: 'Согласование СТ ОИД',
buildPendingFilter: function (userId) { return "aproover/Id eq ".concat(userId, " and status eq '\u041D\u0430 \u0441\u043E\u0433\u043B\u0430\u0441\u043E\u0432\u0430\u043D\u0438\u0438'"); },
approvalSelect: 'Id,CTId,status,aproover/Id',
approvalExpand: 'aproover',
parentIdField: 'CTId',
buildParentQuery: function (parentIds) { return ({
listName: 'Сравнительная таблица ОИД',
filter: parentIds.map(function (id) { return "Id eq ".concat(id); }).join(' or '),
select: 'Id,Title,number,project,status,Created'
}); }
},
approvedByMe: {
approvalListName: 'Согласование СТ ОИД',
buildPendingFilter: function (userId) { return "aproover/Id eq ".concat(userId, " and status eq '\u0421\u043E\u0433\u043B\u0430\u0441\u043E\u0432\u0430\u043D\u043E'"); },
approvalSelect: 'Id,CTId,status,aproover/Id',
approvalExpand: 'aproover',
parentIdField: 'CTId',
buildParentQuery: function (parentIds) { return ({
listName: 'Сравнительная таблица ОИД',
filter: parentIds.map(function (id) { return "Id eq ".concat(id); }).join(' or '),
select: 'Id,Title,number,project,status,Created'
}); }
},
buildLinkUrl: function (row) {
var base = 'https://tnsukz.sharepoint.com/sites/sed/SitePages/Сравнительная-таблица-ОИД.aspx';
var param = row.status === 'Черновик' || row.status === 'formed' ? 'oidId' : 'oidid';
return "".concat(base, "?").concat(param, "=").concat(row.id);
},
createNewUrl: 'https://tnsukz.sharepoint.com/sites/sed/SitePages/Сравнительная-таблица-ОИД.aspx',
createNewLabel: 'Создать новую форму ОИД',
mapItem: function (raw) {
var _a, _b, _c, _d, _e;
var spStatus = String((_a = raw['status']) !== null && _a !== void 0 ? _a : '');
return {
typeKey: 'oid',
id: String(raw['Id']),
title: "\u0421\u0422 \u2116".concat((_b = raw['number']) !== null && _b !== void 0 ? _b : raw['Title']),
subtitle: String((_c = raw['project']) !== null && _c !== void 0 ? _c : ''),
status: spStatus,
statusLabel: (_d = STATUS_LABEL[spStatus]) !== null && _d !== void 0 ? _d : spStatus,
statusVariant: (_e = STATUS_VARIANT[spStatus]) !== null && _e !== void 0 ? _e : 'neutral',
createdDate: raw['Created'] ? String(raw['Created']) : undefined
};
},
columns: [
{ key: 'title', label: 'Номер СТ', minWidth: 120 },
{ key: 'subtitle', label: 'Проект', minWidth: 180 },
{ key: 'status', label: 'Статус', minWidth: 130 },
{ key: 'created', label: 'Создана', minWidth: 110 }
]
};
}
//# sourceMappingURL=oidDocumentConfig.js.map
File diff suppressed because one or more lines are too long
@@ -0,0 +1,3 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
//# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../../../../src/webparts/ctFormsPortal/components/config/types.ts"],"names":[],"mappings":"","sourcesContent":["import { IconName, BadgeVariant } from '@tnsu/ui-kit';\n\n// Очередь реестра (вкладки).\nexport type QueueKey = 'pending' | 'mine' | 'approved';\n\n// Конфиг типа формы (CT/OID/KPS). Пока — минимум для оболочки/реестра.\n// Позже расширим: SP-списки, фильтры, buildLinkUrl, мастер и т.д.\nexport interface IFormTypeConfig {\n typeKey: string;\n typeLabel: string;\n icon: IconName;\n createLabel: string;\n}\n\n// Унифицированная строка реестра. statusVariant — цвет бейджа (маппинг в конфиге типа).\nexport interface IRegistryRow {\n id: string;\n typeKey: string;\n queue: QueueKey;\n number: string;\n project: string;\n status: string;\n statusVariant: BadgeVariant;\n created: string;\n}\n"]}
@@ -0,0 +1,3 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
//# sourceMappingURL=documentRegistry.js.map
@@ -0,0 +1 @@
{"version":3,"file":"documentRegistry.js","sourceRoot":"","sources":["../../../../../src/webparts/ctFormsPortal/components/registry/documentRegistry.ts"],"names":[],"mappings":"","sourcesContent":["import { WebPartContext } from '@microsoft/sp-webpart-base';\n\n// Унифицированная строка реестра (перенесено из ct-forms-list/myDocuments).\nexport interface DashboardRow {\n typeKey: string;\n id: string;\n title: string;\n subtitle?: string;\n status: string;\n statusLabel: string;\n statusVariant: 'neutral' | 'info' | 'success' | 'error';\n createdDate?: string;\n}\n\nexport interface ColumnDef {\n key: string;\n label: string;\n minWidth?: number;\n}\n\nexport interface ApprovalSectionConfig {\n approvalListName: string;\n buildPendingFilter: (userId: number) => string;\n approvalSelect: string;\n approvalExpand?: string;\n /** поле в записи согласования, хранящее ID родителя */\n parentIdField: string;\n buildParentQuery: (parentIds: number[]) => { listName: string; filter: string; select: string };\n}\n\n// Конфиг типа документа (CT/OID/KPS): где лежат SP-списки, как фильтровать,\n// как маппить в строку и как открыть форму. Различия типов живут ЗДЕСЬ.\nexport interface DocumentTypeConfig {\n typeKey: string;\n typeLabel: string;\n /** Имя иконки Fluent (легаси из myDocuments); в портале сайдбар берёт kit-иконку по typeKey. */\n icon?: string;\n /** Абсолютный URL сайта со списками этого типа. Не задан — текущий сайт. */\n siteUrl?: string;\n myDocsListName: string;\n buildMyDocsFilter: (userId: number) => string;\n myDocsSelect: string;\n myDocsExpand?: string;\n /** null = нет согласования у этого типа */\n pendingApproval: ApprovalSectionConfig | null;\n /** null = нет вкладки \"Согласованные мной\" */\n approvedByMe: ApprovalSectionConfig | null;\n mapItem: (raw: Record<string, unknown>, context: WebPartContext) => DashboardRow;\n buildLinkUrl: (row: DashboardRow) => string;\n createNewUrl?: string;\n createNewLabel?: string;\n columns: ColumnDef[];\n}\n"]}
@@ -0,0 +1,175 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.DocumentsService = void 0;
var tslib_1 = require("tslib");
var sp_http_1 = require("@microsoft/sp-http");
// Загрузка реестра из SharePoint (перенесено из ct-forms-list/myDocuments).
// Поддерживает типы на разных сайт-коллекшенах (KPS на /sites/kps).
var DocumentsService = /** @class */ (function () {
function DocumentsService(context) {
this.context = context;
this.userIdCache = new Map();
}
Object.defineProperty(DocumentsService.prototype, "currentUserId", {
get: function () {
var _a;
return Number((_a = this.context.pageContext.legacyPageContext['userId']) !== null && _a !== void 0 ? _a : 0);
},
enumerable: false,
configurable: true
});
Object.defineProperty(DocumentsService.prototype, "webUrl", {
get: function () {
return this.context.pageContext.web.absoluteUrl;
},
enumerable: false,
configurable: true
});
DocumentsService.prototype.siteUrlFor = function (config) {
var _a;
return (_a = config.siteUrl) !== null && _a !== void 0 ? _a : this.webUrl;
};
DocumentsService.prototype.resolveUserId = function (config) {
return tslib_1.__awaiter(this, void 0, void 0, function () {
var site, cached, response, json;
return tslib_1.__generator(this, function (_a) {
switch (_a.label) {
case 0:
site = this.siteUrlFor(config);
if (site === this.webUrl)
return [2 /*return*/, this.currentUserId];
cached = this.userIdCache.get(site);
if (cached !== undefined)
return [2 /*return*/, cached];
return [4 /*yield*/, this.context.spHttpClient.get("".concat(site, "/_api/web/currentUser?$select=Id"), sp_http_1.SPHttpClient.configurations.v1)];
case 1:
response = _a.sent();
if (!response.ok) {
throw new Error("\u041D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C \u043E\u043F\u0440\u0435\u0434\u0435\u043B\u0438\u0442\u044C \u043F\u043E\u043B\u044C\u0437\u043E\u0432\u0430\u0442\u0435\u043B\u044F \u043D\u0430 \u0441\u0430\u0439\u0442\u0435 \"".concat(site, "\": ").concat(response.status));
}
return [4 /*yield*/, response.json()];
case 2:
json = _a.sent();
this.userIdCache.set(site, json.Id);
return [2 /*return*/, json.Id];
}
});
});
};
DocumentsService.prototype.buildListUrl = function (siteUrl, listName) {
return "".concat(siteUrl, "/_api/web/lists/getByTitle('").concat(listName, "')/items");
};
DocumentsService.prototype.fetchItems = function (siteUrl, listName, params) {
return tslib_1.__awaiter(this, void 0, void 0, function () {
var q, url, response, json;
return tslib_1.__generator(this, function (_a) {
switch (_a.label) {
case 0:
q = [];
if (params.filter)
q.push("$filter=".concat(params.filter));
if (params.select)
q.push("$select=".concat(params.select));
if (params.expand)
q.push("$expand=".concat(params.expand));
if (params.orderby)
q.push("$orderby=".concat(params.orderby));
if (params.top !== undefined)
q.push("$top=".concat(params.top));
url = q.length > 0 ? "".concat(this.buildListUrl(siteUrl, listName), "?").concat(q.join('&')) : this.buildListUrl(siteUrl, listName);
return [4 /*yield*/, this.context.spHttpClient.get(url, sp_http_1.SPHttpClient.configurations.v1)];
case 1:
response = _a.sent();
if (!response.ok) {
throw new Error("\u041E\u0448\u0438\u0431\u043A\u0430 \u0437\u0430\u0433\u0440\u0443\u0437\u043A\u0438 \u0441\u043F\u0438\u0441\u043A\u0430 \"".concat(listName, "\": ").concat(response.status));
}
return [4 /*yield*/, response.json()];
case 2:
json = _a.sent();
return [2 /*return*/, json.value];
}
});
});
};
DocumentsService.prototype.fetchMyDocs = function (config) {
return tslib_1.__awaiter(this, void 0, void 0, function () {
var siteUrl, userId, items;
var _this = this;
return tslib_1.__generator(this, function (_a) {
switch (_a.label) {
case 0:
siteUrl = this.siteUrlFor(config);
return [4 /*yield*/, this.resolveUserId(config)];
case 1:
userId = _a.sent();
return [4 /*yield*/, this.fetchItems(siteUrl, config.myDocsListName, {
filter: config.buildMyDocsFilter(userId),
select: config.myDocsSelect,
expand: config.myDocsExpand,
orderby: 'Created desc',
top: 100
})];
case 2:
items = _a.sent();
return [2 /*return*/, items.map(function (item) { return config.mapItem(item, _this.context); })];
}
});
});
};
DocumentsService.prototype.fetchApprovedByMe = function (config) {
return tslib_1.__awaiter(this, void 0, void 0, function () {
return tslib_1.__generator(this, function (_a) {
if (!config.approvedByMe)
return [2 /*return*/, []];
return [2 /*return*/, this._fetchApprovalSection(config, config.approvedByMe)];
});
});
};
DocumentsService.prototype.fetchPendingApprovals = function (config) {
return tslib_1.__awaiter(this, void 0, void 0, function () {
return tslib_1.__generator(this, function (_a) {
if (!config.pendingApproval)
return [2 /*return*/, []];
return [2 /*return*/, this._fetchApprovalSection(config, config.pendingApproval)];
});
});
};
DocumentsService.prototype._fetchApprovalSection = function (config, pa) {
return tslib_1.__awaiter(this, void 0, void 0, function () {
var siteUrl, userId, approvalItems, parentIds, parentQuery, parentItems;
var _this = this;
return tslib_1.__generator(this, function (_a) {
switch (_a.label) {
case 0:
siteUrl = this.siteUrlFor(config);
return [4 /*yield*/, this.resolveUserId(config)];
case 1:
userId = _a.sent();
return [4 /*yield*/, this.fetchItems(siteUrl, pa.approvalListName, {
filter: pa.buildPendingFilter(userId),
select: pa.approvalSelect,
expand: pa.approvalExpand,
top: 200
})];
case 2:
approvalItems = _a.sent();
parentIds = Array.from(new Set(approvalItems.map(function (item) { return Number(item[pa.parentIdField]); }).filter(function (id) { return !isNaN(id) && id > 0; })));
if (parentIds.length === 0)
return [2 /*return*/, []];
parentQuery = pa.buildParentQuery(parentIds);
return [4 /*yield*/, this.fetchItems(siteUrl, parentQuery.listName, {
filter: parentQuery.filter,
select: parentQuery.select,
top: 200
})];
case 3:
parentItems = _a.sent();
return [2 /*return*/, parentItems.map(function (item) { return config.mapItem(item, _this.context); })];
}
});
});
};
return DocumentsService;
}());
exports.DocumentsService = DocumentsService;
//# sourceMappingURL=documentsService.js.map
File diff suppressed because one or more lines are too long
@@ -0,0 +1,39 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.useDocumentDashboard = useDocumentDashboard;
var react_1 = require("react");
var documentsService_1 = require("./documentsService");
// Грузит три очереди (мои / согласованные / на согласовании) по всем типам сразу.
function useDocumentDashboard(context, documentTypes) {
var _a = (0, react_1.useState)([]), myDocs = _a[0], setMyDocs = _a[1];
var _b = (0, react_1.useState)([]), approvedDocs = _b[0], setApprovedDocs = _b[1];
var _c = (0, react_1.useState)([]), pendingDocs = _c[0], setPendingDocs = _c[1];
var _d = (0, react_1.useState)(true), isLoading = _d[0], setIsLoading = _d[1];
var _e = (0, react_1.useState)(null), error = _e[0], setError = _e[1];
var _f = (0, react_1.useState)(0), tick = _f[0], setTick = _f[1];
var reload = (0, react_1.useCallback)(function () { return setTick(function (t) { return t + 1; }); }, []);
(0, react_1.useEffect)(function () {
var service = new documentsService_1.DocumentsService(context);
setIsLoading(true);
setError(null);
Promise.all([
Promise.all(documentTypes.map(function (cfg) { return service.fetchMyDocs(cfg); })),
Promise.all(documentTypes.map(function (cfg) { return service.fetchApprovedByMe(cfg); })),
Promise.all(documentTypes.map(function (cfg) { return service.fetchPendingApprovals(cfg); }))
])
.then(function (_a) {
var myDocGroups = _a[0], approvedGroups = _a[1], pendingGroups = _a[2];
setMyDocs(myDocGroups.reduce(function (acc, arr) { return acc.concat(arr); }, []));
setApprovedDocs(approvedGroups.reduce(function (acc, arr) { return acc.concat(arr); }, []));
setPendingDocs(pendingGroups.reduce(function (acc, arr) { return acc.concat(arr); }, []));
setIsLoading(false);
})
.catch(function (err) {
var _a;
setError((_a = err.message) !== null && _a !== void 0 ? _a : 'Произошла ошибка при загрузке данных');
setIsLoading(false);
});
}, [context, tick]); // eslint-disable-line react-hooks/exhaustive-deps
return { myDocs: myDocs, approvedDocs: approvedDocs, pendingDocs: pendingDocs, isLoading: isLoading, error: error, reload: reload };
}
//# sourceMappingURL=useDocumentDashboard.js.map
@@ -0,0 +1 @@
{"version":3,"file":"useDocumentDashboard.js","sourceRoot":"","sources":["../../../../../src/webparts/ctFormsPortal/components/registry/useDocumentDashboard.ts"],"names":[],"mappings":"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","sourcesContent":["import { useState, useEffect, useCallback } from 'react';\nimport { WebPartContext } from '@microsoft/sp-webpart-base';\nimport { DashboardRow, DocumentTypeConfig } from './documentRegistry';\nimport { DocumentsService } from './documentsService';\n\ninterface IDashboardState {\n myDocs: DashboardRow[];\n approvedDocs: DashboardRow[];\n pendingDocs: DashboardRow[];\n isLoading: boolean;\n error: string | null;\n reload: () => void;\n}\n\n// Грузит три очереди (мои / согласованные / на согласовании) по всем типам сразу.\nexport function useDocumentDashboard(\n context: WebPartContext,\n documentTypes: DocumentTypeConfig[]\n): IDashboardState {\n const [myDocs, setMyDocs] = useState<DashboardRow[]>([]);\n const [approvedDocs, setApprovedDocs] = useState<DashboardRow[]>([]);\n const [pendingDocs, setPendingDocs] = useState<DashboardRow[]>([]);\n const [isLoading, setIsLoading] = useState<boolean>(true);\n const [error, setError] = useState<string | null>(null);\n const [tick, setTick] = useState<number>(0);\n\n const reload = useCallback(() => setTick((t) => t + 1), []);\n\n useEffect(() => {\n const service = new DocumentsService(context);\n setIsLoading(true);\n setError(null);\n\n Promise.all([\n Promise.all(documentTypes.map((cfg) => service.fetchMyDocs(cfg))),\n Promise.all(documentTypes.map((cfg) => service.fetchApprovedByMe(cfg))),\n Promise.all(documentTypes.map((cfg) => service.fetchPendingApprovals(cfg)))\n ])\n .then(([myDocGroups, approvedGroups, pendingGroups]) => {\n setMyDocs(myDocGroups.reduce((acc, arr) => acc.concat(arr), [] as DashboardRow[]));\n setApprovedDocs(approvedGroups.reduce((acc, arr) => acc.concat(arr), [] as DashboardRow[]));\n setPendingDocs(pendingGroups.reduce((acc, arr) => acc.concat(arr), [] as DashboardRow[]));\n setIsLoading(false);\n })\n .catch((err: Error) => {\n setError(err.message ?? 'Произошла ошибка при загрузке данных');\n setIsLoading(false);\n });\n }, [context, tick]); // eslint-disable-line react-hooks/exhaustive-deps\n\n return { myDocs, approvedDocs, pendingDocs, isLoading, error, reload };\n}\n"]}