каркас вроде есть

This commit is contained in:
gorbenko.pavel
2026-07-29 12:30:40 +05:00
parent ad04d9bda4
commit 04f04133f2
114 changed files with 8347 additions and 5143 deletions
@@ -0,0 +1,38 @@
:root {
--navy-950: #0A1B30;
--navy-900: #0F2440;
--navy-800: #1B3A5C;
--navy-700: #244B73;
--navy-600: #2D5C8A;
--navy-100: #E8EEF4;
--navy-50: #F4F7FA;
--gold-600: #B8881A;
--gold-500: #D4A01E;
--gold-400: #E5B635;
--gold-300: #F0CC5E;
--gold-100: #FDF5E0;
--gold-50: #FEFBF0;
--gray-900: #111827;
--gray-700: #374151;
--gray-500: #6B7280;
--gray-400: #9CA3AF;
--gray-300: #D1D5DB;
--gray-200: #E5E7EB;
--gray-100: #F3F4F6;
--gray-50: #F9FAFB;
--red-500: #DC2626;
--red-50: #FEF2F2;
--green-600: #15803D;
--green-50: #F0FDF4;
--blue-600: #2563EB;
--blue-50: #EFF6FF;
--amber-700: #854D0E;
--sh-1: 0 1px 2px rgba(15, 36, 64, 0.04), 0 1px 3px rgba(15, 36, 64, 0.06);
--sh-2: 0 4px 16px rgba(15, 36, 64, 0.07);
--sh-3: 0 12px 32px rgba(15, 36, 64, 0.1);
--sh-gold: 0 4px 20px rgba(212, 160, 30, 0.18);
--sh-modal: 0 24px 70px rgba(10, 27, 48, 0.25);
--r-sm: 6px;
--r-md: 10px;
--r-lg: 14px;
}
@@ -6,68 +6,29 @@ var ReactDom = tslib_1.__importStar(require("react-dom"));
var sp_core_library_1 = require("@microsoft/sp-core-library");
var sp_property_pane_1 = require("@microsoft/sp-property-pane");
var sp_webpart_base_1 = require("@microsoft/sp-webpart-base");
var strings = tslib_1.__importStar(require("ApprovalAdminWebPartStrings"));
var ApprovalAdmin_1 = tslib_1.__importDefault(require("./components/ApprovalAdmin"));
var ApprovalAdminWebPart = /** @class */ (function (_super) {
tslib_1.__extends(ApprovalAdminWebPart, _super);
function ApprovalAdminWebPart() {
var _this = _super !== null && _super.apply(this, arguments) || this;
_this._isDarkTheme = false;
_this._environmentMessage = '';
return _this;
return _super !== null && _super.apply(this, arguments) || this;
}
ApprovalAdminWebPart.prototype.render = function () {
var element = React.createElement(ApprovalAdmin_1.default, {
description: this.properties.description,
isDarkTheme: this._isDarkTheme,
environmentMessage: this._environmentMessage,
hasTeamsContext: !!this.context.sdks.microsoftTeams,
userDisplayName: this.context.pageContext.user.displayName
context: this.context,
apiBaseUrl: this.properties.apiBaseUrl,
useDevAuth: this.properties.useDevAuth,
devUserEmail: this.properties.devUserEmail,
aadResourceId: this.properties.aadResourceId
});
ReactDom.render(element, this.domElement);
};
ApprovalAdminWebPart.prototype.onInit = function () {
var _this = this;
return this._getEnvironmentMessage().then(function (message) {
_this._environmentMessage = message;
});
};
ApprovalAdminWebPart.prototype._getEnvironmentMessage = function () {
var _this = this;
if (!!this.context.sdks.microsoftTeams) { // running in Teams, office.com or Outlook
return this.context.sdks.microsoftTeams.teamsJs.app.getContext()
.then(function (context) {
var environmentMessage = '';
switch (context.app.host.name) {
case 'Office': // running in Office
environmentMessage = _this.context.isServedFromLocalhost ? strings.AppLocalEnvironmentOffice : strings.AppOfficeEnvironment;
break;
case 'Outlook': // running in Outlook
environmentMessage = _this.context.isServedFromLocalhost ? strings.AppLocalEnvironmentOutlook : strings.AppOutlookEnvironment;
break;
case 'Teams': // running in Teams
case 'TeamsModern':
environmentMessage = _this.context.isServedFromLocalhost ? strings.AppLocalEnvironmentTeams : strings.AppTeamsTabEnvironment;
break;
default:
environmentMessage = strings.UnknownEnvironment;
}
return environmentMessage;
});
}
return Promise.resolve(this.context.isServedFromLocalhost ? strings.AppLocalEnvironmentSharePoint : strings.AppSharePointEnvironment);
};
ApprovalAdminWebPart.prototype.onThemeChanged = function (currentTheme) {
if (!currentTheme) {
return;
}
this._isDarkTheme = !!currentTheme.isInverted;
var semanticColors = currentTheme.semanticColors;
if (semanticColors) {
this.domElement.style.setProperty('--bodyText', semanticColors.bodyText || null);
this.domElement.style.setProperty('--link', semanticColors.link || null);
this.domElement.style.setProperty('--linkHovered', semanticColors.linkHovered || null);
}
var _a;
this.properties.apiBaseUrl = this.properties.apiBaseUrl || 'https://localhost:5443';
this.properties.useDevAuth = (_a = this.properties.useDevAuth) !== null && _a !== void 0 ? _a : true;
this.properties.devUserEmail = this.properties.devUserEmail || 'dev@tnsu.kz';
this.properties.aadResourceId = this.properties.aadResourceId || 'api://9232d9e2-9807-4753-808f-07250e5474b0';
return Promise.resolve();
};
ApprovalAdminWebPart.prototype.onDispose = function () {
ReactDom.unmountComponentAtNode(this.domElement);
@@ -84,14 +45,28 @@ var ApprovalAdminWebPart = /** @class */ (function (_super) {
pages: [
{
header: {
description: strings.PropertyPaneDescription
description: 'Подключение к Tnsu.ApprovalRoute.Api'
},
groups: [
{
groupName: strings.BasicGroupName,
groupName: 'API',
groupFields: [
(0, sp_property_pane_1.PropertyPaneTextField)('description', {
label: strings.DescriptionFieldLabel
(0, sp_property_pane_1.PropertyPaneTextField)('apiBaseUrl', {
label: 'Api Base Url',
description: 'Например, https://localhost:5443 локально или прод-адрес API.'
}),
(0, sp_property_pane_1.PropertyPaneToggle)('useDevAuth', {
label: 'Локальный Dev-режим (без Azure AD)',
onText: 'Вкл. (заголовок X-Dev-User-Email)',
offText: 'Выкл. (AAD-токен через SPFx)'
}),
(0, sp_property_pane_1.PropertyPaneTextField)('devUserEmail', {
label: 'Dev: email пользователя',
description: 'Используется только при включённом Dev-режиме.'
}),
(0, sp_property_pane_1.PropertyPaneTextField)('aadResourceId', {
label: 'AAD Resource Id (App ID URI)',
description: 'Используется только при выключенном Dev-режиме. Должен совпадать с webApiPermissionRequests в package-solution.json.'
})
]
}
File diff suppressed because one or more lines are too long
@@ -22,7 +22,10 @@
"description": { "default": "approval-admin description" },
"officeFabricIconFontName": "Page",
"properties": {
"description": "approval-admin"
"apiBaseUrl": "https://localhost:5443",
"useDevAuth": true,
"devUserEmail": "dev@tnsu.kz",
"aadResourceId": "api://9232d9e2-9807-4753-808f-07250e5474b0"
}
}]
}
@@ -3,46 +3,62 @@ Object.defineProperty(exports, "__esModule", { value: true });
var tslib_1 = require("tslib");
var React = tslib_1.__importStar(require("react"));
var ApprovalAdmin_module_scss_1 = tslib_1.__importDefault(require("./ApprovalAdmin.module.scss"));
var sp_lodash_subset_1 = require("@microsoft/sp-lodash-subset");
var ApprovalAdmin = /** @class */ (function (_super) {
tslib_1.__extends(ApprovalAdmin, _super);
function ApprovalAdmin() {
return _super !== null && _super.apply(this, arguments) || this;
}
ApprovalAdmin.prototype.render = function () {
var _a = this.props, description = _a.description, isDarkTheme = _a.isDarkTheme, environmentMessage = _a.environmentMessage, hasTeamsContext = _a.hasTeamsContext, userDisplayName = _a.userDisplayName;
return (React.createElement("section", { className: "".concat(ApprovalAdmin_module_scss_1.default.approvalAdmin, " ").concat(hasTeamsContext ? ApprovalAdmin_module_scss_1.default.teams : '') },
React.createElement("div", { className: ApprovalAdmin_module_scss_1.default.welcome },
React.createElement("img", { alt: "", src: isDarkTheme ? require('../assets/welcome-dark.png') : require('../assets/welcome-light.png'), className: ApprovalAdmin_module_scss_1.default.welcomeImage }),
React.createElement("h2", null,
"Well done, ",
(0, sp_lodash_subset_1.escape)(userDisplayName),
"!"),
React.createElement("div", null, environmentMessage),
React.createElement("div", null,
"Web part property value: ",
React.createElement("strong", null, (0, sp_lodash_subset_1.escape)(description)))),
React.createElement("div", null,
React.createElement("h3", null, "Welcome to SharePoint Framerk!"),
React.createElement("p", null, "The SharePoint Framework (SPFx) is a extensibility model for Microsoft Viva, Microsoft Teams and SharePoint. It's the easiest way to extend Microsoft 365 with automatic Single Sign On, automatic hosting and industry standard tooling."),
React.createElement("h4", null, "Learn more about SPFx development:"),
React.createElement("ul", { className: ApprovalAdmin_module_scss_1.default.links },
React.createElement("li", null,
React.createElement("a", { href: "https://aka.ms/spfx", target: "_blank", rel: "noreferrer" }, "SharePoint Framework Overview")),
React.createElement("li", null,
React.createElement("a", { href: "https://aka.ms/spfx-yeoman-graph", target: "_blank", rel: "noreferrer" }, "Use Microsoft Graph in your solution")),
React.createElement("li", null,
React.createElement("a", { href: "https://aka.ms/spfx-yeoman-teams", target: "_blank", rel: "noreferrer" }, "Build for Microsoft Teams using SharePoint Framework")),
React.createElement("li", null,
React.createElement("a", { href: "https://aka.ms/spfx-yeoman-viva", target: "_blank", rel: "noreferrer" }, "Build for Microsoft Viva Connections using SharePoint Framework")),
React.createElement("li", null,
React.createElement("a", { href: "https://aka.ms/spfx-yeoman-store", target: "_blank", rel: "noreferrer" }, "Publish SharePoint Framework applications to the marketplace")),
React.createElement("li", null,
React.createElement("a", { href: "https://aka.ms/spfx-yeoman-api", target: "_blank", rel: "noreferrer" }, "SharePoint Framework API reference")),
React.createElement("li", null,
React.createElement("a", { href: "https://aka.ms/m365pnp", target: "_blank", rel: "noreferrer" }, "Microsoft 365 Developer Community"))))));
};
return ApprovalAdmin;
}(React.Component));
var AdminApp_1 = require("./app/AdminApp");
var api_1 = require("../services/api");
var httpClient_1 = require("../services/httpClient");
var FONTS_LINK_ID = 'tnsu-approval-admin-fonts';
function ensureFontsLoaded() {
if (typeof document === 'undefined' || document.getElementById(FONTS_LINK_ID))
return;
var link = document.createElement('link');
link.id = FONTS_LINK_ID;
link.rel = 'stylesheet';
link.href = 'https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=Lora:wght@500;600;700&display=swap';
document.head.appendChild(link);
}
function createHttpClient(props) {
return tslib_1.__awaiter(this, void 0, void 0, function () {
var aadClient;
return tslib_1.__generator(this, function (_a) {
switch (_a.label) {
case 0:
if (props.useDevAuth) {
return [2 /*return*/, new httpClient_1.DevApiHttpClient(props.apiBaseUrl, props.devUserEmail || 'dev@tnsu.kz')];
}
return [4 /*yield*/, props.context.aadHttpClientFactory.getClient(props.aadResourceId)];
case 1:
aadClient = _a.sent();
return [2 /*return*/, new httpClient_1.AadApiHttpClient(aadClient, props.apiBaseUrl)];
}
});
});
}
var ApprovalAdmin = function (props) {
var _a = React.useState(), api = _a[0], setApi = _a[1];
var _b = React.useState(), error = _b[0], setError = _b[1];
React.useEffect(function () {
ensureFontsLoaded();
}, []);
React.useEffect(function () {
var cancelled = false;
setApi(undefined);
setError(undefined);
createHttpClient(props)
.then(function (client) { if (!cancelled)
setApi(new api_1.ApprovalRouteApi(client)); })
.catch(function (e) { if (!cancelled)
setError(e.message); });
return function () { cancelled = true; };
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [props.useDevAuth, props.apiBaseUrl, props.devUserEmail, props.aadResourceId]);
return (React.createElement("section", { className: ApprovalAdmin_module_scss_1.default.approvalAdmin },
error && (React.createElement("div", { className: ApprovalAdmin_module_scss_1.default.stateBox },
"\u041D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C \u043F\u043E\u0434\u043A\u043B\u044E\u0447\u0438\u0442\u044C\u0441\u044F \u043A API: ",
error,
React.createElement("br", null),
"\u041F\u0440\u043E\u0432\u0435\u0440\u044C\u0442\u0435 \u0441\u0432\u043E\u0439\u0441\u0442\u0432\u0430 \u0432\u0435\u0431-\u0447\u0430\u0441\u0442\u0438 (Api Base Url, \u0440\u0435\u0436\u0438\u043C \u0430\u0432\u0442\u043E\u0440\u0438\u0437\u0430\u0446\u0438\u0438) \u0432 \u043F\u0430\u043D\u0435\u043B\u0438 \u043D\u0430\u0441\u0442\u0440\u043E\u0435\u043A.")),
!error && !api && React.createElement("div", { className: ApprovalAdmin_module_scss_1.default.stateBox }, "\u041F\u043E\u0434\u043A\u043B\u044E\u0447\u0435\u043D\u0438\u0435 \u043A API..."),
api && React.createElement(AdminApp_1.AdminApp, { api: api, currentUserEmail: props.context.pageContext.user.email })));
};
exports.default = ApprovalAdmin;
//# sourceMappingURL=ApprovalAdmin.js.map
@@ -1 +1 @@
{"version":3,"file":"ApprovalAdmin.js","sourceRoot":"","sources":["../../../../src/webparts/approvalAdmin/components/ApprovalAdmin.tsx"],"names":[],"mappings":";;;AAAA,mDAA+B;AAC/B,kGAAiD;AAEjD,gEAAqD;AAErD;IAA2C,yCAAoC;IAA/E;;IAqCA,CAAC;IApCQ,8BAAM,GAAb;QACQ,IAAA,KAMF,IAAI,CAAC,KAAK,EALZ,WAAW,iBAAA,EACX,WAAW,iBAAA,EACX,kBAAkB,wBAAA,EAClB,eAAe,qBAAA,EACf,eAAe,qBACH,CAAC;QAEf,OAAO,CACL,iCAAS,SAAS,EAAE,UAAG,mCAAM,CAAC,aAAa,cAAI,eAAe,CAAC,CAAC,CAAC,mCAAM,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAE;YAClF,6BAAK,SAAS,EAAE,mCAAM,CAAC,OAAO;gBAC5B,6BAAK,GAAG,EAAC,EAAE,EAAC,GAAG,EAAE,WAAW,CAAC,CAAC,CAAC,OAAO,CAAC,4BAA4B,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,6BAA6B,CAAC,EAAE,SAAS,EAAE,mCAAM,CAAC,YAAY,GAAI;gBACjJ;;oBAAgB,IAAA,yBAAM,EAAC,eAAe,CAAC;wBAAO;gBAC9C,iCAAM,kBAAkB,CAAO;gBAC/B;;oBAA8B,oCAAS,IAAA,yBAAM,EAAC,WAAW,CAAC,CAAU,CAAM,CACtE;YACN;gBACE,iEAAuC;gBACvC,2QAEI;gBACJ,qEAA2C;gBAC3C,4BAAI,SAAS,EAAE,mCAAM,CAAC,KAAK;oBACzB;wBAAI,2BAAG,IAAI,EAAC,qBAAqB,EAAC,MAAM,EAAC,QAAQ,EAAC,GAAG,EAAC,YAAY,oCAAkC,CAAK;oBACzG;wBAAI,2BAAG,IAAI,EAAC,kCAAkC,EAAC,MAAM,EAAC,QAAQ,EAAC,GAAG,EAAC,YAAY,2CAAyC,CAAK;oBAC7H;wBAAI,2BAAG,IAAI,EAAC,kCAAkC,EAAC,MAAM,EAAC,QAAQ,EAAC,GAAG,EAAC,YAAY,2DAAyD,CAAK;oBAC7I;wBAAI,2BAAG,IAAI,EAAC,iCAAiC,EAAC,MAAM,EAAC,QAAQ,EAAC,GAAG,EAAC,YAAY,sEAAoE,CAAK;oBACvJ;wBAAI,2BAAG,IAAI,EAAC,kCAAkC,EAAC,MAAM,EAAC,QAAQ,EAAC,GAAG,EAAC,YAAY,mEAAiE,CAAK;oBACrJ;wBAAI,2BAAG,IAAI,EAAC,gCAAgC,EAAC,MAAM,EAAC,QAAQ,EAAC,GAAG,EAAC,YAAY,yCAAuC,CAAK;oBACzH;wBAAI,2BAAG,IAAI,EAAC,wBAAwB,EAAC,MAAM,EAAC,QAAQ,EAAC,GAAG,EAAC,YAAY,wCAAsC,CAAK,CAC7G,CACD,CACE,CACX,CAAC;IACJ,CAAC;IACH,oBAAC;AAAD,CAAC,AArCD,CAA2C,KAAK,CAAC,SAAS,GAqCzD","sourcesContent":["import * as React from 'react';\r\nimport styles from './ApprovalAdmin.module.scss';\r\nimport type { IApprovalAdminProps } from './IApprovalAdminProps';\r\nimport { escape } from '@microsoft/sp-lodash-subset';\r\n\r\nexport default class ApprovalAdmin extends React.Component<IApprovalAdminProps> {\r\n public render(): React.ReactElement<IApprovalAdminProps> {\r\n const {\r\n description,\r\n isDarkTheme,\r\n environmentMessage,\r\n hasTeamsContext,\r\n userDisplayName\r\n } = this.props;\r\n\r\n return (\r\n <section className={`${styles.approvalAdmin} ${hasTeamsContext ? styles.teams : ''}`}>\r\n <div className={styles.welcome}>\r\n <img alt=\"\" src={isDarkTheme ? require('../assets/welcome-dark.png') : require('../assets/welcome-light.png')} className={styles.welcomeImage} />\r\n <h2>Well done, {escape(userDisplayName)}!</h2>\r\n <div>{environmentMessage}</div>\r\n <div>Web part property value: <strong>{escape(description)}</strong></div>\r\n </div>\r\n <div>\r\n <h3>Welcome to SharePoint Framerk!</h3>\r\n <p>\r\n The SharePoint Framework (SPFx) is a extensibility model for Microsoft Viva, Microsoft Teams and SharePoint. It&#39;s the easiest way to extend Microsoft 365 with automatic Single Sign On, automatic hosting and industry standard tooling.\r\n </p>\r\n <h4>Learn more about SPFx development:</h4>\r\n <ul className={styles.links}>\r\n <li><a href=\"https://aka.ms/spfx\" target=\"_blank\" rel=\"noreferrer\">SharePoint Framework Overview</a></li>\r\n <li><a href=\"https://aka.ms/spfx-yeoman-graph\" target=\"_blank\" rel=\"noreferrer\">Use Microsoft Graph in your solution</a></li>\r\n <li><a href=\"https://aka.ms/spfx-yeoman-teams\" target=\"_blank\" rel=\"noreferrer\">Build for Microsoft Teams using SharePoint Framework</a></li>\r\n <li><a href=\"https://aka.ms/spfx-yeoman-viva\" target=\"_blank\" rel=\"noreferrer\">Build for Microsoft Viva Connections using SharePoint Framework</a></li>\r\n <li><a href=\"https://aka.ms/spfx-yeoman-store\" target=\"_blank\" rel=\"noreferrer\">Publish SharePoint Framework applications to the marketplace</a></li>\r\n <li><a href=\"https://aka.ms/spfx-yeoman-api\" target=\"_blank\" rel=\"noreferrer\">SharePoint Framework API reference</a></li>\r\n <li><a href=\"https://aka.ms/m365pnp\" target=\"_blank\" rel=\"noreferrer\">Microsoft 365 Developer Community</a></li>\r\n </ul>\r\n </div>\r\n </section>\r\n );\r\n }\r\n}\r\n"]}
{"version":3,"file":"ApprovalAdmin.js","sourceRoot":"","sources":["../../../../src/webparts/approvalAdmin/components/ApprovalAdmin.tsx"],"names":[],"mappings":";;;AAAA,mDAA+B;AAC/B,kGAAiD;AAEjD,2CAA0C;AAC1C,uCAAmD;AACnD,qDAAyF;AAEzF,IAAM,aAAa,GAAG,2BAA2B,CAAC;AAElD,SAAS,iBAAiB;IACxB,IAAI,OAAO,QAAQ,KAAK,WAAW,IAAI,QAAQ,CAAC,cAAc,CAAC,aAAa,CAAC;QAAE,OAAO;IACtF,IAAM,IAAI,GAAG,QAAQ,CAAC,aAAa,CAAC,MAAM,CAAC,CAAC;IAC5C,IAAI,CAAC,EAAE,GAAG,aAAa,CAAC;IACxB,IAAI,CAAC,GAAG,GAAG,YAAY,CAAC;IACxB,IAAI,CAAC,IAAI,GAAG,+GAA+G,CAAC;IAC5H,QAAQ,CAAC,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC;AAClC,CAAC;AAED,SAAe,gBAAgB,CAAC,KAA0B;;;;;;oBACxD,IAAI,KAAK,CAAC,UAAU,EAAE,CAAC;wBACrB,sBAAO,IAAI,6BAAgB,CAAC,KAAK,CAAC,UAAU,EAAE,KAAK,CAAC,YAAY,IAAI,aAAa,CAAC,EAAC;oBACrF,CAAC;oBACiB,qBAAM,KAAK,CAAC,OAAO,CAAC,oBAAoB,CAAC,SAAS,CAAC,KAAK,CAAC,aAAa,CAAC,EAAA;;oBAAnF,SAAS,GAAG,SAAuE;oBACzF,sBAAO,IAAI,6BAAgB,CAAC,SAAS,EAAE,KAAK,CAAC,UAAU,CAAC,EAAC;;;;CAC1D;AAED,IAAM,aAAa,GAAkC,UAAA,KAAK;IAClD,IAAA,KAAgB,KAAK,CAAC,QAAQ,EAAgC,EAA7D,GAAG,QAAA,EAAE,MAAM,QAAkD,CAAC;IAC/D,IAAA,KAAoB,KAAK,CAAC,QAAQ,EAAsB,EAAvD,KAAK,QAAA,EAAE,QAAQ,QAAwC,CAAC;IAE/D,KAAK,CAAC,SAAS,CAAC;QACd,iBAAiB,EAAE,CAAC;IACtB,CAAC,EAAE,EAAE,CAAC,CAAC;IAEP,KAAK,CAAC,SAAS,CAAC;QACd,IAAI,SAAS,GAAG,KAAK,CAAC;QACtB,MAAM,CAAC,SAAS,CAAC,CAAC;QAClB,QAAQ,CAAC,SAAS,CAAC,CAAC;QAEpB,gBAAgB,CAAC,KAAK,CAAC;aACpB,IAAI,CAAC,UAAA,MAAM,IAAM,IAAI,CAAC,SAAS;YAAE,MAAM,CAAC,IAAI,sBAAgB,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;aACzE,KAAK,CAAC,UAAC,CAAQ,IAAO,IAAI,CAAC,SAAS;YAAE,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QAEjE,OAAO,cAAQ,SAAS,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC;QACnC,uDAAuD;IACzD,CAAC,EAAE,CAAC,KAAK,CAAC,UAAU,EAAE,KAAK,CAAC,UAAU,EAAE,KAAK,CAAC,YAAY,EAAE,KAAK,CAAC,aAAa,CAAC,CAAC,CAAC;IAElF,OAAO,CACL,iCAAS,SAAS,EAAE,mCAAM,CAAC,aAAa;QACrC,KAAK,IAAI,CACR,6BAAK,SAAS,EAAE,mCAAM,CAAC,QAAQ;;YACG,KAAK;YACrC,+BAAM;wXAEF,CACP;QACA,CAAC,KAAK,IAAI,CAAC,GAAG,IAAI,6BAAK,SAAS,EAAE,mCAAM,CAAC,QAAQ,uFAA4B;QAC7E,GAAG,IAAI,oBAAC,mBAAQ,IAAC,GAAG,EAAE,GAAG,EAAE,gBAAgB,EAAE,KAAK,CAAC,OAAO,CAAC,WAAW,CAAC,IAAI,CAAC,KAAK,GAAI,CAC9E,CACX,CAAC;AACJ,CAAC,CAAC;AAEF,kBAAe,aAAa,CAAC","sourcesContent":["import * as React from 'react';\nimport styles from './ApprovalAdmin.module.scss';\nimport type { IApprovalAdminProps } from './IApprovalAdminProps';\nimport { AdminApp } from './app/AdminApp';\nimport { ApprovalRouteApi } from '../services/api';\nimport { AadApiHttpClient, DevApiHttpClient, IHttpClient } from '../services/httpClient';\n\nconst FONTS_LINK_ID = 'tnsu-approval-admin-fonts';\n\nfunction ensureFontsLoaded(): void {\n if (typeof document === 'undefined' || document.getElementById(FONTS_LINK_ID)) return;\n const link = document.createElement('link');\n link.id = FONTS_LINK_ID;\n link.rel = 'stylesheet';\n link.href = 'https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=Lora:wght@500;600;700&display=swap';\n document.head.appendChild(link);\n}\n\nasync function createHttpClient(props: IApprovalAdminProps): Promise<IHttpClient> {\n if (props.useDevAuth) {\n return new DevApiHttpClient(props.apiBaseUrl, props.devUserEmail || 'dev@tnsu.kz');\n }\n const aadClient = await props.context.aadHttpClientFactory.getClient(props.aadResourceId);\n return new AadApiHttpClient(aadClient, props.apiBaseUrl);\n}\n\nconst ApprovalAdmin: React.FC<IApprovalAdminProps> = props => {\n const [api, setApi] = React.useState<ApprovalRouteApi | undefined>();\n const [error, setError] = React.useState<string | undefined>();\n\n React.useEffect(() => {\n ensureFontsLoaded();\n }, []);\n\n React.useEffect(() => {\n let cancelled = false;\n setApi(undefined);\n setError(undefined);\n\n createHttpClient(props)\n .then(client => { if (!cancelled) setApi(new ApprovalRouteApi(client)); })\n .catch((e: Error) => { if (!cancelled) setError(e.message); });\n\n return () => { cancelled = true; };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [props.useDevAuth, props.apiBaseUrl, props.devUserEmail, props.aadResourceId]);\n\n return (\n <section className={styles.approvalAdmin}>\n {error && (\n <div className={styles.stateBox}>\n Не удалось подключиться к API: {error}\n <br />\n Проверьте свойства веб-части (Api Base Url, режим авторизации) в панели настроек.\n </div>\n )}\n {!error && !api && <div className={styles.stateBox}>Подключение к API...</div>}\n {api && <AdminApp api={api} currentUserEmail={props.context.pageContext.user.email} />}\n </section>\n );\n};\n\nexport default ApprovalAdmin;\n"]}
@@ -1,32 +1,52 @@
/*
Your use of the content in the files referenced here is subject to the terms of the license at http://aka.ms/fabric-assets-license
*/
:root {
--navy-950: #0A1B30;
--navy-900: #0F2440;
--navy-800: #1B3A5C;
--navy-700: #244B73;
--navy-600: #2D5C8A;
--navy-100: #E8EEF4;
--navy-50: #F4F7FA;
--gold-600: #B8881A;
--gold-500: #D4A01E;
--gold-400: #E5B635;
--gold-300: #F0CC5E;
--gold-100: #FDF5E0;
--gold-50: #FEFBF0;
--gray-900: #111827;
--gray-700: #374151;
--gray-500: #6B7280;
--gray-400: #9CA3AF;
--gray-300: #D1D5DB;
--gray-200: #E5E7EB;
--gray-100: #F3F4F6;
--gray-50: #F9FAFB;
--red-500: #DC2626;
--red-50: #FEF2F2;
--green-600: #15803D;
--green-50: #F0FDF4;
--blue-600: #2563EB;
--blue-50: #EFF6FF;
--amber-700: #854D0E;
--sh-1: 0 1px 2px rgba(15, 36, 64, 0.04), 0 1px 3px rgba(15, 36, 64, 0.06);
--sh-2: 0 4px 16px rgba(15, 36, 64, 0.07);
--sh-3: 0 12px 32px rgba(15, 36, 64, 0.1);
--sh-gold: 0 4px 20px rgba(212, 160, 30, 0.18);
--sh-modal: 0 24px 70px rgba(10, 27, 48, 0.25);
--r-sm: 6px;
--r-md: 10px;
--r-lg: 14px;
}
.approvalAdmin {
overflow: hidden;
padding: 1em;
color: "[theme:bodyText, default: #323130]";
color: var(--bodyText);
}
.approvalAdmin.teams {
font-family: "Segoe UI", -apple-system, BlinkMacSystemFont, "Roboto", "Helvetica Neue", sans-serif;
font-family: "Inter", -apple-system, "Segoe UI", sans-serif;
}
.welcome {
.stateBox {
padding: 40px 24px;
text-align: center;
}
.welcomeImage {
width: 100%;
max-width: 420px;
}
.links a {
text-decoration: none;
color: "[theme:link, default:#03787c]";
color: var(--link);
}
.links a:hover {
text-decoration: underline;
color: "[theme:linkHovered, default: #014446]";
color: var(--linkHovered);
color: #6B7280;
font-size: 13.5px;
background: #F9FAFB;
border-radius: 14px;
border: 1px solid #E5E7EB;
}
@@ -1 +1 @@
{"version":3,"file":"IApprovalAdminProps.js","sourceRoot":"","sources":["../../../../src/webparts/approvalAdmin/components/IApprovalAdminProps.ts"],"names":[],"mappings":"","sourcesContent":["export interface IApprovalAdminProps {\r\n description: string;\r\n isDarkTheme: boolean;\r\n environmentMessage: string;\r\n hasTeamsContext: boolean;\r\n userDisplayName: string;\r\n}\r\n"]}
{"version":3,"file":"IApprovalAdminProps.js","sourceRoot":"","sources":["../../../../src/webparts/approvalAdmin/components/IApprovalAdminProps.ts"],"names":[],"mappings":"","sourcesContent":["import { WebPartContext } from '@microsoft/sp-webpart-base';\n\nexport interface IApprovalAdminProps {\n context: WebPartContext;\n apiBaseUrl: string;\n useDevAuth: boolean;\n devUserEmail: string;\n aadResourceId: string;\n}\n"]}
@@ -0,0 +1,38 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.AdminApp = void 0;
var tslib_1 = require("tslib");
var React = tslib_1.__importStar(require("react"));
var AdminApp_module_scss_1 = tslib_1.__importDefault(require("./AdminApp.module.scss"));
var TopNav_1 = require("./TopNav");
var DashboardScreen_1 = require("../screens/DashboardScreen");
var RequestTypesScreen_1 = require("../screens/RequestTypesScreen");
var RoutesScreen_1 = require("../screens/RoutesScreen");
var RouteStepsScreen_1 = require("../screens/RouteStepsScreen");
var ResolveScreen_1 = require("../screens/ResolveScreen");
var AdminApp = function (_a) {
var api = _a.api, currentUserEmail = _a.currentUserEmail;
var _b = React.useState('dashboard'), screen = _b[0], setScreen = _b[1];
var _c = React.useState(), selectedRoute = _c[0], setSelectedRoute = _c[1];
var _d = React.useState(''), selectedRequestTypeName = _d[0], setSelectedRequestTypeName = _d[1];
var navigate = function (key) {
setScreen(key);
};
var openSteps = function (route) {
setSelectedRoute(route);
api.requestTypes.get(route.requestTypeId)
.then(function (rt) { return setSelectedRequestTypeName(rt.name); })
.catch(function () { return setSelectedRequestTypeName("#".concat(route.requestTypeId)); });
setScreen('routeSteps');
};
return (React.createElement("div", { className: AdminApp_module_scss_1.default.shell },
React.createElement(TopNav_1.TopNav, { active: screen, onSelect: navigate }),
React.createElement("div", { className: AdminApp_module_scss_1.default.content },
screen === 'dashboard' && React.createElement(DashboardScreen_1.DashboardScreen, { api: api, onNavigate: navigate }),
screen === 'requestTypes' && React.createElement(RequestTypesScreen_1.RequestTypesScreen, { api: api }),
screen === 'routes' && React.createElement(RoutesScreen_1.RoutesScreen, { api: api, onOpenSteps: openSteps }),
screen === 'routeSteps' && selectedRoute && (React.createElement(RouteStepsScreen_1.RouteStepsScreen, { api: api, route: selectedRoute, requestTypeName: selectedRequestTypeName, onBack: function () { return setScreen('routes'); } })),
screen === 'resolve' && React.createElement(ResolveScreen_1.ResolveScreen, { api: api, currentUserEmail: currentUserEmail }))));
};
exports.AdminApp = AdminApp;
//# sourceMappingURL=AdminApp.js.map
@@ -0,0 +1 @@
{"version":3,"file":"AdminApp.js","sourceRoot":"","sources":["../../../../../src/webparts/approvalAdmin/components/app/AdminApp.tsx"],"names":[],"mappings":";;;;AAAA,mDAA+B;AAC/B,wFAA4C;AAC5C,mCAAkC;AAIlC,8DAA6D;AAC7D,oEAAmE;AACnE,wDAAuD;AACvD,gEAA+D;AAC/D,0DAAyD;AAOlD,IAAM,QAAQ,GAA6B,UAAC,EAAyB;QAAvB,GAAG,SAAA,EAAE,gBAAgB,sBAAA;IAClE,IAAA,KAAsB,KAAK,CAAC,QAAQ,CAAY,WAAW,CAAC,EAA3D,MAAM,QAAA,EAAE,SAAS,QAA0C,CAAC;IAC7D,IAAA,KAAoC,KAAK,CAAC,QAAQ,EAA6B,EAA9E,aAAa,QAAA,EAAE,gBAAgB,QAA+C,CAAC;IAChF,IAAA,KAAwD,KAAK,CAAC,QAAQ,CAAC,EAAE,CAAC,EAAzE,uBAAuB,QAAA,EAAE,0BAA0B,QAAsB,CAAC;IAEjF,IAAM,QAAQ,GAAG,UAAC,GAAc;QAC9B,SAAS,CAAC,GAAG,CAAC,CAAC;IACjB,CAAC,CAAC;IAEF,IAAM,SAAS,GAAG,UAAC,KAAoB;QACrC,gBAAgB,CAAC,KAAK,CAAC,CAAC;QACxB,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,KAAK,CAAC,aAAa,CAAC;aACtC,IAAI,CAAC,UAAA,EAAE,IAAI,OAAA,0BAA0B,CAAC,EAAE,CAAC,IAAI,CAAC,EAAnC,CAAmC,CAAC;aAC/C,KAAK,CAAC,cAAM,OAAA,0BAA0B,CAAC,WAAI,KAAK,CAAC,aAAa,CAAE,CAAC,EAArD,CAAqD,CAAC,CAAC;QACtE,SAAS,CAAC,YAAY,CAAC,CAAC;IAC1B,CAAC,CAAC;IAEF,OAAO,CACL,6BAAK,SAAS,EAAE,8BAAM,CAAC,KAAK;QAC1B,oBAAC,eAAM,IAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,QAAQ,GAAI;QAC9C,6BAAK,SAAS,EAAE,8BAAM,CAAC,OAAO;YAC3B,MAAM,KAAK,WAAW,IAAI,oBAAC,iCAAe,IAAC,GAAG,EAAE,GAAG,EAAE,UAAU,EAAE,QAAQ,GAAI;YAC7E,MAAM,KAAK,cAAc,IAAI,oBAAC,uCAAkB,IAAC,GAAG,EAAE,GAAG,GAAI;YAC7D,MAAM,KAAK,QAAQ,IAAI,oBAAC,2BAAY,IAAC,GAAG,EAAE,GAAG,EAAE,WAAW,EAAE,SAAS,GAAI;YACzE,MAAM,KAAK,YAAY,IAAI,aAAa,IAAI,CAC3C,oBAAC,mCAAgB,IACf,GAAG,EAAE,GAAG,EACR,KAAK,EAAE,aAAa,EACpB,eAAe,EAAE,uBAAuB,EACxC,MAAM,EAAE,cAAM,OAAA,SAAS,CAAC,QAAQ,CAAC,EAAnB,CAAmB,GACjC,CACH;YACA,MAAM,KAAK,SAAS,IAAI,oBAAC,6BAAa,IAAC,GAAG,EAAE,GAAG,EAAE,gBAAgB,EAAE,gBAAgB,GAAI,CACpF,CACF,CACP,CAAC;AACJ,CAAC,CAAC;AApCW,QAAA,QAAQ,YAoCnB","sourcesContent":["import * as React from 'react';\nimport styles from './AdminApp.module.scss';\nimport { TopNav } from './TopNav';\nimport { ScreenKey } from './types';\nimport { ApprovalRouteApi } from '../../services/api';\nimport { TemplateRoute } from '../../models';\nimport { DashboardScreen } from '../screens/DashboardScreen';\nimport { RequestTypesScreen } from '../screens/RequestTypesScreen';\nimport { RoutesScreen } from '../screens/RoutesScreen';\nimport { RouteStepsScreen } from '../screens/RouteStepsScreen';\nimport { ResolveScreen } from '../screens/ResolveScreen';\n\nexport interface IAdminAppProps {\n api: ApprovalRouteApi;\n currentUserEmail?: string;\n}\n\nexport const AdminApp: React.FC<IAdminAppProps> = ({ api, currentUserEmail }) => {\n const [screen, setScreen] = React.useState<ScreenKey>('dashboard');\n const [selectedRoute, setSelectedRoute] = React.useState<TemplateRoute | undefined>();\n const [selectedRequestTypeName, setSelectedRequestTypeName] = React.useState('');\n\n const navigate = (key: ScreenKey): void => {\n setScreen(key);\n };\n\n const openSteps = (route: TemplateRoute): void => {\n setSelectedRoute(route);\n api.requestTypes.get(route.requestTypeId)\n .then(rt => setSelectedRequestTypeName(rt.name))\n .catch(() => setSelectedRequestTypeName(`#${route.requestTypeId}`));\n setScreen('routeSteps');\n };\n\n return (\n <div className={styles.shell}>\n <TopNav active={screen} onSelect={navigate} />\n <div className={styles.content}>\n {screen === 'dashboard' && <DashboardScreen api={api} onNavigate={navigate} />}\n {screen === 'requestTypes' && <RequestTypesScreen api={api} />}\n {screen === 'routes' && <RoutesScreen api={api} onOpenSteps={openSteps} />}\n {screen === 'routeSteps' && selectedRoute && (\n <RouteStepsScreen\n api={api}\n route={selectedRoute}\n requestTypeName={selectedRequestTypeName}\n onBack={() => setScreen('routes')}\n />\n )}\n {screen === 'resolve' && <ResolveScreen api={api} currentUserEmail={currentUserEmail} />}\n </div>\n </div>\n );\n};\n"]}
@@ -0,0 +1,63 @@
:root {
--navy-950: #0A1B30;
--navy-900: #0F2440;
--navy-800: #1B3A5C;
--navy-700: #244B73;
--navy-600: #2D5C8A;
--navy-100: #E8EEF4;
--navy-50: #F4F7FA;
--gold-600: #B8881A;
--gold-500: #D4A01E;
--gold-400: #E5B635;
--gold-300: #F0CC5E;
--gold-100: #FDF5E0;
--gold-50: #FEFBF0;
--gray-900: #111827;
--gray-700: #374151;
--gray-500: #6B7280;
--gray-400: #9CA3AF;
--gray-300: #D1D5DB;
--gray-200: #E5E7EB;
--gray-100: #F3F4F6;
--gray-50: #F9FAFB;
--red-500: #DC2626;
--red-50: #FEF2F2;
--green-600: #15803D;
--green-50: #F0FDF4;
--blue-600: #2563EB;
--blue-50: #EFF6FF;
--amber-700: #854D0E;
--sh-1: 0 1px 2px rgba(15, 36, 64, 0.04), 0 1px 3px rgba(15, 36, 64, 0.06);
--sh-2: 0 4px 16px rgba(15, 36, 64, 0.07);
--sh-3: 0 12px 32px rgba(15, 36, 64, 0.1);
--sh-gold: 0 4px 20px rgba(212, 160, 30, 0.18);
--sh-modal: 0 24px 70px rgba(10, 27, 48, 0.25);
--r-sm: 6px;
--r-md: 10px;
--r-lg: 14px;
}
.shell {
background: linear-gradient(180deg, #F6F8FA 0%, #EEF2F6 100%);
border-radius: 14px;
border: 1px solid #E5E7EB;
overflow: hidden;
box-shadow: 0 1px 2px rgba(15, 36, 64, 0.04), 0 1px 3px rgba(15, 36, 64, 0.06);
color: #111827;
font-family: "Inter", -apple-system, "Segoe UI", sans-serif;
font-size: 14px;
line-height: 1.55;
}
.content {
padding: 28px 30px 36px;
max-width: 1080px;
margin: 0 auto;
}
.centerState {
padding: 60px 30px;
text-align: center;
color: #9CA3AF;
font-size: 13.5px;
}
@@ -0,0 +1,24 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.TopNav = void 0;
var tslib_1 = require("tslib");
var React = tslib_1.__importStar(require("react"));
var TopNav_module_scss_1 = tslib_1.__importDefault(require("./TopNav.module.scss"));
var TABS = [
{ key: 'dashboard', label: 'Дашборд' },
{ key: 'requestTypes', label: 'Типы заявок' },
{ key: 'routes', label: 'Маршруты' },
{ key: 'resolve', label: 'Проверка маршрута' }
];
var TopNav = function (_a) {
var active = _a.active, onSelect = _a.onSelect;
return (React.createElement("nav", { className: TopNav_module_scss_1.default.topnav },
React.createElement("div", { className: TopNav_module_scss_1.default.brand },
React.createElement("div", { className: TopNav_module_scss_1.default.mark }, "T"),
React.createElement("div", { className: TopNav_module_scss_1.default.brandText },
"TNSU ",
React.createElement("em", null, "Approval Route"))),
React.createElement("div", { className: TopNav_module_scss_1.default.tabs }, TABS.map(function (tab) { return (React.createElement("button", { key: tab.key, className: "".concat(TopNav_module_scss_1.default.tab, " ").concat((active === tab.key || (active === 'routeSteps' && tab.key === 'routes')) ? TopNav_module_scss_1.default.on : ''), onClick: function () { return onSelect(tab.key); } }, tab.label)); }))));
};
exports.TopNav = TopNav;
//# sourceMappingURL=TopNav.js.map
@@ -0,0 +1 @@
{"version":3,"file":"TopNav.js","sourceRoot":"","sources":["../../../../../src/webparts/approvalAdmin/components/app/TopNav.tsx"],"names":[],"mappings":";;;;AAAA,mDAA+B;AAC/B,oFAA0C;AAQ1C,IAAM,IAAI,GAAW;IACnB,EAAE,GAAG,EAAE,WAAW,EAAE,KAAK,EAAE,SAAS,EAAE;IACtC,EAAE,GAAG,EAAE,cAAc,EAAE,KAAK,EAAE,aAAa,EAAE;IAC7C,EAAE,GAAG,EAAE,QAAQ,EAAE,KAAK,EAAE,UAAU,EAAE;IACpC,EAAE,GAAG,EAAE,SAAS,EAAE,KAAK,EAAE,mBAAmB,EAAE;CAC/C,CAAC;AAOK,IAAM,MAAM,GAA2B,UAAC,EAAoB;QAAlB,MAAM,YAAA,EAAE,QAAQ,cAAA;IAAO,OAAA,CACtE,6BAAK,SAAS,EAAE,4BAAM,CAAC,MAAM;QAC3B,6BAAK,SAAS,EAAE,4BAAM,CAAC,KAAK;YAC1B,6BAAK,SAAS,EAAE,4BAAM,CAAC,IAAI,QAAS;YACpC,6BAAK,SAAS,EAAE,4BAAM,CAAC,SAAS;;gBAAO,iDAAuB,CAAM,CAChE;QACN,6BAAK,SAAS,EAAE,4BAAM,CAAC,IAAI,IACxB,IAAI,CAAC,GAAG,CAAC,UAAA,GAAG,IAAI,OAAA,CACf,gCACE,GAAG,EAAE,GAAG,CAAC,GAAG,EACZ,SAAS,EAAE,UAAG,4BAAM,CAAC,GAAG,cAAI,CAAC,MAAM,KAAK,GAAG,CAAC,GAAG,IAAI,CAAC,MAAM,KAAK,YAAY,IAAI,GAAG,CAAC,GAAG,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,4BAAM,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAE,EACxH,OAAO,EAAE,cAAM,OAAA,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC,EAAjB,CAAiB,IAE/B,GAAG,CAAC,KAAK,CACH,CACV,EARgB,CAQhB,CAAC,CACE,CACF,CACP;AAlBuE,CAkBvE,CAAC;AAlBW,QAAA,MAAM,UAkBjB","sourcesContent":["import * as React from 'react';\nimport styles from './TopNav.module.scss';\nimport { ScreenKey } from './types';\n\ninterface ITab {\n key: ScreenKey;\n label: string;\n}\n\nconst TABS: ITab[] = [\n { key: 'dashboard', label: 'Дашборд' },\n { key: 'requestTypes', label: 'Типы заявок' },\n { key: 'routes', label: 'Маршруты' },\n { key: 'resolve', label: 'Проверка маршрута' }\n];\n\nexport interface ITopNavProps {\n active: ScreenKey;\n onSelect: (key: ScreenKey) => void;\n}\n\nexport const TopNav: React.FC<ITopNavProps> = ({ active, onSelect }) => (\n <nav className={styles.topnav}>\n <div className={styles.brand}>\n <div className={styles.mark}>T</div>\n <div className={styles.brandText}>TNSU <em>Approval Route</em></div>\n </div>\n <div className={styles.tabs}>\n {TABS.map(tab => (\n <button\n key={tab.key}\n className={`${styles.tab} ${(active === tab.key || (active === 'routeSteps' && tab.key === 'routes')) ? styles.on : ''}`}\n onClick={() => onSelect(tab.key)}\n >\n {tab.label}\n </button>\n ))}\n </div>\n </nav>\n);\n"]}
@@ -0,0 +1,112 @@
:root {
--navy-950: #0A1B30;
--navy-900: #0F2440;
--navy-800: #1B3A5C;
--navy-700: #244B73;
--navy-600: #2D5C8A;
--navy-100: #E8EEF4;
--navy-50: #F4F7FA;
--gold-600: #B8881A;
--gold-500: #D4A01E;
--gold-400: #E5B635;
--gold-300: #F0CC5E;
--gold-100: #FDF5E0;
--gold-50: #FEFBF0;
--gray-900: #111827;
--gray-700: #374151;
--gray-500: #6B7280;
--gray-400: #9CA3AF;
--gray-300: #D1D5DB;
--gray-200: #E5E7EB;
--gray-100: #F3F4F6;
--gray-50: #F9FAFB;
--red-500: #DC2626;
--red-50: #FEF2F2;
--green-600: #15803D;
--green-50: #F0FDF4;
--blue-600: #2563EB;
--blue-50: #EFF6FF;
--amber-700: #854D0E;
--sh-1: 0 1px 2px rgba(15, 36, 64, 0.04), 0 1px 3px rgba(15, 36, 64, 0.06);
--sh-2: 0 4px 16px rgba(15, 36, 64, 0.07);
--sh-3: 0 12px 32px rgba(15, 36, 64, 0.1);
--sh-gold: 0 4px 20px rgba(212, 160, 30, 0.18);
--sh-modal: 0 24px 70px rgba(10, 27, 48, 0.25);
--r-sm: 6px;
--r-md: 10px;
--r-lg: 14px;
}
.topnav {
background: linear-gradient(135deg, #0A1B30 0%, #1B3A5C 85%);
padding: 0 22px;
display: flex;
align-items: center;
height: 54px;
box-shadow: 0 1px 0 rgba(212, 160, 30, 0.35);
border-radius: 10px 10px 0 0;
flex-wrap: wrap;
}
.brand {
display: flex;
align-items: center;
gap: 10px;
margin-right: 28px;
flex-shrink: 0;
}
.mark {
width: 30px;
height: 30px;
border-radius: 7px;
background: linear-gradient(180deg, #E5B635, #B8881A);
display: flex;
align-items: center;
justify-content: center;
color: #0A1B30;
font-weight: 800;
font-size: 13px;
font-family: "Lora", Georgia, serif;
}
.brandText {
color: #fff;
font-weight: 600;
font-size: 13px;
letter-spacing: 0.4px;
white-space: nowrap;
}
.brandText em {
font-style: normal;
color: #E5B635;
}
.tabs {
display: flex;
height: 100%;
flex-wrap: wrap;
}
.tab {
padding: 0 16px;
border: none;
background: 0;
color: rgba(255, 255, 255, 0.5);
font-size: 13px;
font-weight: 500;
cursor: pointer;
font-family: inherit;
transition: 0.18s;
height: 54px;
border-bottom: 2px solid transparent;
letter-spacing: 0.2px;
}
.tab:hover {
color: rgba(255, 255, 255, 0.85);
}
.tab.on {
color: #F0CC5E;
border-bottom-color: #E5B635;
}
@@ -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/approvalAdmin/components/app/types.ts"],"names":[],"mappings":"","sourcesContent":["export type ScreenKey = 'dashboard' | 'requestTypes' | 'routes' | 'routeSteps' | 'resolve';\n"]}
@@ -0,0 +1,27 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.DashboardScreen = void 0;
var tslib_1 = require("tslib");
var React = tslib_1.__importStar(require("react"));
var ui_1 = require("../ui");
var DashboardScreen = function (_a) {
var api = _a.api, onNavigate = _a.onNavigate;
var _b = React.useState(), requestTypesCount = _b[0], setRequestTypesCount = _b[1];
var _c = React.useState(), routesCount = _c[0], setRoutesCount = _c[1];
React.useEffect(function () {
api.requestTypes.list().then(function (items) { return setRequestTypesCount(items.length); }).catch(function () { return setRequestTypesCount(undefined); });
api.templateRoutes.list().then(function (items) { return setRoutesCount(items.length); }).catch(function () { return setRoutesCount(undefined); });
}, [api]);
return (React.createElement("div", null,
React.createElement(ui_1.StatRow, null,
React.createElement(ui_1.StatCard, { label: "\u0422\u0438\u043F\u043E\u0432 \u0437\u0430\u044F\u0432\u043E\u043A", value: requestTypesCount !== null && requestTypesCount !== void 0 ? requestTypesCount : '—' }),
React.createElement(ui_1.StatCard, { label: "\u041C\u0430\u0440\u0448\u0440\u0443\u0442\u043E\u0432 \u0441\u043E\u0433\u043B\u0430\u0441\u043E\u0432\u0430\u043D\u0438\u044F", value: routesCount !== null && routesCount !== void 0 ? routesCount : '—' })),
React.createElement("div", { style: { fontSize: 10, textTransform: 'uppercase', letterSpacing: 1.6, color: 'var(--gray-400)', fontWeight: 600, marginBottom: 12 } }, "\u0420\u0430\u0437\u0434\u0435\u043B\u044B"),
React.createElement("hr", { style: { height: 1, background: 'linear-gradient(90deg, var(--gold-400), transparent 70%)', border: 0, margin: '6px 0 22px' } }),
React.createElement(ui_1.CardGrid, null,
React.createElement(ui_1.NavCard, { icon: "clipboard", title: "\u0422\u0438\u043F\u044B \u0437\u0430\u044F\u0432\u043E\u043A", description: "\u0421\u043F\u0440\u0430\u0432\u043E\u0447\u043D\u0438\u043A \u0442\u0438\u043F\u043E\u0432 \u0437\u0430\u044F\u0432\u043E\u043A \u0438 \u0438\u0445 \u0441\u0442\u0440\u0430\u043D\u0438\u0446", onClick: function () { return onNavigate('requestTypes'); } }),
React.createElement(ui_1.NavCard, { icon: "route", title: "\u041C\u0430\u0440\u0448\u0440\u0443\u0442\u044B \u0441\u043E\u0433\u043B\u0430\u0441\u043E\u0432\u0430\u043D\u0438\u044F", description: "\u041C\u0430\u0440\u0448\u0440\u0443\u0442\u044B, \u0448\u0430\u0433\u0438 \u0438 \u0438\u0441\u043F\u043E\u043B\u043D\u0438\u0442\u0435\u043B\u0438", onClick: function () { return onNavigate('routes'); } }),
React.createElement(ui_1.NavCard, { icon: "search", title: "\u041F\u0440\u043E\u0432\u0435\u0440\u043A\u0430 \u043C\u0430\u0440\u0448\u0440\u0443\u0442\u0430", description: "\u041A\u0430\u043A \u0440\u0430\u0437\u0440\u0435\u0448\u0438\u0442\u0441\u044F \u043C\u0430\u0440\u0448\u0440\u0443\u0442 \u0434\u043B\u044F \u043A\u043E\u043D\u043A\u0440\u0435\u0442\u043D\u043E\u0433\u043E \u043F\u043E\u043B\u044C\u0437\u043E\u0432\u0430\u0442\u0435\u043B\u044F", onClick: function () { return onNavigate('resolve'); } }))));
};
exports.DashboardScreen = DashboardScreen;
//# sourceMappingURL=DashboardScreen.js.map
@@ -0,0 +1 @@
{"version":3,"file":"DashboardScreen.js","sourceRoot":"","sources":["../../../../../src/webparts/approvalAdmin/components/screens/DashboardScreen.tsx"],"names":[],"mappings":";;;;AAAA,mDAA+B;AAG/B,4BAA6D;AAOtD,IAAM,eAAe,GAAoC,UAAC,EAAmB;QAAjB,GAAG,SAAA,EAAE,UAAU,gBAAA;IAC1E,IAAA,KAA4C,KAAK,CAAC,QAAQ,EAAsB,EAA/E,iBAAiB,QAAA,EAAE,oBAAoB,QAAwC,CAAC;IACjF,IAAA,KAAgC,KAAK,CAAC,QAAQ,EAAsB,EAAnE,WAAW,QAAA,EAAE,cAAc,QAAwC,CAAC;IAE3E,KAAK,CAAC,SAAS,CAAC;QACd,GAAG,CAAC,YAAY,CAAC,IAAI,EAAE,CAAC,IAAI,CAAC,UAAA,KAAK,IAAI,OAAA,oBAAoB,CAAC,KAAK,CAAC,MAAM,CAAC,EAAlC,CAAkC,CAAC,CAAC,KAAK,CAAC,cAAM,OAAA,oBAAoB,CAAC,SAAS,CAAC,EAA/B,CAA+B,CAAC,CAAC;QACvH,GAAG,CAAC,cAAc,CAAC,IAAI,EAAE,CAAC,IAAI,CAAC,UAAA,KAAK,IAAI,OAAA,cAAc,CAAC,KAAK,CAAC,MAAM,CAAC,EAA5B,CAA4B,CAAC,CAAC,KAAK,CAAC,cAAM,OAAA,cAAc,CAAC,SAAS,CAAC,EAAzB,CAAyB,CAAC,CAAC;IAC/G,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;IAEV,OAAO,CACL;QACE,oBAAC,YAAO;YACN,oBAAC,aAAQ,IAAC,KAAK,EAAC,qEAAc,EAAC,KAAK,EAAE,iBAAiB,aAAjB,iBAAiB,cAAjB,iBAAiB,GAAI,GAAG,GAAI;YAClE,oBAAC,aAAQ,IAAC,KAAK,EAAC,iIAAwB,EAAC,KAAK,EAAE,WAAW,aAAX,WAAW,cAAX,WAAW,GAAI,GAAG,GAAI,CAC9D;QAEV,6BAAK,KAAK,EAAE,EAAE,QAAQ,EAAE,EAAE,EAAE,aAAa,EAAE,WAAW,EAAE,aAAa,EAAE,GAAG,EAAE,KAAK,EAAE,iBAAiB,EAAE,UAAU,EAAE,GAAG,EAAE,YAAY,EAAE,EAAE,EAAE,iDAEnI;QACN,4BAAI,KAAK,EAAE,EAAE,MAAM,EAAE,CAAC,EAAE,UAAU,EAAE,0DAA0D,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,YAAY,EAAE,GAAI;QACrI,oBAAC,aAAQ;YACP,oBAAC,YAAO,IACN,IAAI,EAAC,WAAW,EAChB,KAAK,EAAC,+DAAa,EACnB,WAAW,EAAC,iMAAsC,EAClD,OAAO,EAAE,cAAM,OAAA,UAAU,CAAC,cAAc,CAAC,EAA1B,CAA0B,GACzC;YACF,oBAAC,YAAO,IACN,IAAI,EAAC,OAAO,EACZ,KAAK,EAAC,2HAAuB,EAC7B,WAAW,EAAC,sJAA8B,EAC1C,OAAO,EAAE,cAAM,OAAA,UAAU,CAAC,QAAQ,CAAC,EAApB,CAAoB,GACnC;YACF,oBAAC,YAAO,IACN,IAAI,EAAC,QAAQ,EACb,KAAK,EAAC,mGAAmB,EACzB,WAAW,EAAC,2RAAqD,EACjE,OAAO,EAAE,cAAM,OAAA,UAAU,CAAC,SAAS,CAAC,EAArB,CAAqB,GACpC,CACO,CACP,CACP,CAAC;AACJ,CAAC,CAAC;AA1CW,QAAA,eAAe,mBA0C1B","sourcesContent":["import * as React from 'react';\nimport { ApprovalRouteApi } from '../../services/api';\nimport { ScreenKey } from '../app/types';\nimport { CardGrid, NavCard, StatCard, StatRow } from '../ui';\n\nexport interface IDashboardScreenProps {\n api: ApprovalRouteApi;\n onNavigate: (key: ScreenKey) => void;\n}\n\nexport const DashboardScreen: React.FC<IDashboardScreenProps> = ({ api, onNavigate }) => {\n const [requestTypesCount, setRequestTypesCount] = React.useState<number | undefined>();\n const [routesCount, setRoutesCount] = React.useState<number | undefined>();\n\n React.useEffect(() => {\n api.requestTypes.list().then(items => setRequestTypesCount(items.length)).catch(() => setRequestTypesCount(undefined));\n api.templateRoutes.list().then(items => setRoutesCount(items.length)).catch(() => setRoutesCount(undefined));\n }, [api]);\n\n return (\n <div>\n <StatRow>\n <StatCard label=\"Типов заявок\" value={requestTypesCount ?? '—'} />\n <StatCard label=\"Маршрутов согласования\" value={routesCount ?? '—'} />\n </StatRow>\n\n <div style={{ fontSize: 10, textTransform: 'uppercase', letterSpacing: 1.6, color: 'var(--gray-400)', fontWeight: 600, marginBottom: 12 }}>\n Разделы\n </div>\n <hr style={{ height: 1, background: 'linear-gradient(90deg, var(--gold-400), transparent 70%)', border: 0, margin: '6px 0 22px' }} />\n <CardGrid>\n <NavCard\n icon=\"clipboard\"\n title=\"Типы заявок\"\n description=\"Справочник типов заявок и их страниц\"\n onClick={() => onNavigate('requestTypes')}\n />\n <NavCard\n icon=\"route\"\n title=\"Маршруты согласования\"\n description=\"Маршруты, шаги и исполнители\"\n onClick={() => onNavigate('routes')}\n />\n <NavCard\n icon=\"search\"\n title=\"Проверка маршрута\"\n description=\"Как разрешится маршрут для конкретного пользователя\"\n onClick={() => onNavigate('resolve')}\n />\n </CardGrid>\n </div>\n );\n};\n"]}
@@ -0,0 +1,105 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.RequestTypesScreen = void 0;
var tslib_1 = require("tslib");
var React = tslib_1.__importStar(require("react"));
var ui_1 = require("../ui");
var EMPTY_FORM = { name: '', pageUrl: '' };
var RequestTypesScreen = function (_a) {
var api = _a.api;
var _b = React.useState([]), items = _b[0], setItems = _b[1];
var _c = React.useState(true), loading = _c[0], setLoading = _c[1];
var _d = React.useState(), loadError = _d[0], setLoadError = _d[1];
var _e = React.useState(), editing = _e[0], setEditing = _e[1];
var _f = React.useState(false), showForm = _f[0], setShowForm = _f[1];
var _g = React.useState(EMPTY_FORM), form = _g[0], setForm = _g[1];
var _h = React.useState(), formError = _h[0], setFormError = _h[1];
var _j = React.useState(false), saving = _j[0], setSaving = _j[1];
var _k = React.useState(), deleting = _k[0], setDeleting = _k[1];
var _l = React.useState(), deleteError = _l[0], setDeleteError = _l[1];
var _m = React.useState(false), deleteBusy = _m[0], setDeleteBusy = _m[1];
var load = React.useCallback(function () {
setLoading(true);
setLoadError(undefined);
api.requestTypes
.list()
.then(setItems)
.catch(function (e) { return setLoadError(e.message); })
.finally(function () { return setLoading(false); });
}, [api]);
React.useEffect(function () { load(); }, [load]);
var openCreate = function () {
setEditing(undefined);
setForm(EMPTY_FORM);
setFormError(undefined);
setShowForm(true);
};
var openEdit = function (item) {
setEditing(item);
setForm({ name: item.name, pageUrl: item.pageUrl });
setFormError(undefined);
setShowForm(true);
};
var submit = function () {
if (!form.name.trim() || !form.pageUrl.trim()) {
setFormError('Заполните имя и Url страницы.');
return;
}
setSaving(true);
setFormError(undefined);
var request = editing
? api.requestTypes.update(editing.id, { name: form.name.trim(), pageUrl: form.pageUrl.trim() })
: api.requestTypes.create({ name: form.name.trim(), pageUrl: form.pageUrl.trim() });
request
.then(function () {
setShowForm(false);
load();
})
.catch(function (e) { return setFormError(e.message); })
.finally(function () { return setSaving(false); });
};
var confirmDelete = function () {
if (!deleting)
return;
setDeleteBusy(true);
setDeleteError(undefined);
api.requestTypes
.remove(deleting.id)
.then(function () {
setDeleting(undefined);
load();
})
.catch(function (e) { return setDeleteError(e.message); })
.finally(function () { return setDeleteBusy(false); });
};
return (React.createElement("div", null,
React.createElement(ui_1.Breadcrumbs, { items: [{ label: 'Главная' }, { label: 'Типы заявок' }] }),
React.createElement(ui_1.PageHeader, { icon: "clipboard", subtitle: "\u0421\u043F\u0440\u0430\u0432\u043E\u0447\u043D\u0438\u043A", title: "\u0422\u0438\u043F\u044B \u0437\u0430\u044F\u0432\u043E\u043A", actions: React.createElement(ui_1.Button, { variant: "accent", icon: "plus", onClick: openCreate }, "\u0414\u043E\u0431\u0430\u0432\u0438\u0442\u044C \u0442\u0438\u043F") }),
loadError && React.createElement(ui_1.Alert, { kind: "err" }, loadError),
deleteError && React.createElement(ui_1.Alert, { kind: "err" }, deleteError),
React.createElement(ui_1.DataTable, { columns: [
{ key: 'name', header: 'Название', render: function (r) { return React.createElement(ui_1.PrimaryCell, null, r.name); } },
{ key: 'url', header: 'Url страницы', render: function (r) { return r.pageUrl; } },
{
key: 'actions',
header: '',
width: '100px',
align: 'right',
render: function (r) { return (React.createElement(ui_1.ActionsCell, null,
React.createElement(ui_1.IconButton, { icon: "edit", label: "\u0420\u0435\u0434\u0430\u043A\u0442\u0438\u0440\u043E\u0432\u0430\u0442\u044C", onClick: function () { return openEdit(r); } }),
React.createElement(ui_1.IconButton, { icon: "trash", label: "\u0423\u0434\u0430\u043B\u0438\u0442\u044C", danger: true, onClick: function () { setDeleteError(undefined); setDeleting(r); } }))); }
}
], rows: items, keyOf: function (r) { return r.id; }, emptyMessage: loading ? 'Загрузка...' : 'Типов заявок пока нет.' }),
showForm && (React.createElement(ui_1.Modal, { title: editing ? 'Редактировать тип заявки' : 'Новый тип заявки', onClose: function () { return setShowForm(false); }, footer: React.createElement(React.Fragment, null,
React.createElement(ui_1.Button, { variant: "secondary", onClick: function () { return setShowForm(false); }, disabled: saving }, "\u041E\u0442\u043C\u0435\u043D\u0430"),
React.createElement(ui_1.Button, { variant: "accent", icon: "check", onClick: submit, disabled: saving }, saving ? 'Сохранение...' : 'Сохранить')) },
formError && React.createElement(ui_1.Alert, { kind: "err" }, formError),
React.createElement(ui_1.FormGrid, null,
React.createElement(ui_1.Field, { label: "\u041D\u0430\u0437\u0432\u0430\u043D\u0438\u0435", required: true, full: true },
React.createElement(ui_1.TextInput, { value: form.name, onChange: function (e) { return setForm(tslib_1.__assign(tslib_1.__assign({}, form), { name: e.target.value })); }, placeholder: "\u041D\u0430\u043F\u0440\u0438\u043C\u0435\u0440, \u0417\u0430\u044F\u0432\u043A\u0430 \u043D\u0430 \u043A\u043E\u043C\u0430\u043D\u0434\u0438\u0440\u043E\u0432\u043A\u0443" })),
React.createElement(ui_1.Field, { label: "Url \u0441\u0442\u0440\u0430\u043D\u0438\u0446\u044B", required: true, full: true, hint: "\u0421\u0442\u0440\u0430\u043D\u0438\u0446\u0430 \u0444\u043E\u0440\u043C\u044B \u0437\u0430\u044F\u0432\u043A\u0438 \u043D\u0430 \u043F\u043E\u0440\u0442\u0430\u043B\u0435." },
React.createElement(ui_1.TextInput, { value: form.pageUrl, onChange: function (e) { return setForm(tslib_1.__assign(tslib_1.__assign({}, form), { pageUrl: e.target.value })); }, placeholder: "/pages/business-trip" }))))),
deleting && (React.createElement(ui_1.ConfirmDialog, { title: "\u0423\u0434\u0430\u043B\u0438\u0442\u044C \u0442\u0438\u043F \u0437\u0430\u044F\u0432\u043A\u0438?", text: "\u0422\u0438\u043F \u00AB".concat(deleting.name, "\u00BB \u0431\u0443\u0434\u0435\u0442 \u0443\u0434\u0430\u043B\u0451\u043D \u0431\u0435\u0437\u0432\u043E\u0437\u0432\u0440\u0430\u0442\u043D\u043E. \u0421\u0432\u044F\u0437\u0430\u043D\u043D\u044B\u0435 \u043C\u0430\u0440\u0448\u0440\u0443\u0442\u044B \u043C\u043E\u0433\u0443\u0442 \u043F\u0435\u0440\u0435\u0441\u0442\u0430\u0442\u044C \u0440\u0430\u0437\u0440\u0435\u0448\u0430\u0442\u044C\u0441\u044F."), confirmLabel: "\u0423\u0434\u0430\u043B\u0438\u0442\u044C", busy: deleteBusy, onCancel: function () { return setDeleting(undefined); }, onConfirm: confirmDelete }))));
};
exports.RequestTypesScreen = RequestTypesScreen;
//# sourceMappingURL=RequestTypesScreen.js.map
File diff suppressed because one or more lines are too long
@@ -0,0 +1,78 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.ResolveScreen = void 0;
var tslib_1 = require("tslib");
var React = tslib_1.__importStar(require("react"));
var models_1 = require("../../models");
var ui_1 = require("../ui");
var STATUS_OPTIONS = [models_1.RouteStepStatus.Approval, models_1.RouteStepStatus.Execution]
.map(function (v) { return ({ value: v, label: models_1.routeStepStatusLabels[v] }); });
var ResolveScreen = function (_a) {
var api = _a.api, currentUserEmail = _a.currentUserEmail;
var _b = React.useState([]), requestTypes = _b[0], setRequestTypes = _b[1];
var _c = React.useState(''), requestTypeId = _c[0], setRequestTypeId = _c[1];
var _d = React.useState(''), userEmail = _d[0], setUserEmail = _d[1];
var _e = React.useState(''), projectId = _e[0], setProjectId = _e[1];
var _f = React.useState(models_1.RouteStepStatus.Approval), status = _f[0], setStatus = _f[1];
var _g = React.useState(false), busy = _g[0], setBusy = _g[1];
var _h = React.useState(), error = _h[0], setError = _h[1];
var _j = React.useState(), result = _j[0], setResult = _j[1];
React.useEffect(function () {
api.requestTypes.list().then(setRequestTypes).catch(function () { });
}, [api]);
var run = function () {
if (!requestTypeId) {
setError('Выберите тип заявки.');
return;
}
setBusy(true);
setError(undefined);
setResult(undefined);
api.templateRoutes
.resolve({
requestTypeId: Number(requestTypeId),
userEmail: userEmail.trim() || undefined,
projectId: projectId.trim() || undefined,
status: status
})
.then(setResult)
.catch(function (e) { return setError(e.message); })
.finally(function () { return setBusy(false); });
};
return (React.createElement("div", null,
React.createElement(ui_1.Breadcrumbs, { items: [{ label: 'Главная' }, { label: 'Проверка маршрута' }] }),
React.createElement(ui_1.PageHeader, { icon: "search", subtitle: "\u0414\u0438\u0430\u0433\u043D\u043E\u0441\u0442\u0438\u043A\u0430", title: "\u041F\u0440\u043E\u0432\u0435\u0440\u043A\u0430 \u0440\u0430\u0437\u0440\u0435\u0448\u0435\u043D\u0438\u044F \u043C\u0430\u0440\u0448\u0440\u0443\u0442\u0430" }),
React.createElement("div", { style: { background: '#fff', borderRadius: 14, padding: 26, boxShadow: 'var(--sh-1)', border: '1px solid var(--gray-200)', marginBottom: 24 } },
React.createElement(ui_1.FormGrid, null,
React.createElement(ui_1.Field, { label: "\u0422\u0438\u043F \u0437\u0430\u044F\u0432\u043A\u0438", required: true, full: true },
React.createElement(ui_1.Select, { options: requestTypes.map(function (rt) { return ({ value: String(rt.id), label: rt.name }); }), placeholder: "\u0412\u044B\u0431\u0435\u0440\u0438\u0442\u0435 \u0442\u0438\u043F \u0437\u0430\u044F\u0432\u043A\u0438", value: requestTypeId, onChange: function (e) { return setRequestTypeId(e.target.value); } })),
React.createElement(ui_1.Field, { label: "Email \u0438\u043D\u0438\u0446\u0438\u0430\u0442\u043E\u0440\u0430", hint: currentUserEmail ? "\u041F\u0443\u0441\u0442\u043E = \u0442\u0435\u043A\u0443\u0449\u0438\u0439 \u043F\u043E\u043B\u044C\u0437\u043E\u0432\u0430\u0442\u0435\u043B\u044C (".concat(currentUserEmail, ").") : 'Пусто = текущий пользователь из токена.' },
React.createElement(ui_1.TextInput, { value: userEmail, onChange: function (e) { return setUserEmail(e.target.value); }, placeholder: "user@tnsu.kz" })),
React.createElement(ui_1.Field, { label: "Id \u043F\u0440\u043E\u0435\u043A\u0442\u0430 (Guid)", hint: "\u0417\u0430\u043F\u043E\u043B\u043D\u0438\u0442\u0435, \u0435\u0441\u043B\u0438 \u043C\u0430\u0440\u0448\u0440\u0443\u0442 \u0437\u0430\u0432\u0438\u0441\u0438\u0442 \u043E\u0442 \u043F\u0440\u043E\u0435\u043A\u0442\u0430." },
React.createElement(ui_1.TextInput, { value: projectId, onChange: function (e) { return setProjectId(e.target.value); }, placeholder: "\u041D\u0435\u043E\u0431\u044F\u0437\u0430\u0442\u0435\u043B\u044C\u043D\u043E" })),
React.createElement(ui_1.Field, { label: "\u0421\u0442\u0430\u0442\u0443\u0441", required: true },
React.createElement(ui_1.Select, { options: STATUS_OPTIONS, value: status, onChange: function (e) { return setStatus(e.target.value); } }))),
React.createElement("div", { style: { marginTop: 18 } },
React.createElement(ui_1.Button, { variant: "accent", icon: "search", onClick: run, disabled: busy }, busy ? 'Проверка...' : 'Проверить'))),
error && React.createElement(ui_1.Alert, { kind: "err" }, error),
result && (React.createElement(React.Fragment, null,
React.createElement(ui_1.Alert, { kind: "ok" },
"\u041C\u0430\u0440\u0448\u0440\u0443\u0442 #",
result.routeId,
" \u00B7 ",
models_1.routeTypeLabels[result.routeType],
result.requiresControl ? ' · требуется контроль исполнения' : ''),
React.createElement(ui_1.DataTable, { columns: [
{ key: 'order', header: '№', width: '50px', render: function (r) { return r.orderNo; } },
{ key: 'executor', header: 'Исполнитель', render: function (r) { var _a; return React.createElement(ui_1.PrimaryCell, null, (_a = r.executorEmail) !== null && _a !== void 0 ? _a : '—'); } },
{ key: 'position', header: 'Должность', render: function (r) { var _a; return (_a = r.position) !== null && _a !== void 0 ? _a : '—'; } },
{
key: 'reassign',
header: 'Переназначение',
align: 'center',
render: function (r) { return (r.allowReassignToOtherApprovers ? 'Разрешено' : 'Нет'); }
}
], rows: result.steps, keyOf: function (r) { return r.id; }, emptyMessage: "\u041C\u0430\u0440\u0448\u0440\u0443\u0442 \u0440\u0430\u0437\u0440\u0435\u0448\u0438\u043B\u0441\u044F \u0431\u0435\u0437 \u0448\u0430\u0433\u043E\u0432 \u0434\u043B\u044F \u044D\u0442\u043E\u0433\u043E \u0441\u0442\u0430\u0442\u0443\u0441\u0430." })))));
};
exports.ResolveScreen = ResolveScreen;
//# sourceMappingURL=ResolveScreen.js.map
File diff suppressed because one or more lines are too long
@@ -0,0 +1,213 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.RouteStepsScreen = void 0;
var tslib_1 = require("tslib");
var React = tslib_1.__importStar(require("react"));
var models_1 = require("../../models");
var ui_1 = require("../ui");
function emptyForm(nextOrderNo) {
return {
orderNo: String(nextOrderNo),
status: models_1.RouteStepStatus.Approval,
kind: 'email',
executorEmail: '',
position: '',
projectRole: models_1.ProjectRole.ProjectManager,
headType: models_1.HeadType.Chief,
allowReassignToOtherApprovers: false
};
}
var STATUS_OPTIONS = [models_1.RouteStepStatus.Approval, models_1.RouteStepStatus.Execution]
.map(function (v) { return ({ value: v, label: models_1.routeStepStatusLabels[v] }); });
var PROJECT_ROLE_OPTIONS = Object.values(models_1.ProjectRole)
.filter(function (v) { return v !== models_1.ProjectRole.None; })
.map(function (v) { return ({ value: v, label: models_1.projectRoleLabels[v] }); });
var HEAD_TYPE_OPTIONS = Object.values(models_1.HeadType)
.filter(function (v) { return v !== models_1.HeadType.None; })
.map(function (v) { return ({ value: v, label: models_1.headTypeLabels[v] }); });
var RouteStepsScreen = function (_a) {
var api = _a.api, route = _a.route, requestTypeName = _a.requestTypeName, onBack = _a.onBack;
var _b = React.useState([]), items = _b[0], setItems = _b[1];
var _c = React.useState(true), loading = _c[0], setLoading = _c[1];
var _d = React.useState(), loadError = _d[0], setLoadError = _d[1];
var _e = React.useState(), warning = _e[0], setWarning = _e[1];
var _f = React.useState(), editing = _f[0], setEditing = _f[1];
var _g = React.useState(false), showForm = _g[0], setShowForm = _g[1];
var _h = React.useState(emptyForm(1)), form = _h[0], setForm = _h[1];
var _j = React.useState(), formError = _j[0], setFormError = _j[1];
var _k = React.useState(false), saving = _k[0], setSaving = _k[1];
var _l = React.useState(), deleting = _l[0], setDeleting = _l[1];
var _m = React.useState(), deleteError = _m[0], setDeleteError = _m[1];
var _o = React.useState(false), deleteBusy = _o[0], setDeleteBusy = _o[1];
var load = React.useCallback(function () {
setLoading(true);
setLoadError(undefined);
api.templateRouteSteps
.list(route.id)
.then(setItems)
.catch(function (e) { return setLoadError(e.message); })
.finally(function () { return setLoading(false); });
}, [api, route.id]);
React.useEffect(function () { load(); }, [load]);
var openCreate = function () {
setEditing(undefined);
var nextOrder = items.length > 0 ? Math.max.apply(Math, items.map(function (s) { return s.orderNo; })) + 1 : 1;
setForm(emptyForm(nextOrder));
setFormError(undefined);
setShowForm(true);
};
var openEdit = function (item) {
var _a, _b;
setEditing(item);
setForm({
orderNo: String(item.orderNo),
status: item.status,
kind: (0, models_1.detectExecutorKind)(item),
executorEmail: (_a = item.executorEmail) !== null && _a !== void 0 ? _a : '',
position: (_b = item.position) !== null && _b !== void 0 ? _b : '',
projectRole: item.projectRole && item.projectRole !== models_1.ProjectRole.None ? item.projectRole : models_1.ProjectRole.ProjectManager,
headType: item.headType && item.headType !== models_1.HeadType.None ? item.headType : models_1.HeadType.Chief,
allowReassignToOtherApprovers: item.allowReassignToOtherApprovers
});
setFormError(undefined);
setShowForm(true);
};
var submit = function () {
var orderNo = Number(form.orderNo);
if (!orderNo || orderNo < 1) {
setFormError('Порядковый номер шага обязателен.');
return;
}
if (form.kind === 'email' && !form.executorEmail.trim()) {
setFormError('Укажите email исполнителя.');
return;
}
if (form.kind === 'position' && !form.position.trim()) {
setFormError('Укажите должность.');
return;
}
var payload = {
orderNo: orderNo,
status: form.status,
allowReassignToOtherApprovers: form.allowReassignToOtherApprovers,
executorEmail: form.kind === 'email' ? form.executorEmail.trim() : null,
position: form.kind === 'position' ? form.position.trim() : null,
projectRole: form.kind === 'projectRole' ? form.projectRole : null,
headType: form.kind === 'headType' ? form.headType : null
};
setSaving(true);
setFormError(undefined);
var request = editing
? api.templateRouteSteps.update(route.id, editing.id, payload)
: api.templateRouteSteps.create(route.id, payload);
request
.then(function (step) {
var _a;
setShowForm(false);
setWarning((_a = step.warning) !== null && _a !== void 0 ? _a : undefined);
load();
})
.catch(function (e) { return setFormError(e.message); })
.finally(function () { return setSaving(false); });
};
var confirmDelete = function () {
if (!deleting)
return;
setDeleteBusy(true);
setDeleteError(undefined);
api.templateRouteSteps
.remove(route.id, deleting.id)
.then(function () {
setDeleting(undefined);
load();
})
.catch(function (e) { return setDeleteError(e.message); })
.finally(function () { return setDeleteBusy(false); });
};
var sorted = tslib_1.__spreadArray([], items, true).sort(function (a, b) { return a.orderNo - b.orderNo; });
return (React.createElement("div", null,
React.createElement(ui_1.Breadcrumbs, { items: [
{ label: 'Главная' },
{ label: 'Маршруты согласования', onClick: onBack },
{ label: "\u0428\u0430\u0433\u0438 \u043C\u0430\u0440\u0448\u0440\u0443\u0442\u0430 #".concat(route.id) }
] }),
React.createElement(ui_1.PageHeader, { icon: "flag-check", subtitle: "".concat(requestTypeName, " \u00B7 ").concat(models_1.routeTypeLabels[route.routeType]), title: "\u041C\u0430\u0440\u0448\u0440\u0443\u0442 #".concat(route.id), actions: React.createElement(React.Fragment, null,
React.createElement(ui_1.Button, { variant: "ghost", icon: "arrow-left", onClick: onBack }, "\u041A \u043C\u0430\u0440\u0448\u0440\u0443\u0442\u0430\u043C"),
React.createElement(ui_1.Button, { variant: "accent", icon: "plus", onClick: openCreate }, "\u0414\u043E\u0431\u0430\u0432\u0438\u0442\u044C \u0448\u0430\u0433")) }),
warning && React.createElement(ui_1.Alert, { kind: "warn" }, warning),
loadError && React.createElement(ui_1.Alert, { kind: "err" }, loadError),
deleteError && React.createElement(ui_1.Alert, { kind: "err" }, deleteError),
React.createElement(ui_1.DataTable, { columns: [
{ key: 'order', header: '№', width: '50px', render: function (r) { return r.orderNo; } },
{
key: 'executor',
header: 'Исполнитель',
render: function (r) { return React.createElement(ui_1.PrimaryCell, null, describeExecutor(r)); }
},
{
key: 'status',
header: 'Статус',
render: function (r) { return (React.createElement(ui_1.Badge, { variant: r.status === models_1.RouteStepStatus.Approval ? 'gold' : 'blue' }, models_1.routeStepStatusLabels[r.status])); }
},
{
key: 'reassign',
header: 'Переназначение',
align: 'center',
render: function (r) { return (r.allowReassignToOtherApprovers ? React.createElement(ui_1.Badge, { variant: "ok" }, "\u0420\u0430\u0437\u0440\u0435\u0448\u0435\u043D\u043E") : React.createElement(ui_1.Badge, { variant: "neutral" }, "\u041D\u0435\u0442")); }
},
{
key: 'actions',
header: '',
width: '100px',
align: 'right',
render: function (r) { return (React.createElement(ui_1.ActionsCell, null,
React.createElement(ui_1.IconButton, { icon: "edit", label: "\u0420\u0435\u0434\u0430\u043A\u0442\u0438\u0440\u043E\u0432\u0430\u0442\u044C", onClick: function () { return openEdit(r); } }),
React.createElement(ui_1.IconButton, { icon: "trash", label: "\u0423\u0434\u0430\u043B\u0438\u0442\u044C", danger: true, onClick: function () { setDeleteError(undefined); setDeleting(r); } }))); }
}
], rows: sorted, keyOf: function (r) { return r.id; }, emptyMessage: loading ? 'Загрузка...' : 'Шагов пока нет — добавьте первый.' }),
showForm && (React.createElement(ui_1.Modal, { title: editing ? "\u0428\u0430\u0433 #".concat(editing.orderNo) : 'Новый шаг маршрута', onClose: function () { return setShowForm(false); }, footer: React.createElement(React.Fragment, null,
React.createElement(ui_1.Button, { variant: "secondary", onClick: function () { return setShowForm(false); }, disabled: saving }, "\u041E\u0442\u043C\u0435\u043D\u0430"),
React.createElement(ui_1.Button, { variant: "accent", icon: "check", onClick: submit, disabled: saving }, saving ? 'Сохранение...' : 'Сохранить')) },
formError && React.createElement(ui_1.Alert, { kind: "err" }, formError),
React.createElement(ui_1.FormGrid, null,
React.createElement(ui_1.Field, { label: "\u041F\u043E\u0440\u044F\u0434\u043A\u043E\u0432\u044B\u0439 \u043D\u043E\u043C\u0435\u0440", required: true },
React.createElement(ui_1.TextInput, { type: "number", min: 1, value: form.orderNo, onChange: function (e) { return setForm(tslib_1.__assign(tslib_1.__assign({}, form), { orderNo: e.target.value })); } })),
React.createElement(ui_1.Field, { label: "\u0421\u0442\u0430\u0442\u0443\u0441 \u0448\u0430\u0433\u0430", required: true },
React.createElement(ui_1.Select, { options: STATUS_OPTIONS, value: form.status, onChange: function (e) { return setForm(tslib_1.__assign(tslib_1.__assign({}, form), { status: e.target.value })); } })),
React.createElement(ui_1.Field, { label: "\u041A\u0442\u043E \u0438\u0441\u043F\u043E\u043B\u043D\u0438\u0442\u0435\u043B\u044C", required: true, full: true, hint: "\u0414\u043E\u043B\u0436\u043D\u043E \u0431\u044B\u0442\u044C \u0437\u0430\u043F\u043E\u043B\u043D\u0435\u043D\u043E \u0440\u043E\u0432\u043D\u043E \u043E\u0434\u043D\u043E \u0438\u0437 \u043F\u043E\u043B\u0435\u0439." },
React.createElement(ui_1.RadioGroup, { name: "executorKind", value: form.kind, onChange: function (kind) { return setForm(tslib_1.__assign(tslib_1.__assign({}, form), { kind: kind })); }, options: [
{ value: 'email', label: 'Конкретный сотрудник (email)' },
{ value: 'position', label: 'По должности в компании инициатора' },
{
value: 'projectRole',
label: 'По роли в проекте',
disabled: !route.byProject,
disabledHint: 'Недоступно: маршрут не учитывает проект (по проекту = нет).'
},
{ value: 'headType', label: 'По типу руководителя' }
] })),
form.kind === 'email' && (React.createElement(ui_1.Field, { label: "Email \u0438\u0441\u043F\u043E\u043B\u043D\u0438\u0442\u0435\u043B\u044F", required: true, full: true },
React.createElement(ui_1.TextInput, { value: form.executorEmail, onChange: function (e) { return setForm(tslib_1.__assign(tslib_1.__assign({}, form), { executorEmail: e.target.value })); }, placeholder: "user@tnsu.kz" }))),
form.kind === 'position' && (React.createElement(ui_1.Field, { label: "\u0414\u043E\u043B\u0436\u043D\u043E\u0441\u0442\u044C", required: true, full: true, hint: "\u0418\u0449\u0435\u0442\u0441\u044F \u0432 \u0441\u043F\u0440\u0430\u0432\u043E\u0447\u043D\u0438\u043A\u0435 api.tnsu.kz \u043F\u043E \u043A\u043E\u043C\u043F\u0430\u043D\u0438\u0438 \u0438\u043D\u0438\u0446\u0438\u0430\u0442\u043E\u0440\u0430." },
React.createElement(ui_1.TextInput, { value: form.position, onChange: function (e) { return setForm(tslib_1.__assign(tslib_1.__assign({}, form), { position: e.target.value })); }, placeholder: "\u0413\u043B\u0430\u0432\u043D\u044B\u0439 \u0438\u043D\u0436\u0435\u043D\u0435\u0440" }))),
form.kind === 'projectRole' && (React.createElement(ui_1.Field, { label: "\u0420\u043E\u043B\u044C \u0432 \u043F\u0440\u043E\u0435\u043A\u0442\u0435", required: true, full: true },
React.createElement(ui_1.Select, { options: PROJECT_ROLE_OPTIONS, value: form.projectRole, onChange: function (e) { return setForm(tslib_1.__assign(tslib_1.__assign({}, form), { projectRole: e.target.value })); } }))),
form.kind === 'headType' && (React.createElement(ui_1.Field, { label: "\u0422\u0438\u043F \u0440\u0443\u043A\u043E\u0432\u043E\u0434\u0438\u0442\u0435\u043B\u044F", required: true, full: true },
React.createElement(ui_1.Select, { options: HEAD_TYPE_OPTIONS, value: form.headType, onChange: function (e) { return setForm(tslib_1.__assign(tslib_1.__assign({}, form), { headType: e.target.value })); } }))),
React.createElement(ui_1.Field, { label: "\u041F\u0435\u0440\u0435\u043D\u0430\u0437\u043D\u0430\u0447\u0435\u043D\u0438\u0435", full: true },
React.createElement(ui_1.Checkbox, { label: "\u0420\u0430\u0437\u0440\u0435\u0448\u0438\u0442\u044C \u043F\u0435\u0440\u0435\u043D\u0430\u0437\u043D\u0430\u0447\u0430\u0442\u044C \u0434\u0440\u0443\u0433\u0438\u043C \u0441\u043E\u0433\u043B\u0430\u0441\u0443\u044E\u0449\u0438\u043C", checked: form.allowReassignToOtherApprovers, onChange: function (v) { return setForm(tslib_1.__assign(tslib_1.__assign({}, form), { allowReassignToOtherApprovers: v })); } }))))),
deleting && (React.createElement(ui_1.ConfirmDialog, { title: "\u0423\u0434\u0430\u043B\u0438\u0442\u044C \u0448\u0430\u0433?", text: "\u0428\u0430\u0433 #".concat(deleting.orderNo, " \u0431\u0443\u0434\u0435\u0442 \u0443\u0434\u0430\u043B\u0451\u043D \u0431\u0435\u0437\u0432\u043E\u0437\u0432\u0440\u0430\u0442\u043D\u043E."), confirmLabel: "\u0423\u0434\u0430\u043B\u0438\u0442\u044C", busy: deleteBusy, onCancel: function () { return setDeleting(undefined); }, onConfirm: confirmDelete }))));
};
exports.RouteStepsScreen = RouteStepsScreen;
function describeExecutor(step) {
if (step.executorEmail)
return step.executorEmail;
if (step.position)
return step.position;
if (step.projectRole && step.projectRole !== models_1.ProjectRole.None)
return "\u0420\u043E\u043B\u044C: ".concat(models_1.projectRoleLabels[step.projectRole]);
if (step.headType && step.headType !== models_1.HeadType.None)
return "\u0420\u0443\u043A\u043E\u0432\u043E\u0434\u0438\u0442\u0435\u043B\u044C: ".concat(models_1.headTypeLabels[step.headType]);
return '—';
}
//# sourceMappingURL=RouteStepsScreen.js.map
File diff suppressed because one or more lines are too long
@@ -0,0 +1,186 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.RoutesScreen = void 0;
var tslib_1 = require("tslib");
var React = tslib_1.__importStar(require("react"));
var models_1 = require("../../models");
var ui_1 = require("../ui");
var EMPTY_FORM = {
requestTypeId: '',
routeType: models_1.RouteType.Sequential,
companyId: '',
departmentId: '',
byProject: false,
requiresControl: false
};
var ROUTE_TYPE_OPTIONS = Object.values(models_1.RouteType)
.filter(function (v) { return v !== models_1.RouteType.None; })
.map(function (v) { return ({ value: v, label: models_1.routeTypeLabels[v] }); });
var RoutesScreen = function (_a) {
var api = _a.api, onOpenSteps = _a.onOpenSteps;
var _b = React.useState([]), requestTypes = _b[0], setRequestTypes = _b[1];
var _c = React.useState([]), items = _c[0], setItems = _c[1];
var _d = React.useState(''), filterRequestTypeId = _d[0], setFilterRequestTypeId = _d[1];
var _e = React.useState(true), loading = _e[0], setLoading = _e[1];
var _f = React.useState(), loadError = _f[0], setLoadError = _f[1];
var _g = React.useState(), editing = _g[0], setEditing = _g[1];
var _h = React.useState(false), showForm = _h[0], setShowForm = _h[1];
var _j = React.useState(EMPTY_FORM), form = _j[0], setForm = _j[1];
var _k = React.useState(), formError = _k[0], setFormError = _k[1];
var _l = React.useState(false), saving = _l[0], setSaving = _l[1];
var _m = React.useState(), deleting = _m[0], setDeleting = _m[1];
var _o = React.useState(), deleteError = _o[0], setDeleteError = _o[1];
var _p = React.useState(false), deleteBusy = _p[0], setDeleteBusy = _p[1];
React.useEffect(function () {
api.requestTypes.list().then(setRequestTypes).catch(function () { });
}, [api]);
var load = React.useCallback(function () {
setLoading(true);
setLoadError(undefined);
var requestTypeId = filterRequestTypeId ? Number(filterRequestTypeId) : undefined;
api.templateRoutes
.list(requestTypeId)
.then(setItems)
.catch(function (e) { return setLoadError(e.message); })
.finally(function () { return setLoading(false); });
}, [api, filterRequestTypeId]);
React.useEffect(function () { load(); }, [load]);
var requestTypeName = function (id) { var _a, _b; return (_b = (_a = requestTypes.find(function (rt) { return rt.id === id; })) === null || _a === void 0 ? void 0 : _a.name) !== null && _b !== void 0 ? _b : "#".concat(id); };
var openCreate = function () {
setEditing(undefined);
setForm(tslib_1.__assign(tslib_1.__assign({}, EMPTY_FORM), { requestTypeId: filterRequestTypeId }));
setFormError(undefined);
setShowForm(true);
};
var openEdit = function (item) {
var _a;
setEditing(item);
setForm({
requestTypeId: String(item.requestTypeId),
routeType: item.routeType,
companyId: item.companyId,
departmentId: (_a = item.departmentId) !== null && _a !== void 0 ? _a : '',
byProject: item.byProject,
requiresControl: item.requiresControl
});
setFormError(undefined);
setShowForm(true);
};
var submit = function () {
if (!editing && !form.requestTypeId) {
setFormError('Выберите тип заявки.');
return;
}
if (!form.companyId.trim()) {
setFormError('Компания обязательна (Guid из справочника api.tnsu.kz).');
return;
}
setSaving(true);
setFormError(undefined);
var departmentId = form.departmentId.trim() ? form.departmentId.trim() : null;
var request = editing
? api.templateRoutes.update(editing.id, {
routeType: form.routeType,
companyId: form.companyId.trim(),
departmentId: departmentId,
byProject: form.byProject,
requiresControl: form.requiresControl
})
: api.templateRoutes.create({
routeType: form.routeType,
requestTypeId: Number(form.requestTypeId),
companyId: form.companyId.trim(),
departmentId: departmentId,
byProject: form.byProject,
requiresControl: form.requiresControl
});
request
.then(function () {
setShowForm(false);
load();
})
.catch(function (e) { return setFormError(e.message); })
.finally(function () { return setSaving(false); });
};
var confirmDelete = function () {
if (!deleting)
return;
setDeleteBusy(true);
setDeleteError(undefined);
api.templateRoutes
.remove(deleting.id)
.then(function () {
setDeleting(undefined);
load();
})
.catch(function (e) { return setDeleteError(e.message); })
.finally(function () { return setDeleteBusy(false); });
};
return (React.createElement("div", null,
React.createElement(ui_1.Breadcrumbs, { items: [{ label: 'Главная' }, { label: 'Маршруты согласования' }] }),
React.createElement(ui_1.PageHeader, { icon: "route", subtitle: "\u041D\u0430\u0441\u0442\u0440\u043E\u0439\u043A\u0430 \u0441\u043E\u0433\u043B\u0430\u0441\u043E\u0432\u0430\u043D\u0438\u0439", title: "\u041C\u0430\u0440\u0448\u0440\u0443\u0442\u044B \u0441\u043E\u0433\u043B\u0430\u0441\u043E\u0432\u0430\u043D\u0438\u044F", actions: React.createElement(ui_1.Button, { variant: "accent", icon: "plus", onClick: openCreate }, "\u0414\u043E\u0431\u0430\u0432\u0438\u0442\u044C \u043C\u0430\u0440\u0448\u0440\u0443\u0442") }),
React.createElement("div", { style: { marginBottom: 18, maxWidth: 320 } },
React.createElement(ui_1.Field, { label: "\u0424\u0438\u043B\u044C\u0442\u0440 \u043F\u043E \u0442\u0438\u043F\u0443 \u0437\u0430\u044F\u0432\u043A\u0438" },
React.createElement(ui_1.Select, { options: requestTypes.map(function (rt) { return ({ value: String(rt.id), label: rt.name }); }), placeholder: "\u0412\u0441\u0435 \u0442\u0438\u043F\u044B \u0437\u0430\u044F\u0432\u043E\u043A", value: filterRequestTypeId, onChange: function (e) { return setFilterRequestTypeId(e.target.value); } }))),
loadError && React.createElement(ui_1.Alert, { kind: "err" }, loadError),
deleteError && React.createElement(ui_1.Alert, { kind: "err" }, deleteError),
React.createElement(ui_1.DataTable, { columns: [
{
key: 'requestType',
header: 'Тип заявки',
render: function (r) { return React.createElement(ui_1.PrimaryCell, null, requestTypeName(r.requestTypeId)); }
},
{
key: 'routeType',
header: 'Тип маршрута',
render: function (r) { return React.createElement(ui_1.Badge, { variant: r.routeType === models_1.RouteType.Parallel ? 'blue' : 'gold' }, models_1.routeTypeLabels[r.routeType]); }
},
{ key: 'company', header: 'Компания', render: function (r) { return React.createElement("code", null, shortGuid(r.companyId)); } },
{ key: 'department', header: 'Отдел', render: function (r) { return (r.departmentId ? React.createElement("code", null, shortGuid(r.departmentId)) : '—'); } },
{
key: 'byProject',
header: 'По проекту',
align: 'center',
render: function (r) { return (r.byProject ? React.createElement(ui_1.Badge, { variant: "ok" }, "\u0414\u0430") : React.createElement(ui_1.Badge, { variant: "neutral" }, "\u041D\u0435\u0442")); }
},
{
key: 'control',
header: 'Контроль',
align: 'center',
render: function (r) { return (r.requiresControl ? React.createElement(ui_1.Badge, { variant: "gold" }, "\u0422\u0440\u0435\u0431\u0443\u0435\u0442\u0441\u044F") : React.createElement(ui_1.Badge, { variant: "neutral" }, "\u041D\u0435\u0442")); }
},
{
key: 'actions',
header: '',
width: '160px',
align: 'right',
render: function (r) { return (React.createElement(ui_1.ActionsCell, null,
React.createElement(ui_1.Button, { size: "sm", variant: "secondary", icon: "clipboard", onClick: function () { return onOpenSteps(r); } }, "\u0428\u0430\u0433\u0438"),
React.createElement(ui_1.IconButton, { icon: "edit", label: "\u0420\u0435\u0434\u0430\u043A\u0442\u0438\u0440\u043E\u0432\u0430\u0442\u044C", onClick: function () { return openEdit(r); } }),
React.createElement(ui_1.IconButton, { icon: "trash", label: "\u0423\u0434\u0430\u043B\u0438\u0442\u044C", danger: true, onClick: function () { setDeleteError(undefined); setDeleting(r); } }))); }
}
], rows: items, keyOf: function (r) { return r.id; }, emptyMessage: loading ? 'Загрузка...' : 'Маршрутов пока нет.' }),
showForm && (React.createElement(ui_1.Modal, { title: editing ? "\u041C\u0430\u0440\u0448\u0440\u0443\u0442 #".concat(editing.id) : 'Новый маршрут', onClose: function () { return setShowForm(false); }, footer: React.createElement(React.Fragment, null,
React.createElement(ui_1.Button, { variant: "secondary", onClick: function () { return setShowForm(false); }, disabled: saving }, "\u041E\u0442\u043C\u0435\u043D\u0430"),
React.createElement(ui_1.Button, { variant: "accent", icon: "check", onClick: submit, disabled: saving }, saving ? 'Сохранение...' : 'Сохранить')) },
formError && React.createElement(ui_1.Alert, { kind: "err" }, formError),
React.createElement(ui_1.FormGrid, null,
React.createElement(ui_1.Field, { label: "\u0422\u0438\u043F \u0437\u0430\u044F\u0432\u043A\u0438", required: true, full: true },
React.createElement(ui_1.Select, { options: requestTypes.map(function (rt) { return ({ value: String(rt.id), label: rt.name }); }), placeholder: "\u0412\u044B\u0431\u0435\u0440\u0438\u0442\u0435 \u0442\u0438\u043F \u0437\u0430\u044F\u0432\u043A\u0438", value: form.requestTypeId, disabled: !!editing, onChange: function (e) { return setForm(tslib_1.__assign(tslib_1.__assign({}, form), { requestTypeId: e.target.value })); } })),
React.createElement(ui_1.Field, { label: "\u0422\u0438\u043F \u043C\u0430\u0440\u0448\u0440\u0443\u0442\u0430", required: true },
React.createElement(ui_1.Select, { options: ROUTE_TYPE_OPTIONS, value: form.routeType, onChange: function (e) { return setForm(tslib_1.__assign(tslib_1.__assign({}, form), { routeType: e.target.value })); } })),
React.createElement(ui_1.Field, { label: "\u041A\u043E\u043C\u043F\u0430\u043D\u0438\u044F (Guid)", required: true, hint: "\u0418\u0437 \u0441\u043F\u0440\u0430\u0432\u043E\u0447\u043D\u0438\u043A\u0430 api.tnsu.kz." },
React.createElement(ui_1.TextInput, { value: form.companyId, onChange: function (e) { return setForm(tslib_1.__assign(tslib_1.__assign({}, form), { companyId: e.target.value })); }, placeholder: "00000000-0000-0000-0000-000000000000" })),
React.createElement(ui_1.Field, { label: "\u041E\u0442\u0434\u0435\u043B (Guid)", hint: "\u041D\u0435\u043E\u0431\u044F\u0437\u0430\u0442\u0435\u043B\u044C\u043D\u043E \u2014 \u043C\u0430\u0440\u0448\u0440\u0443\u0442 \u00AB\u043F\u043E \u0443\u043C\u043E\u043B\u0447\u0430\u043D\u0438\u044E\u00BB \u0434\u043B\u044F \u043A\u043E\u043C\u043F\u0430\u043D\u0438\u0438." },
React.createElement(ui_1.TextInput, { value: form.departmentId, onChange: function (e) { return setForm(tslib_1.__assign(tslib_1.__assign({}, form), { departmentId: e.target.value })); }, placeholder: "\u041E\u0441\u0442\u0430\u0432\u044C\u0442\u0435 \u043F\u0443\u0441\u0442\u044B\u043C \u0434\u043B\u044F \u043C\u0430\u0440\u0448\u0440\u0443\u0442\u0430 \u043F\u043E \u0443\u043C\u043E\u043B\u0447\u0430\u043D\u0438\u044E" })),
React.createElement(ui_1.Field, { label: "\u041F\u043E \u043F\u0440\u043E\u0435\u043A\u0442\u0443" },
React.createElement(ui_1.Checkbox, { label: "\u041C\u0430\u0440\u0448\u0440\u0443\u0442 \u0443\u0447\u0438\u0442\u044B\u0432\u0430\u0435\u0442 \u043A\u043E\u043D\u043A\u0440\u0435\u0442\u043D\u044B\u0439 \u043F\u0440\u043E\u0435\u043A\u0442", checked: form.byProject, onChange: function (v) { return setForm(tslib_1.__assign(tslib_1.__assign({}, form), { byProject: v })); } })),
React.createElement(ui_1.Field, { label: "\u041A\u043E\u043D\u0442\u0440\u043E\u043B\u044C" },
React.createElement(ui_1.Checkbox, { label: "\u0422\u0440\u0435\u0431\u0443\u0435\u0442\u0441\u044F \u043E\u0442\u0434\u0435\u043B\u044C\u043D\u044B\u0439 \u043A\u043E\u043D\u0442\u0440\u043E\u043B\u044C \u0438\u0441\u043F\u043E\u043B\u043D\u0435\u043D\u0438\u044F", checked: form.requiresControl, onChange: function (v) { return setForm(tslib_1.__assign(tslib_1.__assign({}, form), { requiresControl: v })); } }))))),
deleting && (React.createElement(ui_1.ConfirmDialog, { title: "\u0423\u0434\u0430\u043B\u0438\u0442\u044C \u043C\u0430\u0440\u0448\u0440\u0443\u0442?", text: "\u041C\u0430\u0440\u0448\u0440\u0443\u0442 #".concat(deleting.id, " \u0438 \u0432\u0441\u0435 \u0435\u0433\u043E \u0448\u0430\u0433\u0438 \u0431\u0443\u0434\u0443\u0442 \u0443\u0434\u0430\u043B\u0435\u043D\u044B \u0431\u0435\u0437\u0432\u043E\u0437\u0432\u0440\u0430\u0442\u043D\u043E."), confirmLabel: "\u0423\u0434\u0430\u043B\u0438\u0442\u044C", busy: deleteBusy, onCancel: function () { return setDeleting(undefined); }, onConfirm: confirmDelete }))));
};
exports.RoutesScreen = RoutesScreen;
function shortGuid(guid) {
return guid.length > 8 ? "".concat(guid.slice(0, 8), "\u2026") : guid;
}
//# sourceMappingURL=RoutesScreen.js.map
File diff suppressed because one or more lines are too long
@@ -0,0 +1,16 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.Alert = void 0;
var tslib_1 = require("tslib");
var React = tslib_1.__importStar(require("react"));
var Alert_module_scss_1 = tslib_1.__importDefault(require("./Alert.module.scss"));
var Icon_1 = require("./Icon");
var ICONS = { warn: 'warning', ok: 'check', err: 'close' };
var Alert = function (_a) {
var kind = _a.kind, children = _a.children;
return (React.createElement("div", { className: "".concat(Alert_module_scss_1.default.alert, " ").concat(Alert_module_scss_1.default[kind]) },
React.createElement(Icon_1.Icon, { name: ICONS[kind], size: "sm" }),
React.createElement("span", null, children)));
};
exports.Alert = Alert;
//# sourceMappingURL=Alert.js.map
@@ -0,0 +1 @@
{"version":3,"file":"Alert.js","sourceRoot":"","sources":["../../../../../src/webparts/approvalAdmin/components/ui/Alert.tsx"],"names":[],"mappings":";;;;AAAA,mDAA+B;AAC/B,kFAAyC;AACzC,+BAA8B;AAS9B,IAAM,KAAK,GAAG,EAAE,IAAI,EAAE,SAAS,EAAE,EAAE,EAAE,OAAO,EAAE,GAAG,EAAE,OAAO,EAAW,CAAC;AAE/D,IAAM,KAAK,GAA0B,UAAC,EAAkB;QAAhB,IAAI,UAAA,EAAE,QAAQ,cAAA;IAAO,OAAA,CAClE,6BAAK,SAAS,EAAE,UAAG,2BAAM,CAAC,KAAK,cAAI,2BAAM,CAAC,IAAI,CAAC,CAAE;QAC/C,oBAAC,WAAI,IAAC,IAAI,EAAE,KAAK,CAAC,IAAI,CAAC,EAAE,IAAI,EAAC,IAAI,GAAG;QACrC,kCAAO,QAAQ,CAAQ,CACnB,CACP;AALmE,CAKnE,CAAC;AALW,QAAA,KAAK,SAKhB","sourcesContent":["import * as React from 'react';\nimport styles from './Alert.module.scss';\nimport { Icon } from './Icon';\n\nexport type AlertKind = 'warn' | 'ok' | 'err';\n\nexport interface IAlertProps {\n kind: AlertKind;\n children: React.ReactNode;\n}\n\nconst ICONS = { warn: 'warning', ok: 'check', err: 'close' } as const;\n\nexport const Alert: React.FC<IAlertProps> = ({ kind, children }) => (\n <div className={`${styles.alert} ${styles[kind]}`}>\n <Icon name={ICONS[kind]} size=\"sm\" />\n <span>{children}</span>\n </div>\n);\n"]}
@@ -0,0 +1,68 @@
:root {
--navy-950: #0A1B30;
--navy-900: #0F2440;
--navy-800: #1B3A5C;
--navy-700: #244B73;
--navy-600: #2D5C8A;
--navy-100: #E8EEF4;
--navy-50: #F4F7FA;
--gold-600: #B8881A;
--gold-500: #D4A01E;
--gold-400: #E5B635;
--gold-300: #F0CC5E;
--gold-100: #FDF5E0;
--gold-50: #FEFBF0;
--gray-900: #111827;
--gray-700: #374151;
--gray-500: #6B7280;
--gray-400: #9CA3AF;
--gray-300: #D1D5DB;
--gray-200: #E5E7EB;
--gray-100: #F3F4F6;
--gray-50: #F9FAFB;
--red-500: #DC2626;
--red-50: #FEF2F2;
--green-600: #15803D;
--green-50: #F0FDF4;
--blue-600: #2563EB;
--blue-50: #EFF6FF;
--amber-700: #854D0E;
--sh-1: 0 1px 2px rgba(15, 36, 64, 0.04), 0 1px 3px rgba(15, 36, 64, 0.06);
--sh-2: 0 4px 16px rgba(15, 36, 64, 0.07);
--sh-3: 0 12px 32px rgba(15, 36, 64, 0.1);
--sh-gold: 0 4px 20px rgba(212, 160, 30, 0.18);
--sh-modal: 0 24px 70px rgba(10, 27, 48, 0.25);
--r-sm: 6px;
--r-md: 10px;
--r-lg: 14px;
}
.alert {
padding: 12px 16px;
border-radius: 10px;
font-size: 12.5px;
display: flex;
align-items: center;
gap: 10px;
margin-bottom: 18px;
border: 1px solid;
line-height: 1.5;
}
.warn {
background: #FEFBF0;
color: #854D0E;
border-color: #F0CC5E;
}
.ok {
background: #F0FDF4;
color: #15803D;
border-color: #BBF7D0;
}
.err {
background: #FEF2F2;
color: #DC2626;
border-color: #FECACA;
}
@@ -0,0 +1,17 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.CountBadge = exports.Badge = void 0;
var tslib_1 = require("tslib");
var React = tslib_1.__importStar(require("react"));
var Badge_module_scss_1 = tslib_1.__importDefault(require("./Badge.module.scss"));
var Badge = function (_a) {
var _b = _a.variant, variant = _b === void 0 ? 'neutral' : _b, children = _a.children;
return (React.createElement("span", { className: "".concat(Badge_module_scss_1.default.badge, " ").concat(Badge_module_scss_1.default[variant]) }, children));
};
exports.Badge = Badge;
var CountBadge = function (_a) {
var count = _a.count;
return (React.createElement("span", { className: Badge_module_scss_1.default.count }, count));
};
exports.CountBadge = CountBadge;
//# sourceMappingURL=Badge.js.map
@@ -0,0 +1 @@
{"version":3,"file":"Badge.js","sourceRoot":"","sources":["../../../../../src/webparts/approvalAdmin/components/ui/Badge.tsx"],"names":[],"mappings":";;;;AAAA,mDAA+B;AAC/B,kFAAyC;AASlC,IAAM,KAAK,GAA0B,UAAC,EAAiC;QAA/B,eAAmB,EAAnB,OAAO,mBAAG,SAAS,KAAA,EAAE,QAAQ,cAAA;IAAO,OAAA,CACjF,8BAAM,SAAS,EAAE,UAAG,2BAAM,CAAC,KAAK,cAAI,2BAAM,CAAC,OAAO,CAAC,CAAE,IAAG,QAAQ,CAAQ,CACzE;AAFkF,CAElF,CAAC;AAFW,QAAA,KAAK,SAEhB;AAEK,IAAM,UAAU,GAAgC,UAAC,EAAS;QAAP,KAAK,WAAA;IAAO,OAAA,CACpE,8BAAM,SAAS,EAAE,2BAAM,CAAC,KAAK,IAAG,KAAK,CAAQ,CAC9C;AAFqE,CAErE,CAAC;AAFW,QAAA,UAAU,cAErB","sourcesContent":["import * as React from 'react';\nimport styles from './Badge.module.scss';\n\nexport type BadgeVariant = 'ok' | 'gold' | 'neutral' | 'blue' | 'red';\n\nexport interface IBadgeProps {\n variant?: BadgeVariant;\n children: React.ReactNode;\n}\n\nexport const Badge: React.FC<IBadgeProps> = ({ variant = 'neutral', children }) => (\n <span className={`${styles.badge} ${styles[variant]}`}>{children}</span>\n);\n\nexport const CountBadge: React.FC<{ count: number }> = ({ count }) => (\n <span className={styles.count}>{count}</span>\n);\n"]}
@@ -0,0 +1,96 @@
:root {
--navy-950: #0A1B30;
--navy-900: #0F2440;
--navy-800: #1B3A5C;
--navy-700: #244B73;
--navy-600: #2D5C8A;
--navy-100: #E8EEF4;
--navy-50: #F4F7FA;
--gold-600: #B8881A;
--gold-500: #D4A01E;
--gold-400: #E5B635;
--gold-300: #F0CC5E;
--gold-100: #FDF5E0;
--gold-50: #FEFBF0;
--gray-900: #111827;
--gray-700: #374151;
--gray-500: #6B7280;
--gray-400: #9CA3AF;
--gray-300: #D1D5DB;
--gray-200: #E5E7EB;
--gray-100: #F3F4F6;
--gray-50: #F9FAFB;
--red-500: #DC2626;
--red-50: #FEF2F2;
--green-600: #15803D;
--green-50: #F0FDF4;
--blue-600: #2563EB;
--blue-50: #EFF6FF;
--amber-700: #854D0E;
--sh-1: 0 1px 2px rgba(15, 36, 64, 0.04), 0 1px 3px rgba(15, 36, 64, 0.06);
--sh-2: 0 4px 16px rgba(15, 36, 64, 0.07);
--sh-3: 0 12px 32px rgba(15, 36, 64, 0.1);
--sh-gold: 0 4px 20px rgba(212, 160, 30, 0.18);
--sh-modal: 0 24px 70px rgba(10, 27, 48, 0.25);
--r-sm: 6px;
--r-md: 10px;
--r-lg: 14px;
}
.badge {
display: inline-flex;
align-items: center;
gap: 5px;
padding: 3px 11px;
border-radius: 100px;
font-size: 11px;
font-weight: 600;
letter-spacing: 0.2px;
white-space: nowrap;
}
.badge::before {
content: "";
width: 5px;
height: 5px;
border-radius: 50%;
background: currentColor;
}
.ok {
background: #F0FDF4;
color: #15803D;
}
.gold {
background: #FDF5E0;
color: #854D0E;
}
.neutral {
background: #F3F4F6;
color: #6B7280;
}
.blue {
background: #EFF6FF;
color: #2563EB;
}
.red {
background: #FEF2F2;
color: #DC2626;
}
.count {
display: inline-flex;
align-items: center;
justify-content: center;
min-width: 22px;
height: 22px;
border-radius: 11px;
background: #0F2440;
color: #F0CC5E;
font-size: 11px;
font-weight: 600;
padding: 0 6px;
}
@@ -0,0 +1,25 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.IconButton = exports.Button = void 0;
var tslib_1 = require("tslib");
var React = tslib_1.__importStar(require("react"));
var Button_module_scss_1 = tslib_1.__importDefault(require("./Button.module.scss"));
var Icon_1 = require("./Icon");
var Button = function (_a) {
var _b = _a.variant, variant = _b === void 0 ? 'secondary' : _b, _c = _a.size, size = _c === void 0 ? 'md' : _c, icon = _a.icon, className = _a.className, children = _a.children, rest = tslib_1.__rest(_a, ["variant", "size", "icon", "className", "children"]);
var classes = [Button_module_scss_1.default.btn, Button_module_scss_1.default[variant], size === 'sm' ? Button_module_scss_1.default.sm : '', className]
.filter(Boolean)
.join(' ');
return (React.createElement("button", tslib_1.__assign({ type: "button", className: classes }, rest),
icon && React.createElement(Icon_1.Icon, { name: icon, size: "sm" }),
children));
};
exports.Button = Button;
var IconButton = function (_a) {
var icon = _a.icon, danger = _a.danger, label = _a.label, className = _a.className, rest = tslib_1.__rest(_a, ["icon", "danger", "label", "className"]);
var classes = [Button_module_scss_1.default.iconButton, danger ? Button_module_scss_1.default.danger : '', className].filter(Boolean).join(' ');
return (React.createElement("button", tslib_1.__assign({ type: "button", className: classes, "aria-label": label, title: label }, rest),
React.createElement(Icon_1.Icon, { name: icon, size: "sm" })));
};
exports.IconButton = IconButton;
//# sourceMappingURL=Button.js.map
@@ -0,0 +1 @@
{"version":3,"file":"Button.js","sourceRoot":"","sources":["../../../../../src/webparts/approvalAdmin/components/ui/Button.tsx"],"names":[],"mappings":";;;;AAAA,mDAA+B;AAC/B,oFAA0C;AAC1C,+BAAwC;AAUjC,IAAM,MAAM,GAA2B,UAAC,EAO9C;IANC,IAAA,eAAqB,EAArB,OAAO,mBAAG,WAAW,KAAA,EACrB,YAAW,EAAX,IAAI,mBAAG,IAAI,KAAA,EACX,IAAI,UAAA,EACJ,SAAS,eAAA,EACT,QAAQ,cAAA,EACL,IAAI,sBANsC,oDAO9C,CADQ;IAEP,IAAM,OAAO,GAAG,CAAC,4BAAM,CAAC,GAAG,EAAE,4BAAM,CAAC,OAAO,CAAC,EAAE,IAAI,KAAK,IAAI,CAAC,CAAC,CAAC,4BAAM,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,SAAS,CAAC;SACrF,MAAM,CAAC,OAAO,CAAC;SACf,IAAI,CAAC,GAAG,CAAC,CAAC;IAEb,OAAO,CACL,iDAAQ,IAAI,EAAC,QAAQ,EAAC,SAAS,EAAE,OAAO,IAAM,IAAI;QAC/C,IAAI,IAAI,oBAAC,WAAI,IAAC,IAAI,EAAE,IAAI,EAAE,IAAI,EAAC,IAAI,GAAG;QACtC,QAAQ,CACF,CACV,CAAC;AACJ,CAAC,CAAC;AAlBW,QAAA,MAAM,UAkBjB;AAQK,IAAM,UAAU,GAA+B,UAAC,EAA2C;IAAzC,IAAA,IAAI,UAAA,EAAE,MAAM,YAAA,EAAE,KAAK,WAAA,EAAE,SAAS,eAAA,EAAK,IAAI,sBAAzC,wCAA2C,CAAF;IAC9F,IAAM,OAAO,GAAG,CAAC,4BAAM,CAAC,UAAU,EAAE,MAAM,CAAC,CAAC,CAAC,4BAAM,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,SAAS,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IACtG,OAAO,CACL,iDAAQ,IAAI,EAAC,QAAQ,EAAC,SAAS,EAAE,OAAO,gBAAc,KAAK,EAAE,KAAK,EAAE,KAAK,IAAM,IAAI;QACjF,oBAAC,WAAI,IAAC,IAAI,EAAE,IAAI,EAAE,IAAI,EAAC,IAAI,GAAG,CACvB,CACV,CAAC;AACJ,CAAC,CAAC;AAPW,QAAA,UAAU,cAOrB","sourcesContent":["import * as React from 'react';\nimport styles from './Button.module.scss';\nimport { Icon, IconName } from './Icon';\n\nexport type ButtonVariant = 'primary' | 'accent' | 'secondary' | 'ghost' | 'danger';\n\nexport interface IButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {\n variant?: ButtonVariant;\n size?: 'md' | 'sm';\n icon?: IconName;\n}\n\nexport const Button: React.FC<IButtonProps> = ({\n variant = 'secondary',\n size = 'md',\n icon,\n className,\n children,\n ...rest\n}) => {\n const classes = [styles.btn, styles[variant], size === 'sm' ? styles.sm : '', className]\n .filter(Boolean)\n .join(' ');\n\n return (\n <button type=\"button\" className={classes} {...rest}>\n {icon && <Icon name={icon} size=\"sm\" />}\n {children}\n </button>\n );\n};\n\nexport interface IIconButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {\n icon: IconName;\n danger?: boolean;\n label: string;\n}\n\nexport const IconButton: React.FC<IIconButtonProps> = ({ icon, danger, label, className, ...rest }) => {\n const classes = [styles.iconButton, danger ? styles.danger : '', className].filter(Boolean).join(' ');\n return (\n <button type=\"button\" className={classes} aria-label={label} title={label} {...rest}>\n <Icon name={icon} size=\"sm\" />\n </button>\n );\n};\n"]}
@@ -0,0 +1,144 @@
:root {
--navy-950: #0A1B30;
--navy-900: #0F2440;
--navy-800: #1B3A5C;
--navy-700: #244B73;
--navy-600: #2D5C8A;
--navy-100: #E8EEF4;
--navy-50: #F4F7FA;
--gold-600: #B8881A;
--gold-500: #D4A01E;
--gold-400: #E5B635;
--gold-300: #F0CC5E;
--gold-100: #FDF5E0;
--gold-50: #FEFBF0;
--gray-900: #111827;
--gray-700: #374151;
--gray-500: #6B7280;
--gray-400: #9CA3AF;
--gray-300: #D1D5DB;
--gray-200: #E5E7EB;
--gray-100: #F3F4F6;
--gray-50: #F9FAFB;
--red-500: #DC2626;
--red-50: #FEF2F2;
--green-600: #15803D;
--green-50: #F0FDF4;
--blue-600: #2563EB;
--blue-50: #EFF6FF;
--amber-700: #854D0E;
--sh-1: 0 1px 2px rgba(15, 36, 64, 0.04), 0 1px 3px rgba(15, 36, 64, 0.06);
--sh-2: 0 4px 16px rgba(15, 36, 64, 0.07);
--sh-3: 0 12px 32px rgba(15, 36, 64, 0.1);
--sh-gold: 0 4px 20px rgba(212, 160, 30, 0.18);
--sh-modal: 0 24px 70px rgba(10, 27, 48, 0.25);
--r-sm: 6px;
--r-md: 10px;
--r-lg: 14px;
}
.btn {
display: inline-flex;
align-items: center;
gap: 7px;
padding: 10px 20px;
border-radius: 6px;
font-size: 13px;
font-weight: 500;
font-family: "Inter", -apple-system, "Segoe UI", sans-serif;
cursor: pointer;
border: 1px solid transparent;
transition: 0.18s;
white-space: nowrap;
letter-spacing: 0.15px;
text-decoration: none;
}
.btn:disabled {
opacity: 0.5;
cursor: not-allowed;
transform: none !important;
box-shadow: none !important;
}
.primary {
background: #1B3A5C;
color: #fff;
border-color: #1B3A5C;
}
.primary:hover:not(:disabled) {
background: #244B73;
box-shadow: 0 4px 16px rgba(15, 36, 64, 0.07);
}
.accent {
background: linear-gradient(135deg, #E5B635, #D4A01E);
color: #0A1B30;
font-weight: 600;
border: 1px solid #D4A01E;
}
.accent:hover {
box-shadow: 0 4px 20px rgba(212, 160, 30, 0.18);
transform: translateY(-1px);
}
.secondary {
background: #fff;
color: #1B3A5C;
border-color: #D1D5DB;
}
.secondary:hover:not(:disabled) {
border-color: #D4A01E;
color: #0F2440;
}
.ghost {
background: transparent;
color: #244B73;
border-color: transparent;
}
.ghost:hover:not(:disabled) {
background: #E8EEF4;
}
.danger {
background: #fff;
color: #DC2626;
border-color: #D1D5DB;
}
.danger:hover:not(:disabled) {
border-color: #DC2626;
background: #FEF2F2;
}
.sm {
padding: 7px 14px;
font-size: 12.5px;
}
.iconButton {
width: 33px;
height: 33px;
padding: 0;
display: inline-flex;
align-items: center;
justify-content: center;
border-radius: 6px;
background: transparent;
border: 1px solid transparent;
cursor: pointer;
color: #9CA3AF;
transition: 0.18s;
}
.iconButton:hover:not(:disabled) {
color: #1B3A5C;
background: #F4F7FA;
border-color: #E5E7EB;
}
.iconButton.danger:hover:not(:disabled) {
color: #DC2626;
background: #FEF2F2;
}
.iconButton:disabled {
opacity: 0.4;
cursor: not-allowed;
}
@@ -0,0 +1,37 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.StatCard = exports.StatRow = exports.NavCard = exports.CardGrid = void 0;
var tslib_1 = require("tslib");
var React = tslib_1.__importStar(require("react"));
var Card_module_scss_1 = tslib_1.__importDefault(require("./Card.module.scss"));
var Icon_1 = require("./Icon");
var CardGrid = function (_a) {
var children = _a.children;
return (React.createElement("div", { className: Card_module_scss_1.default.grid }, children));
};
exports.CardGrid = CardGrid;
var NavCard = function (_a) {
var icon = _a.icon, title = _a.title, description = _a.description, onClick = _a.onClick;
return (React.createElement("a", { className: Card_module_scss_1.default.navCard, onClick: function (e) { e.preventDefault(); onClick(); }, href: "#" },
React.createElement("div", { className: Card_module_scss_1.default.navIcon },
React.createElement(Icon_1.Icon, { name: icon })),
React.createElement("div", null,
React.createElement("div", { className: Card_module_scss_1.default.navTitle }, title),
React.createElement("div", { className: Card_module_scss_1.default.navDesc }, description)),
React.createElement("div", { className: Card_module_scss_1.default.navArrow },
React.createElement(Icon_1.Icon, { name: "chevron-right", size: "sm" }))));
};
exports.NavCard = NavCard;
var StatRow = function (_a) {
var children = _a.children;
return (React.createElement("div", { className: Card_module_scss_1.default.statRow }, children));
};
exports.StatRow = StatRow;
var StatCard = function (_a) {
var label = _a.label, value = _a.value;
return (React.createElement("div", { className: Card_module_scss_1.default.statCard },
React.createElement("div", { className: Card_module_scss_1.default.statLabel }, label),
React.createElement("div", { className: Card_module_scss_1.default.statValue }, value)));
};
exports.StatCard = StatCard;
//# sourceMappingURL=Card.js.map
@@ -0,0 +1 @@
{"version":3,"file":"Card.js","sourceRoot":"","sources":["../../../../../src/webparts/approvalAdmin/components/ui/Card.tsx"],"names":[],"mappings":";;;;AAAA,mDAA+B;AAC/B,gFAAwC;AACxC,+BAAwC;AAEjC,IAAM,QAAQ,GAA4C,UAAC,EAAY;QAAV,QAAQ,cAAA;IAAO,OAAA,CACjF,6BAAK,SAAS,EAAE,0BAAM,CAAC,IAAI,IAAG,QAAQ,CAAO,CAC9C;AAFkF,CAElF,CAAC;AAFW,QAAA,QAAQ,YAEnB;AASK,IAAM,OAAO,GAA4B,UAAC,EAAqC;QAAnC,IAAI,UAAA,EAAE,KAAK,WAAA,EAAE,WAAW,iBAAA,EAAE,OAAO,aAAA;IAAO,OAAA,CACzF,2BAAG,SAAS,EAAE,0BAAM,CAAC,OAAO,EAAE,OAAO,EAAE,UAAA,CAAC,IAAM,CAAC,CAAC,cAAc,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,IAAI,EAAC,GAAG;QACtF,6BAAK,SAAS,EAAE,0BAAM,CAAC,OAAO;YAAE,oBAAC,WAAI,IAAC,IAAI,EAAE,IAAI,GAAI,CAAM;QAC1D;YACE,6BAAK,SAAS,EAAE,0BAAM,CAAC,QAAQ,IAAG,KAAK,CAAO;YAC9C,6BAAK,SAAS,EAAE,0BAAM,CAAC,OAAO,IAAG,WAAW,CAAO,CAC/C;QACN,6BAAK,SAAS,EAAE,0BAAM,CAAC,QAAQ;YAAE,oBAAC,WAAI,IAAC,IAAI,EAAC,eAAe,EAAC,IAAI,EAAC,IAAI,GAAG,CAAM,CAC5E,CACL;AAT0F,CAS1F,CAAC;AATW,QAAA,OAAO,WASlB;AAEK,IAAM,OAAO,GAA4C,UAAC,EAAY;QAAV,QAAQ,cAAA;IAAO,OAAA,CAChF,6BAAK,SAAS,EAAE,0BAAM,CAAC,OAAO,IAAG,QAAQ,CAAO,CACjD;AAFiF,CAEjF,CAAC;AAFW,QAAA,OAAO,WAElB;AAEK,IAAM,QAAQ,GAAwD,UAAC,EAAgB;QAAd,KAAK,WAAA,EAAE,KAAK,WAAA;IAAO,OAAA,CACjG,6BAAK,SAAS,EAAE,0BAAM,CAAC,QAAQ;QAC7B,6BAAK,SAAS,EAAE,0BAAM,CAAC,SAAS,IAAG,KAAK,CAAO;QAC/C,6BAAK,SAAS,EAAE,0BAAM,CAAC,SAAS,IAAG,KAAK,CAAO,CAC3C,CACP;AALkG,CAKlG,CAAC;AALW,QAAA,QAAQ,YAKnB","sourcesContent":["import * as React from 'react';\nimport styles from './Card.module.scss';\nimport { Icon, IconName } from './Icon';\n\nexport const CardGrid: React.FC<{ children: React.ReactNode }> = ({ children }) => (\n <div className={styles.grid}>{children}</div>\n);\n\nexport interface INavCardProps {\n icon: IconName;\n title: string;\n description: string;\n onClick: () => void;\n}\n\nexport const NavCard: React.FC<INavCardProps> = ({ icon, title, description, onClick }) => (\n <a className={styles.navCard} onClick={e => { e.preventDefault(); onClick(); }} href=\"#\">\n <div className={styles.navIcon}><Icon name={icon} /></div>\n <div>\n <div className={styles.navTitle}>{title}</div>\n <div className={styles.navDesc}>{description}</div>\n </div>\n <div className={styles.navArrow}><Icon name=\"chevron-right\" size=\"sm\" /></div>\n </a>\n);\n\nexport const StatRow: React.FC<{ children: React.ReactNode }> = ({ children }) => (\n <div className={styles.statRow}>{children}</div>\n);\n\nexport const StatCard: React.FC<{ label: string; value: React.ReactNode }> = ({ label, value }) => (\n <div className={styles.statCard}>\n <div className={styles.statLabel}>{label}</div>\n <div className={styles.statValue}>{value}</div>\n </div>\n);\n"]}
@@ -0,0 +1,155 @@
:root {
--navy-950: #0A1B30;
--navy-900: #0F2440;
--navy-800: #1B3A5C;
--navy-700: #244B73;
--navy-600: #2D5C8A;
--navy-100: #E8EEF4;
--navy-50: #F4F7FA;
--gold-600: #B8881A;
--gold-500: #D4A01E;
--gold-400: #E5B635;
--gold-300: #F0CC5E;
--gold-100: #FDF5E0;
--gold-50: #FEFBF0;
--gray-900: #111827;
--gray-700: #374151;
--gray-500: #6B7280;
--gray-400: #9CA3AF;
--gray-300: #D1D5DB;
--gray-200: #E5E7EB;
--gray-100: #F3F4F6;
--gray-50: #F9FAFB;
--red-500: #DC2626;
--red-50: #FEF2F2;
--green-600: #15803D;
--green-50: #F0FDF4;
--blue-600: #2563EB;
--blue-50: #EFF6FF;
--amber-700: #854D0E;
--sh-1: 0 1px 2px rgba(15, 36, 64, 0.04), 0 1px 3px rgba(15, 36, 64, 0.06);
--sh-2: 0 4px 16px rgba(15, 36, 64, 0.07);
--sh-3: 0 12px 32px rgba(15, 36, 64, 0.1);
--sh-gold: 0 4px 20px rgba(212, 160, 30, 0.18);
--sh-modal: 0 24px 70px rgba(10, 27, 48, 0.25);
--r-sm: 6px;
--r-md: 10px;
--r-lg: 14px;
}
.grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(290px, 1fr));
gap: 14px;
margin-bottom: 24px;
}
.navCard {
background: #fff;
border-radius: 10px;
padding: 22px 24px;
display: flex;
align-items: center;
gap: 16px;
box-shadow: 0 1px 2px rgba(15, 36, 64, 0.04), 0 1px 3px rgba(15, 36, 64, 0.06);
cursor: pointer;
transition: 0.22s;
border: 1px solid #E5E7EB;
text-decoration: none;
color: inherit;
position: relative;
overflow: hidden;
}
.navCard:hover {
box-shadow: 0 12px 32px rgba(15, 36, 64, 0.1);
transform: translateY(-2px);
border-color: #F0CC5E;
}
.navCard::before {
content: "";
position: absolute;
left: 0;
top: 0;
bottom: 0;
width: 3px;
background: linear-gradient(180deg, #E5B635, #B8881A);
opacity: 0;
transition: 0.22s;
}
.navCard:hover::before {
opacity: 1;
}
.navIcon {
width: 44px;
height: 44px;
border-radius: 10px;
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
background: #F4F7FA;
color: #244B73;
border: 1px solid #E8EEF4;
transition: 0.22s;
}
.navCard:hover .navIcon {
background: #FEFBF0;
color: #B8881A;
border-color: #F0CC5E;
}
.navTitle {
font-size: 14.5px;
font-weight: 600;
color: #0F2440;
letter-spacing: 0.1px;
}
.navDesc {
font-size: 12px;
color: #9CA3AF;
margin-top: 2px;
}
.navArrow {
color: #D1D5DB;
margin-left: auto;
display: flex;
transition: 0.22s;
}
.navCard:hover .navArrow {
color: #D4A01E;
transform: translateX(3px);
}
.statRow {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(170px, 1fr));
gap: 14px;
margin-bottom: 24px;
}
.statCard {
background: #fff;
border-radius: 10px;
padding: 18px 20px;
box-shadow: 0 1px 2px rgba(15, 36, 64, 0.04), 0 1px 3px rgba(15, 36, 64, 0.06);
border: 1px solid #E5E7EB;
}
.statLabel {
font-size: 10px;
text-transform: uppercase;
letter-spacing: 1.2px;
color: #9CA3AF;
font-weight: 600;
margin-bottom: 6px;
}
.statValue {
font-family: "Lora", Georgia, serif;
font-size: 26px;
font-weight: 600;
color: #0F2440;
}
@@ -0,0 +1,33 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.ActionsCell = exports.PrimaryCell = void 0;
exports.DataTable = DataTable;
var tslib_1 = require("tslib");
var React = tslib_1.__importStar(require("react"));
var DataTable_module_scss_1 = tslib_1.__importDefault(require("./DataTable.module.scss"));
function DataTable(_a) {
var columns = _a.columns, rows = _a.rows, keyOf = _a.keyOf, emptyMessage = _a.emptyMessage;
return (React.createElement("div", { className: DataTable_module_scss_1.default.wrap },
React.createElement("table", { className: DataTable_module_scss_1.default.table },
React.createElement("thead", null,
React.createElement("tr", null, columns.map(function (col) {
var _a;
return (React.createElement("th", { key: col.key, style: { width: col.width, textAlign: (_a = col.align) !== null && _a !== void 0 ? _a : 'left' } }, col.header));
}))),
React.createElement("tbody", null, rows.map(function (row) { return (React.createElement("tr", { key: keyOf(row) }, columns.map(function (col) {
var _a;
return (React.createElement("td", { key: col.key, style: { textAlign: (_a = col.align) !== null && _a !== void 0 ? _a : 'left' } }, col.render(row)));
}))); }))),
rows.length === 0 && React.createElement("div", { className: DataTable_module_scss_1.default.empty }, emptyMessage)));
}
var PrimaryCell = function (_a) {
var children = _a.children;
return (React.createElement("span", { className: DataTable_module_scss_1.default.primaryCell }, children));
};
exports.PrimaryCell = PrimaryCell;
var ActionsCell = function (_a) {
var children = _a.children;
return (React.createElement("span", { className: DataTable_module_scss_1.default.actionsCell }, children));
};
exports.ActionsCell = ActionsCell;
//# sourceMappingURL=DataTable.js.map
@@ -0,0 +1 @@
{"version":3,"file":"DataTable.js","sourceRoot":"","sources":["../../../../../src/webparts/approvalAdmin/components/ui/DataTable.tsx"],"names":[],"mappings":";;;AAkBA,8BA4BC;;AA9CD,mDAA+B;AAC/B,0FAA6C;AAiB7C,SAAgB,SAAS,CAAI,EAA0D;QAAxD,OAAO,aAAA,EAAE,IAAI,UAAA,EAAE,KAAK,WAAA,EAAE,YAAY,kBAAA;IAC/D,OAAO,CACL,6BAAK,SAAS,EAAE,+BAAM,CAAC,IAAI;QACzB,+BAAO,SAAS,EAAE,+BAAM,CAAC,KAAK;YAC5B;gBACE,gCACG,OAAO,CAAC,GAAG,CAAC,UAAA,GAAG;;oBAAI,OAAA,CAClB,4BAAI,GAAG,EAAE,GAAG,CAAC,GAAG,EAAE,KAAK,EAAE,EAAE,KAAK,EAAE,GAAG,CAAC,KAAK,EAAE,SAAS,EAAE,MAAA,GAAG,CAAC,KAAK,mCAAI,MAAM,EAAE,IAC1E,GAAG,CAAC,MAAM,CACR,CACN,CAAA;iBAAA,CAAC,CACC,CACC;YACR,mCACG,IAAI,CAAC,GAAG,CAAC,UAAA,GAAG,IAAI,OAAA,CACf,4BAAI,GAAG,EAAE,KAAK,CAAC,GAAG,CAAC,IAChB,OAAO,CAAC,GAAG,CAAC,UAAA,GAAG;;gBAAI,OAAA,CAClB,4BAAI,GAAG,EAAE,GAAG,CAAC,GAAG,EAAE,KAAK,EAAE,EAAE,SAAS,EAAE,MAAA,GAAG,CAAC,KAAK,mCAAI,MAAM,EAAE,IACxD,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CACb,CACN,CAAA;aAAA,CAAC,CACC,CACN,EARgB,CAQhB,CAAC,CACI,CACF;QACP,IAAI,CAAC,MAAM,KAAK,CAAC,IAAI,6BAAK,SAAS,EAAE,+BAAM,CAAC,KAAK,IAAG,YAAY,CAAO,CACpE,CACP,CAAC;AACJ,CAAC;AAEM,IAAM,WAAW,GAA4C,UAAC,EAAY;QAAV,QAAQ,cAAA;IAAO,OAAA,CACpF,8BAAM,SAAS,EAAE,+BAAM,CAAC,WAAW,IAAG,QAAQ,CAAQ,CACvD;AAFqF,CAErF,CAAC;AAFW,QAAA,WAAW,eAEtB;AAEK,IAAM,WAAW,GAA4C,UAAC,EAAY;QAAV,QAAQ,cAAA;IAAO,OAAA,CACpF,8BAAM,SAAS,EAAE,+BAAM,CAAC,WAAW,IAAG,QAAQ,CAAQ,CACvD;AAFqF,CAErF,CAAC;AAFW,QAAA,WAAW,eAEtB","sourcesContent":["import * as React from 'react';\nimport styles from './DataTable.module.scss';\n\nexport interface IColumn<T> {\n key: string;\n header: string;\n render: (row: T) => React.ReactNode;\n align?: 'left' | 'right' | 'center';\n width?: string;\n}\n\nexport interface IDataTableProps<T> {\n columns: IColumn<T>[];\n rows: T[];\n keyOf: (row: T) => string | number;\n emptyMessage: string;\n}\n\nexport function DataTable<T>({ columns, rows, keyOf, emptyMessage }: IDataTableProps<T>): React.ReactElement {\n return (\n <div className={styles.wrap}>\n <table className={styles.table}>\n <thead>\n <tr>\n {columns.map(col => (\n <th key={col.key} style={{ width: col.width, textAlign: col.align ?? 'left' }}>\n {col.header}\n </th>\n ))}\n </tr>\n </thead>\n <tbody>\n {rows.map(row => (\n <tr key={keyOf(row)}>\n {columns.map(col => (\n <td key={col.key} style={{ textAlign: col.align ?? 'left' }}>\n {col.render(row)}\n </td>\n ))}\n </tr>\n ))}\n </tbody>\n </table>\n {rows.length === 0 && <div className={styles.empty}>{emptyMessage}</div>}\n </div>\n );\n}\n\nexport const PrimaryCell: React.FC<{ children: React.ReactNode }> = ({ children }) => (\n <span className={styles.primaryCell}>{children}</span>\n);\n\nexport const ActionsCell: React.FC<{ children: React.ReactNode }> = ({ children }) => (\n <span className={styles.actionsCell}>{children}</span>\n);\n"]}
@@ -0,0 +1,106 @@
:root {
--navy-950: #0A1B30;
--navy-900: #0F2440;
--navy-800: #1B3A5C;
--navy-700: #244B73;
--navy-600: #2D5C8A;
--navy-100: #E8EEF4;
--navy-50: #F4F7FA;
--gold-600: #B8881A;
--gold-500: #D4A01E;
--gold-400: #E5B635;
--gold-300: #F0CC5E;
--gold-100: #FDF5E0;
--gold-50: #FEFBF0;
--gray-900: #111827;
--gray-700: #374151;
--gray-500: #6B7280;
--gray-400: #9CA3AF;
--gray-300: #D1D5DB;
--gray-200: #E5E7EB;
--gray-100: #F3F4F6;
--gray-50: #F9FAFB;
--red-500: #DC2626;
--red-50: #FEF2F2;
--green-600: #15803D;
--green-50: #F0FDF4;
--blue-600: #2563EB;
--blue-50: #EFF6FF;
--amber-700: #854D0E;
--sh-1: 0 1px 2px rgba(15, 36, 64, 0.04), 0 1px 3px rgba(15, 36, 64, 0.06);
--sh-2: 0 4px 16px rgba(15, 36, 64, 0.07);
--sh-3: 0 12px 32px rgba(15, 36, 64, 0.1);
--sh-gold: 0 4px 20px rgba(212, 160, 30, 0.18);
--sh-modal: 0 24px 70px rgba(10, 27, 48, 0.25);
--r-sm: 6px;
--r-md: 10px;
--r-lg: 14px;
}
.wrap {
background: #fff;
border-radius: 14px;
box-shadow: 0 1px 2px rgba(15, 36, 64, 0.04), 0 1px 3px rgba(15, 36, 64, 0.06);
overflow: hidden;
border: 1px solid #E5E7EB;
}
.table {
width: 100%;
border-collapse: collapse;
font-size: 13.5px;
}
.table thead th {
text-align: left;
padding: 13px 18px;
font-size: 10.5px;
text-transform: uppercase;
letter-spacing: 1.2px;
color: #9CA3AF;
font-weight: 600;
background: #F9FAFB;
border-bottom: 1px solid #E5E7EB;
white-space: nowrap;
}
.table tbody td {
padding: 15px 18px;
border-bottom: 1px solid #F3F4F6;
color: #374151;
vertical-align: middle;
}
.table tbody tr:last-child td {
border-bottom: 0;
}
.table tbody tr {
transition: 0.12s;
}
.table tbody tr:hover {
background: #FEFBF0;
}
.primaryCell {
font-weight: 500;
color: #0F2440;
display: flex;
align-items: center;
gap: 10px;
}
.actionsCell {
display: flex;
gap: 4px;
justify-content: flex-end;
align-items: center;
}
.empty {
padding: 48px 20px;
text-align: center;
color: #9CA3AF;
font-size: 13px;
}
@@ -0,0 +1,49 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.Checkbox = exports.Select = exports.Textarea = exports.TextInput = exports.Field = exports.FormGrid = void 0;
exports.RadioGroup = RadioGroup;
var tslib_1 = require("tslib");
var React = tslib_1.__importStar(require("react"));
var FormField_module_scss_1 = tslib_1.__importDefault(require("./FormField.module.scss"));
var FormGrid = function (_a) {
var children = _a.children;
return (React.createElement("div", { className: FormField_module_scss_1.default.grid }, children));
};
exports.FormGrid = FormGrid;
var Field = function (_a) {
var label = _a.label, required = _a.required, full = _a.full, error = _a.error, hint = _a.hint, children = _a.children;
return (React.createElement("div", { className: "".concat(FormField_module_scss_1.default.field, " ").concat(full ? FormField_module_scss_1.default.full : '') },
React.createElement("span", { className: FormField_module_scss_1.default.label },
label,
" ",
required && React.createElement("span", { className: FormField_module_scss_1.default.req }, "*")),
children,
error && React.createElement("span", { className: FormField_module_scss_1.default.errorText }, error),
!error && hint && React.createElement("span", { className: FormField_module_scss_1.default.hint }, hint)));
};
exports.Field = Field;
var TextInput = function (props) { return (React.createElement("input", tslib_1.__assign({}, props, { className: FormField_module_scss_1.default.input }))); };
exports.TextInput = TextInput;
var Textarea = function (props) { return (React.createElement("textarea", tslib_1.__assign({}, props, { className: FormField_module_scss_1.default.textarea }))); };
exports.Textarea = Textarea;
var Select = function (_a) {
var options = _a.options, placeholder = _a.placeholder, rest = tslib_1.__rest(_a, ["options", "placeholder"]);
return (React.createElement("select", tslib_1.__assign({}, rest, { className: FormField_module_scss_1.default.select }),
placeholder && React.createElement("option", { value: "" }, placeholder),
options.map(function (opt) { return (React.createElement("option", { key: opt.value, value: opt.value }, opt.label)); })));
};
exports.Select = Select;
var Checkbox = function (_a) {
var label = _a.label, checked = _a.checked, onChange = _a.onChange, disabled = _a.disabled;
return (React.createElement("label", { className: FormField_module_scss_1.default.checkboxRow },
React.createElement("input", { type: "checkbox", className: FormField_module_scss_1.default.checkbox, checked: checked, disabled: disabled, onChange: function (e) { return onChange(e.target.checked); } }),
label));
};
exports.Checkbox = Checkbox;
function RadioGroup(_a) {
var name = _a.name, value = _a.value, options = _a.options, onChange = _a.onChange;
return (React.createElement("div", { className: FormField_module_scss_1.default.radioGroup }, options.map(function (opt) { return (React.createElement("label", { key: opt.value, className: "".concat(FormField_module_scss_1.default.radioRow, " ").concat(opt.disabled ? FormField_module_scss_1.default.disabled : ''), title: opt.disabled ? opt.disabledHint : undefined },
React.createElement("input", { type: "radio", className: FormField_module_scss_1.default.radio, name: name, checked: value === opt.value, disabled: opt.disabled, onChange: function () { return onChange(opt.value); } }),
opt.label)); })));
}
//# sourceMappingURL=FormField.js.map
File diff suppressed because one or more lines are too long
@@ -0,0 +1,168 @@
:root {
--navy-950: #0A1B30;
--navy-900: #0F2440;
--navy-800: #1B3A5C;
--navy-700: #244B73;
--navy-600: #2D5C8A;
--navy-100: #E8EEF4;
--navy-50: #F4F7FA;
--gold-600: #B8881A;
--gold-500: #D4A01E;
--gold-400: #E5B635;
--gold-300: #F0CC5E;
--gold-100: #FDF5E0;
--gold-50: #FEFBF0;
--gray-900: #111827;
--gray-700: #374151;
--gray-500: #6B7280;
--gray-400: #9CA3AF;
--gray-300: #D1D5DB;
--gray-200: #E5E7EB;
--gray-100: #F3F4F6;
--gray-50: #F9FAFB;
--red-500: #DC2626;
--red-50: #FEF2F2;
--green-600: #15803D;
--green-50: #F0FDF4;
--blue-600: #2563EB;
--blue-50: #EFF6FF;
--amber-700: #854D0E;
--sh-1: 0 1px 2px rgba(15, 36, 64, 0.04), 0 1px 3px rgba(15, 36, 64, 0.06);
--sh-2: 0 4px 16px rgba(15, 36, 64, 0.07);
--sh-3: 0 12px 32px rgba(15, 36, 64, 0.1);
--sh-gold: 0 4px 20px rgba(212, 160, 30, 0.18);
--sh-modal: 0 24px 70px rgba(10, 27, 48, 0.25);
--r-sm: 6px;
--r-md: 10px;
--r-lg: 14px;
}
.grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 18px 22px;
}
.field {
display: flex;
flex-direction: column;
gap: 6px;
}
.full {
grid-column: 1/-1;
}
.label {
font-size: 12px;
font-weight: 500;
color: #374151;
letter-spacing: 0.2px;
}
.req {
color: #B8881A;
}
.input,
.select,
.textarea {
padding: 11px 13px;
border: 1px solid #D1D5DB;
border-radius: 6px;
font-size: 13.5px;
font-family: "Inter", -apple-system, "Segoe UI", sans-serif;
color: #111827;
transition: 0.18s;
background: #fff;
width: 100%;
}
.input:focus,
.select:focus,
.textarea:focus {
outline: 0;
border-color: #D4A01E;
box-shadow: 0 0 0 3px rgba(212, 160, 30, 0.12);
}
.input::placeholder,
.select::placeholder,
.textarea::placeholder {
color: #9CA3AF;
}
.input:disabled,
.select:disabled,
.textarea:disabled {
background: #F9FAFB;
color: #9CA3AF;
cursor: not-allowed;
}
.textarea {
resize: vertical;
min-height: 80px;
}
.select {
appearance: none;
background-image: url("data:image/svg+xml,%3Csvg width='12' height='8' viewBox='0 0 12 8' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M1 1.5L6 6.5L11 1.5' stroke='%236B7280' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
background-repeat: no-repeat;
background-position: right 12px center;
padding-right: 36px;
}
.errorText {
font-size: 11.5px;
color: #DC2626;
}
.hint {
font-size: 11.5px;
color: #9CA3AF;
}
.checkboxRow {
display: flex;
align-items: center;
gap: 9px;
font-size: 13px;
color: #374151;
cursor: pointer;
user-select: none;
}
.checkbox {
width: 17px;
height: 17px;
accent-color: #D4A01E;
cursor: pointer;
}
.radioGroup {
display: flex;
flex-direction: column;
gap: 8px;
}
.radioRow {
display: flex;
align-items: center;
gap: 9px;
font-size: 13px;
color: #374151;
cursor: pointer;
user-select: none;
}
.radioRow.disabled {
opacity: 0.45;
cursor: not-allowed;
}
.radio {
width: 16px;
height: 16px;
accent-color: #D4A01E;
cursor: pointer;
}
.radio:disabled {
cursor: not-allowed;
}
@@ -0,0 +1,28 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.Icon = void 0;
var tslib_1 = require("tslib");
var React = tslib_1.__importStar(require("react"));
var spriteInjected = false;
var SPRITE_SVG = "\n<symbol id=\"i-route\" viewBox=\"0 0 24 24\"><circle cx=\"6\" cy=\"6\" r=\"3\"/><circle cx=\"18\" cy=\"18\" r=\"3\"/><path d=\"M9 6h7a3 3 0 0 1 3 3v0a3 3 0 0 1-3 3H8a3 3 0 0 0-3 3v0a3 3 0 0 0 3 3h7\"/></symbol>\n<symbol id=\"i-clipboard\" viewBox=\"0 0 24 24\"><path d=\"M9 3h6v2a1 1 0 0 1-1 1h-4a1 1 0 0 1-1-1z\"/><rect x=\"5\" y=\"5\" width=\"14\" height=\"16\" rx=\"2\"/><path d=\"M9 12h6M9 16h4\"/></symbol>\n<symbol id=\"i-flag-check\" viewBox=\"0 0 24 24\"><path d=\"M4 3v18\"/><path d=\"M4 4h13l-2 4 2 4H4\"/></symbol>\n<symbol id=\"i-people\" viewBox=\"0 0 24 24\"><circle cx=\"9\" cy=\"7\" r=\"3\"/><path d=\"M3 21v-2a4 4 0 0 1 4-4h4a4 4 0 0 1 4 4v2\"/><circle cx=\"17\" cy=\"8\" r=\"2.5\"/><path d=\"M17 13.5a3.5 3.5 0 0 1 3.5 3.5v1\"/></symbol>\n<symbol id=\"i-warning\" viewBox=\"0 0 24 24\"><path d=\"M12 2 1 21h22z\" stroke-linejoin=\"round\"/><path d=\"M12 9v5M12 17h.01\"/></symbol>\n<symbol id=\"i-check\" viewBox=\"0 0 24 24\"><path d=\"M5 12l5 5L20 7\"/></symbol>\n<symbol id=\"i-close\" viewBox=\"0 0 24 24\"><path d=\"M18 6 6 18M6 6l12 12\"/></symbol>\n<symbol id=\"i-plus\" viewBox=\"0 0 24 24\"><path d=\"M12 5v14M5 12h14\"/></symbol>\n<symbol id=\"i-edit\" viewBox=\"0 0 24 24\"><path d=\"M16.5 3.5a2.12 2.12 0 0 1 3 3L7 19l-4 1 1-4z\"/></symbol>\n<symbol id=\"i-trash\" viewBox=\"0 0 24 24\"><path d=\"M4 7h16M10 11v6M14 11v6\"/><path d=\"M5 7l1 12a2 2 0 0 0 2 2h8a2 2 0 0 0 2-2l1-12\"/><path d=\"M9 7V4a1 1 0 0 1 1-1h4a1 1 0 0 1 1 1v3\"/></symbol>\n<symbol id=\"i-more\" viewBox=\"0 0 24 24\"><circle cx=\"12\" cy=\"5\" r=\"1.5\" fill=\"currentColor\" stroke=\"none\"/><circle cx=\"12\" cy=\"12\" r=\"1.5\" fill=\"currentColor\" stroke=\"none\"/><circle cx=\"12\" cy=\"19\" r=\"1.5\" fill=\"currentColor\" stroke=\"none\"/></symbol>\n<symbol id=\"i-chevron-right\" viewBox=\"0 0 24 24\"><path d=\"m9 6 6 6-6 6\"/></symbol>\n<symbol id=\"i-chevron-down\" viewBox=\"0 0 24 24\"><path d=\"m6 9 6 6 6-6\"/></symbol>\n<symbol id=\"i-chevron-left\" viewBox=\"0 0 24 24\"><path d=\"m15 6-6 6 6 6\"/></symbol>\n<symbol id=\"i-arrow-left\" viewBox=\"0 0 24 24\"><path d=\"M19 12H5m0 0 5-5m-5 5 5 5\"/></symbol>\n<symbol id=\"i-search\" viewBox=\"0 0 24 24\"><circle cx=\"11\" cy=\"11\" r=\"7\"/><path d=\"m21 21-4.35-4.35\"/></symbol>\n<symbol id=\"i-sort\" viewBox=\"0 0 24 24\"><path d=\"M7 3v18M7 3l-4 4m4-4 4 4M17 21V3m0 18-4-4m4 4 4-4\"/></symbol>\n<symbol id=\"i-mail\" viewBox=\"0 0 24 24\"><rect x=\"3\" y=\"5\" width=\"18\" height=\"14\" rx=\"2\"/><path d=\"m4 6 8 7 8-7\"/></symbol>\n<symbol id=\"i-briefcase\" viewBox=\"0 0 24 24\"><rect x=\"3\" y=\"7\" width=\"18\" height=\"13\" rx=\"2\"/><path d=\"M8 7V5a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2\"/><path d=\"M3 12h18\"/></symbol>\n<symbol id=\"i-flag\" viewBox=\"0 0 24 24\"><path d=\"M5 3v18\"/><path d=\"M5 4h13l-3 4 3 4H5\"/></symbol>\n<symbol id=\"i-crown\" viewBox=\"0 0 24 24\"><path d=\"m3 8 4 3 5-6 5 6 4-3-2 11H5z\"/></symbol>\n<symbol id=\"i-refresh\" viewBox=\"0 0 24 24\"><path d=\"M21 12a9 9 0 1 1-3-6.7\"/><path d=\"M21 3v6h-6\"/></symbol>\n";
function ensureSpriteInjected() {
if (spriteInjected || typeof document === 'undefined')
return;
var holder = document.createElement('div');
holder.style.display = 'none';
holder.innerHTML = "<svg xmlns=\"http://www.w3.org/2000/svg\">".concat(SPRITE_SVG, "</svg>");
document.body.appendChild(holder);
spriteInjected = true;
}
var SIZE_PX = { sm: 16, md: 20, lg: 24 };
var Icon = function (_a) {
var name = _a.name, _b = _a.size, size = _b === void 0 ? 'md' : _b, className = _a.className, style = _a.style;
React.useEffect(function () {
ensureSpriteInjected();
}, []);
var px = SIZE_PX[size];
return (React.createElement("svg", { width: px, height: px, className: className, style: tslib_1.__assign({ flexShrink: 0, fill: 'none', stroke: 'currentColor', strokeWidth: 1.5, strokeLinecap: 'round', strokeLinejoin: 'round' }, style) },
React.createElement("use", { href: "#i-".concat(name) })));
};
exports.Icon = Icon;
//# sourceMappingURL=Icon.js.map
File diff suppressed because one or more lines are too long
@@ -0,0 +1,32 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.ConfirmDialog = exports.Modal = void 0;
var tslib_1 = require("tslib");
var React = tslib_1.__importStar(require("react"));
var Modal_module_scss_1 = tslib_1.__importDefault(require("./Modal.module.scss"));
var Button_1 = require("./Button");
var Modal = function (_a) {
var title = _a.title, onClose = _a.onClose, footer = _a.footer, children = _a.children;
return (React.createElement("div", { className: Modal_module_scss_1.default.overlay, onClick: function (e) { if (e.target === e.currentTarget)
onClose(); } },
React.createElement("div", { className: Modal_module_scss_1.default.box },
React.createElement("div", { className: Modal_module_scss_1.default.header },
React.createElement("div", { className: Modal_module_scss_1.default.title }, title),
React.createElement(Button_1.IconButton, { icon: "close", label: "\u0417\u0430\u043A\u0440\u044B\u0442\u044C", onClick: onClose })),
React.createElement("div", { className: Modal_module_scss_1.default.body }, children),
footer && React.createElement("div", { className: Modal_module_scss_1.default.footer }, footer))));
};
exports.Modal = Modal;
var ConfirmDialog = function (_a) {
var title = _a.title, text = _a.text, confirmLabel = _a.confirmLabel, onConfirm = _a.onConfirm, onCancel = _a.onCancel, busy = _a.busy;
return (React.createElement("div", { className: Modal_module_scss_1.default.overlay, onClick: function (e) { if (e.target === e.currentTarget)
onCancel(); } },
React.createElement("div", { className: "".concat(Modal_module_scss_1.default.box, " ").concat(Modal_module_scss_1.default.narrow) },
React.createElement("div", { className: Modal_module_scss_1.default.title }, title),
React.createElement("p", { className: Modal_module_scss_1.default.text }, text),
React.createElement("div", { className: Modal_module_scss_1.default.narrowFooter },
React.createElement(Button_1.Button, { variant: "secondary", onClick: onCancel, disabled: busy }, "\u041E\u0442\u043C\u0435\u043D\u0430"),
React.createElement(Button_1.Button, { variant: "danger", icon: "trash", onClick: onConfirm, disabled: busy }, confirmLabel)))));
};
exports.ConfirmDialog = ConfirmDialog;
//# sourceMappingURL=Modal.js.map
@@ -0,0 +1 @@
{"version":3,"file":"Modal.js","sourceRoot":"","sources":["../../../../../src/webparts/approvalAdmin/components/ui/Modal.tsx"],"names":[],"mappings":";;;;AAAA,mDAA+B;AAC/B,kFAAyC;AACzC,mCAA8C;AASvC,IAAM,KAAK,GAA0B,UAAC,EAAoC;QAAlC,KAAK,WAAA,EAAE,OAAO,aAAA,EAAE,MAAM,YAAA,EAAE,QAAQ,cAAA;IAAO,OAAA,CACpF,6BAAK,SAAS,EAAE,2BAAM,CAAC,OAAO,EAAE,OAAO,EAAE,UAAA,CAAC,IAAM,IAAI,CAAC,CAAC,MAAM,KAAK,CAAC,CAAC,aAAa;YAAE,OAAO,EAAE,CAAC,CAAC,CAAC;QAC5F,6BAAK,SAAS,EAAE,2BAAM,CAAC,GAAG;YACxB,6BAAK,SAAS,EAAE,2BAAM,CAAC,MAAM;gBAC3B,6BAAK,SAAS,EAAE,2BAAM,CAAC,KAAK,IAAG,KAAK,CAAO;gBAC3C,oBAAC,mBAAU,IAAC,IAAI,EAAC,OAAO,EAAC,KAAK,EAAC,4CAAS,EAAC,OAAO,EAAE,OAAO,GAAI,CACzD;YACN,6BAAK,SAAS,EAAE,2BAAM,CAAC,IAAI,IAAG,QAAQ,CAAO;YAC5C,MAAM,IAAI,6BAAK,SAAS,EAAE,2BAAM,CAAC,MAAM,IAAG,MAAM,CAAO,CACpD,CACF,CACP;AAXqF,CAWrF,CAAC;AAXW,QAAA,KAAK,SAWhB;AAWK,IAAM,aAAa,GAAkC,UAAC,EAAwD;QAAtD,KAAK,WAAA,EAAE,IAAI,UAAA,EAAE,YAAY,kBAAA,EAAE,SAAS,eAAA,EAAE,QAAQ,cAAA,EAAE,IAAI,UAAA;IAAO,OAAA,CACxH,6BAAK,SAAS,EAAE,2BAAM,CAAC,OAAO,EAAE,OAAO,EAAE,UAAA,CAAC,IAAM,IAAI,CAAC,CAAC,MAAM,KAAK,CAAC,CAAC,aAAa;YAAE,QAAQ,EAAE,CAAC,CAAC,CAAC;QAC7F,6BAAK,SAAS,EAAE,UAAG,2BAAM,CAAC,GAAG,cAAI,2BAAM,CAAC,MAAM,CAAE;YAC9C,6BAAK,SAAS,EAAE,2BAAM,CAAC,KAAK,IAAG,KAAK,CAAO;YAC3C,2BAAG,SAAS,EAAE,2BAAM,CAAC,IAAI,IAAG,IAAI,CAAK;YACrC,6BAAK,SAAS,EAAE,2BAAM,CAAC,YAAY;gBACjC,oBAAC,eAAM,IAAC,OAAO,EAAC,WAAW,EAAC,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,IAAI,2CAAiB;gBAC9E,oBAAC,eAAM,IAAC,OAAO,EAAC,QAAQ,EAAC,IAAI,EAAC,OAAO,EAAC,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,IAAI,IAAG,YAAY,CAAU,CAC7F,CACF,CACF,CACP;AAXyH,CAWzH,CAAC;AAXW,QAAA,aAAa,iBAWxB","sourcesContent":["import * as React from 'react';\nimport styles from './Modal.module.scss';\nimport { Button, IconButton } from './Button';\n\nexport interface IModalProps {\n title: string;\n onClose: () => void;\n footer?: React.ReactNode;\n children: React.ReactNode;\n}\n\nexport const Modal: React.FC<IModalProps> = ({ title, onClose, footer, children }) => (\n <div className={styles.overlay} onClick={e => { if (e.target === e.currentTarget) onClose(); }}>\n <div className={styles.box}>\n <div className={styles.header}>\n <div className={styles.title}>{title}</div>\n <IconButton icon=\"close\" label=\"Закрыть\" onClick={onClose} />\n </div>\n <div className={styles.body}>{children}</div>\n {footer && <div className={styles.footer}>{footer}</div>}\n </div>\n </div>\n);\n\nexport interface IConfirmDialogProps {\n title: string;\n text: string;\n confirmLabel: string;\n onConfirm: () => void;\n onCancel: () => void;\n busy?: boolean;\n}\n\nexport const ConfirmDialog: React.FC<IConfirmDialogProps> = ({ title, text, confirmLabel, onConfirm, onCancel, busy }) => (\n <div className={styles.overlay} onClick={e => { if (e.target === e.currentTarget) onCancel(); }}>\n <div className={`${styles.box} ${styles.narrow}`}>\n <div className={styles.title}>{title}</div>\n <p className={styles.text}>{text}</p>\n <div className={styles.narrowFooter}>\n <Button variant=\"secondary\" onClick={onCancel} disabled={busy}>Отмена</Button>\n <Button variant=\"danger\" icon=\"trash\" onClick={onConfirm} disabled={busy}>{confirmLabel}</Button>\n </div>\n </div>\n </div>\n);\n"]}
@@ -0,0 +1,109 @@
:root {
--navy-950: #0A1B30;
--navy-900: #0F2440;
--navy-800: #1B3A5C;
--navy-700: #244B73;
--navy-600: #2D5C8A;
--navy-100: #E8EEF4;
--navy-50: #F4F7FA;
--gold-600: #B8881A;
--gold-500: #D4A01E;
--gold-400: #E5B635;
--gold-300: #F0CC5E;
--gold-100: #FDF5E0;
--gold-50: #FEFBF0;
--gray-900: #111827;
--gray-700: #374151;
--gray-500: #6B7280;
--gray-400: #9CA3AF;
--gray-300: #D1D5DB;
--gray-200: #E5E7EB;
--gray-100: #F3F4F6;
--gray-50: #F9FAFB;
--red-500: #DC2626;
--red-50: #FEF2F2;
--green-600: #15803D;
--green-50: #F0FDF4;
--blue-600: #2563EB;
--blue-50: #EFF6FF;
--amber-700: #854D0E;
--sh-1: 0 1px 2px rgba(15, 36, 64, 0.04), 0 1px 3px rgba(15, 36, 64, 0.06);
--sh-2: 0 4px 16px rgba(15, 36, 64, 0.07);
--sh-3: 0 12px 32px rgba(15, 36, 64, 0.1);
--sh-gold: 0 4px 20px rgba(212, 160, 30, 0.18);
--sh-modal: 0 24px 70px rgba(10, 27, 48, 0.25);
--r-sm: 6px;
--r-md: 10px;
--r-lg: 14px;
}
.overlay {
position: fixed;
inset: 0;
background: rgba(10, 27, 48, 0.5);
display: flex;
align-items: center;
justify-content: center;
z-index: 300;
backdrop-filter: blur(6px);
padding: 20px;
}
.box {
background: #fff;
border-radius: 14px;
box-shadow: 0 24px 70px rgba(10, 27, 48, 0.25);
width: 480px;
max-width: 100%;
max-height: calc(100vh - 40px);
overflow-y: auto;
border-top: 3px solid #D4A01E;
}
.narrow {
width: 420px;
text-align: center;
padding: 30px;
}
.header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 22px 26px 0;
}
.title {
font-family: "Lora", Georgia, serif;
font-size: 19px;
font-weight: 600;
color: #0F2440;
}
.body {
padding: 18px 26px 26px;
}
.narrowBody {
padding: 0;
}
.text {
font-size: 13px;
color: #6B7280;
margin-bottom: 24px;
line-height: 1.6;
}
.footer {
display: flex;
justify-content: flex-end;
gap: 10px;
padding: 16px 26px 22px;
}
.narrowFooter {
justify-content: center;
padding: 0;
margin-top: 24px;
}
@@ -0,0 +1,27 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.PageHeader = exports.Breadcrumbs = void 0;
var tslib_1 = require("tslib");
var React = tslib_1.__importStar(require("react"));
var PageChrome_module_scss_1 = tslib_1.__importDefault(require("./PageChrome.module.scss"));
var Icon_1 = require("./Icon");
var Breadcrumbs = function (_a) {
var items = _a.items;
return (React.createElement("div", { className: PageChrome_module_scss_1.default.bread }, items.map(function (item, i) { return (React.createElement(React.Fragment, { key: i },
i > 0 && React.createElement("span", { className: PageChrome_module_scss_1.default.sep }, "\u25C6"),
item.onClick ? (React.createElement("button", { className: PageChrome_module_scss_1.default.breadLink, onClick: item.onClick }, item.label)) : (React.createElement("span", null, item.label)))); })));
};
exports.Breadcrumbs = Breadcrumbs;
var PageHeader = function (_a) {
var icon = _a.icon, subtitle = _a.subtitle, title = _a.title, actions = _a.actions;
return (React.createElement("div", { className: PageChrome_module_scss_1.default.ph },
React.createElement("div", { className: PageChrome_module_scss_1.default.phLeft },
React.createElement("div", { className: PageChrome_module_scss_1.default.phIcon },
React.createElement(Icon_1.Icon, { name: icon })),
React.createElement("div", null,
React.createElement("div", { className: PageChrome_module_scss_1.default.phSub }, subtitle),
React.createElement("div", { className: PageChrome_module_scss_1.default.phTitle }, title))),
actions && React.createElement("div", { className: PageChrome_module_scss_1.default.phRight }, actions)));
};
exports.PageHeader = PageHeader;
//# sourceMappingURL=PageChrome.js.map
@@ -0,0 +1 @@
{"version":3,"file":"PageChrome.js","sourceRoot":"","sources":["../../../../../src/webparts/approvalAdmin/components/ui/PageChrome.tsx"],"names":[],"mappings":";;;;AAAA,mDAA+B;AAC/B,4FAA8C;AAC9C,+BAAwC;AAOjC,IAAM,WAAW,GAA2C,UAAC,EAAS;QAAP,KAAK,WAAA;IAAO,OAAA,CAChF,6BAAK,SAAS,EAAE,gCAAM,CAAC,KAAK,IACzB,KAAK,CAAC,GAAG,CAAC,UAAC,IAAI,EAAE,CAAC,IAAK,OAAA,CACtB,oBAAC,KAAK,CAAC,QAAQ,IAAC,GAAG,EAAE,CAAC;QACnB,CAAC,GAAG,CAAC,IAAI,8BAAM,SAAS,EAAE,gCAAM,CAAC,GAAG,aAAgB;QACpD,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,CACd,gCAAQ,SAAS,EAAE,gCAAM,CAAC,SAAS,EAAE,OAAO,EAAE,IAAI,CAAC,OAAO,IAAG,IAAI,CAAC,KAAK,CAAU,CAClF,CAAC,CAAC,CAAC,CACF,kCAAO,IAAI,CAAC,KAAK,CAAQ,CAC1B,CACc,CAClB,EATuB,CASvB,CAAC,CACE,CACP;AAbiF,CAajF,CAAC;AAbW,QAAA,WAAW,eAatB;AASK,IAAM,UAAU,GAA+B,UAAC,EAAkC;QAAhC,IAAI,UAAA,EAAE,QAAQ,cAAA,EAAE,KAAK,WAAA,EAAE,OAAO,aAAA;IAAO,OAAA,CAC5F,6BAAK,SAAS,EAAE,gCAAM,CAAC,EAAE;QACvB,6BAAK,SAAS,EAAE,gCAAM,CAAC,MAAM;YAC3B,6BAAK,SAAS,EAAE,gCAAM,CAAC,MAAM;gBAAE,oBAAC,WAAI,IAAC,IAAI,EAAE,IAAI,GAAI,CAAM;YACzD;gBACE,6BAAK,SAAS,EAAE,gCAAM,CAAC,KAAK,IAAG,QAAQ,CAAO;gBAC9C,6BAAK,SAAS,EAAE,gCAAM,CAAC,OAAO,IAAG,KAAK,CAAO,CACzC,CACF;QACL,OAAO,IAAI,6BAAK,SAAS,EAAE,gCAAM,CAAC,OAAO,IAAG,OAAO,CAAO,CACvD,CACP;AAX6F,CAW7F,CAAC;AAXW,QAAA,UAAU,cAWrB","sourcesContent":["import * as React from 'react';\nimport styles from './PageChrome.module.scss';\nimport { Icon, IconName } from './Icon';\n\nexport interface IBreadcrumbItem {\n label: string;\n onClick?: () => void;\n}\n\nexport const Breadcrumbs: React.FC<{ items: IBreadcrumbItem[] }> = ({ items }) => (\n <div className={styles.bread}>\n {items.map((item, i) => (\n <React.Fragment key={i}>\n {i > 0 && <span className={styles.sep}>&#9670;</span>}\n {item.onClick ? (\n <button className={styles.breadLink} onClick={item.onClick}>{item.label}</button>\n ) : (\n <span>{item.label}</span>\n )}\n </React.Fragment>\n ))}\n </div>\n);\n\nexport interface IPageHeaderProps {\n icon: IconName;\n subtitle: string;\n title: string;\n actions?: React.ReactNode;\n}\n\nexport const PageHeader: React.FC<IPageHeaderProps> = ({ icon, subtitle, title, actions }) => (\n <div className={styles.ph}>\n <div className={styles.phLeft}>\n <div className={styles.phIcon}><Icon name={icon} /></div>\n <div>\n <div className={styles.phSub}>{subtitle}</div>\n <div className={styles.phTitle}>{title}</div>\n </div>\n </div>\n {actions && <div className={styles.phRight}>{actions}</div>}\n </div>\n);\n"]}
@@ -0,0 +1,148 @@
:root {
--navy-950: #0A1B30;
--navy-900: #0F2440;
--navy-800: #1B3A5C;
--navy-700: #244B73;
--navy-600: #2D5C8A;
--navy-100: #E8EEF4;
--navy-50: #F4F7FA;
--gold-600: #B8881A;
--gold-500: #D4A01E;
--gold-400: #E5B635;
--gold-300: #F0CC5E;
--gold-100: #FDF5E0;
--gold-50: #FEFBF0;
--gray-900: #111827;
--gray-700: #374151;
--gray-500: #6B7280;
--gray-400: #9CA3AF;
--gray-300: #D1D5DB;
--gray-200: #E5E7EB;
--gray-100: #F3F4F6;
--gray-50: #F9FAFB;
--red-500: #DC2626;
--red-50: #FEF2F2;
--green-600: #15803D;
--green-50: #F0FDF4;
--blue-600: #2563EB;
--blue-50: #EFF6FF;
--amber-700: #854D0E;
--sh-1: 0 1px 2px rgba(15, 36, 64, 0.04), 0 1px 3px rgba(15, 36, 64, 0.06);
--sh-2: 0 4px 16px rgba(15, 36, 64, 0.07);
--sh-3: 0 12px 32px rgba(15, 36, 64, 0.1);
--sh-gold: 0 4px 20px rgba(212, 160, 30, 0.18);
--sh-modal: 0 24px 70px rgba(10, 27, 48, 0.25);
--r-sm: 6px;
--r-md: 10px;
--r-lg: 14px;
}
.bread {
display: flex;
align-items: center;
gap: 7px;
font-size: 12px;
color: #9CA3AF;
margin-bottom: 14px;
letter-spacing: 0.2px;
flex-wrap: wrap;
}
.breadLink {
color: #244B73;
text-decoration: none;
background: none;
border: 0;
padding: 0;
font: inherit;
cursor: pointer;
}
.breadLink:hover {
color: #B8881A;
}
.sep {
color: #E5B635;
font-size: 10px;
}
.ph {
background: linear-gradient(135deg, #0A1B30 0%, #1B3A5C 85%);
border-radius: 14px;
padding: 26px 30px;
position: relative;
overflow: hidden;
display: flex;
align-items: center;
justify-content: space-between;
box-shadow: 0 4px 16px rgba(15, 36, 64, 0.07);
margin-bottom: 24px;
flex-wrap: wrap;
gap: 14px;
}
.ph::before {
content: "";
position: absolute;
left: 0;
right: 0;
bottom: 0;
height: 2px;
background: linear-gradient(90deg, #D4A01E, transparent 60%);
}
.ph::after {
content: "";
position: absolute;
right: -60px;
top: -60px;
width: 220px;
height: 220px;
border-radius: 50%;
background: radial-gradient(circle, rgba(212, 160, 30, 0.14), transparent 65%);
}
.phLeft {
display: flex;
align-items: center;
gap: 18px;
position: relative;
z-index: 1;
}
.phIcon {
width: 46px;
height: 46px;
border-radius: 10px;
background: rgba(212, 160, 30, 0.14);
border: 1px solid rgba(212, 160, 30, 0.3);
color: #F0CC5E;
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
}
.phSub {
font-size: 10.5px;
color: rgba(255, 255, 255, 0.55);
text-transform: uppercase;
letter-spacing: 1.8px;
font-weight: 500;
}
.phTitle {
font-family: "Lora", Georgia, serif;
font-size: 24px;
font-weight: 600;
color: #fff;
margin-top: 3px;
letter-spacing: 0.2px;
}
.phRight {
display: flex;
gap: 8px;
align-items: center;
flex-wrap: wrap;
position: relative;
z-index: 1;
}
@@ -0,0 +1,13 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
var tslib_1 = require("tslib");
tslib_1.__exportStar(require("./Icon"), exports);
tslib_1.__exportStar(require("./Button"), exports);
tslib_1.__exportStar(require("./Badge"), exports);
tslib_1.__exportStar(require("./Alert"), exports);
tslib_1.__exportStar(require("./PageChrome"), exports);
tslib_1.__exportStar(require("./Card"), exports);
tslib_1.__exportStar(require("./DataTable"), exports);
tslib_1.__exportStar(require("./Modal"), exports);
tslib_1.__exportStar(require("./FormField"), exports);
//# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../../src/webparts/approvalAdmin/components/ui/index.ts"],"names":[],"mappings":";;;AAAA,iDAAuB;AACvB,mDAAyB;AACzB,kDAAwB;AACxB,kDAAwB;AACxB,uDAA6B;AAC7B,iDAAuB;AACvB,sDAA4B;AAC5B,kDAAwB;AACxB,sDAA4B","sourcesContent":["export * from './Icon';\nexport * from './Button';\nexport * from './Badge';\nexport * from './Alert';\nexport * from './PageChrome';\nexport * from './Card';\nexport * from './DataTable';\nexport * from './Modal';\nexport * from './FormField';\n"]}
@@ -0,0 +1,69 @@
"use strict";
// Зеркало DTO и enum'ов backend (Tnsu.ApprovalRoute.Api / Application / Domain).
// Имена и форма типов должны 1:1 соответствовать C#-стороне — см.
// backend/src/Tnsu.ApprovalRoute.Domain/Enums/*.cs и
// backend/src/Tnsu.ApprovalRoute.Application/**/*.cs
var _a, _b, _c, _d;
Object.defineProperty(exports, "__esModule", { value: true });
exports.headTypeLabels = exports.projectRoleLabels = exports.routeStepStatusLabels = exports.routeTypeLabels = exports.HeadType = exports.ProjectRole = exports.RouteStepStatus = exports.RouteType = void 0;
exports.detectExecutorKind = detectExecutorKind;
var RouteType;
(function (RouteType) {
RouteType["None"] = "None";
RouteType["Parallel"] = "Parallel";
RouteType["Sequential"] = "Sequential";
})(RouteType || (exports.RouteType = RouteType = {}));
var RouteStepStatus;
(function (RouteStepStatus) {
RouteStepStatus["None"] = "None";
RouteStepStatus["Approval"] = "Approval";
RouteStepStatus["Execution"] = "Execution";
})(RouteStepStatus || (exports.RouteStepStatus = RouteStepStatus = {}));
var ProjectRole;
(function (ProjectRole) {
ProjectRole["None"] = "None";
ProjectRole["ProjectManager"] = "ProjectManager";
ProjectRole["Planner"] = "Planner";
ProjectRole["ManagerHSE"] = "ManagerHSE";
ProjectRole["Security"] = "Security";
})(ProjectRole || (exports.ProjectRole = ProjectRole = {}));
var HeadType;
(function (HeadType) {
HeadType["None"] = "None";
HeadType["Chief"] = "Chief";
HeadType["DirectionHead"] = "DirectionHead";
HeadType["DirectorTansu"] = "DirectorTansu";
})(HeadType || (exports.HeadType = HeadType = {}));
exports.routeTypeLabels = (_a = {},
_a[RouteType.None] = 'Не задан',
_a[RouteType.Parallel] = 'Параллельный',
_a[RouteType.Sequential] = 'Последовательный',
_a);
exports.routeStepStatusLabels = (_b = {},
_b[RouteStepStatus.None] = 'Не задан',
_b[RouteStepStatus.Approval] = 'Согласование',
_b[RouteStepStatus.Execution] = 'Исполнение',
_b);
exports.projectRoleLabels = (_c = {},
_c[ProjectRole.None] = '—',
_c[ProjectRole.ProjectManager] = 'Руководитель проекта',
_c[ProjectRole.Planner] = 'Планировщик',
_c[ProjectRole.ManagerHSE] = 'Менеджер HSE',
_c[ProjectRole.Security] = 'Безопасность',
_c);
exports.headTypeLabels = (_d = {},
_d[HeadType.None] = '—',
_d[HeadType.Chief] = 'Непосредственный руководитель',
_d[HeadType.DirectionHead] = 'Руководитель направления',
_d[HeadType.DirectorTansu] = 'Директор Tansu',
_d);
function detectExecutorKind(step) {
if (step.executorEmail)
return 'email';
if (step.position)
return 'position';
if (step.projectRole && step.projectRole !== ProjectRole.None)
return 'projectRole';
return 'headType';
}
//# sourceMappingURL=index.js.map
File diff suppressed because one or more lines are too long
@@ -0,0 +1,78 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.ApprovalRouteApi = void 0;
var tslib_1 = require("tslib");
// Тонкий 1:1 фасад над контроллерами бэкенда:
// backend/src/Tnsu.ApprovalRoute.Api/Controllers/{RequestTypes,TemplateRoutes,TemplateRouteSteps}Controller.cs
var ApprovalRouteApi = /** @class */ (function () {
function ApprovalRouteApi(http) {
var _this = this;
this.http = http;
this.requestTypes = {
list: function () {
return _this.http.get('/api/request-types');
},
get: function (id) {
return _this.http.get("/api/request-types/".concat(id));
},
create: function (body) {
return _this.http.post('/api/request-types', body);
},
update: function (id, body) {
return _this.http.put("/api/request-types/".concat(id), body);
},
patch: function (id, body) {
return _this.http.patch("/api/request-types/".concat(id), body);
},
remove: function (id) {
return _this.http.delete("/api/request-types/".concat(id));
}
};
this.templateRoutes = {
list: function (requestTypeId) {
return _this.http.get('/api/template-routes', { requestTypeId: requestTypeId });
},
get: function (id) {
return _this.http.get("/api/template-routes/".concat(id));
},
create: function (body) {
return _this.http.post('/api/template-routes', body);
},
update: function (id, body) {
return _this.http.put("/api/template-routes/".concat(id), body);
},
patch: function (id, body) {
return _this.http.patch("/api/template-routes/".concat(id), body);
},
remove: function (id) {
return _this.http.delete("/api/template-routes/".concat(id));
},
resolve: function (query) {
return _this.http.get('/api/template-routes/resolve', tslib_1.__assign({}, query));
}
};
this.templateRouteSteps = {
list: function (routeId) {
return _this.http.get("/api/template-routes/".concat(routeId, "/steps"));
},
get: function (routeId, id) {
return _this.http.get("/api/template-routes/".concat(routeId, "/steps/").concat(id));
},
create: function (routeId, body) {
return _this.http.post("/api/template-routes/".concat(routeId, "/steps"), body);
},
update: function (routeId, id, body) {
return _this.http.put("/api/template-routes/".concat(routeId, "/steps/").concat(id), body);
},
patch: function (routeId, id, body) {
return _this.http.patch("/api/template-routes/".concat(routeId, "/steps/").concat(id), body);
},
remove: function (routeId, id) {
return _this.http.delete("/api/template-routes/".concat(routeId, "/steps/").concat(id));
}
};
}
return ApprovalRouteApi;
}());
exports.ApprovalRouteApi = ApprovalRouteApi;
//# sourceMappingURL=api.js.map
File diff suppressed because one or more lines are too long
@@ -0,0 +1,172 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.DevApiHttpClient = exports.AadApiHttpClient = exports.ApiClientError = void 0;
var tslib_1 = require("tslib");
var sp_http_1 = require("@microsoft/sp-http");
var ApiClientError = /** @class */ (function (_super) {
tslib_1.__extends(ApiClientError, _super);
function ApiClientError(status, messages) {
var _this = _super.call(this, messages.join('; ') || "HTTP ".concat(status)) || this;
_this.status = status;
_this.messages = messages;
return _this;
}
return ApiClientError;
}(Error));
exports.ApiClientError = ApiClientError;
function buildUrl(baseUrl, path, query) {
var normalizedBase = baseUrl.endsWith('/') ? baseUrl : "".concat(baseUrl, "/");
var url = new URL(path.replace(/^\//, ''), normalizedBase);
if (query) {
Object.keys(query).forEach(function (key) {
var value = query[key];
if (value !== undefined && value !== null && value !== '') {
url.searchParams.set(key, String(value));
}
});
}
return url.toString();
}
function parseResponse(response) {
return tslib_1.__awaiter(this, void 0, void 0, function () {
var text, json, messages;
var _a, _b;
return tslib_1.__generator(this, function (_c) {
switch (_c.label) {
case 0:
if (response.status === 204) {
return [2 /*return*/, undefined];
}
return [4 /*yield*/, response.text()];
case 1:
text = _c.sent();
try {
json = text ? JSON.parse(text) : undefined;
}
catch (_d) {
// Ответ пришёл не в конверте ApiResponse/ApiError (например, "сырая" страница
// необработанного исключения ASP.NET Core или ошибка от прокси перед API).
if (!response.ok) {
throw new ApiClientError(response.status, [text || "HTTP ".concat(response.status)]);
}
throw new ApiClientError(response.status, ['Сервер вернул не-JSON ответ.']);
}
if (!response.ok) {
messages = (_a = json === null || json === void 0 ? void 0 : json.messages) !== null && _a !== void 0 ? _a : ["HTTP ".concat(response.status)];
throw new ApiClientError(response.status, messages);
}
return [2 /*return*/, (_b = json === null || json === void 0 ? void 0 : json.data) !== null && _b !== void 0 ? _b : undefined];
}
});
});
}
/**
* Прод/staging: вызовы через AAD-защищённое SPFx-подключение (AadHttpClient).
* Требует webApiPermissionRequests в package-solution.json и админского
* согласия в SharePoint admin center → Advanced → API access.
*/
var AadApiHttpClient = /** @class */ (function () {
function AadApiHttpClient(client, baseUrl) {
this.client = client;
this.baseUrl = baseUrl;
}
AadApiHttpClient.prototype.get = function (path, query) {
var url = buildUrl(this.baseUrl, path, query);
return this.client
.get(url, sp_http_1.AadHttpClient.configurations.v1)
.then(function (r) { return parseResponse(r); });
};
AadApiHttpClient.prototype.post = function (path, body) {
return this.send('POST', path, body);
};
AadApiHttpClient.prototype.put = function (path, body) {
return this.send('PUT', path, body);
};
AadApiHttpClient.prototype.patch = function (path, body) {
return this.send('PATCH', path, body);
};
AadApiHttpClient.prototype.delete = function (path) {
return tslib_1.__awaiter(this, void 0, void 0, function () {
return tslib_1.__generator(this, function (_a) {
switch (_a.label) {
case 0: return [4 /*yield*/, this.send('DELETE', path)];
case 1:
_a.sent();
return [2 /*return*/];
}
});
});
};
AadApiHttpClient.prototype.send = function (method, path, body) {
var url = buildUrl(this.baseUrl, path);
return this.client
.fetch(url, sp_http_1.AadHttpClient.configurations.v1, {
method: method,
headers: { 'Content-Type': 'application/json' },
body: body !== undefined ? JSON.stringify(body) : undefined
})
.then(function (r) { return parseResponse(r); });
};
return AadApiHttpClient;
}());
exports.AadApiHttpClient = AadApiHttpClient;
/**
* Локальная отладка: обычный fetch с заголовком X-Dev-User-Email.
* Работает только если бэкенд поднят с пустыми Entra:TenantId/Entra:Audience
* (см. backend/.../Auth/AuthSetup.cs — тогда включается DevAuthHandler).
*/
var DevApiHttpClient = /** @class */ (function () {
function DevApiHttpClient(baseUrl, devUserEmail) {
this.baseUrl = baseUrl;
this.devUserEmail = devUserEmail;
}
DevApiHttpClient.prototype.get = function (path, query) {
return this.send('GET', path, undefined, query);
};
DevApiHttpClient.prototype.post = function (path, body) {
return this.send('POST', path, body);
};
DevApiHttpClient.prototype.put = function (path, body) {
return this.send('PUT', path, body);
};
DevApiHttpClient.prototype.patch = function (path, body) {
return this.send('PATCH', path, body);
};
DevApiHttpClient.prototype.delete = function (path) {
return tslib_1.__awaiter(this, void 0, void 0, function () {
return tslib_1.__generator(this, function (_a) {
switch (_a.label) {
case 0: return [4 /*yield*/, this.send('DELETE', path)];
case 1:
_a.sent();
return [2 /*return*/];
}
});
});
};
DevApiHttpClient.prototype.send = function (method, path, body, query) {
return tslib_1.__awaiter(this, void 0, void 0, function () {
var url, response;
return tslib_1.__generator(this, function (_a) {
switch (_a.label) {
case 0:
url = buildUrl(this.baseUrl, path, query);
return [4 /*yield*/, fetch(url, {
method: method,
headers: {
'Content-Type': 'application/json',
'X-Dev-User-Email': this.devUserEmail
},
body: body !== undefined ? JSON.stringify(body) : undefined
})];
case 1:
response = _a.sent();
return [2 /*return*/, parseResponse(response)];
}
});
});
};
return DevApiHttpClient;
}());
exports.DevApiHttpClient = DevApiHttpClient;
//# sourceMappingURL=httpClient.js.map
File diff suppressed because one or more lines are too long