каркас вроде есть
This commit is contained in:
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"Entra": {
|
||||||
|
"TenantId": "",
|
||||||
|
"Audience": ""
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -32,6 +32,12 @@
|
|||||||
"id": "be919a96-acbc-43a8-9a43-2dba926e6cd2",
|
"id": "be919a96-acbc-43a8-9a43-2dba926e6cd2",
|
||||||
"version": "1.0.0.0"
|
"version": "1.0.0.0"
|
||||||
}
|
}
|
||||||
|
],
|
||||||
|
"webApiPermissionRequests": [
|
||||||
|
{
|
||||||
|
"resource": "api://9232d9e2-9807-4753-808f-07250e5474b0",
|
||||||
|
"scope": "user_impersonation"
|
||||||
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"paths": {
|
"paths": {
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<testsuites name="jest tests" tests="0" failures="0" errors="0" time="0.015">
|
<testsuites name="jest tests" tests="0" failures="0" errors="0" time="0.003">
|
||||||
</testsuites>
|
</testsuites>
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
<?xml version="1.0" ?>
|
<?xml version="1.0" ?>
|
||||||
<!DOCTYPE coverage SYSTEM "http://cobertura.sourceforge.net/xml/coverage-04.dtd">
|
<!DOCTYPE coverage SYSTEM "http://cobertura.sourceforge.net/xml/coverage-04.dtd">
|
||||||
<coverage lines-valid="0" lines-covered="0" line-rate="NaN" branches-valid="0" branches-covered="0" branch-rate="NaN" timestamp="1785243495459" complexity="0" version="0.1">
|
<coverage lines-valid="0" lines-covered="0" line-rate="NaN" branches-valid="0" branches-covered="0" branch-rate="NaN" timestamp="1785308991254" complexity="0" version="0.1">
|
||||||
<sources>
|
<sources>
|
||||||
<source>C:\work\git\tnsu-approval-route\frontend</source>
|
<source>C:\work\git\tnsu-approval-route\frontend</source>
|
||||||
</sources>
|
</sources>
|
||||||
|
|||||||
@@ -86,7 +86,7 @@
|
|||||||
<div class='footer quiet pad2 space-top1 center small'>
|
<div class='footer quiet pad2 space-top1 center small'>
|
||||||
Code coverage generated by
|
Code coverage generated by
|
||||||
<a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
|
<a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
|
||||||
at 2026-07-28T12:58:15.470Z
|
at 2026-07-29T07:09:51.260Z
|
||||||
</div>
|
</div>
|
||||||
<script src="prettify.js"></script>
|
<script src="prettify.js"></script>
|
||||||
<script>
|
<script>
|
||||||
|
|||||||
@@ -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_core_library_1 = require("@microsoft/sp-core-library");
|
||||||
var sp_property_pane_1 = require("@microsoft/sp-property-pane");
|
var sp_property_pane_1 = require("@microsoft/sp-property-pane");
|
||||||
var sp_webpart_base_1 = require("@microsoft/sp-webpart-base");
|
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 ApprovalAdmin_1 = tslib_1.__importDefault(require("./components/ApprovalAdmin"));
|
||||||
var ApprovalAdminWebPart = /** @class */ (function (_super) {
|
var ApprovalAdminWebPart = /** @class */ (function (_super) {
|
||||||
tslib_1.__extends(ApprovalAdminWebPart, _super);
|
tslib_1.__extends(ApprovalAdminWebPart, _super);
|
||||||
function ApprovalAdminWebPart() {
|
function ApprovalAdminWebPart() {
|
||||||
var _this = _super !== null && _super.apply(this, arguments) || this;
|
return _super !== null && _super.apply(this, arguments) || this;
|
||||||
_this._isDarkTheme = false;
|
|
||||||
_this._environmentMessage = '';
|
|
||||||
return _this;
|
|
||||||
}
|
}
|
||||||
ApprovalAdminWebPart.prototype.render = function () {
|
ApprovalAdminWebPart.prototype.render = function () {
|
||||||
var element = React.createElement(ApprovalAdmin_1.default, {
|
var element = React.createElement(ApprovalAdmin_1.default, {
|
||||||
description: this.properties.description,
|
context: this.context,
|
||||||
isDarkTheme: this._isDarkTheme,
|
apiBaseUrl: this.properties.apiBaseUrl,
|
||||||
environmentMessage: this._environmentMessage,
|
useDevAuth: this.properties.useDevAuth,
|
||||||
hasTeamsContext: !!this.context.sdks.microsoftTeams,
|
devUserEmail: this.properties.devUserEmail,
|
||||||
userDisplayName: this.context.pageContext.user.displayName
|
aadResourceId: this.properties.aadResourceId
|
||||||
});
|
});
|
||||||
ReactDom.render(element, this.domElement);
|
ReactDom.render(element, this.domElement);
|
||||||
};
|
};
|
||||||
ApprovalAdminWebPart.prototype.onInit = function () {
|
ApprovalAdminWebPart.prototype.onInit = function () {
|
||||||
var _this = this;
|
var _a;
|
||||||
return this._getEnvironmentMessage().then(function (message) {
|
this.properties.apiBaseUrl = this.properties.apiBaseUrl || 'https://localhost:5443';
|
||||||
_this._environmentMessage = message;
|
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';
|
||||||
ApprovalAdminWebPart.prototype._getEnvironmentMessage = function () {
|
return Promise.resolve();
|
||||||
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);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
ApprovalAdminWebPart.prototype.onDispose = function () {
|
ApprovalAdminWebPart.prototype.onDispose = function () {
|
||||||
ReactDom.unmountComponentAtNode(this.domElement);
|
ReactDom.unmountComponentAtNode(this.domElement);
|
||||||
@@ -84,14 +45,28 @@ var ApprovalAdminWebPart = /** @class */ (function (_super) {
|
|||||||
pages: [
|
pages: [
|
||||||
{
|
{
|
||||||
header: {
|
header: {
|
||||||
description: strings.PropertyPaneDescription
|
description: 'Подключение к Tnsu.ApprovalRoute.Api'
|
||||||
},
|
},
|
||||||
groups: [
|
groups: [
|
||||||
{
|
{
|
||||||
groupName: strings.BasicGroupName,
|
groupName: 'API',
|
||||||
groupFields: [
|
groupFields: [
|
||||||
(0, sp_property_pane_1.PropertyPaneTextField)('description', {
|
(0, sp_property_pane_1.PropertyPaneTextField)('apiBaseUrl', {
|
||||||
label: strings.DescriptionFieldLabel
|
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" },
|
"description": { "default": "approval-admin description" },
|
||||||
"officeFabricIconFontName": "Page",
|
"officeFabricIconFontName": "Page",
|
||||||
"properties": {
|
"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 tslib_1 = require("tslib");
|
||||||
var React = tslib_1.__importStar(require("react"));
|
var React = tslib_1.__importStar(require("react"));
|
||||||
var ApprovalAdmin_module_scss_1 = tslib_1.__importDefault(require("./ApprovalAdmin.module.scss"));
|
var ApprovalAdmin_module_scss_1 = tslib_1.__importDefault(require("./ApprovalAdmin.module.scss"));
|
||||||
var sp_lodash_subset_1 = require("@microsoft/sp-lodash-subset");
|
var AdminApp_1 = require("./app/AdminApp");
|
||||||
var ApprovalAdmin = /** @class */ (function (_super) {
|
var api_1 = require("../services/api");
|
||||||
tslib_1.__extends(ApprovalAdmin, _super);
|
var httpClient_1 = require("../services/httpClient");
|
||||||
function ApprovalAdmin() {
|
var FONTS_LINK_ID = 'tnsu-approval-admin-fonts';
|
||||||
return _super !== null && _super.apply(this, arguments) || this;
|
function ensureFontsLoaded() {
|
||||||
}
|
if (typeof document === 'undefined' || document.getElementById(FONTS_LINK_ID))
|
||||||
ApprovalAdmin.prototype.render = function () {
|
return;
|
||||||
var _a = this.props, description = _a.description, isDarkTheme = _a.isDarkTheme, environmentMessage = _a.environmentMessage, hasTeamsContext = _a.hasTeamsContext, userDisplayName = _a.userDisplayName;
|
var link = document.createElement('link');
|
||||||
return (React.createElement("section", { className: "".concat(ApprovalAdmin_module_scss_1.default.approvalAdmin, " ").concat(hasTeamsContext ? ApprovalAdmin_module_scss_1.default.teams : '') },
|
link.id = FONTS_LINK_ID;
|
||||||
React.createElement("div", { className: ApprovalAdmin_module_scss_1.default.welcome },
|
link.rel = 'stylesheet';
|
||||||
React.createElement("img", { alt: "", src: isDarkTheme ? require('../assets/welcome-dark.png') : require('../assets/welcome-light.png'), className: ApprovalAdmin_module_scss_1.default.welcomeImage }),
|
link.href = 'https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=Lora:wght@500;600;700&display=swap';
|
||||||
React.createElement("h2", null,
|
document.head.appendChild(link);
|
||||||
"Well done, ",
|
}
|
||||||
(0, sp_lodash_subset_1.escape)(userDisplayName),
|
function createHttpClient(props) {
|
||||||
"!"),
|
return tslib_1.__awaiter(this, void 0, void 0, function () {
|
||||||
React.createElement("div", null, environmentMessage),
|
var aadClient;
|
||||||
React.createElement("div", null,
|
return tslib_1.__generator(this, function (_a) {
|
||||||
"Web part property value: ",
|
switch (_a.label) {
|
||||||
React.createElement("strong", null, (0, sp_lodash_subset_1.escape)(description)))),
|
case 0:
|
||||||
React.createElement("div", null,
|
if (props.useDevAuth) {
|
||||||
React.createElement("h3", null, "Welcome to SharePoint Framerk!"),
|
return [2 /*return*/, new httpClient_1.DevApiHttpClient(props.apiBaseUrl, props.devUserEmail || 'dev@tnsu.kz')];
|
||||||
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:"),
|
return [4 /*yield*/, props.context.aadHttpClientFactory.getClient(props.aadResourceId)];
|
||||||
React.createElement("ul", { className: ApprovalAdmin_module_scss_1.default.links },
|
case 1:
|
||||||
React.createElement("li", null,
|
aadClient = _a.sent();
|
||||||
React.createElement("a", { href: "https://aka.ms/spfx", target: "_blank", rel: "noreferrer" }, "SharePoint Framework Overview")),
|
return [2 /*return*/, new httpClient_1.AadApiHttpClient(aadClient, props.apiBaseUrl)];
|
||||||
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,
|
var ApprovalAdmin = function (props) {
|
||||||
React.createElement("a", { href: "https://aka.ms/spfx-yeoman-viva", target: "_blank", rel: "noreferrer" }, "Build for Microsoft Viva Connections using SharePoint Framework")),
|
var _a = React.useState(), api = _a[0], setApi = _a[1];
|
||||||
React.createElement("li", null,
|
var _b = React.useState(), error = _b[0], setError = _b[1];
|
||||||
React.createElement("a", { href: "https://aka.ms/spfx-yeoman-store", target: "_blank", rel: "noreferrer" }, "Publish SharePoint Framework applications to the marketplace")),
|
React.useEffect(function () {
|
||||||
React.createElement("li", null,
|
ensureFontsLoaded();
|
||||||
React.createElement("a", { href: "https://aka.ms/spfx-yeoman-api", target: "_blank", rel: "noreferrer" }, "SharePoint Framework API reference")),
|
}, []);
|
||||||
React.createElement("li", null,
|
React.useEffect(function () {
|
||||||
React.createElement("a", { href: "https://aka.ms/m365pnp", target: "_blank", rel: "noreferrer" }, "Microsoft 365 Developer Community"))))));
|
var cancelled = false;
|
||||||
};
|
setApi(undefined);
|
||||||
return ApprovalAdmin;
|
setError(undefined);
|
||||||
}(React.Component));
|
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;
|
exports.default = ApprovalAdmin;
|
||||||
//# sourceMappingURL=ApprovalAdmin.js.map
|
//# 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'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"]}
|
||||||
+47
-27
@@ -1,32 +1,52 @@
|
|||||||
/*
|
:root {
|
||||||
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
|
--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 {
|
.approvalAdmin {
|
||||||
overflow: hidden;
|
font-family: "Inter", -apple-system, "Segoe UI", sans-serif;
|
||||||
padding: 1em;
|
|
||||||
color: "[theme:bodyText, default: #323130]";
|
|
||||||
color: var(--bodyText);
|
|
||||||
}
|
|
||||||
.approvalAdmin.teams {
|
|
||||||
font-family: "Segoe UI", -apple-system, BlinkMacSystemFont, "Roboto", "Helvetica Neue", sans-serif;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.welcome {
|
.stateBox {
|
||||||
|
padding: 40px 24px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
color: #6B7280;
|
||||||
|
font-size: 13.5px;
|
||||||
.welcomeImage {
|
background: #F9FAFB;
|
||||||
width: 100%;
|
border-radius: 14px;
|
||||||
max-width: 420px;
|
border: 1px solid #E5E7EB;
|
||||||
}
|
|
||||||
|
|
||||||
.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);
|
|
||||||
}
|
}
|
||||||
@@ -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"]}
|
||||||
+105
@@ -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
|
||||||
+1
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
|
||||||
+1
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}>◆</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"]}
|
||||||
+148
@@ -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
@@ -15,6 +15,7 @@
|
|||||||
"@fluentui/react": "^8.106.4",
|
"@fluentui/react": "^8.106.4",
|
||||||
"@microsoft/sp-component-base": "1.22.1",
|
"@microsoft/sp-component-base": "1.22.1",
|
||||||
"@microsoft/sp-core-library": "1.22.1",
|
"@microsoft/sp-core-library": "1.22.1",
|
||||||
|
"@microsoft/sp-http": "1.22.1",
|
||||||
"@microsoft/sp-lodash-subset": "1.22.1",
|
"@microsoft/sp-lodash-subset": "1.22.1",
|
||||||
"@microsoft/sp-office-ui-fabric-core": "1.22.1",
|
"@microsoft/sp-office-ui-fabric-core": "1.22.1",
|
||||||
"@microsoft/sp-property-pane": "1.22.1",
|
"@microsoft/sp-property-pane": "1.22.1",
|
||||||
|
|||||||
@@ -0,0 +1,687 @@
|
|||||||
|
{
|
||||||
|
"version": "2.1.0",
|
||||||
|
"$schema": "http://json.schemastore.org/sarif-2.1.0-rtm.5",
|
||||||
|
"runs": [
|
||||||
|
{
|
||||||
|
"tool": {
|
||||||
|
"driver": {
|
||||||
|
"name": "ESLint",
|
||||||
|
"informationUri": "https://eslint.org",
|
||||||
|
"version": "8.57.1",
|
||||||
|
"rules": [
|
||||||
|
{
|
||||||
|
"id": "@rushstack/no-new-null",
|
||||||
|
"helpUri": "https://www.npmjs.com/package/@rushstack/eslint-plugin",
|
||||||
|
"properties": {},
|
||||||
|
"shortDescription": {
|
||||||
|
"text": "Prevent usage of JavaScript's \"null\" keyword in new type declarations. To avoid hampering usage of preexisting APIs that require \"null\", the rule ignores declarations that are local variables, private members, or types that are not exported."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"artifacts": [
|
||||||
|
{
|
||||||
|
"location": {
|
||||||
|
"uri": "src/webparts/approvalAdmin/services/httpClient.ts"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"location": {
|
||||||
|
"uri": "src/webparts/approvalAdmin/models/index.ts"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"results": [
|
||||||
|
{
|
||||||
|
"level": "warning",
|
||||||
|
"message": {
|
||||||
|
"text": "Usage of \"null\" is deprecated except when describing legacy APIs; use \"undefined\" instead"
|
||||||
|
},
|
||||||
|
"locations": [
|
||||||
|
{
|
||||||
|
"physicalLocation": {
|
||||||
|
"artifactLocation": {
|
||||||
|
"uri": "src/webparts/approvalAdmin/services/httpClient.ts",
|
||||||
|
"index": 0
|
||||||
|
},
|
||||||
|
"region": {
|
||||||
|
"startLine": 21,
|
||||||
|
"startColumn": 82,
|
||||||
|
"endLine": 21,
|
||||||
|
"endColumn": 86,
|
||||||
|
"snippet": {
|
||||||
|
"text": "null"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"ruleId": "@rushstack/no-new-null",
|
||||||
|
"ruleIndex": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"level": "warning",
|
||||||
|
"message": {
|
||||||
|
"text": "Usage of \"null\" is deprecated except when describing legacy APIs; use \"undefined\" instead"
|
||||||
|
},
|
||||||
|
"locations": [
|
||||||
|
{
|
||||||
|
"physicalLocation": {
|
||||||
|
"artifactLocation": {
|
||||||
|
"uri": "src/webparts/approvalAdmin/models/index.ts",
|
||||||
|
"index": 1
|
||||||
|
},
|
||||||
|
"region": {
|
||||||
|
"startLine": 86,
|
||||||
|
"startColumn": 26,
|
||||||
|
"endLine": 86,
|
||||||
|
"endColumn": 30,
|
||||||
|
"snippet": {
|
||||||
|
"text": "null"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"ruleId": "@rushstack/no-new-null",
|
||||||
|
"ruleIndex": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"level": "warning",
|
||||||
|
"message": {
|
||||||
|
"text": "Usage of \"null\" is deprecated except when describing legacy APIs; use \"undefined\" instead"
|
||||||
|
},
|
||||||
|
"locations": [
|
||||||
|
{
|
||||||
|
"physicalLocation": {
|
||||||
|
"artifactLocation": {
|
||||||
|
"uri": "src/webparts/approvalAdmin/models/index.ts",
|
||||||
|
"index": 1
|
||||||
|
},
|
||||||
|
"region": {
|
||||||
|
"startLine": 95,
|
||||||
|
"startColumn": 26,
|
||||||
|
"endLine": 95,
|
||||||
|
"endColumn": 30,
|
||||||
|
"snippet": {
|
||||||
|
"text": "null"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"ruleId": "@rushstack/no-new-null",
|
||||||
|
"ruleIndex": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"level": "warning",
|
||||||
|
"message": {
|
||||||
|
"text": "Usage of \"null\" is deprecated except when describing legacy APIs; use \"undefined\" instead"
|
||||||
|
},
|
||||||
|
"locations": [
|
||||||
|
{
|
||||||
|
"physicalLocation": {
|
||||||
|
"artifactLocation": {
|
||||||
|
"uri": "src/webparts/approvalAdmin/models/index.ts",
|
||||||
|
"index": 1
|
||||||
|
},
|
||||||
|
"region": {
|
||||||
|
"startLine": 103,
|
||||||
|
"startColumn": 26,
|
||||||
|
"endLine": 103,
|
||||||
|
"endColumn": 30,
|
||||||
|
"snippet": {
|
||||||
|
"text": "null"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"ruleId": "@rushstack/no-new-null",
|
||||||
|
"ruleIndex": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"level": "warning",
|
||||||
|
"message": {
|
||||||
|
"text": "Usage of \"null\" is deprecated except when describing legacy APIs; use \"undefined\" instead"
|
||||||
|
},
|
||||||
|
"locations": [
|
||||||
|
{
|
||||||
|
"physicalLocation": {
|
||||||
|
"artifactLocation": {
|
||||||
|
"uri": "src/webparts/approvalAdmin/models/index.ts",
|
||||||
|
"index": 1
|
||||||
|
},
|
||||||
|
"region": {
|
||||||
|
"startLine": 111,
|
||||||
|
"startColumn": 27,
|
||||||
|
"endLine": 111,
|
||||||
|
"endColumn": 31,
|
||||||
|
"snippet": {
|
||||||
|
"text": "null"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"ruleId": "@rushstack/no-new-null",
|
||||||
|
"ruleIndex": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"level": "warning",
|
||||||
|
"message": {
|
||||||
|
"text": "Usage of \"null\" is deprecated except when describing legacy APIs; use \"undefined\" instead"
|
||||||
|
},
|
||||||
|
"locations": [
|
||||||
|
{
|
||||||
|
"physicalLocation": {
|
||||||
|
"artifactLocation": {
|
||||||
|
"uri": "src/webparts/approvalAdmin/models/index.ts",
|
||||||
|
"index": 1
|
||||||
|
},
|
||||||
|
"region": {
|
||||||
|
"startLine": 122,
|
||||||
|
"startColumn": 27,
|
||||||
|
"endLine": 122,
|
||||||
|
"endColumn": 31,
|
||||||
|
"snippet": {
|
||||||
|
"text": "null"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"ruleId": "@rushstack/no-new-null",
|
||||||
|
"ruleIndex": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"level": "warning",
|
||||||
|
"message": {
|
||||||
|
"text": "Usage of \"null\" is deprecated except when describing legacy APIs; use \"undefined\" instead"
|
||||||
|
},
|
||||||
|
"locations": [
|
||||||
|
{
|
||||||
|
"physicalLocation": {
|
||||||
|
"artifactLocation": {
|
||||||
|
"uri": "src/webparts/approvalAdmin/models/index.ts",
|
||||||
|
"index": 1
|
||||||
|
},
|
||||||
|
"region": {
|
||||||
|
"startLine": 123,
|
||||||
|
"startColumn": 22,
|
||||||
|
"endLine": 123,
|
||||||
|
"endColumn": 26,
|
||||||
|
"snippet": {
|
||||||
|
"text": "null"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"ruleId": "@rushstack/no-new-null",
|
||||||
|
"ruleIndex": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"level": "warning",
|
||||||
|
"message": {
|
||||||
|
"text": "Usage of \"null\" is deprecated except when describing legacy APIs; use \"undefined\" instead"
|
||||||
|
},
|
||||||
|
"locations": [
|
||||||
|
{
|
||||||
|
"physicalLocation": {
|
||||||
|
"artifactLocation": {
|
||||||
|
"uri": "src/webparts/approvalAdmin/models/index.ts",
|
||||||
|
"index": 1
|
||||||
|
},
|
||||||
|
"region": {
|
||||||
|
"startLine": 124,
|
||||||
|
"startColumn": 30,
|
||||||
|
"endLine": 124,
|
||||||
|
"endColumn": 34,
|
||||||
|
"snippet": {
|
||||||
|
"text": "null"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"ruleId": "@rushstack/no-new-null",
|
||||||
|
"ruleIndex": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"level": "warning",
|
||||||
|
"message": {
|
||||||
|
"text": "Usage of \"null\" is deprecated except when describing legacy APIs; use \"undefined\" instead"
|
||||||
|
},
|
||||||
|
"locations": [
|
||||||
|
{
|
||||||
|
"physicalLocation": {
|
||||||
|
"artifactLocation": {
|
||||||
|
"uri": "src/webparts/approvalAdmin/models/index.ts",
|
||||||
|
"index": 1
|
||||||
|
},
|
||||||
|
"region": {
|
||||||
|
"startLine": 125,
|
||||||
|
"startColumn": 24,
|
||||||
|
"endLine": 125,
|
||||||
|
"endColumn": 28,
|
||||||
|
"snippet": {
|
||||||
|
"text": "null"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"ruleId": "@rushstack/no-new-null",
|
||||||
|
"ruleIndex": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"level": "warning",
|
||||||
|
"message": {
|
||||||
|
"text": "Usage of \"null\" is deprecated except when describing legacy APIs; use \"undefined\" instead"
|
||||||
|
},
|
||||||
|
"locations": [
|
||||||
|
{
|
||||||
|
"physicalLocation": {
|
||||||
|
"artifactLocation": {
|
||||||
|
"uri": "src/webparts/approvalAdmin/models/index.ts",
|
||||||
|
"index": 1
|
||||||
|
},
|
||||||
|
"region": {
|
||||||
|
"startLine": 129,
|
||||||
|
"startColumn": 22,
|
||||||
|
"endLine": 129,
|
||||||
|
"endColumn": 26,
|
||||||
|
"snippet": {
|
||||||
|
"text": "null"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"ruleId": "@rushstack/no-new-null",
|
||||||
|
"ruleIndex": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"level": "warning",
|
||||||
|
"message": {
|
||||||
|
"text": "Usage of \"null\" is deprecated except when describing legacy APIs; use \"undefined\" instead"
|
||||||
|
},
|
||||||
|
"locations": [
|
||||||
|
{
|
||||||
|
"physicalLocation": {
|
||||||
|
"artifactLocation": {
|
||||||
|
"uri": "src/webparts/approvalAdmin/models/index.ts",
|
||||||
|
"index": 1
|
||||||
|
},
|
||||||
|
"region": {
|
||||||
|
"startLine": 134,
|
||||||
|
"startColumn": 27,
|
||||||
|
"endLine": 134,
|
||||||
|
"endColumn": 31,
|
||||||
|
"snippet": {
|
||||||
|
"text": "null"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"ruleId": "@rushstack/no-new-null",
|
||||||
|
"ruleIndex": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"level": "warning",
|
||||||
|
"message": {
|
||||||
|
"text": "Usage of \"null\" is deprecated except when describing legacy APIs; use \"undefined\" instead"
|
||||||
|
},
|
||||||
|
"locations": [
|
||||||
|
{
|
||||||
|
"physicalLocation": {
|
||||||
|
"artifactLocation": {
|
||||||
|
"uri": "src/webparts/approvalAdmin/models/index.ts",
|
||||||
|
"index": 1
|
||||||
|
},
|
||||||
|
"region": {
|
||||||
|
"startLine": 135,
|
||||||
|
"startColumn": 22,
|
||||||
|
"endLine": 135,
|
||||||
|
"endColumn": 26,
|
||||||
|
"snippet": {
|
||||||
|
"text": "null"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"ruleId": "@rushstack/no-new-null",
|
||||||
|
"ruleIndex": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"level": "warning",
|
||||||
|
"message": {
|
||||||
|
"text": "Usage of \"null\" is deprecated except when describing legacy APIs; use \"undefined\" instead"
|
||||||
|
},
|
||||||
|
"locations": [
|
||||||
|
{
|
||||||
|
"physicalLocation": {
|
||||||
|
"artifactLocation": {
|
||||||
|
"uri": "src/webparts/approvalAdmin/models/index.ts",
|
||||||
|
"index": 1
|
||||||
|
},
|
||||||
|
"region": {
|
||||||
|
"startLine": 136,
|
||||||
|
"startColumn": 30,
|
||||||
|
"endLine": 136,
|
||||||
|
"endColumn": 34,
|
||||||
|
"snippet": {
|
||||||
|
"text": "null"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"ruleId": "@rushstack/no-new-null",
|
||||||
|
"ruleIndex": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"level": "warning",
|
||||||
|
"message": {
|
||||||
|
"text": "Usage of \"null\" is deprecated except when describing legacy APIs; use \"undefined\" instead"
|
||||||
|
},
|
||||||
|
"locations": [
|
||||||
|
{
|
||||||
|
"physicalLocation": {
|
||||||
|
"artifactLocation": {
|
||||||
|
"uri": "src/webparts/approvalAdmin/models/index.ts",
|
||||||
|
"index": 1
|
||||||
|
},
|
||||||
|
"region": {
|
||||||
|
"startLine": 137,
|
||||||
|
"startColumn": 24,
|
||||||
|
"endLine": 137,
|
||||||
|
"endColumn": 28,
|
||||||
|
"snippet": {
|
||||||
|
"text": "null"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"ruleId": "@rushstack/no-new-null",
|
||||||
|
"ruleIndex": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"level": "warning",
|
||||||
|
"message": {
|
||||||
|
"text": "Usage of \"null\" is deprecated except when describing legacy APIs; use \"undefined\" instead"
|
||||||
|
},
|
||||||
|
"locations": [
|
||||||
|
{
|
||||||
|
"physicalLocation": {
|
||||||
|
"artifactLocation": {
|
||||||
|
"uri": "src/webparts/approvalAdmin/models/index.ts",
|
||||||
|
"index": 1
|
||||||
|
},
|
||||||
|
"region": {
|
||||||
|
"startLine": 146,
|
||||||
|
"startColumn": 28,
|
||||||
|
"endLine": 146,
|
||||||
|
"endColumn": 32,
|
||||||
|
"snippet": {
|
||||||
|
"text": "null"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"ruleId": "@rushstack/no-new-null",
|
||||||
|
"ruleIndex": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"level": "warning",
|
||||||
|
"message": {
|
||||||
|
"text": "Usage of \"null\" is deprecated except when describing legacy APIs; use \"undefined\" instead"
|
||||||
|
},
|
||||||
|
"locations": [
|
||||||
|
{
|
||||||
|
"physicalLocation": {
|
||||||
|
"artifactLocation": {
|
||||||
|
"uri": "src/webparts/approvalAdmin/models/index.ts",
|
||||||
|
"index": 1
|
||||||
|
},
|
||||||
|
"region": {
|
||||||
|
"startLine": 147,
|
||||||
|
"startColumn": 23,
|
||||||
|
"endLine": 147,
|
||||||
|
"endColumn": 27,
|
||||||
|
"snippet": {
|
||||||
|
"text": "null"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"ruleId": "@rushstack/no-new-null",
|
||||||
|
"ruleIndex": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"level": "warning",
|
||||||
|
"message": {
|
||||||
|
"text": "Usage of \"null\" is deprecated except when describing legacy APIs; use \"undefined\" instead"
|
||||||
|
},
|
||||||
|
"locations": [
|
||||||
|
{
|
||||||
|
"physicalLocation": {
|
||||||
|
"artifactLocation": {
|
||||||
|
"uri": "src/webparts/approvalAdmin/models/index.ts",
|
||||||
|
"index": 1
|
||||||
|
},
|
||||||
|
"region": {
|
||||||
|
"startLine": 148,
|
||||||
|
"startColumn": 31,
|
||||||
|
"endLine": 148,
|
||||||
|
"endColumn": 35,
|
||||||
|
"snippet": {
|
||||||
|
"text": "null"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"ruleId": "@rushstack/no-new-null",
|
||||||
|
"ruleIndex": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"level": "warning",
|
||||||
|
"message": {
|
||||||
|
"text": "Usage of \"null\" is deprecated except when describing legacy APIs; use \"undefined\" instead"
|
||||||
|
},
|
||||||
|
"locations": [
|
||||||
|
{
|
||||||
|
"physicalLocation": {
|
||||||
|
"artifactLocation": {
|
||||||
|
"uri": "src/webparts/approvalAdmin/models/index.ts",
|
||||||
|
"index": 1
|
||||||
|
},
|
||||||
|
"region": {
|
||||||
|
"startLine": 149,
|
||||||
|
"startColumn": 25,
|
||||||
|
"endLine": 149,
|
||||||
|
"endColumn": 29,
|
||||||
|
"snippet": {
|
||||||
|
"text": "null"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"ruleId": "@rushstack/no-new-null",
|
||||||
|
"ruleIndex": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"level": "warning",
|
||||||
|
"message": {
|
||||||
|
"text": "Usage of \"null\" is deprecated except when describing legacy APIs; use \"undefined\" instead"
|
||||||
|
},
|
||||||
|
"locations": [
|
||||||
|
{
|
||||||
|
"physicalLocation": {
|
||||||
|
"artifactLocation": {
|
||||||
|
"uri": "src/webparts/approvalAdmin/models/index.ts",
|
||||||
|
"index": 1
|
||||||
|
},
|
||||||
|
"region": {
|
||||||
|
"startLine": 166,
|
||||||
|
"startColumn": 27,
|
||||||
|
"endLine": 166,
|
||||||
|
"endColumn": 31,
|
||||||
|
"snippet": {
|
||||||
|
"text": "null"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"ruleId": "@rushstack/no-new-null",
|
||||||
|
"ruleIndex": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"level": "warning",
|
||||||
|
"message": {
|
||||||
|
"text": "Usage of \"null\" is deprecated except when describing legacy APIs; use \"undefined\" instead"
|
||||||
|
},
|
||||||
|
"locations": [
|
||||||
|
{
|
||||||
|
"physicalLocation": {
|
||||||
|
"artifactLocation": {
|
||||||
|
"uri": "src/webparts/approvalAdmin/models/index.ts",
|
||||||
|
"index": 1
|
||||||
|
},
|
||||||
|
"region": {
|
||||||
|
"startLine": 167,
|
||||||
|
"startColumn": 22,
|
||||||
|
"endLine": 167,
|
||||||
|
"endColumn": 26,
|
||||||
|
"snippet": {
|
||||||
|
"text": "null"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"ruleId": "@rushstack/no-new-null",
|
||||||
|
"ruleIndex": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"level": "warning",
|
||||||
|
"message": {
|
||||||
|
"text": "Usage of \"null\" is deprecated except when describing legacy APIs; use \"undefined\" instead"
|
||||||
|
},
|
||||||
|
"locations": [
|
||||||
|
{
|
||||||
|
"physicalLocation": {
|
||||||
|
"artifactLocation": {
|
||||||
|
"uri": "src/webparts/approvalAdmin/models/index.ts",
|
||||||
|
"index": 1
|
||||||
|
},
|
||||||
|
"region": {
|
||||||
|
"startLine": 183,
|
||||||
|
"startColumn": 28,
|
||||||
|
"endLine": 183,
|
||||||
|
"endColumn": 32,
|
||||||
|
"snippet": {
|
||||||
|
"text": "null"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"ruleId": "@rushstack/no-new-null",
|
||||||
|
"ruleIndex": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"level": "warning",
|
||||||
|
"message": {
|
||||||
|
"text": "Usage of \"null\" is deprecated except when describing legacy APIs; use \"undefined\" instead"
|
||||||
|
},
|
||||||
|
"locations": [
|
||||||
|
{
|
||||||
|
"physicalLocation": {
|
||||||
|
"artifactLocation": {
|
||||||
|
"uri": "src/webparts/approvalAdmin/models/index.ts",
|
||||||
|
"index": 1
|
||||||
|
},
|
||||||
|
"region": {
|
||||||
|
"startLine": 184,
|
||||||
|
"startColumn": 23,
|
||||||
|
"endLine": 184,
|
||||||
|
"endColumn": 27,
|
||||||
|
"snippet": {
|
||||||
|
"text": "null"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"ruleId": "@rushstack/no-new-null",
|
||||||
|
"ruleIndex": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"level": "warning",
|
||||||
|
"message": {
|
||||||
|
"text": "Usage of \"null\" is deprecated except when describing legacy APIs; use \"undefined\" instead"
|
||||||
|
},
|
||||||
|
"locations": [
|
||||||
|
{
|
||||||
|
"physicalLocation": {
|
||||||
|
"artifactLocation": {
|
||||||
|
"uri": "src/webparts/approvalAdmin/models/index.ts",
|
||||||
|
"index": 1
|
||||||
|
},
|
||||||
|
"region": {
|
||||||
|
"startLine": 185,
|
||||||
|
"startColumn": 31,
|
||||||
|
"endLine": 185,
|
||||||
|
"endColumn": 35,
|
||||||
|
"snippet": {
|
||||||
|
"text": "null"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"ruleId": "@rushstack/no-new-null",
|
||||||
|
"ruleIndex": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"level": "warning",
|
||||||
|
"message": {
|
||||||
|
"text": "Usage of \"null\" is deprecated except when describing legacy APIs; use \"undefined\" instead"
|
||||||
|
},
|
||||||
|
"locations": [
|
||||||
|
{
|
||||||
|
"physicalLocation": {
|
||||||
|
"artifactLocation": {
|
||||||
|
"uri": "src/webparts/approvalAdmin/models/index.ts",
|
||||||
|
"index": 1
|
||||||
|
},
|
||||||
|
"region": {
|
||||||
|
"startLine": 186,
|
||||||
|
"startColumn": 25,
|
||||||
|
"endLine": 186,
|
||||||
|
"endColumn": 29,
|
||||||
|
"snippet": {
|
||||||
|
"text": "null"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"ruleId": "@rushstack/no-new-null",
|
||||||
|
"ruleIndex": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
define([], function() {
|
|
||||||
return {
|
|
||||||
"PropertyPaneDescription": "Description",
|
|
||||||
"BasicGroupName": "Group Name",
|
|
||||||
"DescriptionFieldLabel": "Description Field",
|
|
||||||
"AppLocalEnvironmentSharePoint": "The app is running on your local environment as SharePoint web part",
|
|
||||||
"AppLocalEnvironmentTeams": "The app is running on your local environment as Microsoft Teams app",
|
|
||||||
"AppLocalEnvironmentOffice": "The app is running on your local environment in office.com",
|
|
||||||
"AppLocalEnvironmentOutlook": "The app is running on your local environment in Outlook",
|
|
||||||
"AppSharePointEnvironment": "The app is running on SharePoint page",
|
|
||||||
"AppTeamsTabEnvironment": "The app is running in Microsoft Teams",
|
|
||||||
"AppOfficeEnvironment": "The app is running in office.com",
|
|
||||||
"AppOutlookEnvironment": "The app is running in Outlook",
|
|
||||||
"UnknownEnvironment": "The app is running in an unknown environment"
|
|
||||||
}
|
|
||||||
});
|
|
||||||
@@ -1,78 +0,0 @@
|
|||||||
"use strict";
|
|
||||||
self["webpackHotUpdate_4825e72c_4ff3_479e_917e_f6b04e003a82_0_0_1"]("approval-admin-web-part",{
|
|
||||||
|
|
||||||
/***/ 867:
|
|
||||||
/*!****************************************************************!*\
|
|
||||||
!*** ./lib/webparts/approvalAdmin/components/ApprovalAdmin.js ***!
|
|
||||||
\****************************************************************/
|
|
||||||
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
|
|
||||||
|
|
||||||
__webpack_require__.r(__webpack_exports__);
|
|
||||||
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
|
|
||||||
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
|
|
||||||
/* harmony export */ });
|
|
||||||
/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! tslib */ 196);
|
|
||||||
/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! react */ 959);
|
|
||||||
/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_0__);
|
|
||||||
/* harmony import */ var _ApprovalAdmin_module_scss__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./ApprovalAdmin.module.scss */ 845);
|
|
||||||
/* harmony import */ var _microsoft_sp_lodash_subset__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! @microsoft/sp-lodash-subset */ 529);
|
|
||||||
/* harmony import */ var _microsoft_sp_lodash_subset__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(_microsoft_sp_lodash_subset__WEBPACK_IMPORTED_MODULE_2__);
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
var ApprovalAdmin = /** @class */ (function (_super) {
|
|
||||||
(0,tslib__WEBPACK_IMPORTED_MODULE_3__.__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__WEBPACK_IMPORTED_MODULE_0__.createElement("section", { className: "".concat(_ApprovalAdmin_module_scss__WEBPACK_IMPORTED_MODULE_1__["default"].approvalAdmin, " ").concat(hasTeamsContext ? _ApprovalAdmin_module_scss__WEBPACK_IMPORTED_MODULE_1__["default"].teams : '') },
|
|
||||||
react__WEBPACK_IMPORTED_MODULE_0__.createElement("div", { className: _ApprovalAdmin_module_scss__WEBPACK_IMPORTED_MODULE_1__["default"].welcome },
|
|
||||||
react__WEBPACK_IMPORTED_MODULE_0__.createElement("img", { alt: "", src: isDarkTheme ? __webpack_require__(/*! ../assets/welcome-dark.png */ 91) : __webpack_require__(/*! ../assets/welcome-light.png */ 459), className: _ApprovalAdmin_module_scss__WEBPACK_IMPORTED_MODULE_1__["default"].welcomeImage }),
|
|
||||||
react__WEBPACK_IMPORTED_MODULE_0__.createElement("h2", null,
|
|
||||||
"Well done, ",
|
|
||||||
(0,_microsoft_sp_lodash_subset__WEBPACK_IMPORTED_MODULE_2__.escape)(userDisplayName),
|
|
||||||
"!"),
|
|
||||||
react__WEBPACK_IMPORTED_MODULE_0__.createElement("div", null, environmentMessage),
|
|
||||||
react__WEBPACK_IMPORTED_MODULE_0__.createElement("div", null,
|
|
||||||
"Web part property value: ",
|
|
||||||
react__WEBPACK_IMPORTED_MODULE_0__.createElement("strong", null, (0,_microsoft_sp_lodash_subset__WEBPACK_IMPORTED_MODULE_2__.escape)(description)))),
|
|
||||||
react__WEBPACK_IMPORTED_MODULE_0__.createElement("div", null,
|
|
||||||
react__WEBPACK_IMPORTED_MODULE_0__.createElement("h3", null, "Welcome to SharePoint Framerk!"),
|
|
||||||
react__WEBPACK_IMPORTED_MODULE_0__.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__WEBPACK_IMPORTED_MODULE_0__.createElement("h4", null, "Learn more about SPFx development:"),
|
|
||||||
react__WEBPACK_IMPORTED_MODULE_0__.createElement("ul", { className: _ApprovalAdmin_module_scss__WEBPACK_IMPORTED_MODULE_1__["default"].links },
|
|
||||||
react__WEBPACK_IMPORTED_MODULE_0__.createElement("li", null,
|
|
||||||
react__WEBPACK_IMPORTED_MODULE_0__.createElement("a", { href: "https://aka.ms/spfx", target: "_blank", rel: "noreferrer" }, "SharePoint Framework Overview")),
|
|
||||||
react__WEBPACK_IMPORTED_MODULE_0__.createElement("li", null,
|
|
||||||
react__WEBPACK_IMPORTED_MODULE_0__.createElement("a", { href: "https://aka.ms/spfx-yeoman-graph", target: "_blank", rel: "noreferrer" }, "Use Microsoft Graph in your solution")),
|
|
||||||
react__WEBPACK_IMPORTED_MODULE_0__.createElement("li", null,
|
|
||||||
react__WEBPACK_IMPORTED_MODULE_0__.createElement("a", { href: "https://aka.ms/spfx-yeoman-teams", target: "_blank", rel: "noreferrer" }, "Build for Microsoft Teams using SharePoint Framework")),
|
|
||||||
react__WEBPACK_IMPORTED_MODULE_0__.createElement("li", null,
|
|
||||||
react__WEBPACK_IMPORTED_MODULE_0__.createElement("a", { href: "https://aka.ms/spfx-yeoman-viva", target: "_blank", rel: "noreferrer" }, "Build for Microsoft Viva Connections using SharePoint Framework")),
|
|
||||||
react__WEBPACK_IMPORTED_MODULE_0__.createElement("li", null,
|
|
||||||
react__WEBPACK_IMPORTED_MODULE_0__.createElement("a", { href: "https://aka.ms/spfx-yeoman-store", target: "_blank", rel: "noreferrer" }, "Publish SharePoint Framework applications to the marketplace")),
|
|
||||||
react__WEBPACK_IMPORTED_MODULE_0__.createElement("li", null,
|
|
||||||
react__WEBPACK_IMPORTED_MODULE_0__.createElement("a", { href: "https://aka.ms/spfx-yeoman-api", target: "_blank", rel: "noreferrer" }, "SharePoint Framework API reference")),
|
|
||||||
react__WEBPACK_IMPORTED_MODULE_0__.createElement("li", null,
|
|
||||||
react__WEBPACK_IMPORTED_MODULE_0__.createElement("a", { href: "https://aka.ms/m365pnp", target: "_blank", rel: "noreferrer" }, "Microsoft 365 Developer Community"))))));
|
|
||||||
};
|
|
||||||
return ApprovalAdmin;
|
|
||||||
}(react__WEBPACK_IMPORTED_MODULE_0__.Component));
|
|
||||||
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (ApprovalAdmin);
|
|
||||||
|
|
||||||
|
|
||||||
/***/ })
|
|
||||||
|
|
||||||
},
|
|
||||||
/******/ function(__webpack_require__) { // webpackRuntimeModules
|
|
||||||
/******/ /* webpack/runtime/getFullHash */
|
|
||||||
/******/ (() => {
|
|
||||||
/******/ __webpack_require__.h = () => ("cf8054e229417e8f142a")
|
|
||||||
/******/ })();
|
|
||||||
/******/
|
|
||||||
/******/ }
|
|
||||||
);
|
|
||||||
//# sourceMappingURL=approval-admin-web-part.1f1564c39406710efeef.hot-update.js.map
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
{"version":3,"file":"approval-admin-web-part.1f1564c39406710efeef.hot-update.js","mappings":";;;;;;;;;;;;;;;;;;;;AAA+B;AACkB;AAEI;AAErD;IAA2C,wEAAoC;IAA/E;;IAqCA,CAAC;IApCQ,8BAAM,GAAb;QACQ,SAMF,IAAI,CAAC,KAAK,EALZ,WAAW,mBACX,WAAW,mBACX,kBAAkB,0BAClB,eAAe,uBACf,eAAe,qBACH,CAAC;QAEf,OAAO,CACL,8DAAS,SAAS,EAAE,UAAG,kEAAM,CAAC,aAAa,cAAI,eAAe,CAAC,CAAC,CAAC,kEAAM,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAE;YAClF,0DAAK,SAAS,EAAE,kEAAM,CAAC,OAAO;gBAC5B,0DAAK,GAAG,EAAC,EAAE,EAAC,GAAG,EAAE,WAAW,CAAC,CAAC,CAAC,mBAAO,CAAC,oCAA4B,CAAC,CAAC,CAAC,CAAC,mBAAO,CAAC,sCAA6B,CAAC,EAAE,SAAS,EAAE,kEAAM,CAAC,YAAY,GAAI;gBACjJ;;oBAAgB,mEAAM,CAAC,eAAe,CAAC;wBAAO;gBAC9C,8DAAM,kBAAkB,CAAO;gBAC/B;;oBAA8B,iEAAS,mEAAM,CAAC,WAAW,CAAC,CAAU,CAAM,CACtE;YACN;gBACE,8FAAuC;gBACvC,wSAEI;gBACJ,kGAA2C;gBAC3C,yDAAI,SAAS,EAAE,kEAAM,CAAC,KAAK;oBACzB;wBAAI,wDAAG,IAAI,EAAC,qBAAqB,EAAC,MAAM,EAAC,QAAQ,EAAC,GAAG,EAAC,YAAY,oCAAkC,CAAK;oBACzG;wBAAI,wDAAG,IAAI,EAAC,kCAAkC,EAAC,MAAM,EAAC,QAAQ,EAAC,GAAG,EAAC,YAAY,2CAAyC,CAAK;oBAC7H;wBAAI,wDAAG,IAAI,EAAC,kCAAkC,EAAC,MAAM,EAAC,QAAQ,EAAC,GAAG,EAAC,YAAY,2DAAyD,CAAK;oBAC7I;wBAAI,wDAAG,IAAI,EAAC,iCAAiC,EAAC,MAAM,EAAC,QAAQ,EAAC,GAAG,EAAC,YAAY,sEAAoE,CAAK;oBACvJ;wBAAI,wDAAG,IAAI,EAAC,kCAAkC,EAAC,MAAM,EAAC,QAAQ,EAAC,GAAG,EAAC,YAAY,mEAAiE,CAAK;oBACrJ;wBAAI,wDAAG,IAAI,EAAC,gCAAgC,EAAC,MAAM,EAAC,QAAQ,EAAC,GAAG,EAAC,YAAY,yCAAuC,CAAK;oBACzH;wBAAI,wDAAG,IAAI,EAAC,wBAAwB,EAAC,MAAM,EAAC,QAAQ,EAAC,GAAG,EAAC,YAAY,wCAAsC,CAAK,CAC7G,CACD,CACE,CACX,CAAC;IACJ,CAAC;IACH,oBAAC;AAAD,CAAC,CArC0C,4CAAe,GAqCzD;;;;;;;;;;UC1CD,sD","sources":["webpack:///.././src/webparts/approvalAdmin/components/ApprovalAdmin.tsx","webpack:///../webpack/runtime/getFullHash"],"sourcesContent":["import * as React from 'react';\nimport styles from './ApprovalAdmin.module.scss';\nimport type { IApprovalAdminProps } from './IApprovalAdminProps';\nimport { escape } from '@microsoft/sp-lodash-subset';\n\nexport default class ApprovalAdmin extends React.Component<IApprovalAdminProps> {\n public render(): React.ReactElement<IApprovalAdminProps> {\n const {\n description,\n isDarkTheme,\n environmentMessage,\n hasTeamsContext,\n userDisplayName\n } = this.props;\n\n return (\n <section className={`${styles.approvalAdmin} ${hasTeamsContext ? styles.teams : ''}`}>\n <div className={styles.welcome}>\n <img alt=\"\" src={isDarkTheme ? require('../assets/welcome-dark.png') : require('../assets/welcome-light.png')} className={styles.welcomeImage} />\n <h2>Well done, {escape(userDisplayName)}!</h2>\n <div>{environmentMessage}</div>\n <div>Web part property value: <strong>{escape(description)}</strong></div>\n </div>\n <div>\n <h3>Welcome to SharePoint Framerk!</h3>\n <p>\n 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.\n </p>\n <h4>Learn more about SPFx development:</h4>\n <ul className={styles.links}>\n <li><a href=\"https://aka.ms/spfx\" target=\"_blank\" rel=\"noreferrer\">SharePoint Framework Overview</a></li>\n <li><a href=\"https://aka.ms/spfx-yeoman-graph\" target=\"_blank\" rel=\"noreferrer\">Use Microsoft Graph in your solution</a></li>\n <li><a href=\"https://aka.ms/spfx-yeoman-teams\" target=\"_blank\" rel=\"noreferrer\">Build for Microsoft Teams using SharePoint Framework</a></li>\n <li><a href=\"https://aka.ms/spfx-yeoman-viva\" target=\"_blank\" rel=\"noreferrer\">Build for Microsoft Viva Connections using SharePoint Framework</a></li>\n <li><a href=\"https://aka.ms/spfx-yeoman-store\" target=\"_blank\" rel=\"noreferrer\">Publish SharePoint Framework applications to the marketplace</a></li>\n <li><a href=\"https://aka.ms/spfx-yeoman-api\" target=\"_blank\" rel=\"noreferrer\">SharePoint Framework API reference</a></li>\n <li><a href=\"https://aka.ms/m365pnp\" target=\"_blank\" rel=\"noreferrer\">Microsoft 365 Developer Community</a></li>\n </ul>\n </div>\n </section>\n );\n }\n}\n","__webpack_require__.h = () => (\"cf8054e229417e8f142a\")"],"names":[],"sourceRoot":""}
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
{"c":["approval-admin-web-part"],"r":[],"m":[]}
|
|
||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
Binary file not shown.
|
Before Width: | Height: | Size: 12 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 12 KiB |
@@ -26,7 +26,10 @@
|
|||||||
},
|
},
|
||||||
"officeFabricIconFontName": "Page",
|
"officeFabricIconFontName": "Page",
|
||||||
"properties": {
|
"properties": {
|
||||||
"description": "approval-admin"
|
"apiBaseUrl": "https://localhost:5443",
|
||||||
|
"useDevAuth": true,
|
||||||
|
"devUserEmail": "dev@tnsu.kz",
|
||||||
|
"aadResourceId": "api://9232d9e2-9807-4753-808f-07250e5474b0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
@@ -65,13 +68,9 @@
|
|||||||
"id": "974a7777-0990-4136-8fa6-95d80114c2e0",
|
"id": "974a7777-0990-4136-8fa6-95d80114c2e0",
|
||||||
"version": "1.22.1"
|
"version": "1.22.1"
|
||||||
},
|
},
|
||||||
"ApprovalAdminWebPartStrings": {
|
"@microsoft/sp-http": {
|
||||||
"type": "path",
|
|
||||||
"path": "ApprovalAdminWebPartStrings_en-us.js"
|
|
||||||
},
|
|
||||||
"@microsoft/sp-lodash-subset": {
|
|
||||||
"type": "component",
|
"type": "component",
|
||||||
"id": "73e1dc6c-8441-42cc-ad47-4bd3659f8a3a",
|
"id": "c07208f0-ea3b-4c1a-9965-ac1b825211a6",
|
||||||
"version": "1.22.1"
|
"version": "1.22.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,153 @@
|
|||||||
|
// ============================================
|
||||||
|
// Tansu Construction — Design Tokens
|
||||||
|
// Дизайн-система V5: Executive Premium
|
||||||
|
// Общий файл для всех SPFx-проектов Tnsu.
|
||||||
|
// Импорт в .module.scss: @import '../../../../styles/design-tokens';
|
||||||
|
// (глубина '../' зависит от расположения файла относительно src/)
|
||||||
|
// ============================================
|
||||||
|
|
||||||
|
// ---- Navy (Primary) ----
|
||||||
|
$navy-950: #0A1B30;
|
||||||
|
$navy-900: #0F2440;
|
||||||
|
$navy-800: #1B3A5C;
|
||||||
|
$navy-700: #244B73;
|
||||||
|
$navy-600: #2D5C8A;
|
||||||
|
$navy-100: #E8EEF4;
|
||||||
|
$navy-50: #F4F7FA;
|
||||||
|
|
||||||
|
// ---- Gold (Accent) ----
|
||||||
|
$gold-600: #B8881A;
|
||||||
|
$gold-500: #D4A01E;
|
||||||
|
$gold-400: #E5B635;
|
||||||
|
$gold-300: #F0CC5E;
|
||||||
|
$gold-100: #FDF5E0;
|
||||||
|
$gold-50: #FEFBF0;
|
||||||
|
|
||||||
|
// ---- Gray (Neutral) ----
|
||||||
|
$gray-900: #111827;
|
||||||
|
$gray-700: #374151;
|
||||||
|
$gray-500: #6B7280;
|
||||||
|
$gray-400: #9CA3AF;
|
||||||
|
$gray-300: #D1D5DB;
|
||||||
|
$gray-200: #E5E7EB;
|
||||||
|
$gray-100: #F3F4F6;
|
||||||
|
$gray-50: #F9FAFB;
|
||||||
|
|
||||||
|
// ---- Semantic ----
|
||||||
|
$red-500: #DC2626;
|
||||||
|
$red-50: #FEF2F2;
|
||||||
|
$green-600: #15803D;
|
||||||
|
$green-50: #F0FDF4;
|
||||||
|
$blue-600: #2563EB;
|
||||||
|
$blue-50: #EFF6FF;
|
||||||
|
$amber-700: #854D0E;
|
||||||
|
|
||||||
|
// ---- Typography ----
|
||||||
|
// Заголовки страниц и секций — Lora (serif)
|
||||||
|
// Весь остальной текст — Inter (sans)
|
||||||
|
$font-serif: 'Lora', Georgia, serif;
|
||||||
|
$font-sans: 'Inter', -apple-system, 'Segoe UI', sans-serif;
|
||||||
|
|
||||||
|
$fs-page-title: 24px; // Lora 600
|
||||||
|
$fs-form-title: 22px; // Lora 600
|
||||||
|
$fs-section-title: 15px; // Lora 600
|
||||||
|
$fs-body: 14px; // Inter 400
|
||||||
|
$fs-sm: 13px;
|
||||||
|
$fs-label: 12px; // Inter 500
|
||||||
|
$fs-caption: 11px;
|
||||||
|
$fs-overline: 10.5px; // uppercase, letter-spacing 1.4-1.8px
|
||||||
|
|
||||||
|
// ---- Radius ----
|
||||||
|
$r-sm: 6px;
|
||||||
|
$r-md: 10px;
|
||||||
|
$r-lg: 14px;
|
||||||
|
$r-full: 100px;
|
||||||
|
|
||||||
|
// ---- Shadows ----
|
||||||
|
$sh-1: 0 1px 2px rgba(15,36,64,.04), 0 1px 3px rgba(15,36,64,.06);
|
||||||
|
$sh-2: 0 4px 16px rgba(15,36,64,.07);
|
||||||
|
$sh-3: 0 12px 32px rgba(15,36,64,.10);
|
||||||
|
$sh-gold: 0 4px 20px rgba(212,160,30,.18);
|
||||||
|
$sh-modal: 0 24px 70px rgba(10,27,48,.25);
|
||||||
|
|
||||||
|
// ---- Gradients (фирменные) ----
|
||||||
|
$grad-header: linear-gradient(135deg, $navy-950 0%, $navy-800 85%);
|
||||||
|
$grad-gold: linear-gradient(135deg, $gold-400, $gold-500);
|
||||||
|
$grad-gold-v: linear-gradient(180deg, $gold-400, $gold-600);
|
||||||
|
$grad-hairline: linear-gradient(90deg, $gold-400, transparent 70%);
|
||||||
|
|
||||||
|
// ---- Spacing ----
|
||||||
|
$sp-1: 4px; $sp-2: 8px; $sp-3: 12px;
|
||||||
|
$sp-4: 16px; $sp-5: 20px; $sp-6: 24px;
|
||||||
|
$sp-7: 28px; $sp-8: 32px;
|
||||||
|
|
||||||
|
// ---- CSS Custom Properties ----
|
||||||
|
:root {
|
||||||
|
--navy-950: #{$navy-950}; --navy-900: #{$navy-900}; --navy-800: #{$navy-800};
|
||||||
|
--navy-700: #{$navy-700}; --navy-600: #{$navy-600};
|
||||||
|
--navy-100: #{$navy-100}; --navy-50: #{$navy-50};
|
||||||
|
|
||||||
|
--gold-600: #{$gold-600}; --gold-500: #{$gold-500}; --gold-400: #{$gold-400};
|
||||||
|
--gold-300: #{$gold-300}; --gold-100: #{$gold-100}; --gold-50: #{$gold-50};
|
||||||
|
|
||||||
|
--gray-900: #{$gray-900}; --gray-700: #{$gray-700}; --gray-500: #{$gray-500};
|
||||||
|
--gray-400: #{$gray-400}; --gray-300: #{$gray-300}; --gray-200: #{$gray-200};
|
||||||
|
--gray-100: #{$gray-100}; --gray-50: #{$gray-50};
|
||||||
|
|
||||||
|
--red-500: #{$red-500}; --red-50: #{$red-50};
|
||||||
|
--green-600: #{$green-600}; --green-50: #{$green-50};
|
||||||
|
--blue-600: #{$blue-600}; --blue-50: #{$blue-50};
|
||||||
|
--amber-700: #{$amber-700};
|
||||||
|
|
||||||
|
--sh-1: #{$sh-1}; --sh-2: #{$sh-2}; --sh-3: #{$sh-3};
|
||||||
|
--sh-gold: #{$sh-gold}; --sh-modal: #{$sh-modal};
|
||||||
|
|
||||||
|
--r-sm: #{$r-sm}; --r-md: #{$r-md}; --r-lg: #{$r-lg};
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- Готовые миксины ----
|
||||||
|
@mixin page-header {
|
||||||
|
background: $grad-header;
|
||||||
|
border-radius: $r-lg;
|
||||||
|
padding: 26px 30px;
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
&::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
left: 0; right: 0; bottom: 0;
|
||||||
|
height: 2px;
|
||||||
|
background: linear-gradient(90deg, $gold-500, transparent 60%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@mixin card-hover-gold {
|
||||||
|
transition: .22s;
|
||||||
|
&:hover {
|
||||||
|
box-shadow: $sh-3;
|
||||||
|
transform: translateY(-2px);
|
||||||
|
border-color: $gold-300;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@mixin btn-accent {
|
||||||
|
background: $grad-gold;
|
||||||
|
color: $navy-950;
|
||||||
|
font-weight: 600;
|
||||||
|
border: 1px solid $gold-500;
|
||||||
|
&:hover { box-shadow: $sh-gold; transform: translateY(-1px); }
|
||||||
|
}
|
||||||
|
|
||||||
|
@mixin input-focus-gold {
|
||||||
|
&:focus {
|
||||||
|
outline: 0;
|
||||||
|
border-color: $gold-500;
|
||||||
|
box-shadow: 0 0 0 3px rgba(212,160,30,.12);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@mixin hairline {
|
||||||
|
height: 1px;
|
||||||
|
background: $grad-hairline;
|
||||||
|
border: 0;
|
||||||
|
}
|
||||||
@@ -22,7 +22,10 @@
|
|||||||
"description": { "default": "approval-admin description" },
|
"description": { "default": "approval-admin description" },
|
||||||
"officeFabricIconFontName": "Page",
|
"officeFabricIconFontName": "Page",
|
||||||
"properties": {
|
"properties": {
|
||||||
"description": "approval-admin"
|
"apiBaseUrl": "https://localhost:5443",
|
||||||
|
"useDevAuth": true,
|
||||||
|
"devUserEmail": "dev@tnsu.kz",
|
||||||
|
"aadResourceId": "api://9232d9e2-9807-4753-808f-07250e5474b0"
|
||||||
}
|
}
|
||||||
}]
|
}]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,90 +3,41 @@ import * as ReactDom from 'react-dom';
|
|||||||
import { Version } from '@microsoft/sp-core-library';
|
import { Version } from '@microsoft/sp-core-library';
|
||||||
import {
|
import {
|
||||||
type IPropertyPaneConfiguration,
|
type IPropertyPaneConfiguration,
|
||||||
PropertyPaneTextField
|
PropertyPaneTextField,
|
||||||
|
PropertyPaneToggle
|
||||||
} from '@microsoft/sp-property-pane';
|
} from '@microsoft/sp-property-pane';
|
||||||
import { BaseClientSideWebPart } from '@microsoft/sp-webpart-base';
|
import { BaseClientSideWebPart } from '@microsoft/sp-webpart-base';
|
||||||
import { IReadonlyTheme } from '@microsoft/sp-component-base';
|
|
||||||
|
|
||||||
import * as strings from 'ApprovalAdminWebPartStrings';
|
|
||||||
import ApprovalAdmin from './components/ApprovalAdmin';
|
import ApprovalAdmin from './components/ApprovalAdmin';
|
||||||
import { IApprovalAdminProps } from './components/IApprovalAdminProps';
|
import { IApprovalAdminProps } from './components/IApprovalAdminProps';
|
||||||
|
|
||||||
export interface IApprovalAdminWebPartProps {
|
export interface IApprovalAdminWebPartProps {
|
||||||
description: string;
|
apiBaseUrl: string;
|
||||||
|
useDevAuth: boolean;
|
||||||
|
devUserEmail: string;
|
||||||
|
aadResourceId: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class ApprovalAdminWebPart extends BaseClientSideWebPart<IApprovalAdminWebPartProps> {
|
export default class ApprovalAdminWebPart extends BaseClientSideWebPart<IApprovalAdminWebPartProps> {
|
||||||
|
|
||||||
private _isDarkTheme: boolean = false;
|
|
||||||
private _environmentMessage: string = '';
|
|
||||||
|
|
||||||
public render(): void {
|
public render(): void {
|
||||||
const element: React.ReactElement<IApprovalAdminProps> = React.createElement(
|
const element: React.ReactElement<IApprovalAdminProps> = React.createElement(ApprovalAdmin, {
|
||||||
ApprovalAdmin,
|
context: this.context,
|
||||||
{
|
apiBaseUrl: this.properties.apiBaseUrl,
|
||||||
description: this.properties.description,
|
useDevAuth: this.properties.useDevAuth,
|
||||||
isDarkTheme: this._isDarkTheme,
|
devUserEmail: this.properties.devUserEmail,
|
||||||
environmentMessage: this._environmentMessage,
|
aadResourceId: this.properties.aadResourceId
|
||||||
hasTeamsContext: !!this.context.sdks.microsoftTeams,
|
});
|
||||||
userDisplayName: this.context.pageContext.user.displayName
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
ReactDom.render(element, this.domElement);
|
ReactDom.render(element, this.domElement);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected onInit(): Promise<void> {
|
protected onInit(): Promise<void> {
|
||||||
return this._getEnvironmentMessage().then(message => {
|
this.properties.apiBaseUrl = this.properties.apiBaseUrl || 'https://localhost:5443';
|
||||||
this._environmentMessage = message;
|
this.properties.useDevAuth = this.properties.useDevAuth ?? 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();
|
||||||
|
|
||||||
|
|
||||||
private _getEnvironmentMessage(): Promise<string> {
|
|
||||||
if (!!this.context.sdks.microsoftTeams) { // running in Teams, office.com or Outlook
|
|
||||||
return this.context.sdks.microsoftTeams.teamsJs.app.getContext()
|
|
||||||
.then(context => {
|
|
||||||
let environmentMessage: string = '';
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected onThemeChanged(currentTheme: IReadonlyTheme | undefined): void {
|
|
||||||
if (!currentTheme) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this._isDarkTheme = !!currentTheme.isInverted;
|
|
||||||
const {
|
|
||||||
semanticColors
|
|
||||||
} = currentTheme;
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected onDispose(): void {
|
protected onDispose(): void {
|
||||||
@@ -102,14 +53,28 @@ export default class ApprovalAdminWebPart extends BaseClientSideWebPart<IApprova
|
|||||||
pages: [
|
pages: [
|
||||||
{
|
{
|
||||||
header: {
|
header: {
|
||||||
description: strings.PropertyPaneDescription
|
description: 'Подключение к Tnsu.ApprovalRoute.Api'
|
||||||
},
|
},
|
||||||
groups: [
|
groups: [
|
||||||
{
|
{
|
||||||
groupName: strings.BasicGroupName,
|
groupName: 'API',
|
||||||
groupFields: [
|
groupFields: [
|
||||||
PropertyPaneTextField('description', {
|
PropertyPaneTextField('apiBaseUrl', {
|
||||||
label: strings.DescriptionFieldLabel
|
label: 'Api Base Url',
|
||||||
|
description: 'Например, https://localhost:5443 локально или прод-адрес API.'
|
||||||
|
}),
|
||||||
|
PropertyPaneToggle('useDevAuth', {
|
||||||
|
label: 'Локальный Dev-режим (без Azure AD)',
|
||||||
|
onText: 'Вкл. (заголовок X-Dev-User-Email)',
|
||||||
|
offText: 'Выкл. (AAD-токен через SPFx)'
|
||||||
|
}),
|
||||||
|
PropertyPaneTextField('devUserEmail', {
|
||||||
|
label: 'Dev: email пользователя',
|
||||||
|
description: 'Используется только при включённом Dev-режиме.'
|
||||||
|
}),
|
||||||
|
PropertyPaneTextField('aadResourceId', {
|
||||||
|
label: 'AAD Resource Id (App ID URI)',
|
||||||
|
description: 'Используется только при выключенном Dev-режиме. Должен совпадать с webApiPermissionRequests в package-solution.json.'
|
||||||
})
|
})
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,34 +1,15 @@
|
|||||||
@import '~@fluentui/react/dist/sass/References.scss';
|
@import '../../../styles/design-tokens';
|
||||||
|
|
||||||
.approvalAdmin {
|
.approvalAdmin {
|
||||||
overflow: hidden;
|
font-family: $font-sans;
|
||||||
padding: 1em;
|
|
||||||
color: "[theme:bodyText, default: #323130]";
|
|
||||||
color: var(--bodyText);
|
|
||||||
&.teams {
|
|
||||||
font-family: $ms-font-family-fallbacks;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.welcome {
|
.stateBox {
|
||||||
|
padding: 40px 24px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
color: $gray-500;
|
||||||
|
font-size: 13.5px;
|
||||||
|
background: $gray-50;
|
||||||
|
border-radius: $r-lg;
|
||||||
|
border: 1px solid $gray-200;
|
||||||
}
|
}
|
||||||
|
|
||||||
.welcomeImage {
|
|
||||||
width: 100%;
|
|
||||||
max-width: 420px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.links {
|
|
||||||
a {
|
|
||||||
text-decoration: none;
|
|
||||||
color: "[theme:link, default:#03787c]";
|
|
||||||
color: var(--link); // note: CSS Custom Properties support is limited to modern browsers only
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
text-decoration: underline;
|
|
||||||
color: "[theme:linkHovered, default: #014446]";
|
|
||||||
color: var(--linkHovered); // note: CSS Custom Properties support is limited to modern browsers only
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,43 +1,63 @@
|
|||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import styles from './ApprovalAdmin.module.scss';
|
import styles from './ApprovalAdmin.module.scss';
|
||||||
import type { IApprovalAdminProps } from './IApprovalAdminProps';
|
import type { IApprovalAdminProps } from './IApprovalAdminProps';
|
||||||
import { escape } from '@microsoft/sp-lodash-subset';
|
import { AdminApp } from './app/AdminApp';
|
||||||
|
import { ApprovalRouteApi } from '../services/api';
|
||||||
|
import { AadApiHttpClient, DevApiHttpClient, IHttpClient } from '../services/httpClient';
|
||||||
|
|
||||||
export default class ApprovalAdmin extends React.Component<IApprovalAdminProps> {
|
const FONTS_LINK_ID = 'tnsu-approval-admin-fonts';
|
||||||
public render(): React.ReactElement<IApprovalAdminProps> {
|
|
||||||
const {
|
|
||||||
description,
|
|
||||||
isDarkTheme,
|
|
||||||
environmentMessage,
|
|
||||||
hasTeamsContext,
|
|
||||||
userDisplayName
|
|
||||||
} = this.props;
|
|
||||||
|
|
||||||
return (
|
function ensureFontsLoaded(): void {
|
||||||
<section className={`${styles.approvalAdmin} ${hasTeamsContext ? styles.teams : ''}`}>
|
if (typeof document === 'undefined' || document.getElementById(FONTS_LINK_ID)) return;
|
||||||
<div className={styles.welcome}>
|
const link = document.createElement('link');
|
||||||
<img alt="" src={isDarkTheme ? require('../assets/welcome-dark.png') : require('../assets/welcome-light.png')} className={styles.welcomeImage} />
|
link.id = FONTS_LINK_ID;
|
||||||
<h2>Well done, {escape(userDisplayName)}!</h2>
|
link.rel = 'stylesheet';
|
||||||
<div>{environmentMessage}</div>
|
link.href = 'https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=Lora:wght@500;600;700&display=swap';
|
||||||
<div>Web part property value: <strong>{escape(description)}</strong></div>
|
document.head.appendChild(link);
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<h3>Welcome to SharePoint Framerk!</h3>
|
|
||||||
<p>
|
|
||||||
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.
|
|
||||||
</p>
|
|
||||||
<h4>Learn more about SPFx development:</h4>
|
|
||||||
<ul className={styles.links}>
|
|
||||||
<li><a href="https://aka.ms/spfx" target="_blank" rel="noreferrer">SharePoint Framework Overview</a></li>
|
|
||||||
<li><a href="https://aka.ms/spfx-yeoman-graph" target="_blank" rel="noreferrer">Use Microsoft Graph in your solution</a></li>
|
|
||||||
<li><a href="https://aka.ms/spfx-yeoman-teams" target="_blank" rel="noreferrer">Build for Microsoft Teams using SharePoint Framework</a></li>
|
|
||||||
<li><a href="https://aka.ms/spfx-yeoman-viva" target="_blank" rel="noreferrer">Build for Microsoft Viva Connections using SharePoint Framework</a></li>
|
|
||||||
<li><a href="https://aka.ms/spfx-yeoman-store" target="_blank" rel="noreferrer">Publish SharePoint Framework applications to the marketplace</a></li>
|
|
||||||
<li><a href="https://aka.ms/spfx-yeoman-api" target="_blank" rel="noreferrer">SharePoint Framework API reference</a></li>
|
|
||||||
<li><a href="https://aka.ms/m365pnp" target="_blank" rel="noreferrer">Microsoft 365 Developer Community</a></li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function createHttpClient(props: IApprovalAdminProps): Promise<IHttpClient> {
|
||||||
|
if (props.useDevAuth) {
|
||||||
|
return new DevApiHttpClient(props.apiBaseUrl, props.devUserEmail || 'dev@tnsu.kz');
|
||||||
|
}
|
||||||
|
const aadClient = await props.context.aadHttpClientFactory.getClient(props.aadResourceId);
|
||||||
|
return new AadApiHttpClient(aadClient, props.apiBaseUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
const ApprovalAdmin: React.FC<IApprovalAdminProps> = props => {
|
||||||
|
const [api, setApi] = React.useState<ApprovalRouteApi | undefined>();
|
||||||
|
const [error, setError] = React.useState<string | undefined>();
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
ensureFontsLoaded();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
let cancelled = false;
|
||||||
|
setApi(undefined);
|
||||||
|
setError(undefined);
|
||||||
|
|
||||||
|
createHttpClient(props)
|
||||||
|
.then(client => { if (!cancelled) setApi(new ApprovalRouteApi(client)); })
|
||||||
|
.catch((e: Error) => { if (!cancelled) setError(e.message); });
|
||||||
|
|
||||||
|
return () => { cancelled = true; };
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [props.useDevAuth, props.apiBaseUrl, props.devUserEmail, props.aadResourceId]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<section className={styles.approvalAdmin}>
|
||||||
|
{error && (
|
||||||
|
<div className={styles.stateBox}>
|
||||||
|
Не удалось подключиться к API: {error}
|
||||||
|
<br />
|
||||||
|
Проверьте свойства веб-части (Api Base Url, режим авторизации) в панели настроек.
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{!error && !api && <div className={styles.stateBox}>Подключение к API...</div>}
|
||||||
|
{api && <AdminApp api={api} currentUserEmail={props.context.pageContext.user.email} />}
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ApprovalAdmin;
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
|
import { WebPartContext } from '@microsoft/sp-webpart-base';
|
||||||
|
|
||||||
export interface IApprovalAdminProps {
|
export interface IApprovalAdminProps {
|
||||||
description: string;
|
context: WebPartContext;
|
||||||
isDarkTheme: boolean;
|
apiBaseUrl: string;
|
||||||
environmentMessage: string;
|
useDevAuth: boolean;
|
||||||
hasTeamsContext: boolean;
|
devUserEmail: string;
|
||||||
userDisplayName: string;
|
aadResourceId: string;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,26 @@
|
|||||||
|
@import '../../../../styles/design-tokens';
|
||||||
|
|
||||||
|
.shell {
|
||||||
|
background: linear-gradient(180deg, #F6F8FA 0%, #EEF2F6 100%);
|
||||||
|
border-radius: $r-lg;
|
||||||
|
border: 1px solid $gray-200;
|
||||||
|
overflow: hidden;
|
||||||
|
box-shadow: $sh-1;
|
||||||
|
color: $gray-900;
|
||||||
|
font-family: $font-sans;
|
||||||
|
font-size: $fs-body;
|
||||||
|
line-height: 1.55;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content {
|
||||||
|
padding: 28px 30px 36px;
|
||||||
|
max-width: 1080px;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.centerState {
|
||||||
|
padding: 60px 30px;
|
||||||
|
text-align: center;
|
||||||
|
color: $gray-400;
|
||||||
|
font-size: 13.5px;
|
||||||
|
}
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
import * as React from 'react';
|
||||||
|
import styles from './AdminApp.module.scss';
|
||||||
|
import { TopNav } from './TopNav';
|
||||||
|
import { ScreenKey } from './types';
|
||||||
|
import { ApprovalRouteApi } from '../../services/api';
|
||||||
|
import { TemplateRoute } from '../../models';
|
||||||
|
import { DashboardScreen } from '../screens/DashboardScreen';
|
||||||
|
import { RequestTypesScreen } from '../screens/RequestTypesScreen';
|
||||||
|
import { RoutesScreen } from '../screens/RoutesScreen';
|
||||||
|
import { RouteStepsScreen } from '../screens/RouteStepsScreen';
|
||||||
|
import { ResolveScreen } from '../screens/ResolveScreen';
|
||||||
|
|
||||||
|
export interface IAdminAppProps {
|
||||||
|
api: ApprovalRouteApi;
|
||||||
|
currentUserEmail?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const AdminApp: React.FC<IAdminAppProps> = ({ api, currentUserEmail }) => {
|
||||||
|
const [screen, setScreen] = React.useState<ScreenKey>('dashboard');
|
||||||
|
const [selectedRoute, setSelectedRoute] = React.useState<TemplateRoute | undefined>();
|
||||||
|
const [selectedRequestTypeName, setSelectedRequestTypeName] = React.useState('');
|
||||||
|
|
||||||
|
const navigate = (key: ScreenKey): void => {
|
||||||
|
setScreen(key);
|
||||||
|
};
|
||||||
|
|
||||||
|
const openSteps = (route: TemplateRoute): void => {
|
||||||
|
setSelectedRoute(route);
|
||||||
|
api.requestTypes.get(route.requestTypeId)
|
||||||
|
.then(rt => setSelectedRequestTypeName(rt.name))
|
||||||
|
.catch(() => setSelectedRequestTypeName(`#${route.requestTypeId}`));
|
||||||
|
setScreen('routeSteps');
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={styles.shell}>
|
||||||
|
<TopNav active={screen} onSelect={navigate} />
|
||||||
|
<div className={styles.content}>
|
||||||
|
{screen === 'dashboard' && <DashboardScreen api={api} onNavigate={navigate} />}
|
||||||
|
{screen === 'requestTypes' && <RequestTypesScreen api={api} />}
|
||||||
|
{screen === 'routes' && <RoutesScreen api={api} onOpenSteps={openSteps} />}
|
||||||
|
{screen === 'routeSteps' && selectedRoute && (
|
||||||
|
<RouteStepsScreen
|
||||||
|
api={api}
|
||||||
|
route={selectedRoute}
|
||||||
|
requestTypeName={selectedRequestTypeName}
|
||||||
|
onBack={() => setScreen('routes')}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{screen === 'resolve' && <ResolveScreen api={api} currentUserEmail={currentUserEmail} />}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1,71 @@
|
|||||||
|
@import '../../../../styles/design-tokens';
|
||||||
|
|
||||||
|
.topnav {
|
||||||
|
background: $grad-header;
|
||||||
|
padding: 0 22px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
height: 54px;
|
||||||
|
box-shadow: 0 1px 0 rgba(212,160,30,.35);
|
||||||
|
border-radius: $r-md $r-md 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: $grad-gold-v;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
color: $navy-950;
|
||||||
|
font-weight: 800;
|
||||||
|
font-size: 13px;
|
||||||
|
font-family: $font-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
.brandText {
|
||||||
|
color: #fff;
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 13px;
|
||||||
|
letter-spacing: .4px;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.brandText em {
|
||||||
|
font-style: normal;
|
||||||
|
color: $gold-400;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tabs {
|
||||||
|
display: flex;
|
||||||
|
height: 100%;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab {
|
||||||
|
padding: 0 16px;
|
||||||
|
border: none;
|
||||||
|
background: 0;
|
||||||
|
color: rgba(255,255,255,.5);
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 500;
|
||||||
|
cursor: pointer;
|
||||||
|
font-family: inherit;
|
||||||
|
transition: .18s;
|
||||||
|
height: 54px;
|
||||||
|
border-bottom: 2px solid transparent;
|
||||||
|
letter-spacing: .2px;
|
||||||
|
|
||||||
|
&:hover { color: rgba(255,255,255,.85); }
|
||||||
|
&.on { color: $gold-300; border-bottom-color: $gold-400; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
import * as React from 'react';
|
||||||
|
import styles from './TopNav.module.scss';
|
||||||
|
import { ScreenKey } from './types';
|
||||||
|
|
||||||
|
interface ITab {
|
||||||
|
key: ScreenKey;
|
||||||
|
label: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const TABS: ITab[] = [
|
||||||
|
{ key: 'dashboard', label: 'Дашборд' },
|
||||||
|
{ key: 'requestTypes', label: 'Типы заявок' },
|
||||||
|
{ key: 'routes', label: 'Маршруты' },
|
||||||
|
{ key: 'resolve', label: 'Проверка маршрута' }
|
||||||
|
];
|
||||||
|
|
||||||
|
export interface ITopNavProps {
|
||||||
|
active: ScreenKey;
|
||||||
|
onSelect: (key: ScreenKey) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const TopNav: React.FC<ITopNavProps> = ({ active, onSelect }) => (
|
||||||
|
<nav className={styles.topnav}>
|
||||||
|
<div className={styles.brand}>
|
||||||
|
<div className={styles.mark}>T</div>
|
||||||
|
<div className={styles.brandText}>TNSU <em>Approval Route</em></div>
|
||||||
|
</div>
|
||||||
|
<div className={styles.tabs}>
|
||||||
|
{TABS.map(tab => (
|
||||||
|
<button
|
||||||
|
key={tab.key}
|
||||||
|
className={`${styles.tab} ${(active === tab.key || (active === 'routeSteps' && tab.key === 'routes')) ? styles.on : ''}`}
|
||||||
|
onClick={() => onSelect(tab.key)}
|
||||||
|
>
|
||||||
|
{tab.label}
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
);
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export type ScreenKey = 'dashboard' | 'requestTypes' | 'routes' | 'routeSteps' | 'resolve';
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
import * as React from 'react';
|
||||||
|
import { ApprovalRouteApi } from '../../services/api';
|
||||||
|
import { ScreenKey } from '../app/types';
|
||||||
|
import { CardGrid, NavCard, StatCard, StatRow } from '../ui';
|
||||||
|
|
||||||
|
export interface IDashboardScreenProps {
|
||||||
|
api: ApprovalRouteApi;
|
||||||
|
onNavigate: (key: ScreenKey) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const DashboardScreen: React.FC<IDashboardScreenProps> = ({ api, onNavigate }) => {
|
||||||
|
const [requestTypesCount, setRequestTypesCount] = React.useState<number | undefined>();
|
||||||
|
const [routesCount, setRoutesCount] = React.useState<number | undefined>();
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
api.requestTypes.list().then(items => setRequestTypesCount(items.length)).catch(() => setRequestTypesCount(undefined));
|
||||||
|
api.templateRoutes.list().then(items => setRoutesCount(items.length)).catch(() => setRoutesCount(undefined));
|
||||||
|
}, [api]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<StatRow>
|
||||||
|
<StatCard label="Типов заявок" value={requestTypesCount ?? '—'} />
|
||||||
|
<StatCard label="Маршрутов согласования" value={routesCount ?? '—'} />
|
||||||
|
</StatRow>
|
||||||
|
|
||||||
|
<div style={{ fontSize: 10, textTransform: 'uppercase', letterSpacing: 1.6, color: 'var(--gray-400)', fontWeight: 600, marginBottom: 12 }}>
|
||||||
|
Разделы
|
||||||
|
</div>
|
||||||
|
<hr style={{ height: 1, background: 'linear-gradient(90deg, var(--gold-400), transparent 70%)', border: 0, margin: '6px 0 22px' }} />
|
||||||
|
<CardGrid>
|
||||||
|
<NavCard
|
||||||
|
icon="clipboard"
|
||||||
|
title="Типы заявок"
|
||||||
|
description="Справочник типов заявок и их страниц"
|
||||||
|
onClick={() => onNavigate('requestTypes')}
|
||||||
|
/>
|
||||||
|
<NavCard
|
||||||
|
icon="route"
|
||||||
|
title="Маршруты согласования"
|
||||||
|
description="Маршруты, шаги и исполнители"
|
||||||
|
onClick={() => onNavigate('routes')}
|
||||||
|
/>
|
||||||
|
<NavCard
|
||||||
|
icon="search"
|
||||||
|
title="Проверка маршрута"
|
||||||
|
description="Как разрешится маршрут для конкретного пользователя"
|
||||||
|
onClick={() => onNavigate('resolve')}
|
||||||
|
/>
|
||||||
|
</CardGrid>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1,189 @@
|
|||||||
|
import * as React from 'react';
|
||||||
|
import { ApprovalRouteApi } from '../../services/api';
|
||||||
|
import { ApiClientError } from '../../services/httpClient';
|
||||||
|
import { RequestType } from '../../models';
|
||||||
|
import {
|
||||||
|
ActionsCell,
|
||||||
|
Alert,
|
||||||
|
Breadcrumbs,
|
||||||
|
Button,
|
||||||
|
DataTable,
|
||||||
|
Field,
|
||||||
|
FormGrid,
|
||||||
|
IconButton,
|
||||||
|
Modal,
|
||||||
|
ConfirmDialog,
|
||||||
|
PageHeader,
|
||||||
|
PrimaryCell,
|
||||||
|
TextInput
|
||||||
|
} from '../ui';
|
||||||
|
|
||||||
|
export interface IRequestTypesScreenProps {
|
||||||
|
api: ApprovalRouteApi;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IFormState {
|
||||||
|
name: string;
|
||||||
|
pageUrl: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const EMPTY_FORM: IFormState = { name: '', pageUrl: '' };
|
||||||
|
|
||||||
|
export const RequestTypesScreen: React.FC<IRequestTypesScreenProps> = ({ api }) => {
|
||||||
|
const [items, setItems] = React.useState<RequestType[]>([]);
|
||||||
|
const [loading, setLoading] = React.useState(true);
|
||||||
|
const [loadError, setLoadError] = React.useState<string | undefined>();
|
||||||
|
|
||||||
|
const [editing, setEditing] = React.useState<RequestType | undefined>();
|
||||||
|
const [showForm, setShowForm] = React.useState(false);
|
||||||
|
const [form, setForm] = React.useState<IFormState>(EMPTY_FORM);
|
||||||
|
const [formError, setFormError] = React.useState<string | undefined>();
|
||||||
|
const [saving, setSaving] = React.useState(false);
|
||||||
|
|
||||||
|
const [deleting, setDeleting] = React.useState<RequestType | undefined>();
|
||||||
|
const [deleteError, setDeleteError] = React.useState<string | undefined>();
|
||||||
|
const [deleteBusy, setDeleteBusy] = React.useState(false);
|
||||||
|
|
||||||
|
const load = React.useCallback(() => {
|
||||||
|
setLoading(true);
|
||||||
|
setLoadError(undefined);
|
||||||
|
api.requestTypes
|
||||||
|
.list()
|
||||||
|
.then(setItems)
|
||||||
|
.catch((e: ApiClientError) => setLoadError(e.message))
|
||||||
|
.finally(() => setLoading(false));
|
||||||
|
}, [api]);
|
||||||
|
|
||||||
|
React.useEffect(() => { load(); }, [load]);
|
||||||
|
|
||||||
|
const openCreate = (): void => {
|
||||||
|
setEditing(undefined);
|
||||||
|
setForm(EMPTY_FORM);
|
||||||
|
setFormError(undefined);
|
||||||
|
setShowForm(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const openEdit = (item: RequestType): void => {
|
||||||
|
setEditing(item);
|
||||||
|
setForm({ name: item.name, pageUrl: item.pageUrl });
|
||||||
|
setFormError(undefined);
|
||||||
|
setShowForm(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const submit = (): void => {
|
||||||
|
if (!form.name.trim() || !form.pageUrl.trim()) {
|
||||||
|
setFormError('Заполните имя и Url страницы.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setSaving(true);
|
||||||
|
setFormError(undefined);
|
||||||
|
const 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(() => {
|
||||||
|
setShowForm(false);
|
||||||
|
load();
|
||||||
|
})
|
||||||
|
.catch((e: ApiClientError) => setFormError(e.message))
|
||||||
|
.finally(() => setSaving(false));
|
||||||
|
};
|
||||||
|
|
||||||
|
const confirmDelete = (): void => {
|
||||||
|
if (!deleting) return;
|
||||||
|
setDeleteBusy(true);
|
||||||
|
setDeleteError(undefined);
|
||||||
|
api.requestTypes
|
||||||
|
.remove(deleting.id)
|
||||||
|
.then(() => {
|
||||||
|
setDeleting(undefined);
|
||||||
|
load();
|
||||||
|
})
|
||||||
|
.catch((e: ApiClientError) => setDeleteError(e.message))
|
||||||
|
.finally(() => setDeleteBusy(false));
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Breadcrumbs items={[{ label: 'Главная' }, { label: 'Типы заявок' }]} />
|
||||||
|
<PageHeader
|
||||||
|
icon="clipboard"
|
||||||
|
subtitle="Справочник"
|
||||||
|
title="Типы заявок"
|
||||||
|
actions={
|
||||||
|
<Button variant="accent" icon="plus" onClick={openCreate}>Добавить тип</Button>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{loadError && <Alert kind="err">{loadError}</Alert>}
|
||||||
|
{deleteError && <Alert kind="err">{deleteError}</Alert>}
|
||||||
|
|
||||||
|
<DataTable
|
||||||
|
columns={[
|
||||||
|
{ key: 'name', header: 'Название', render: r => <PrimaryCell>{r.name}</PrimaryCell> },
|
||||||
|
{ key: 'url', header: 'Url страницы', render: r => r.pageUrl },
|
||||||
|
{
|
||||||
|
key: 'actions',
|
||||||
|
header: '',
|
||||||
|
width: '100px',
|
||||||
|
align: 'right',
|
||||||
|
render: r => (
|
||||||
|
<ActionsCell>
|
||||||
|
<IconButton icon="edit" label="Редактировать" onClick={() => openEdit(r)} />
|
||||||
|
<IconButton icon="trash" label="Удалить" danger onClick={() => { setDeleteError(undefined); setDeleting(r); }} />
|
||||||
|
</ActionsCell>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
]}
|
||||||
|
rows={items}
|
||||||
|
keyOf={r => r.id}
|
||||||
|
emptyMessage={loading ? 'Загрузка...' : 'Типов заявок пока нет.'}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{showForm && (
|
||||||
|
<Modal
|
||||||
|
title={editing ? 'Редактировать тип заявки' : 'Новый тип заявки'}
|
||||||
|
onClose={() => setShowForm(false)}
|
||||||
|
footer={
|
||||||
|
<>
|
||||||
|
<Button variant="secondary" onClick={() => setShowForm(false)} disabled={saving}>Отмена</Button>
|
||||||
|
<Button variant="accent" icon="check" onClick={submit} disabled={saving}>
|
||||||
|
{saving ? 'Сохранение...' : 'Сохранить'}
|
||||||
|
</Button>
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{formError && <Alert kind="err">{formError}</Alert>}
|
||||||
|
<FormGrid>
|
||||||
|
<Field label="Название" required full>
|
||||||
|
<TextInput
|
||||||
|
value={form.name}
|
||||||
|
onChange={e => setForm({ ...form, name: e.target.value })}
|
||||||
|
placeholder="Например, Заявка на командировку"
|
||||||
|
/>
|
||||||
|
</Field>
|
||||||
|
<Field label="Url страницы" required full hint="Страница формы заявки на портале.">
|
||||||
|
<TextInput
|
||||||
|
value={form.pageUrl}
|
||||||
|
onChange={e => setForm({ ...form, pageUrl: e.target.value })}
|
||||||
|
placeholder="/pages/business-trip"
|
||||||
|
/>
|
||||||
|
</Field>
|
||||||
|
</FormGrid>
|
||||||
|
</Modal>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{deleting && (
|
||||||
|
<ConfirmDialog
|
||||||
|
title="Удалить тип заявки?"
|
||||||
|
text={`Тип «${deleting.name}» будет удалён безвозвратно. Связанные маршруты могут перестать разрешаться.`}
|
||||||
|
confirmLabel="Удалить"
|
||||||
|
busy={deleteBusy}
|
||||||
|
onCancel={() => setDeleting(undefined)}
|
||||||
|
onConfirm={confirmDelete}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1,131 @@
|
|||||||
|
import * as React from 'react';
|
||||||
|
import { ApprovalRouteApi } from '../../services/api';
|
||||||
|
import { ApiClientError } from '../../services/httpClient';
|
||||||
|
import {
|
||||||
|
RequestType,
|
||||||
|
ResolveTemplateRouteResult,
|
||||||
|
routeStepStatusLabels,
|
||||||
|
RouteStepStatus,
|
||||||
|
routeTypeLabels
|
||||||
|
} from '../../models';
|
||||||
|
import {
|
||||||
|
Alert,
|
||||||
|
Breadcrumbs,
|
||||||
|
Button,
|
||||||
|
DataTable,
|
||||||
|
Field,
|
||||||
|
FormGrid,
|
||||||
|
PageHeader,
|
||||||
|
PrimaryCell,
|
||||||
|
Select,
|
||||||
|
TextInput
|
||||||
|
} from '../ui';
|
||||||
|
|
||||||
|
export interface IResolveScreenProps {
|
||||||
|
api: ApprovalRouteApi;
|
||||||
|
currentUserEmail?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const STATUS_OPTIONS = [RouteStepStatus.Approval, RouteStepStatus.Execution]
|
||||||
|
.map(v => ({ value: v, label: routeStepStatusLabels[v] }));
|
||||||
|
|
||||||
|
export const ResolveScreen: React.FC<IResolveScreenProps> = ({ api, currentUserEmail }) => {
|
||||||
|
const [requestTypes, setRequestTypes] = React.useState<RequestType[]>([]);
|
||||||
|
const [requestTypeId, setRequestTypeId] = React.useState('');
|
||||||
|
const [userEmail, setUserEmail] = React.useState('');
|
||||||
|
const [projectId, setProjectId] = React.useState('');
|
||||||
|
const [status, setStatus] = React.useState<RouteStepStatus>(RouteStepStatus.Approval);
|
||||||
|
|
||||||
|
const [busy, setBusy] = React.useState(false);
|
||||||
|
const [error, setError] = React.useState<string | undefined>();
|
||||||
|
const [result, setResult] = React.useState<ResolveTemplateRouteResult | undefined>();
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
api.requestTypes.list().then(setRequestTypes).catch(() => { /* показывается при попытке проверки */ });
|
||||||
|
}, [api]);
|
||||||
|
|
||||||
|
const run = (): void => {
|
||||||
|
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
|
||||||
|
})
|
||||||
|
.then(setResult)
|
||||||
|
.catch((e: ApiClientError) => setError(e.message))
|
||||||
|
.finally(() => setBusy(false));
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Breadcrumbs items={[{ label: 'Главная' }, { label: 'Проверка маршрута' }]} />
|
||||||
|
<PageHeader
|
||||||
|
icon="search"
|
||||||
|
subtitle="Диагностика"
|
||||||
|
title="Проверка разрешения маршрута"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div style={{ background: '#fff', borderRadius: 14, padding: 26, boxShadow: 'var(--sh-1)', border: '1px solid var(--gray-200)', marginBottom: 24 }}>
|
||||||
|
<FormGrid>
|
||||||
|
<Field label="Тип заявки" required full>
|
||||||
|
<Select
|
||||||
|
options={requestTypes.map(rt => ({ value: String(rt.id), label: rt.name }))}
|
||||||
|
placeholder="Выберите тип заявки"
|
||||||
|
value={requestTypeId}
|
||||||
|
onChange={e => setRequestTypeId(e.target.value)}
|
||||||
|
/>
|
||||||
|
</Field>
|
||||||
|
<Field label="Email инициатора" hint={currentUserEmail ? `Пусто = текущий пользователь (${currentUserEmail}).` : 'Пусто = текущий пользователь из токена.'}>
|
||||||
|
<TextInput value={userEmail} onChange={e => setUserEmail(e.target.value)} placeholder="user@tnsu.kz" />
|
||||||
|
</Field>
|
||||||
|
<Field label="Id проекта (Guid)" hint="Заполните, если маршрут зависит от проекта.">
|
||||||
|
<TextInput value={projectId} onChange={e => setProjectId(e.target.value)} placeholder="Необязательно" />
|
||||||
|
</Field>
|
||||||
|
<Field label="Статус" required>
|
||||||
|
<Select options={STATUS_OPTIONS} value={status} onChange={e => setStatus(e.target.value as RouteStepStatus)} />
|
||||||
|
</Field>
|
||||||
|
</FormGrid>
|
||||||
|
<div style={{ marginTop: 18 }}>
|
||||||
|
<Button variant="accent" icon="search" onClick={run} disabled={busy}>
|
||||||
|
{busy ? 'Проверка...' : 'Проверить'}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{error && <Alert kind="err">{error}</Alert>}
|
||||||
|
|
||||||
|
{result && (
|
||||||
|
<>
|
||||||
|
<Alert kind="ok">
|
||||||
|
Маршрут #{result.routeId} · {routeTypeLabels[result.routeType]}
|
||||||
|
{result.requiresControl ? ' · требуется контроль исполнения' : ''}
|
||||||
|
</Alert>
|
||||||
|
<DataTable
|
||||||
|
columns={[
|
||||||
|
{ key: 'order', header: '№', width: '50px', render: r => r.orderNo },
|
||||||
|
{ key: 'executor', header: 'Исполнитель', render: r => <PrimaryCell>{r.executorEmail ?? '—'}</PrimaryCell> },
|
||||||
|
{ key: 'position', header: 'Должность', render: r => r.position ?? '—' },
|
||||||
|
{
|
||||||
|
key: 'reassign',
|
||||||
|
header: 'Переназначение',
|
||||||
|
align: 'center',
|
||||||
|
render: r => (r.allowReassignToOtherApprovers ? 'Разрешено' : 'Нет')
|
||||||
|
}
|
||||||
|
]}
|
||||||
|
rows={result.steps}
|
||||||
|
keyOf={r => r.id}
|
||||||
|
emptyMessage="Маршрут разрешился без шагов для этого статуса."
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1,372 @@
|
|||||||
|
import * as React from 'react';
|
||||||
|
import { ApprovalRouteApi } from '../../services/api';
|
||||||
|
import { ApiClientError } from '../../services/httpClient';
|
||||||
|
import {
|
||||||
|
HeadType,
|
||||||
|
headTypeLabels,
|
||||||
|
ProjectRole,
|
||||||
|
projectRoleLabels,
|
||||||
|
RouteStepStatus,
|
||||||
|
routeStepStatusLabels,
|
||||||
|
routeTypeLabels,
|
||||||
|
StepExecutorKind,
|
||||||
|
TemplateRoute,
|
||||||
|
TemplateRouteStep,
|
||||||
|
detectExecutorKind
|
||||||
|
} from '../../models';
|
||||||
|
import {
|
||||||
|
ActionsCell,
|
||||||
|
Alert,
|
||||||
|
Badge,
|
||||||
|
Breadcrumbs,
|
||||||
|
Button,
|
||||||
|
Checkbox,
|
||||||
|
ConfirmDialog,
|
||||||
|
DataTable,
|
||||||
|
Field,
|
||||||
|
FormGrid,
|
||||||
|
IconButton,
|
||||||
|
Modal,
|
||||||
|
PageHeader,
|
||||||
|
PrimaryCell,
|
||||||
|
RadioGroup,
|
||||||
|
Select,
|
||||||
|
TextInput
|
||||||
|
} from '../ui';
|
||||||
|
|
||||||
|
export interface IRouteStepsScreenProps {
|
||||||
|
api: ApprovalRouteApi;
|
||||||
|
route: TemplateRoute;
|
||||||
|
requestTypeName: string;
|
||||||
|
onBack: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IFormState {
|
||||||
|
orderNo: string;
|
||||||
|
status: RouteStepStatus;
|
||||||
|
kind: StepExecutorKind;
|
||||||
|
executorEmail: string;
|
||||||
|
position: string;
|
||||||
|
projectRole: ProjectRole;
|
||||||
|
headType: HeadType;
|
||||||
|
allowReassignToOtherApprovers: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
function emptyForm(nextOrderNo: number): IFormState {
|
||||||
|
return {
|
||||||
|
orderNo: String(nextOrderNo),
|
||||||
|
status: RouteStepStatus.Approval,
|
||||||
|
kind: 'email',
|
||||||
|
executorEmail: '',
|
||||||
|
position: '',
|
||||||
|
projectRole: ProjectRole.ProjectManager,
|
||||||
|
headType: HeadType.Chief,
|
||||||
|
allowReassignToOtherApprovers: false
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const STATUS_OPTIONS = [RouteStepStatus.Approval, RouteStepStatus.Execution]
|
||||||
|
.map(v => ({ value: v, label: routeStepStatusLabels[v] }));
|
||||||
|
|
||||||
|
const PROJECT_ROLE_OPTIONS = Object.values(ProjectRole)
|
||||||
|
.filter(v => v !== ProjectRole.None)
|
||||||
|
.map(v => ({ value: v, label: projectRoleLabels[v] }));
|
||||||
|
|
||||||
|
const HEAD_TYPE_OPTIONS = Object.values(HeadType)
|
||||||
|
.filter(v => v !== HeadType.None)
|
||||||
|
.map(v => ({ value: v, label: headTypeLabels[v] }));
|
||||||
|
|
||||||
|
export const RouteStepsScreen: React.FC<IRouteStepsScreenProps> = ({ api, route, requestTypeName, onBack }) => {
|
||||||
|
const [items, setItems] = React.useState<TemplateRouteStep[]>([]);
|
||||||
|
const [loading, setLoading] = React.useState(true);
|
||||||
|
const [loadError, setLoadError] = React.useState<string | undefined>();
|
||||||
|
const [warning, setWarning] = React.useState<string | undefined>();
|
||||||
|
|
||||||
|
const [editing, setEditing] = React.useState<TemplateRouteStep | undefined>();
|
||||||
|
const [showForm, setShowForm] = React.useState(false);
|
||||||
|
const [form, setForm] = React.useState<IFormState>(emptyForm(1));
|
||||||
|
const [formError, setFormError] = React.useState<string | undefined>();
|
||||||
|
const [saving, setSaving] = React.useState(false);
|
||||||
|
|
||||||
|
const [deleting, setDeleting] = React.useState<TemplateRouteStep | undefined>();
|
||||||
|
const [deleteError, setDeleteError] = React.useState<string | undefined>();
|
||||||
|
const [deleteBusy, setDeleteBusy] = React.useState(false);
|
||||||
|
|
||||||
|
const load = React.useCallback(() => {
|
||||||
|
setLoading(true);
|
||||||
|
setLoadError(undefined);
|
||||||
|
api.templateRouteSteps
|
||||||
|
.list(route.id)
|
||||||
|
.then(setItems)
|
||||||
|
.catch((e: ApiClientError) => setLoadError(e.message))
|
||||||
|
.finally(() => setLoading(false));
|
||||||
|
}, [api, route.id]);
|
||||||
|
|
||||||
|
React.useEffect(() => { load(); }, [load]);
|
||||||
|
|
||||||
|
const openCreate = (): void => {
|
||||||
|
setEditing(undefined);
|
||||||
|
const nextOrder = items.length > 0 ? Math.max(...items.map(s => s.orderNo)) + 1 : 1;
|
||||||
|
setForm(emptyForm(nextOrder));
|
||||||
|
setFormError(undefined);
|
||||||
|
setShowForm(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const openEdit = (item: TemplateRouteStep): void => {
|
||||||
|
setEditing(item);
|
||||||
|
setForm({
|
||||||
|
orderNo: String(item.orderNo),
|
||||||
|
status: item.status,
|
||||||
|
kind: detectExecutorKind(item),
|
||||||
|
executorEmail: item.executorEmail ?? '',
|
||||||
|
position: item.position ?? '',
|
||||||
|
projectRole: item.projectRole && item.projectRole !== ProjectRole.None ? item.projectRole : ProjectRole.ProjectManager,
|
||||||
|
headType: item.headType && item.headType !== HeadType.None ? item.headType : HeadType.Chief,
|
||||||
|
allowReassignToOtherApprovers: item.allowReassignToOtherApprovers
|
||||||
|
});
|
||||||
|
setFormError(undefined);
|
||||||
|
setShowForm(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const submit = (): void => {
|
||||||
|
const 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
const payload = {
|
||||||
|
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);
|
||||||
|
const request = editing
|
||||||
|
? api.templateRouteSteps.update(route.id, editing.id, payload)
|
||||||
|
: api.templateRouteSteps.create(route.id, payload);
|
||||||
|
|
||||||
|
request
|
||||||
|
.then(step => {
|
||||||
|
setShowForm(false);
|
||||||
|
setWarning(step.warning ?? undefined);
|
||||||
|
load();
|
||||||
|
})
|
||||||
|
.catch((e: ApiClientError) => setFormError(e.message))
|
||||||
|
.finally(() => setSaving(false));
|
||||||
|
};
|
||||||
|
|
||||||
|
const confirmDelete = (): void => {
|
||||||
|
if (!deleting) return;
|
||||||
|
setDeleteBusy(true);
|
||||||
|
setDeleteError(undefined);
|
||||||
|
api.templateRouteSteps
|
||||||
|
.remove(route.id, deleting.id)
|
||||||
|
.then(() => {
|
||||||
|
setDeleting(undefined);
|
||||||
|
load();
|
||||||
|
})
|
||||||
|
.catch((e: ApiClientError) => setDeleteError(e.message))
|
||||||
|
.finally(() => setDeleteBusy(false));
|
||||||
|
};
|
||||||
|
|
||||||
|
const sorted = [...items].sort((a, b) => a.orderNo - b.orderNo);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Breadcrumbs
|
||||||
|
items={[
|
||||||
|
{ label: 'Главная' },
|
||||||
|
{ label: 'Маршруты согласования', onClick: onBack },
|
||||||
|
{ label: `Шаги маршрута #${route.id}` }
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
<PageHeader
|
||||||
|
icon="flag-check"
|
||||||
|
subtitle={`${requestTypeName} · ${routeTypeLabels[route.routeType]}`}
|
||||||
|
title={`Маршрут #${route.id}`}
|
||||||
|
actions={
|
||||||
|
<>
|
||||||
|
<Button variant="ghost" icon="arrow-left" onClick={onBack}>К маршрутам</Button>
|
||||||
|
<Button variant="accent" icon="plus" onClick={openCreate}>Добавить шаг</Button>
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{warning && <Alert kind="warn">{warning}</Alert>}
|
||||||
|
{loadError && <Alert kind="err">{loadError}</Alert>}
|
||||||
|
{deleteError && <Alert kind="err">{deleteError}</Alert>}
|
||||||
|
|
||||||
|
<DataTable
|
||||||
|
columns={[
|
||||||
|
{ key: 'order', header: '№', width: '50px', render: r => r.orderNo },
|
||||||
|
{
|
||||||
|
key: 'executor',
|
||||||
|
header: 'Исполнитель',
|
||||||
|
render: r => <PrimaryCell>{describeExecutor(r)}</PrimaryCell>
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'status',
|
||||||
|
header: 'Статус',
|
||||||
|
render: r => (
|
||||||
|
<Badge variant={r.status === RouteStepStatus.Approval ? 'gold' : 'blue'}>
|
||||||
|
{routeStepStatusLabels[r.status]}
|
||||||
|
</Badge>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'reassign',
|
||||||
|
header: 'Переназначение',
|
||||||
|
align: 'center',
|
||||||
|
render: r => (r.allowReassignToOtherApprovers ? <Badge variant="ok">Разрешено</Badge> : <Badge variant="neutral">Нет</Badge>)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'actions',
|
||||||
|
header: '',
|
||||||
|
width: '100px',
|
||||||
|
align: 'right',
|
||||||
|
render: r => (
|
||||||
|
<ActionsCell>
|
||||||
|
<IconButton icon="edit" label="Редактировать" onClick={() => openEdit(r)} />
|
||||||
|
<IconButton icon="trash" label="Удалить" danger onClick={() => { setDeleteError(undefined); setDeleting(r); }} />
|
||||||
|
</ActionsCell>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
]}
|
||||||
|
rows={sorted}
|
||||||
|
keyOf={r => r.id}
|
||||||
|
emptyMessage={loading ? 'Загрузка...' : 'Шагов пока нет — добавьте первый.'}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{showForm && (
|
||||||
|
<Modal
|
||||||
|
title={editing ? `Шаг #${editing.orderNo}` : 'Новый шаг маршрута'}
|
||||||
|
onClose={() => setShowForm(false)}
|
||||||
|
footer={
|
||||||
|
<>
|
||||||
|
<Button variant="secondary" onClick={() => setShowForm(false)} disabled={saving}>Отмена</Button>
|
||||||
|
<Button variant="accent" icon="check" onClick={submit} disabled={saving}>
|
||||||
|
{saving ? 'Сохранение...' : 'Сохранить'}
|
||||||
|
</Button>
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{formError && <Alert kind="err">{formError}</Alert>}
|
||||||
|
<FormGrid>
|
||||||
|
<Field label="Порядковый номер" required>
|
||||||
|
<TextInput
|
||||||
|
type="number"
|
||||||
|
min={1}
|
||||||
|
value={form.orderNo}
|
||||||
|
onChange={e => setForm({ ...form, orderNo: e.target.value })}
|
||||||
|
/>
|
||||||
|
</Field>
|
||||||
|
<Field label="Статус шага" required>
|
||||||
|
<Select
|
||||||
|
options={STATUS_OPTIONS}
|
||||||
|
value={form.status}
|
||||||
|
onChange={e => setForm({ ...form, status: e.target.value as RouteStepStatus })}
|
||||||
|
/>
|
||||||
|
</Field>
|
||||||
|
|
||||||
|
<Field label="Кто исполнитель" required full hint="Должно быть заполнено ровно одно из полей.">
|
||||||
|
<RadioGroup
|
||||||
|
name="executorKind"
|
||||||
|
value={form.kind}
|
||||||
|
onChange={kind => setForm({ ...form, kind })}
|
||||||
|
options={[
|
||||||
|
{ value: 'email', label: 'Конкретный сотрудник (email)' },
|
||||||
|
{ value: 'position', label: 'По должности в компании инициатора' },
|
||||||
|
{
|
||||||
|
value: 'projectRole',
|
||||||
|
label: 'По роли в проекте',
|
||||||
|
disabled: !route.byProject,
|
||||||
|
disabledHint: 'Недоступно: маршрут не учитывает проект (по проекту = нет).'
|
||||||
|
},
|
||||||
|
{ value: 'headType', label: 'По типу руководителя' }
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
</Field>
|
||||||
|
|
||||||
|
{form.kind === 'email' && (
|
||||||
|
<Field label="Email исполнителя" required full>
|
||||||
|
<TextInput
|
||||||
|
value={form.executorEmail}
|
||||||
|
onChange={e => setForm({ ...form, executorEmail: e.target.value })}
|
||||||
|
placeholder="user@tnsu.kz"
|
||||||
|
/>
|
||||||
|
</Field>
|
||||||
|
)}
|
||||||
|
{form.kind === 'position' && (
|
||||||
|
<Field label="Должность" required full hint="Ищется в справочнике api.tnsu.kz по компании инициатора.">
|
||||||
|
<TextInput
|
||||||
|
value={form.position}
|
||||||
|
onChange={e => setForm({ ...form, position: e.target.value })}
|
||||||
|
placeholder="Главный инженер"
|
||||||
|
/>
|
||||||
|
</Field>
|
||||||
|
)}
|
||||||
|
{form.kind === 'projectRole' && (
|
||||||
|
<Field label="Роль в проекте" required full>
|
||||||
|
<Select
|
||||||
|
options={PROJECT_ROLE_OPTIONS}
|
||||||
|
value={form.projectRole}
|
||||||
|
onChange={e => setForm({ ...form, projectRole: e.target.value as ProjectRole })}
|
||||||
|
/>
|
||||||
|
</Field>
|
||||||
|
)}
|
||||||
|
{form.kind === 'headType' && (
|
||||||
|
<Field label="Тип руководителя" required full>
|
||||||
|
<Select
|
||||||
|
options={HEAD_TYPE_OPTIONS}
|
||||||
|
value={form.headType}
|
||||||
|
onChange={e => setForm({ ...form, headType: e.target.value as HeadType })}
|
||||||
|
/>
|
||||||
|
</Field>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<Field label="Переназначение" full>
|
||||||
|
<Checkbox
|
||||||
|
label="Разрешить переназначать другим согласующим"
|
||||||
|
checked={form.allowReassignToOtherApprovers}
|
||||||
|
onChange={v => setForm({ ...form, allowReassignToOtherApprovers: v })}
|
||||||
|
/>
|
||||||
|
</Field>
|
||||||
|
</FormGrid>
|
||||||
|
</Modal>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{deleting && (
|
||||||
|
<ConfirmDialog
|
||||||
|
title="Удалить шаг?"
|
||||||
|
text={`Шаг #${deleting.orderNo} будет удалён безвозвратно.`}
|
||||||
|
confirmLabel="Удалить"
|
||||||
|
busy={deleteBusy}
|
||||||
|
onCancel={() => setDeleting(undefined)}
|
||||||
|
onConfirm={confirmDelete}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
function describeExecutor(step: TemplateRouteStep): string {
|
||||||
|
if (step.executorEmail) return step.executorEmail;
|
||||||
|
if (step.position) return step.position;
|
||||||
|
if (step.projectRole && step.projectRole !== ProjectRole.None) return `Роль: ${projectRoleLabels[step.projectRole]}`;
|
||||||
|
if (step.headType && step.headType !== HeadType.None) return `Руководитель: ${headTypeLabels[step.headType]}`;
|
||||||
|
return '—';
|
||||||
|
}
|
||||||
@@ -0,0 +1,310 @@
|
|||||||
|
import * as React from 'react';
|
||||||
|
import { ApprovalRouteApi } from '../../services/api';
|
||||||
|
import { ApiClientError } from '../../services/httpClient';
|
||||||
|
import { RequestType, routeTypeLabels, RouteType, TemplateRoute } from '../../models';
|
||||||
|
import {
|
||||||
|
ActionsCell,
|
||||||
|
Alert,
|
||||||
|
Badge,
|
||||||
|
Breadcrumbs,
|
||||||
|
Button,
|
||||||
|
Checkbox,
|
||||||
|
ConfirmDialog,
|
||||||
|
DataTable,
|
||||||
|
Field,
|
||||||
|
FormGrid,
|
||||||
|
IconButton,
|
||||||
|
Modal,
|
||||||
|
PageHeader,
|
||||||
|
PrimaryCell,
|
||||||
|
Select,
|
||||||
|
TextInput
|
||||||
|
} from '../ui';
|
||||||
|
|
||||||
|
export interface IRoutesScreenProps {
|
||||||
|
api: ApprovalRouteApi;
|
||||||
|
onOpenSteps: (route: TemplateRoute) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IFormState {
|
||||||
|
requestTypeId: string;
|
||||||
|
routeType: RouteType;
|
||||||
|
companyId: string;
|
||||||
|
departmentId: string;
|
||||||
|
byProject: boolean;
|
||||||
|
requiresControl: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const EMPTY_FORM: IFormState = {
|
||||||
|
requestTypeId: '',
|
||||||
|
routeType: RouteType.Sequential,
|
||||||
|
companyId: '',
|
||||||
|
departmentId: '',
|
||||||
|
byProject: false,
|
||||||
|
requiresControl: false
|
||||||
|
};
|
||||||
|
|
||||||
|
const ROUTE_TYPE_OPTIONS = Object.values(RouteType)
|
||||||
|
.filter(v => v !== RouteType.None)
|
||||||
|
.map(v => ({ value: v, label: routeTypeLabels[v] }));
|
||||||
|
|
||||||
|
export const RoutesScreen: React.FC<IRoutesScreenProps> = ({ api, onOpenSteps }) => {
|
||||||
|
const [requestTypes, setRequestTypes] = React.useState<RequestType[]>([]);
|
||||||
|
const [items, setItems] = React.useState<TemplateRoute[]>([]);
|
||||||
|
const [filterRequestTypeId, setFilterRequestTypeId] = React.useState<string>('');
|
||||||
|
const [loading, setLoading] = React.useState(true);
|
||||||
|
const [loadError, setLoadError] = React.useState<string | undefined>();
|
||||||
|
|
||||||
|
const [editing, setEditing] = React.useState<TemplateRoute | undefined>();
|
||||||
|
const [showForm, setShowForm] = React.useState(false);
|
||||||
|
const [form, setForm] = React.useState<IFormState>(EMPTY_FORM);
|
||||||
|
const [formError, setFormError] = React.useState<string | undefined>();
|
||||||
|
const [saving, setSaving] = React.useState(false);
|
||||||
|
|
||||||
|
const [deleting, setDeleting] = React.useState<TemplateRoute | undefined>();
|
||||||
|
const [deleteError, setDeleteError] = React.useState<string | undefined>();
|
||||||
|
const [deleteBusy, setDeleteBusy] = React.useState(false);
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
api.requestTypes.list().then(setRequestTypes).catch(() => { /* показывается в общей ошибке ниже */ });
|
||||||
|
}, [api]);
|
||||||
|
|
||||||
|
const load = React.useCallback(() => {
|
||||||
|
setLoading(true);
|
||||||
|
setLoadError(undefined);
|
||||||
|
const requestTypeId = filterRequestTypeId ? Number(filterRequestTypeId) : undefined;
|
||||||
|
api.templateRoutes
|
||||||
|
.list(requestTypeId)
|
||||||
|
.then(setItems)
|
||||||
|
.catch((e: ApiClientError) => setLoadError(e.message))
|
||||||
|
.finally(() => setLoading(false));
|
||||||
|
}, [api, filterRequestTypeId]);
|
||||||
|
|
||||||
|
React.useEffect(() => { load(); }, [load]);
|
||||||
|
|
||||||
|
const requestTypeName = (id: number): string => requestTypes.find(rt => rt.id === id)?.name ?? `#${id}`;
|
||||||
|
|
||||||
|
const openCreate = (): void => {
|
||||||
|
setEditing(undefined);
|
||||||
|
setForm({ ...EMPTY_FORM, requestTypeId: filterRequestTypeId });
|
||||||
|
setFormError(undefined);
|
||||||
|
setShowForm(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const openEdit = (item: TemplateRoute): void => {
|
||||||
|
setEditing(item);
|
||||||
|
setForm({
|
||||||
|
requestTypeId: String(item.requestTypeId),
|
||||||
|
routeType: item.routeType,
|
||||||
|
companyId: item.companyId,
|
||||||
|
departmentId: item.departmentId ?? '',
|
||||||
|
byProject: item.byProject,
|
||||||
|
requiresControl: item.requiresControl
|
||||||
|
});
|
||||||
|
setFormError(undefined);
|
||||||
|
setShowForm(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const submit = (): void => {
|
||||||
|
if (!editing && !form.requestTypeId) {
|
||||||
|
setFormError('Выберите тип заявки.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!form.companyId.trim()) {
|
||||||
|
setFormError('Компания обязательна (Guid из справочника api.tnsu.kz).');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setSaving(true);
|
||||||
|
setFormError(undefined);
|
||||||
|
|
||||||
|
const departmentId = form.departmentId.trim() ? form.departmentId.trim() : null;
|
||||||
|
const request = editing
|
||||||
|
? api.templateRoutes.update(editing.id, {
|
||||||
|
routeType: form.routeType,
|
||||||
|
companyId: form.companyId.trim(),
|
||||||
|
departmentId,
|
||||||
|
byProject: form.byProject,
|
||||||
|
requiresControl: form.requiresControl
|
||||||
|
})
|
||||||
|
: api.templateRoutes.create({
|
||||||
|
routeType: form.routeType,
|
||||||
|
requestTypeId: Number(form.requestTypeId),
|
||||||
|
companyId: form.companyId.trim(),
|
||||||
|
departmentId,
|
||||||
|
byProject: form.byProject,
|
||||||
|
requiresControl: form.requiresControl
|
||||||
|
});
|
||||||
|
|
||||||
|
request
|
||||||
|
.then(() => {
|
||||||
|
setShowForm(false);
|
||||||
|
load();
|
||||||
|
})
|
||||||
|
.catch((e: ApiClientError) => setFormError(e.message))
|
||||||
|
.finally(() => setSaving(false));
|
||||||
|
};
|
||||||
|
|
||||||
|
const confirmDelete = (): void => {
|
||||||
|
if (!deleting) return;
|
||||||
|
setDeleteBusy(true);
|
||||||
|
setDeleteError(undefined);
|
||||||
|
api.templateRoutes
|
||||||
|
.remove(deleting.id)
|
||||||
|
.then(() => {
|
||||||
|
setDeleting(undefined);
|
||||||
|
load();
|
||||||
|
})
|
||||||
|
.catch((e: ApiClientError) => setDeleteError(e.message))
|
||||||
|
.finally(() => setDeleteBusy(false));
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Breadcrumbs items={[{ label: 'Главная' }, { label: 'Маршруты согласования' }]} />
|
||||||
|
<PageHeader
|
||||||
|
icon="route"
|
||||||
|
subtitle="Настройка согласований"
|
||||||
|
title="Маршруты согласования"
|
||||||
|
actions={<Button variant="accent" icon="plus" onClick={openCreate}>Добавить маршрут</Button>}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div style={{ marginBottom: 18, maxWidth: 320 }}>
|
||||||
|
<Field label="Фильтр по типу заявки">
|
||||||
|
<Select
|
||||||
|
options={requestTypes.map(rt => ({ value: String(rt.id), label: rt.name }))}
|
||||||
|
placeholder="Все типы заявок"
|
||||||
|
value={filterRequestTypeId}
|
||||||
|
onChange={e => setFilterRequestTypeId(e.target.value)}
|
||||||
|
/>
|
||||||
|
</Field>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{loadError && <Alert kind="err">{loadError}</Alert>}
|
||||||
|
{deleteError && <Alert kind="err">{deleteError}</Alert>}
|
||||||
|
|
||||||
|
<DataTable
|
||||||
|
columns={[
|
||||||
|
{
|
||||||
|
key: 'requestType',
|
||||||
|
header: 'Тип заявки',
|
||||||
|
render: r => <PrimaryCell>{requestTypeName(r.requestTypeId)}</PrimaryCell>
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'routeType',
|
||||||
|
header: 'Тип маршрута',
|
||||||
|
render: r => <Badge variant={r.routeType === RouteType.Parallel ? 'blue' : 'gold'}>{routeTypeLabels[r.routeType]}</Badge>
|
||||||
|
},
|
||||||
|
{ key: 'company', header: 'Компания', render: r => <code>{shortGuid(r.companyId)}</code> },
|
||||||
|
{ key: 'department', header: 'Отдел', render: r => (r.departmentId ? <code>{shortGuid(r.departmentId)}</code> : '—') },
|
||||||
|
{
|
||||||
|
key: 'byProject',
|
||||||
|
header: 'По проекту',
|
||||||
|
align: 'center',
|
||||||
|
render: r => (r.byProject ? <Badge variant="ok">Да</Badge> : <Badge variant="neutral">Нет</Badge>)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'control',
|
||||||
|
header: 'Контроль',
|
||||||
|
align: 'center',
|
||||||
|
render: r => (r.requiresControl ? <Badge variant="gold">Требуется</Badge> : <Badge variant="neutral">Нет</Badge>)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'actions',
|
||||||
|
header: '',
|
||||||
|
width: '160px',
|
||||||
|
align: 'right',
|
||||||
|
render: r => (
|
||||||
|
<ActionsCell>
|
||||||
|
<Button size="sm" variant="secondary" icon="clipboard" onClick={() => onOpenSteps(r)}>Шаги</Button>
|
||||||
|
<IconButton icon="edit" label="Редактировать" onClick={() => openEdit(r)} />
|
||||||
|
<IconButton icon="trash" label="Удалить" danger onClick={() => { setDeleteError(undefined); setDeleting(r); }} />
|
||||||
|
</ActionsCell>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
]}
|
||||||
|
rows={items}
|
||||||
|
keyOf={r => r.id}
|
||||||
|
emptyMessage={loading ? 'Загрузка...' : 'Маршрутов пока нет.'}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{showForm && (
|
||||||
|
<Modal
|
||||||
|
title={editing ? `Маршрут #${editing.id}` : 'Новый маршрут'}
|
||||||
|
onClose={() => setShowForm(false)}
|
||||||
|
footer={
|
||||||
|
<>
|
||||||
|
<Button variant="secondary" onClick={() => setShowForm(false)} disabled={saving}>Отмена</Button>
|
||||||
|
<Button variant="accent" icon="check" onClick={submit} disabled={saving}>
|
||||||
|
{saving ? 'Сохранение...' : 'Сохранить'}
|
||||||
|
</Button>
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{formError && <Alert kind="err">{formError}</Alert>}
|
||||||
|
<FormGrid>
|
||||||
|
<Field label="Тип заявки" required full>
|
||||||
|
<Select
|
||||||
|
options={requestTypes.map(rt => ({ value: String(rt.id), label: rt.name }))}
|
||||||
|
placeholder="Выберите тип заявки"
|
||||||
|
value={form.requestTypeId}
|
||||||
|
disabled={!!editing}
|
||||||
|
onChange={e => setForm({ ...form, requestTypeId: e.target.value })}
|
||||||
|
/>
|
||||||
|
</Field>
|
||||||
|
<Field label="Тип маршрута" required>
|
||||||
|
<Select
|
||||||
|
options={ROUTE_TYPE_OPTIONS}
|
||||||
|
value={form.routeType}
|
||||||
|
onChange={e => setForm({ ...form, routeType: e.target.value as RouteType })}
|
||||||
|
/>
|
||||||
|
</Field>
|
||||||
|
<Field label="Компания (Guid)" required hint="Из справочника api.tnsu.kz.">
|
||||||
|
<TextInput
|
||||||
|
value={form.companyId}
|
||||||
|
onChange={e => setForm({ ...form, companyId: e.target.value })}
|
||||||
|
placeholder="00000000-0000-0000-0000-000000000000"
|
||||||
|
/>
|
||||||
|
</Field>
|
||||||
|
<Field label="Отдел (Guid)" hint="Необязательно — маршрут «по умолчанию» для компании.">
|
||||||
|
<TextInput
|
||||||
|
value={form.departmentId}
|
||||||
|
onChange={e => setForm({ ...form, departmentId: e.target.value })}
|
||||||
|
placeholder="Оставьте пустым для маршрута по умолчанию"
|
||||||
|
/>
|
||||||
|
</Field>
|
||||||
|
<Field label="По проекту">
|
||||||
|
<Checkbox
|
||||||
|
label="Маршрут учитывает конкретный проект"
|
||||||
|
checked={form.byProject}
|
||||||
|
onChange={v => setForm({ ...form, byProject: v })}
|
||||||
|
/>
|
||||||
|
</Field>
|
||||||
|
<Field label="Контроль">
|
||||||
|
<Checkbox
|
||||||
|
label="Требуется отдельный контроль исполнения"
|
||||||
|
checked={form.requiresControl}
|
||||||
|
onChange={v => setForm({ ...form, requiresControl: v })}
|
||||||
|
/>
|
||||||
|
</Field>
|
||||||
|
</FormGrid>
|
||||||
|
</Modal>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{deleting && (
|
||||||
|
<ConfirmDialog
|
||||||
|
title="Удалить маршрут?"
|
||||||
|
text={`Маршрут #${deleting.id} и все его шаги будут удалены безвозвратно.`}
|
||||||
|
confirmLabel="Удалить"
|
||||||
|
busy={deleteBusy}
|
||||||
|
onCancel={() => setDeleting(undefined)}
|
||||||
|
onConfirm={confirmDelete}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
function shortGuid(guid: string): string {
|
||||||
|
return guid.length > 8 ? `${guid.slice(0, 8)}…` : guid;
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
@import '../../../../styles/design-tokens';
|
||||||
|
|
||||||
|
.alert {
|
||||||
|
padding: 12px 16px;
|
||||||
|
border-radius: $r-md;
|
||||||
|
font-size: 12.5px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
margin-bottom: 18px;
|
||||||
|
border: 1px solid;
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.warn { background: $gold-50; color: $amber-700; border-color: $gold-300; }
|
||||||
|
.ok { background: $green-50; color: $green-600; border-color: #BBF7D0; }
|
||||||
|
.err { background: $red-50; color: $red-500; border-color: #FECACA; }
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
import * as React from 'react';
|
||||||
|
import styles from './Alert.module.scss';
|
||||||
|
import { Icon } from './Icon';
|
||||||
|
|
||||||
|
export type AlertKind = 'warn' | 'ok' | 'err';
|
||||||
|
|
||||||
|
export interface IAlertProps {
|
||||||
|
kind: AlertKind;
|
||||||
|
children: React.ReactNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ICONS = { warn: 'warning', ok: 'check', err: 'close' } as const;
|
||||||
|
|
||||||
|
export const Alert: React.FC<IAlertProps> = ({ kind, children }) => (
|
||||||
|
<div className={`${styles.alert} ${styles[kind]}`}>
|
||||||
|
<Icon name={ICONS[kind]} size="sm" />
|
||||||
|
<span>{children}</span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
@import '../../../../styles/design-tokens';
|
||||||
|
|
||||||
|
.badge {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 5px;
|
||||||
|
padding: 3px 11px;
|
||||||
|
border-radius: $r-full;
|
||||||
|
font-size: 11px;
|
||||||
|
font-weight: 600;
|
||||||
|
letter-spacing: .2px;
|
||||||
|
white-space: nowrap;
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
content: '';
|
||||||
|
width: 5px;
|
||||||
|
height: 5px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: currentColor;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.ok { background: $green-50; color: $green-600; }
|
||||||
|
.gold { background: $gold-100; color: $amber-700; }
|
||||||
|
.neutral { background: $gray-100; color: $gray-500; }
|
||||||
|
.blue { background: $blue-50; color: $blue-600; }
|
||||||
|
.red { background: $red-50; color: $red-500; }
|
||||||
|
|
||||||
|
.count {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
min-width: 22px;
|
||||||
|
height: 22px;
|
||||||
|
border-radius: 11px;
|
||||||
|
background: $navy-900;
|
||||||
|
color: $gold-300;
|
||||||
|
font-size: 11px;
|
||||||
|
font-weight: 600;
|
||||||
|
padding: 0 6px;
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
import * as React from 'react';
|
||||||
|
import styles from './Badge.module.scss';
|
||||||
|
|
||||||
|
export type BadgeVariant = 'ok' | 'gold' | 'neutral' | 'blue' | 'red';
|
||||||
|
|
||||||
|
export interface IBadgeProps {
|
||||||
|
variant?: BadgeVariant;
|
||||||
|
children: React.ReactNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Badge: React.FC<IBadgeProps> = ({ variant = 'neutral', children }) => (
|
||||||
|
<span className={`${styles.badge} ${styles[variant]}`}>{children}</span>
|
||||||
|
);
|
||||||
|
|
||||||
|
export const CountBadge: React.FC<{ count: number }> = ({ count }) => (
|
||||||
|
<span className={styles.count}>{count}</span>
|
||||||
|
);
|
||||||
@@ -0,0 +1,81 @@
|
|||||||
|
@import '../../../../styles/design-tokens';
|
||||||
|
|
||||||
|
.btn {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 7px;
|
||||||
|
padding: 10px 20px;
|
||||||
|
border-radius: $r-sm;
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 500;
|
||||||
|
font-family: $font-sans;
|
||||||
|
cursor: pointer;
|
||||||
|
border: 1px solid transparent;
|
||||||
|
transition: .18s;
|
||||||
|
white-space: nowrap;
|
||||||
|
letter-spacing: .15px;
|
||||||
|
text-decoration: none;
|
||||||
|
|
||||||
|
&:disabled {
|
||||||
|
opacity: .5;
|
||||||
|
cursor: not-allowed;
|
||||||
|
transform: none !important;
|
||||||
|
box-shadow: none !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.primary {
|
||||||
|
background: $navy-800;
|
||||||
|
color: #fff;
|
||||||
|
border-color: $navy-800;
|
||||||
|
&:hover:not(:disabled) { background: $navy-700; box-shadow: $sh-2; }
|
||||||
|
}
|
||||||
|
|
||||||
|
.accent {
|
||||||
|
@include btn-accent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.secondary {
|
||||||
|
background: #fff;
|
||||||
|
color: $navy-800;
|
||||||
|
border-color: $gray-300;
|
||||||
|
&:hover:not(:disabled) { border-color: $gold-500; color: $navy-900; }
|
||||||
|
}
|
||||||
|
|
||||||
|
.ghost {
|
||||||
|
background: transparent;
|
||||||
|
color: $navy-700;
|
||||||
|
border-color: transparent;
|
||||||
|
&:hover:not(:disabled) { background: $navy-100; }
|
||||||
|
}
|
||||||
|
|
||||||
|
.danger {
|
||||||
|
background: #fff;
|
||||||
|
color: $red-500;
|
||||||
|
border-color: $gray-300;
|
||||||
|
&:hover:not(:disabled) { border-color: $red-500; background: $red-50; }
|
||||||
|
}
|
||||||
|
|
||||||
|
.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: $r-sm;
|
||||||
|
background: transparent;
|
||||||
|
border: 1px solid transparent;
|
||||||
|
cursor: pointer;
|
||||||
|
color: $gray-400;
|
||||||
|
transition: .18s;
|
||||||
|
|
||||||
|
&:hover:not(:disabled) { color: $navy-800; background: $navy-50; border-color: $gray-200; }
|
||||||
|
&.danger:hover:not(:disabled) { color: $red-500; background: $red-50; }
|
||||||
|
&:disabled { opacity: .4; cursor: not-allowed; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
import * as React from 'react';
|
||||||
|
import styles from './Button.module.scss';
|
||||||
|
import { Icon, IconName } from './Icon';
|
||||||
|
|
||||||
|
export type ButtonVariant = 'primary' | 'accent' | 'secondary' | 'ghost' | 'danger';
|
||||||
|
|
||||||
|
export interface IButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
|
||||||
|
variant?: ButtonVariant;
|
||||||
|
size?: 'md' | 'sm';
|
||||||
|
icon?: IconName;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Button: React.FC<IButtonProps> = ({
|
||||||
|
variant = 'secondary',
|
||||||
|
size = 'md',
|
||||||
|
icon,
|
||||||
|
className,
|
||||||
|
children,
|
||||||
|
...rest
|
||||||
|
}) => {
|
||||||
|
const classes = [styles.btn, styles[variant], size === 'sm' ? styles.sm : '', className]
|
||||||
|
.filter(Boolean)
|
||||||
|
.join(' ');
|
||||||
|
|
||||||
|
return (
|
||||||
|
<button type="button" className={classes} {...rest}>
|
||||||
|
{icon && <Icon name={icon} size="sm" />}
|
||||||
|
{children}
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export interface IIconButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
|
||||||
|
icon: IconName;
|
||||||
|
danger?: boolean;
|
||||||
|
label: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const IconButton: React.FC<IIconButtonProps> = ({ icon, danger, label, className, ...rest }) => {
|
||||||
|
const classes = [styles.iconButton, danger ? styles.danger : '', className].filter(Boolean).join(' ');
|
||||||
|
return (
|
||||||
|
<button type="button" className={classes} aria-label={label} title={label} {...rest}>
|
||||||
|
<Icon name={icon} size="sm" />
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1,94 @@
|
|||||||
|
@import '../../../../styles/design-tokens';
|
||||||
|
|
||||||
|
.grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fill, minmax(290px, 1fr));
|
||||||
|
gap: 14px;
|
||||||
|
margin-bottom: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navCard {
|
||||||
|
background: #fff;
|
||||||
|
border-radius: $r-md;
|
||||||
|
padding: 22px 24px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 16px;
|
||||||
|
box-shadow: $sh-1;
|
||||||
|
cursor: pointer;
|
||||||
|
@include card-hover-gold;
|
||||||
|
border: 1px solid $gray-200;
|
||||||
|
text-decoration: none;
|
||||||
|
color: inherit;
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
left: 0; top: 0; bottom: 0;
|
||||||
|
width: 3px;
|
||||||
|
background: $grad-gold-v;
|
||||||
|
opacity: 0;
|
||||||
|
transition: .22s;
|
||||||
|
}
|
||||||
|
&:hover::before { opacity: 1; }
|
||||||
|
}
|
||||||
|
|
||||||
|
.navIcon {
|
||||||
|
width: 44px;
|
||||||
|
height: 44px;
|
||||||
|
border-radius: $r-md;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
flex-shrink: 0;
|
||||||
|
background: $navy-50;
|
||||||
|
color: $navy-700;
|
||||||
|
border: 1px solid $navy-100;
|
||||||
|
transition: .22s;
|
||||||
|
|
||||||
|
.navCard:hover & { background: $gold-50; color: $gold-600; border-color: $gold-300; }
|
||||||
|
}
|
||||||
|
|
||||||
|
.navTitle { font-size: 14.5px; font-weight: 600; color: $navy-900; letter-spacing: .1px; }
|
||||||
|
.navDesc { font-size: 12px; color: $gray-400; margin-top: 2px; }
|
||||||
|
|
||||||
|
.navArrow {
|
||||||
|
color: $gray-300;
|
||||||
|
margin-left: auto;
|
||||||
|
display: flex;
|
||||||
|
transition: .22s;
|
||||||
|
.navCard:hover & { color: $gold-500; 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: $r-md;
|
||||||
|
padding: 18px 20px;
|
||||||
|
box-shadow: $sh-1;
|
||||||
|
border: 1px solid $gray-200;
|
||||||
|
}
|
||||||
|
|
||||||
|
.statLabel {
|
||||||
|
font-size: 10px;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 1.2px;
|
||||||
|
color: $gray-400;
|
||||||
|
font-weight: 600;
|
||||||
|
margin-bottom: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.statValue {
|
||||||
|
font-family: $font-serif;
|
||||||
|
font-size: 26px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: $navy-900;
|
||||||
|
}
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
import * as React from 'react';
|
||||||
|
import styles from './Card.module.scss';
|
||||||
|
import { Icon, IconName } from './Icon';
|
||||||
|
|
||||||
|
export const CardGrid: React.FC<{ children: React.ReactNode }> = ({ children }) => (
|
||||||
|
<div className={styles.grid}>{children}</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
export interface INavCardProps {
|
||||||
|
icon: IconName;
|
||||||
|
title: string;
|
||||||
|
description: string;
|
||||||
|
onClick: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const NavCard: React.FC<INavCardProps> = ({ icon, title, description, onClick }) => (
|
||||||
|
<a className={styles.navCard} onClick={e => { e.preventDefault(); onClick(); }} href="#">
|
||||||
|
<div className={styles.navIcon}><Icon name={icon} /></div>
|
||||||
|
<div>
|
||||||
|
<div className={styles.navTitle}>{title}</div>
|
||||||
|
<div className={styles.navDesc}>{description}</div>
|
||||||
|
</div>
|
||||||
|
<div className={styles.navArrow}><Icon name="chevron-right" size="sm" /></div>
|
||||||
|
</a>
|
||||||
|
);
|
||||||
|
|
||||||
|
export const StatRow: React.FC<{ children: React.ReactNode }> = ({ children }) => (
|
||||||
|
<div className={styles.statRow}>{children}</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
export const StatCard: React.FC<{ label: string; value: React.ReactNode }> = ({ label, value }) => (
|
||||||
|
<div className={styles.statCard}>
|
||||||
|
<div className={styles.statLabel}>{label}</div>
|
||||||
|
<div className={styles.statValue}>{value}</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user