commit 31ef6fdbb96e9d337a5c6aaf0ac4049f4f46c135 Author: Тимур Городецкий Date: Mon Aug 17 11:23:57 2026 +0500 feat: config-driven реестр на реальных данных SP diff --git a/.eslintrc.js b/.eslintrc.js new file mode 100644 index 0000000..a790d60 --- /dev/null +++ b/.eslintrc.js @@ -0,0 +1,321 @@ +require('@rushstack/eslint-config/patch/modern-module-resolution'); +module.exports = { + extends: ['@microsoft/eslint-config-spfx/lib/profiles/react'], + parserOptions: { tsconfigRootDir: __dirname }, + overrides: [ + { + files: ['*.ts', '*.tsx'], + parser: '@typescript-eslint/parser', + 'parserOptions': { + 'project': './tsconfig.json', + 'ecmaVersion': 2018, + 'sourceType': 'module' + }, + rules: { + // Prevent usage of the JavaScript null value, while allowing code to access existing APIs that may require null. https://www.npmjs.com/package/@rushstack/eslint-plugin + '@rushstack/no-new-null': 1, + // Require Jest module mocking APIs to be called before any other statements in their code block. https://www.npmjs.com/package/@rushstack/eslint-plugin + '@rushstack/hoist-jest-mock': 1, + // Require chunk names for dynamic imports in SPFx projects. https://www.npmjs.com/package/@rushstack/eslint-plugin + '@rushstack/import-requires-chunk-name': 1, + // Ensure that React components rendered with ReactDOM.render() are unmounted with ReactDOM.unmountComponentAtNode(). https://www.npmjs.com/package/@rushstack/eslint-plugin + '@rushstack/pair-react-dom-render-unmount': 1, + // Require regular expressions to be constructed from string constants rather than dynamically building strings at runtime. https://www.npmjs.com/package/@rushstack/eslint-plugin-security + '@rushstack/security/no-unsafe-regexp': 1, + // STANDARDIZED BY: @typescript-eslint\eslint-plugin\dist\configs\recommended.json + '@typescript-eslint/adjacent-overload-signatures': 1, + // RATIONALE: Code is more readable when the type of every variable is immediately obvious. + // Even if the compiler may be able to infer a type, this inference will be unavailable + // to a person who is reviewing a GitHub diff. This rule makes writing code harder, + // but writing code is a much less important activity than reading it. + // + // STANDARDIZED BY: @typescript-eslint\eslint-plugin\dist\configs\recommended.json + '@typescript-eslint/explicit-function-return-type': [ + 1, + { + 'allowExpressions': true, + 'allowTypedFunctionExpressions': true, + 'allowHigherOrderFunctions': false + } + ], + // STANDARDIZED BY: @typescript-eslint\eslint-plugin\dist\configs\recommended.json + // Rationale to disable: although this is a recommended rule, it is up to dev to select coding style. + // Set to 1 (warning) or 2 (error) to enable. + '@typescript-eslint/explicit-member-accessibility': 0, + // STANDARDIZED BY: @typescript-eslint\eslint-plugin\dist\configs\recommended.json + '@typescript-eslint/no-array-constructor': 1, + // STANDARDIZED BY: @typescript-eslint\eslint-plugin\dist\configs\recommended.json + // + // RATIONALE: The "any" keyword disables static type checking, the main benefit of using TypeScript. + // This rule should be suppressed only in very special cases such as JSON.stringify() + // where the type really can be anything. Even if the type is flexible, another type + // may be more appropriate such as "unknown", "{}", or "Record". + '@typescript-eslint/no-explicit-any': 1, + // RATIONALE: The #1 rule of promises is that every promise chain must be terminated by a catch() + // handler. Thus wherever a Promise arises, the code must either append a catch handler, + // or else return the object to a caller (who assumes this responsibility). Unterminated + // promise chains are a serious issue. Besides causing errors to be silently ignored, + // they can also cause a NodeJS process to terminate unexpectedly. + '@typescript-eslint/no-floating-promises': 2, + // RATIONALE: Catches a common coding mistake. + '@typescript-eslint/no-for-in-array': 2, + // STANDARDIZED BY: @typescript-eslint\eslint-plugin\dist\configs\recommended.json + '@typescript-eslint/no-misused-new': 2, + // RATIONALE: The "namespace" keyword is not recommended for organizing code because JavaScript lacks + // a "using" statement to traverse namespaces. Nested namespaces prevent certain bundler + // optimizations. If you are declaring loose functions/variables, it's better to make them + // static members of a class, since classes support property getters and their private + // members are accessible by unit tests. Also, the exercise of choosing a meaningful + // class name tends to produce more discoverable APIs: for example, search+replacing + // the function "reverse()" is likely to return many false matches, whereas if we always + // write "Text.reverse()" is more unique. For large scale organization, it's recommended + // to decompose your code into separate NPM packages, which ensures that component + // dependencies are tracked more conscientiously. + // + // STANDARDIZED BY: @typescript-eslint\eslint-plugin\dist\configs\recommended.json + '@typescript-eslint/no-namespace': [ + 1, + { + 'allowDeclarations': false, + 'allowDefinitionFiles': false + } + ], + // RATIONALE: Parameter properties provide a shorthand such as "constructor(public title: string)" + // that avoids the effort of declaring "title" as a field. This TypeScript feature makes + // code easier to write, but arguably sacrifices readability: In the notes for + // "@typescript-eslint/member-ordering" we pointed out that fields are central to + // a class's design, so we wouldn't want to bury them in a constructor signature + // just to save some typing. + // + // STANDARDIZED BY: @typescript-eslint\eslint-plugin\dist\configs\recommended.json + // Set to 1 (warning) or 2 (error) to enable the rule + '@typescript-eslint/parameter-properties': 0, + // RATIONALE: When left in shipping code, unused variables often indicate a mistake. Dead code + // may impact performance. + // + // STANDARDIZED BY: @typescript-eslint\eslint-plugin\dist\configs\recommended.json + '@typescript-eslint/no-unused-vars': [ + 1, + { + 'vars': 'all', + // Unused function arguments often indicate a mistake in JavaScript code. However in TypeScript code, + // the compiler catches most of those mistakes, and unused arguments are fairly common for type signatures + // that are overriding a base class method or implementing an interface. + 'args': 'none' + } + ], + // STANDARDIZED BY: @typescript-eslint\eslint-plugin\dist\configs\recommended.json + '@typescript-eslint/no-use-before-define': [ + 2, + { + 'functions': false, + 'classes': true, + 'variables': true, + 'enums': true, + 'typedefs': true + } + ], + // Disallows require statements except in import statements. + // In other words, the use of forms such as var foo = require("foo") are banned. Instead use ES6 style imports or import foo = require("foo") imports. + '@typescript-eslint/no-var-requires': 'error', + // RATIONALE: The "module" keyword is deprecated except when describing legacy libraries. + // + // STANDARDIZED BY: @typescript-eslint\eslint-plugin\dist\configs\recommended.json + '@typescript-eslint/prefer-namespace-keyword': 1, + // STANDARDIZED BY: @typescript-eslint\eslint-plugin\dist\configs\recommended.json + // Rationale to disable: it's up to developer to decide if he wants to add type annotations + // Set to 1 (warning) or 2 (error) to enable the rule + '@typescript-eslint/no-inferrable-types': 0, + // STANDARDIZED BY: @typescript-eslint\eslint-plugin\dist\configs\recommended.json + // Rationale to disable: declaration of empty interfaces may be helpful for generic types scenarios + '@typescript-eslint/no-empty-interface': 0, + // RATIONALE: This rule warns if setters are defined without getters, which is probably a mistake. + 'accessor-pairs': 1, + // RATIONALE: In TypeScript, if you write x["y"] instead of x.y, it disables type checking. + 'dot-notation': [ + 1, + { + 'allowPattern': '^_' + } + ], + // RATIONALE: Catches code that is likely to be incorrect + 'eqeqeq': 1, + // STANDARDIZED BY: eslint\conf\eslint-recommended.js + 'for-direction': 1, + // RATIONALE: Catches a common coding mistake. + 'guard-for-in': 2, + // RATIONALE: If you have more than 2,000 lines in a single source file, it's probably time + // to split up your code. + 'max-lines': ['warn', { max: 2000 }], + // STANDARDIZED BY: eslint\conf\eslint-recommended.js + 'no-async-promise-executor': 2, + // RATIONALE: Deprecated language feature. + 'no-caller': 2, + // STANDARDIZED BY: eslint\conf\eslint-recommended.js + 'no-compare-neg-zero': 2, + // STANDARDIZED BY: eslint\conf\eslint-recommended.js + 'no-cond-assign': 2, + // STANDARDIZED BY: eslint\conf\eslint-recommended.js + 'no-constant-condition': 1, + // STANDARDIZED BY: eslint\conf\eslint-recommended.js + 'no-control-regex': 2, + // STANDARDIZED BY: eslint\conf\eslint-recommended.js + 'no-debugger': 1, + // STANDARDIZED BY: eslint\conf\eslint-recommended.js + 'no-delete-var': 2, + // RATIONALE: Catches code that is likely to be incorrect + // STANDARDIZED BY: eslint\conf\eslint-recommended.js + 'no-duplicate-case': 2, + // STANDARDIZED BY: eslint\conf\eslint-recommended.js + 'no-empty': 1, + // STANDARDIZED BY: eslint\conf\eslint-recommended.js + 'no-empty-character-class': 2, + // STANDARDIZED BY: eslint\conf\eslint-recommended.js + 'no-empty-pattern': 1, + // RATIONALE: Eval is a security concern and a performance concern. + 'no-eval': 1, + // RATIONALE: Catches code that is likely to be incorrect + // STANDARDIZED BY: eslint\conf\eslint-recommended.js + 'no-ex-assign': 2, + // RATIONALE: System types are global and should not be tampered with in a scalable code base. + // If two different libraries (or two versions of the same library) both try to modify + // a type, only one of them can win. Polyfills are acceptable because they implement + // a standardized interoperable contract, but polyfills are generally coded in plain + // JavaScript. + 'no-extend-native': 1, + // Disallow unnecessary labels + 'no-extra-label': 1, + // STANDARDIZED BY: eslint\conf\eslint-recommended.js + 'no-fallthrough': 2, + // STANDARDIZED BY: eslint\conf\eslint-recommended.js + 'no-func-assign': 1, + // RATIONALE: Catches a common coding mistake. + 'no-implied-eval': 2, + // STANDARDIZED BY: eslint\conf\eslint-recommended.js + 'no-invalid-regexp': 2, + // RATIONALE: Catches a common coding mistake. + 'no-label-var': 2, + // RATIONALE: Eliminates redundant code. + 'no-lone-blocks': 1, + // STANDARDIZED BY: eslint\conf\eslint-recommended.js + 'no-misleading-character-class': 2, + // RATIONALE: Catches a common coding mistake. + 'no-multi-str': 2, + // RATIONALE: It's generally a bad practice to call "new Thing()" without assigning the result to + // a variable. Either it's part of an awkward expression like "(new Thing()).doSomething()", + // or else implies that the constructor is doing nontrivial computations, which is often + // a poor class design. + 'no-new': 1, + // RATIONALE: Obsolete language feature that is deprecated. + 'no-new-func': 2, + // RATIONALE: Obsolete language feature that is deprecated. + 'no-new-object': 2, + // RATIONALE: Obsolete notation. + 'no-new-wrappers': 1, + // RATIONALE: Catches code that is likely to be incorrect + // STANDARDIZED BY: eslint\conf\eslint-recommended.js + 'no-octal': 2, + // RATIONALE: Catches code that is likely to be incorrect + 'no-octal-escape': 2, + // RATIONALE: Catches code that is likely to be incorrect + // STANDARDIZED BY: eslint\conf\eslint-recommended.js + 'no-regex-spaces': 2, + // RATIONALE: Catches a common coding mistake. + 'no-return-assign': 2, + // RATIONALE: Security risk. + 'no-script-url': 1, + // STANDARDIZED BY: eslint\conf\eslint-recommended.js + 'no-self-assign': 2, + // RATIONALE: Catches a common coding mistake. + 'no-self-compare': 2, + // RATIONALE: This avoids statements such as "while (a = next(), a && a.length);" that use + // commas to create compound expressions. In general code is more readable if each + // step is split onto a separate line. This also makes it easier to set breakpoints + // in the debugger. + 'no-sequences': 1, + // RATIONALE: Catches code that is likely to be incorrect + // STANDARDIZED BY: eslint\conf\eslint-recommended.js + 'no-shadow-restricted-names': 2, + // RATIONALE: Obsolete language feature that is deprecated. + // STANDARDIZED BY: eslint\conf\eslint-recommended.js + 'no-sparse-arrays': 2, + // RATIONALE: Although in theory JavaScript allows any possible data type to be thrown as an exception, + // such flexibility adds pointless complexity, by requiring every catch block to test + // the type of the object that it receives. Whereas if catch blocks can always assume + // that their object implements the "Error" contract, then the code is simpler, and + // we generally get useful additional information like a call stack. + 'no-throw-literal': 2, + // RATIONALE: Catches a common coding mistake. + 'no-unmodified-loop-condition': 1, + // STANDARDIZED BY: eslint\conf\eslint-recommended.js + 'no-unsafe-finally': 2, + // RATIONALE: Catches a common coding mistake. + 'no-unused-expressions': 1, + // STANDARDIZED BY: eslint\conf\eslint-recommended.js + 'no-unused-labels': 1, + // STANDARDIZED BY: eslint\conf\eslint-recommended.js + 'no-useless-catch': 1, + // RATIONALE: Avoids a potential performance problem. + 'no-useless-concat': 1, + // RATIONALE: The "var" keyword is deprecated because of its confusing "hoisting" behavior. + // Always use "let" or "const" instead. + // + // STANDARDIZED BY: @typescript-eslint\eslint-plugin\dist\configs\recommended.json + 'no-var': 2, + // RATIONALE: Generally not needed in modern code. + 'no-void': 1, + // RATIONALE: Obsolete language feature that is deprecated. + // STANDARDIZED BY: eslint\conf\eslint-recommended.js + 'no-with': 2, + // RATIONALE: Makes logic easier to understand, since constants always have a known value + // @typescript-eslint\eslint-plugin\dist\configs\eslint-recommended.js + 'prefer-const': 1, + // RATIONALE: Catches a common coding mistake where "resolve" and "reject" are confused. + 'promise/param-names': 2, + // RATIONALE: Catches code that is likely to be incorrect + // STANDARDIZED BY: eslint\conf\eslint-recommended.js + 'require-atomic-updates': 2, + // STANDARDIZED BY: eslint\conf\eslint-recommended.js + 'require-yield': 1, + // "Use strict" is redundant when using the TypeScript compiler. + 'strict': [ + 2, + 'never' + ], + // RATIONALE: Catches code that is likely to be incorrect + // STANDARDIZED BY: eslint\conf\eslint-recommended.js + 'use-isnan': 2, + // STANDARDIZED BY: eslint\conf\eslint-recommended.js + // Set to 1 (warning) or 2 (error) to enable. + // Rationale to disable: !!{} + 'no-extra-boolean-cast': 0, + // ==================================================================== + // @microsoft/eslint-plugin-spfx + // ==================================================================== + '@microsoft/spfx/no-require-ensure': 2, + } + }, + { + // For unit tests, we can be a little bit less strict. The settings below revise the + // defaults specified in the extended configurations, as well as above. + files: [ + // Test files + '*.test.ts', + '*.test.tsx', + '*.spec.ts', + '*.spec.tsx', + + // Facebook convention + '**/__mocks__/*.ts', + '**/__mocks__/*.tsx', + '**/__tests__/*.ts', + '**/__tests__/*.tsx', + + // Microsoft convention + '**/test/*.ts', + '**/test/*.tsx' + ], + rules: {} + } + ] +}; \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ab5ea70 --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +node_modules/ +lib/ +dist/ +temp/ +*.sppkg +.heft/ +sharepoint/solution/ diff --git a/.npmignore b/.npmignore new file mode 100644 index 0000000..b2625b8 --- /dev/null +++ b/.npmignore @@ -0,0 +1,22 @@ +# Ignore everything by default +** + +# Use negative patterns to bring back the specific things we want to publish +!/bin/** +!/dist/** +!/EULA/** +!/lib/** +!ThirdPartyNotice.txt + +# Ignore certain files in the above folders +/dist/*.stats.* +/dist/**/*.js.map +/lib/**/*.js.map +/lib/**/test/** + +# NOTE: These don't need to be specified, because NPM includes them automatically. +# +# package.json +# README (and its variants) +# CHANGELOG (and its variants) +# LICENSE / LICENCE diff --git a/.npmrc b/.npmrc new file mode 100644 index 0000000..8e18031 --- /dev/null +++ b/.npmrc @@ -0,0 +1 @@ +@tnsu:registry=http://git.tnsu.kz/api/packages/timur.gorodetskiy/npm/ diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..53ca22c --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,23 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "name": "Hosted workbench", + "type": "msedge", + "request": "launch", + "url": "https://{tenantDomain}/_layouts/workbench.aspx", + "webRoot": "${workspaceRoot}", + "sourceMaps": true, + "sourceMapPathOverrides": { + "webpack:///.././src/*": "${webRoot}/src/*", + "webpack:///../../../src/*": "${webRoot}/src/*", + "webpack:///../../../../src/*": "${webRoot}/src/*", + "webpack:///../../../../../src/*": "${webRoot}/src/*" + }, + "runtimeArgs": [ + "--remote-debugging-port=9222", + "-incognito" + ] + } + ] +} \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..1614166 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,14 @@ +// Place your settings in this file to overwrite default and user settings. +{ + // Configure glob patterns for excluding files and folders in the file explorer. + "files.exclude": { + "**/.git": true, + "**/.DS_Store": true, + "**/bower_components": true, + "**/coverage": true, + "**/jest-output": true, + "**/lib-amd": true, + "src/**/*.scss.ts": true + }, + "typescript.tsdk": ".\\node_modules\\typescript\\lib" +} \ No newline at end of file diff --git a/.yo-rc.json b/.yo-rc.json new file mode 100644 index 0000000..b8d90b2 --- /dev/null +++ b/.yo-rc.json @@ -0,0 +1,19 @@ +{ + "@microsoft/generator-sharepoint": { + "useGulp": false, + "plusBeta": false, + "isCreatingSolution": true, + "nodeVersion": "22.22.2", + "sdksVersions": {}, + "version": "1.22.2", + "libraryName": "ct-forms-portal", + "libraryId": "a1c3b00d-3ac8-449a-ad9d-6d0d522df14f", + "environment": "spo", + "packageManager": "npm", + "solutionName": "ct-forms-portal", + "solutionShortDescription": "ct-forms-portal description", + "skipFeatureDeployment": true, + "isDomainIsolated": false, + "componentType": "webpart" + } +} diff --git a/README.md b/README.md new file mode 100644 index 0000000..140469e --- /dev/null +++ b/README.md @@ -0,0 +1,77 @@ +# ct-forms-portal + +## Summary + +Short summary on functionality and used technologies. + +[picture of the solution in action, if possible] + +## Used SharePoint Framework Version + +![version](https://img.shields.io/badge/version-1.22.2-green.svg) + +## Applies to + +- [SharePoint Framework](https://aka.ms/spfx) +- [Microsoft 365 tenant](https://docs.microsoft.com/sharepoint/dev/spfx/set-up-your-developer-tenant) + +> Get your own free development tenant by subscribing to [Microsoft 365 developer program](http://aka.ms/o365devprogram) + +## Prerequisites + +> Any special pre-requisites? + +## Solution + +| Solution | Author(s) | +| ----------- | ------------------------------------------------------- | +| folder name | Author details (name, company, twitter alias with link) | + +## Version history + +| Version | Date | Comments | +| ------- | ---------------- | --------------- | +| 1.1 | March 10, 2021 | Update comment | +| 1.0 | January 29, 2021 | Initial release | + +## Disclaimer + +**THIS CODE IS PROVIDED _AS IS_ WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT.** + +--- + +## Minimal Path to Awesome + +- Clone this repository +- Ensure that you are at the solution folder +- in the command-line run: + - `npm install -g @rushstack/heft` + - `npm install` + - `heft start` + +> Include any additional steps as needed. + +Other build commands can be listed using `heft --help`. + +## Features + +Description of the extension that expands upon high-level summary above. + +This extension illustrates the following concepts: + +- topic 1 +- topic 2 +- topic 3 + +> Notice that better pictures and documentation will increase the sample usage and the value you are providing for others. Thanks for your submissions advance. + +> Share your web part with others through Microsoft 365 Patterns and Practices program to get visibility and exposure. More details on the community, open-source projects and other activities from http://aka.ms/m365pnp. + +## References + +- [Getting started with SharePoint Framework](https://docs.microsoft.com/sharepoint/dev/spfx/set-up-your-developer-tenant) +- [Building for Microsoft teams](https://docs.microsoft.com/sharepoint/dev/spfx/build-for-teams-overview) +- [Use Microsoft Graph in your solution](https://docs.microsoft.com/sharepoint/dev/spfx/web-parts/get-started/using-microsoft-graph-apis) +- [Publish SharePoint Framework applications to the Marketplace](https://docs.microsoft.com/sharepoint/dev/spfx/publish-to-marketplace-overview) +- [Microsoft 365 Patterns and Practices](https://aka.ms/m365pnp) - Guidance, tooling, samples and open-source controls for your Microsoft 365 development +- [Heft Documentation](https://heft.rushstack.io/) \ No newline at end of file diff --git a/config/config.json b/config/config.json new file mode 100644 index 0000000..48efe04 --- /dev/null +++ b/config/config.json @@ -0,0 +1,18 @@ +{ + "$schema": "https://developer.microsoft.com/json-schemas/spfx-build/config.2.0.schema.json", + "version": "2.0", + "bundles": { + "ct-forms-portal-web-part": { + "components": [ + { + "entrypoint": "./lib/webparts/ctFormsPortal/CtFormsPortalWebPart.js", + "manifest": "./src/webparts/ctFormsPortal/CtFormsPortalWebPart.manifest.json" + } + ] + } + }, + "externals": {}, + "localizedResources": { + "CtFormsPortalWebPartStrings": "lib/webparts/ctFormsPortal/loc/{locale}.js" + } +} diff --git a/config/deploy-azure-storage.json b/config/deploy-azure-storage.json new file mode 100644 index 0000000..dec03a0 --- /dev/null +++ b/config/deploy-azure-storage.json @@ -0,0 +1,7 @@ +{ + "$schema": "https://developer.microsoft.com/json-schemas/spfx-build/deploy-azure-storage.schema.json", + "workingDir": "./release/assets/", + "account": "", + "container": "ct-forms-portal", + "accessKey": "" +} diff --git a/config/package-solution.json b/config/package-solution.json new file mode 100644 index 0000000..c2006b1 --- /dev/null +++ b/config/package-solution.json @@ -0,0 +1,40 @@ +{ + "$schema": "https://developer.microsoft.com/json-schemas/spfx-build/package-solution.schema.json", + "solution": { + "name": "ct-forms-portal-client-side-solution", + "id": "a1c3b00d-3ac8-449a-ad9d-6d0d522df14f", + "version": "1.0.0.0", + "includeClientSideAssets": true, + "skipFeatureDeployment": true, + "isDomainIsolated": false, + "developer": { + "name": "", + "websiteUrl": "", + "privacyUrl": "", + "termsOfUseUrl": "", + "mpnId": "Undefined-1.22.2" + }, + "metadata": { + "shortDescription": { + "default": "ct-forms-portal description" + }, + "longDescription": { + "default": "ct-forms-portal description" + }, + "screenshotPaths": [], + "videoUrl": "", + "categories": [] + }, + "features": [ + { + "title": "ct-forms-portal Feature", + "description": "The feature that activates elements of the ct-forms-portal solution.", + "id": "5edbf144-12c3-4535-bb61-6417cdcc5f7d", + "version": "1.0.0.0" + } + ] + }, + "paths": { + "zippedPackage": "solution/ct-forms-portal.sppkg" + } +} diff --git a/config/rig.json b/config/rig.json new file mode 100644 index 0000000..9d47557 --- /dev/null +++ b/config/rig.json @@ -0,0 +1,4 @@ +{ + "$schema": "https://developer.microsoft.com/json-schemas/rig-package/rig.schema.json", + "rigPackageName": "@microsoft/spfx-web-build-rig" +} diff --git a/config/sass.json b/config/sass.json new file mode 100644 index 0000000..1da4244 --- /dev/null +++ b/config/sass.json @@ -0,0 +1,4 @@ +{ + "$schema": "https://developer.microsoft.com/json-schemas/heft/v0/heft-sass-plugin.schema.json", + "extends": "@microsoft/spfx-web-build-rig/profiles/default/config/sass.json" +} diff --git a/config/serve.json b/config/serve.json new file mode 100644 index 0000000..115bd26 --- /dev/null +++ b/config/serve.json @@ -0,0 +1,6 @@ +{ + "$schema": "https://developer.microsoft.com/json-schemas/spfx-build/spfx-serve.schema.json", + "port": 4321, + "https": true, + "initialPage": "https://tnsukz.sharepoint.com/_layouts/workbench.aspx" +} diff --git a/config/typescript.json b/config/typescript.json new file mode 100644 index 0000000..19e9759 --- /dev/null +++ b/config/typescript.json @@ -0,0 +1,7 @@ +{ + "extends": "@microsoft/spfx-web-build-rig/profiles/default/config/typescript.json", + "staticAssetsToCopy": { + "fileExtensions": [".resx", ".jpg", ".png", ".woff", ".eot", ".ttf", ".svg", ".gif"], + "includeGlobs": ["webparts/*/loc/*.js"] + } +} diff --git a/config/write-manifests.json b/config/write-manifests.json new file mode 100644 index 0000000..bad3526 --- /dev/null +++ b/config/write-manifests.json @@ -0,0 +1,4 @@ +{ + "$schema": "https://developer.microsoft.com/json-schemas/spfx-build/write-manifests.schema.json", + "cdnBasePath": "" +} \ No newline at end of file diff --git a/create-spfx-uikit.ps1 b/create-spfx-uikit.ps1 new file mode 100644 index 0000000..4b1b1cb --- /dev/null +++ b/create-spfx-uikit.ps1 @@ -0,0 +1,315 @@ +# ============================================================= +# SPFx + Heft project bootstrap script (PowerShell) +# ВАРИАНТ С ПОДКЛЮЧЕНИЕМ ОБЩЕЙ UI-БИБЛИОТЕКИ @tnsu/ui-kit. +# Запустите из корня вашей ПУСТОЙ папки проекта: +# .\create-spfx-uikit.ps1 +# +# Если запуск скриптов заблокирован, выполните сначала: +# Set-ExecutionPolicy -Scope CurrentUser -ExecutionPolicy RemoteSigned +# +# Требование: у запускающего должен быть токен @tnsu в ~/.npmrc +# (см. README пакета tnsu-ui-kit) — иначе установка @tnsu/ui-kit не пройдёт. +# ============================================================= + +$ErrorActionPreference = "Stop" + +# ---- Цвета вывода ---- +function Info { param($msg) Write-Host "[INFO] $msg" -ForegroundColor Cyan } +function Success { param($msg) Write-Host "[OK] $msg" -ForegroundColor Green } +function Warn { param($msg) Write-Host "[WARN] $msg" -ForegroundColor Yellow } +function Header { param($msg) Write-Host "`n$msg" -ForegroundColor White } + +Header "=================================================" +Header " SPFx + Heft — автоматическая инициализация" +Header " (с общей UI-библиотекой @tnsu/ui-kit)" +Header "=================================================`n" + +# --------------------------------------------------------- +# 1. Проверка Node.js (SPFx 1.20+ требует Node 18+) +# --------------------------------------------------------- +Info "Проверка Node.js..." + +try { + $nodeVersion = node -e "process.stdout.write(process.version)" 2>&1 +} catch { + Warn "Node.js не найден. Установите Node 18 LTS: https://nodejs.org" + exit 1 +} + +$nodeMajor = [int]($nodeVersion.TrimStart('v').Split('.')[0]) +if ($nodeMajor -lt 18) { + Warn "Обнаружен Node.js $nodeVersion. SPFx 1.20+ требует Node 18.x или выше." + Warn "Установите Node 18 LTS через nvm-windows или с https://nodejs.org" + exit 1 +} +Success "Node.js $nodeVersion — подходит." + +# --------------------------------------------------------- +# 2. Вспомогательная функция: установка глобального пакета +# --------------------------------------------------------- +function Install-GlobalIfMissing { + param( + [string]$Package, + [string]$Command = "" + ) + if ($Command -eq "") { $Command = $Package.Split('/')[-1] } + + if (-not (Get-Command $Command -ErrorAction SilentlyContinue)) { + Info "Устанавливаем $Package глобально..." + npm install -g $Package + Success "$Package установлен." + } else { + Success "$Package уже установлен." + } +} + +Install-GlobalIfMissing "@microsoft/generator-sharepoint" "yo" +Install-GlobalIfMissing "yo" "yo" +Install-GlobalIfMissing "@rushstack/heft" "heft" + +# --------------------------------------------------------- +# 3. Создание package.json +# --------------------------------------------------------- +Info "Создаём package.json..." + +$packageJson = @' +{ + "name": "spfx-heft-project", + "version": "0.0.1", + "private": true, + "scripts": { + "build": "heft build", + "bundle": "heft build && gulp bundle --ship", + "package-solution": "heft build && gulp package-solution --ship", + "serve": "heft build && gulp serve", + "clean": "heft clean" + }, + "overrides": { + "ajv": "^8.0.0" + } +} +'@ + +$packageJson | Set-Content -Encoding UTF8 "package.json" +Success "package.json создан." + +# --------------------------------------------------------- +# 4. Создание структуры папок +# --------------------------------------------------------- +Info "Создаём структуру папок..." +New-Item -ItemType Directory -Force -Path "config" | Out-Null +New-Item -ItemType Directory -Force -Path "src\webparts" | Out-Null +Success "Папки созданы." + +# --------------------------------------------------------- +# 5. Запуск Yeoman-генератора SPFx +# --------------------------------------------------------- +Info "Запускаем Yeoman-генератор SPFx..." +Write-Host "" +Write-Host ">>> Сейчас откроется интерактивный генератор Yeoman." -ForegroundColor Yellow +Write-Host ">>> Выберите тип: WebPart, Framework: No framework (или React)." -ForegroundColor Yellow +Write-Host "" + +yo "@microsoft/sharepoint" + +npm uninstall @microsoft/sp-build-web ajv gulp +npm uninstall @microsoft/rush-stack-compiler-5.3 + +# --------------------------------------------------------- +# 6. Установка Heft и Rush Stack зависимостей +# --------------------------------------------------------- +Info "Устанавливаем Heft и плагины..." + +$devDeps = @( + "@microsoft/spfx-web-build-rig@1.22.1", + "@microsoft/spfx-heft-plugins@1.22.1", + "@microsoft/eslint-config-spfx@1.22.1", + "@microsoft/eslint-plugin-spfx@1.22.17", + "@microsoft/sp-module-interfaces@1.22.1", + "@rushstack/eslint-config@4.5.2", + "@rushstack/heft@1.1.2", + "@types/heft-jest@1.0.2", + "@typescript-eslint/parser@8.46.2" +) + +npm install --save-dev @($devDeps) --save-exact --force --legacy-peer-deps +npm install typescript@~5.8.0 --save-dev + +Success "Heft и плагины установлены." + +# --------------------------------------------------------- +# 7. config/rig.json +# --------------------------------------------------------- +Info "Создаём config\rig.json..." +New-Item -ItemType Directory -Force -Path "config" | Out-Null + +$rigJson = @' +{ + "$schema": "https://developer.microsoft.com/json-schemas/rig-package/rig.schema.json", + "rigPackageName": "@microsoft/spfx-web-build-rig" +} +'@ + +$rigJson | Set-Content -Encoding UTF8 "config\rig.json" +Success "config\rig.json создан." + +# --------------------------------------------------------- +# 8. config/sass.json +# --------------------------------------------------------- +Info "Создаём config\sass.json..." +New-Item -ItemType Directory -Force -Path "config" | Out-Null + +$sassJson = @' +{ + "$schema": "https://developer.microsoft.com/json-schemas/heft/v0/heft-sass-plugin.schema.json", + "extends": "@microsoft/spfx-web-build-rig/profiles/default/config/sass.json" +} +'@ + +$sassJson | Set-Content -Encoding UTF8 "config\sass.json" +Success "config\sass.json создан." + +# --------------------------------------------------------- +# 9. config/typescript.json +# --------------------------------------------------------- +Info "Создаём config\typescript.json..." +New-Item -ItemType Directory -Force -Path "config" | Out-Null + +$typescriptJson = @' +{ + "extends": "@microsoft/spfx-web-build-rig/profiles/default/config/typescript.json", + "staticAssetsToCopy": { + "fileExtensions": [".resx", ".jpg", ".png", ".woff", ".eot", ".ttf", ".svg", ".gif"], + "includeGlobs": ["webparts/*/loc/*.js"] + } +} +'@ + +$typescriptJson | Set-Content -Encoding UTF8 "config\typescript.json" +Success "config\typescript.json создан." + +# --------------------------------------------------------- +# 8. tsconfig.json (если Yeoman ещё не создал) +# --------------------------------------------------------- +if (-not (Test-Path "tsconfig.json")) { + Info "Создаём tsconfig.json..." + + $tsConfig = @' +{ + "extends": "./node_modules/@microsoft/spfx-web-build-rig/profiles/default/tsconfig-base.json" +} +'@ + + $tsConfig | Set-Content -Encoding UTF8 "tsconfig.json" + Success "tsconfig.json создан." +} else { + Success "tsconfig.json уже существует, пропускаем." +} + +# --------------------------------------------------------- +# 9. .eslintrc.js (если не создан) +# --------------------------------------------------------- +if (-not (Test-Path ".eslintrc.js")) { + Info "Создаём .eslintrc.js..." + + $eslintConfig = @' +require('@rushstack/eslint-config/patch/modern-module-resolution'); + +module.exports = { + extends: ['@microsoft/eslint-config-spfx/lib/profiles/default'], + parserOptions: { tsconfigRootDir: __dirname } +}; +'@ + + $eslintConfig | Set-Content -Encoding UTF8 ".eslintrc.js" + Success ".eslintrc.js создан." +} else { + Success ".eslintrc.js уже существует, пропускаем." +} + +# --------------------------------------------------------- +# 10. .gitignore +# --------------------------------------------------------- +Info "Создаём .gitignore..." + +$gitignore = @' +node_modules/ +lib/ +dist/ +temp/ +*.sppkg +.heft/ +sharepoint/solution/ +'@ + +$gitignore | Set-Content -Encoding UTF8 ".gitignore" +Success ".gitignore создан." + +# --------------------------------------------------------- +# 11. Финальный npm install +# --------------------------------------------------------- +$devDeps = @( + "@microsoft/sp-component-base@1.22.1", + "@microsoft/sp-core-library@1.22.1", + "@microsoft/sp-lodash-subset@1.22.1", + "@microsoft/sp-office-ui-fabric-core@1.22.1", + "@microsoft/sp-property-pane@1.22.1", + "@microsoft/sp-webpart-base@1.22.1" +) + +Info "Финальный npm install..." +npm install @($devDeps) --save-exact --legacy-peer-deps +Success "Все зависимости установлены." + +# --------------------------------------------------------- +# 12. Подключение общей UI-библиотеки @tnsu/ui-kit +# --------------------------------------------------------- +Info "Подключаем @tnsu/ui-kit..." + +# .npmrc: адрес registry для скоупа @tnsu (НЕ секрет, можно коммитить). +# Токен на чтение — у каждого разработчика свой, в ~/.npmrc (в проект не кладём). +$npmrc = @' +@tnsu:registry=http://git.tnsu.kz/api/packages/timur.gorodetskiy/npm/ +'@ +$npmrc | Set-Content -Encoding UTF8 ".npmrc" +Success ".npmrc создан (адрес registry @tnsu)." + +try { + npm install @tnsu/ui-kit + Success "@tnsu/ui-kit установлен." + Write-Host " Импорт: import { Button } from '@tnsu/ui-kit';" -ForegroundColor DarkGray + Write-Host " Стили: import '@tnsu/ui-kit/styles.css'; (один раз в корневом компоненте)" -ForegroundColor DarkGray +} catch { + Warn "Не удалось поставить @tnsu/ui-kit. Скорее всего нет токена @tnsu в ~/.npmrc." + Warn "Создай токен (Gitea → Настройки → Приложения → package: Read) и пропиши в ~/.npmrc:" + Warn " //git.tnsu.kz/api/packages/timur.gorodetskiy/npm/:_authToken=ТВОЙ_ТОКЕН" + Warn "Потом выполни: npm install @tnsu/ui-kit" +} + +# --------------------------------------------------------- +# Готово +# --------------------------------------------------------- +Write-Host "" +Write-Host "=================================================" -ForegroundColor Green +Write-Host " Проект успешно инициализирован!" -ForegroundColor Green +Write-Host "=================================================" -ForegroundColor Green +Write-Host "" +Write-Host " Доступные команды:" -ForegroundColor White +Write-Host "" +Write-Host " npm run serve " -NoNewline -ForegroundColor Cyan +Write-Host "— локальная разработка (gulp serve)" +Write-Host " npm run build " -NoNewline -ForegroundColor Cyan +Write-Host "— сборка через Heft" +Write-Host " npm run bundle " -NoNewline -ForegroundColor Cyan +Write-Host "— бандлинг для продакшна" +Write-Host " npm run package-solution " -NoNewline -ForegroundColor Cyan +Write-Host "— создание .sppkg пакета" +Write-Host " npm run clean " -NoNewline -ForegroundColor Cyan +Write-Host "— очистка артефактов сборки" +Write-Host "" +Write-Host " UI-библиотека: " -NoNewline -ForegroundColor Yellow +Write-Host "@tnsu/ui-kit подключён (см. .npmrc)" -ForegroundColor Cyan +Write-Host "" +Write-Host " Следующий шаг: " -NoNewline -ForegroundColor Yellow +Write-Host "npm run serve" -ForegroundColor Cyan +Write-Host "" diff --git a/docs/BEHAVIORS.md b/docs/BEHAVIORS.md new file mode 100644 index 0000000..9ec5c72 --- /dev/null +++ b/docs/BEHAVIORS.md @@ -0,0 +1,98 @@ +# Инвентарь поведения — ct / oid / kps → ct-forms-portal + +Спецификация для нового объединённого проекта. Источники (РЕФЕРЕНС, не трогаем): +- `ct-create-form` (ctCreateForm) — закупка по позициям из заявок TNSU +- `oid-create-form` (oidCreateForm) — ручной ввод работ (ОИД) +- `kps-ct-form` (kpsCtForm) — эталон организации (decomposed, common/) + +Правило: поведение КАЖДОЙ формы переносим точь-в-точь. Общее → shared; различия → per-form. +Заполняется по областям по мере чтения. + +--- + +## Область 1. Машина состояний мастера (навигация по шагам) + +**Вывод: логика ОДИНАКОВА во всех трёх → общий хук `useWizard` (в common/), различия — конфигом.** + +### Общее (переносим как shared `useWizard`) +Состояние: `currentStep` (1..N), `completedSteps: number[]`, `highestStep`, `formData`. +- `goToStep(step)` → `currentStep = step`; `highestStep = max(highestStep, step)`. +- `goNext()` → `currentStep = min(LAST, s+1)`; `highestStep = max(highestStep, next)`. +- `goPrev()` → `currentStep = max(1, s-1)`. **highestStep НЕ меняет.** +- `markStepComplete(step)` → добавить в completedSteps, если ещё нет (без дублей). +- `ensureHighestStep(step)` → `highestStep = max(highestStep, step)`. +- `updateFormData(partial)` → merge в formData. +- **Кликабельность шага в степпере: `step <= highestStep`** (защита от прыжка вперёд) — уже реализовано в `@tnsu/ui-kit` Stepper. + +Это ЧИСТАЯ логика — обязательно покрыть юнит-тестами (clamp 1..LAST, max для highestStep, отсутствие дублей в completed). + +### Сохранение состояния (sessionStorage) — общее, ключ per-form +- Персистим `{ formData, currentStep, completedSteps, highestStep }` в sessionStorage. +- Ключ: ct — `ct_form_{ctId}`; kps — свой `sessionKey(ctId)`. → параметр `sessionKeyPrefix` в конфиге формы. +- Пишем, когда известен id сущности (ct.id / ctId). Читаем при инициализации (ленивый init useState). + +### Вход по URL + ссылка из уведомления — общее поведение, детали per-form +- id сущности из URL: ct — `ctId`|`ctid`; oid/kps — свои. Признак «ссылка из уведомления» = наличие + ПАРАМЕТРА в нижнем регистре (ct: `ctid`). → параметры `urlIdParam` / `notificationParam` в конфиге. +- Логика приземления при входе по URL: + - **ссылка из уведомления** (согласующий): загрузить из SP → `goToStep(3)` (Шаг 3 главный) + + пометить шаги 1..3 завершёнными (⇒ highestStep=4, доступны все). [ct: markComplete 3 и 4; kps: 1,2,3 — СВЕРИТЬ при переносе, привести к единому]. + - **обычный вход по URL** (редактирование): загрузить → приземлиться на «самый дальний + заполненный» шаг (`furthestFilledStep`), пометить завершёнными до него; если статус + approved/rejected → пометить и шаг 4. +- `furthestFilledStep(data)` (kps, эталон): approvers>0 или статус on_approval/approved/rejected → 4; + offers>0 → 3; suppliers>0 → 2; иначе → 1. + +### Различия per-form (СОХРАНИТЬ) +- **ct:** хук `useCtForm`; SP-загрузка — большим useEffect в контейнере (~130 строк): восстанавливает + offers, currencyRates, uploadedKpFiles (по сессии + дочитка из SP), `step3DeliveryKm`, suppliers из + офферов. `getCurrentUserProfile → initiatorDepartment`; `getCurrentUserCompany → isRestrictedView` + (company === 'astel-k'). `hidePrices = isRestrictedView || (approvalSent && !userInApprovalChain)`. + Статусы CTStatus: draft/kp_loading/kp_loaded/formed/on_approval/approved/rejected. +- **oid:** хук `useOidForm(context)` (грузит внутри). Шаг 1 — РУЧНОЙ ВВОД работ (Step1WorksInput, + ComboBox) — принципиально другой. `isReadOnly = formData.approvalSent`. Профиль → department+company. + Статусы OIDStatus: draft/formed/on_approval/approved/rejected (нет kp_*). CurrencyRates — inline в Step2. +- **kps (эталон):** хук `useKpsCtForm(ctId)` + отдельный `useSharePointLoad`. Методы `next`/`prev` + (не goNext/goPrev). `replaceFormData`. `deriveCtStatus` — пока заглушка (=formData.status), TODO + реальная логика. `CT_STATUS_META[status] = {label, kind}` (kind → Pill/Badge). `READ_ONLY_STATUSES`. + Единый контракт `StepProps` для всех шагов. Самый чистый — берём за каркас. + +### Как ляжет в новый проект +- shared `useWizard(config)` — состояние + переходы + сессия (config: lastStep, sessionKeyPrefix, + urlIdParam, notificationParam). +- per-form: свои шаги, статусы (STATUS_META), SP-загрузка (furthestFilled + восстановление данных), + Step1 (ct/kps — выбор позиций; oid — ручной ввод). +- Степпер — `@tnsu/ui-kit` Stepper; шапка — `@tnsu/ui-kit` FormHeader (kps так уже и делает). + +--- + +## Область 2. Оболочка + реестр (config-driven) — ИСТОЧНИК: ct-forms-list / myDocuments + +Готовый каркас для портала. myDocuments — единый дашборд по всем 3 типам, управляется массивом +конфигов DocumentTypeConfig (по одному на CT/OID/KPS в config/*DocumentConfig.ts). Умеет ПРОСМОТР +(реестр+очереди) и СОЗДАНИЕ (кнопка «Создать» → открывает форму-мастер). + +### Что уже есть (берём за основу каркаса) +- Сайдбар: пункты из documentTypes (typeLabel + icon), активный тип, бейдж-счётчик «на согласовании + у меня» на пункте. → заменить на @tnsu/ui-kit Sidebar. +- Контент по активному типу: заголовок, тулбар (поиск + фильтр статуса + кнопка «Создать» по + createNewUrl) и 3 вкладки-очереди: «На согласовании у меня» / «Инициализированные мной» / + «Согласованные мной» (Fluent Pivot). +- DocumentTypeConfig (per-type): typeKey, typeLabel, icon, myDocsListName, buildMyDocsFilter, + myDocsSelect, pendingApproval{...}, approvedByMe{...}, buildLinkUrl(row) (ctId для черновика / + ctid иначе), createNewUrl/Label, mapItem(raw→DashboardRow), columns[]. +- useDocumentDashboard(context, documentTypes) — грузит myDocs/approvedDocs/pendingDocs. +- StatusBadge (variant neutral/info/success/error) → @tnsu/ui-kit Badge; маппинг статусов — в конфиге типа. +- DocumentTable → @tnsu/ui-kit DataTable (колонки уже описаны в конфиге). + +### Fluent → ui-kit при переносе +Pivot → НУЖЕН Tabs (в ките пока НЕТ); SearchBox → TextInput; Dropdown → Select; Spinner/MessageBar → +Alert; Default/PrimaryButton → Button; свой сайдбар → Sidebar; DocumentTable → DataTable. + +### Как это меняет каркас портала +Портал = config-driven: массив конфигов типов (CT/OID/KPS), каждый описывает и РЕЕСТР (списки/очереди +из myDocuments), и СОЗДАНИЕ/РЕДАКТИРОВАНИЕ (мастер из ct/oid/kps). Оболочка (Sidebar + вкладки) — из +myDocuments. Мастера — из kps-каркаса. Эталоны: shell/реестр = myDocuments, мастер = kps. + +### Пробел ui-kit +Tabs (вкладки-очереди, замена Pivot) — добавить в @tnsu/ui-kit (был в списке: из request-portal CategoryTabs). diff --git a/jest-output/JUnit.xml b/jest-output/JUnit.xml new file mode 100644 index 0000000..32f5cbc --- /dev/null +++ b/jest-output/JUnit.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/jest-output/coverage/base.css b/jest-output/coverage/base.css new file mode 100644 index 0000000..f418035 --- /dev/null +++ b/jest-output/coverage/base.css @@ -0,0 +1,224 @@ +body, html { + margin:0; padding: 0; + height: 100%; +} +body { + font-family: Helvetica Neue, Helvetica, Arial; + font-size: 14px; + color:#333; +} +.small { font-size: 12px; } +*, *:after, *:before { + -webkit-box-sizing:border-box; + -moz-box-sizing:border-box; + box-sizing:border-box; + } +h1 { font-size: 20px; margin: 0;} +h2 { font-size: 14px; } +pre { + font: 12px/1.4 Consolas, "Liberation Mono", Menlo, Courier, monospace; + margin: 0; + padding: 0; + -moz-tab-size: 2; + -o-tab-size: 2; + tab-size: 2; +} +a { color:#0074D9; text-decoration:none; } +a:hover { text-decoration:underline; } +.strong { font-weight: bold; } +.space-top1 { padding: 10px 0 0 0; } +.pad2y { padding: 20px 0; } +.pad1y { padding: 10px 0; } +.pad2x { padding: 0 20px; } +.pad2 { padding: 20px; } +.pad1 { padding: 10px; } +.space-left2 { padding-left:55px; } +.space-right2 { padding-right:20px; } +.center { text-align:center; } +.clearfix { display:block; } +.clearfix:after { + content:''; + display:block; + height:0; + clear:both; + visibility:hidden; + } +.fl { float: left; } +@media only screen and (max-width:640px) { + .col3 { width:100%; max-width:100%; } + .hide-mobile { display:none!important; } +} + +.quiet { + color: #7f7f7f; + color: rgba(0,0,0,0.5); +} +.quiet a { opacity: 0.7; } + +.fraction { + font-family: Consolas, 'Liberation Mono', Menlo, Courier, monospace; + font-size: 10px; + color: #555; + background: #E8E8E8; + padding: 4px 5px; + border-radius: 3px; + vertical-align: middle; +} + +div.path a:link, div.path a:visited { color: #333; } +table.coverage { + border-collapse: collapse; + margin: 10px 0 0 0; + padding: 0; +} + +table.coverage td { + margin: 0; + padding: 0; + vertical-align: top; +} +table.coverage td.line-count { + text-align: right; + padding: 0 5px 0 20px; +} +table.coverage td.line-coverage { + text-align: right; + padding-right: 10px; + min-width:20px; +} + +table.coverage td span.cline-any { + display: inline-block; + padding: 0 5px; + width: 100%; +} +.missing-if-branch { + display: inline-block; + margin-right: 5px; + border-radius: 3px; + position: relative; + padding: 0 4px; + background: #333; + color: yellow; +} + +.skip-if-branch { + display: none; + margin-right: 10px; + position: relative; + padding: 0 4px; + background: #ccc; + color: white; +} +.missing-if-branch .typ, .skip-if-branch .typ { + color: inherit !important; +} +.coverage-summary { + border-collapse: collapse; + width: 100%; +} +.coverage-summary tr { border-bottom: 1px solid #bbb; } +.keyline-all { border: 1px solid #ddd; } +.coverage-summary td, .coverage-summary th { padding: 10px; } +.coverage-summary tbody { border: 1px solid #bbb; } +.coverage-summary td { border-right: 1px solid #bbb; } +.coverage-summary td:last-child { border-right: none; } +.coverage-summary th { + text-align: left; + font-weight: normal; + white-space: nowrap; +} +.coverage-summary th.file { border-right: none !important; } +.coverage-summary th.pct { } +.coverage-summary th.pic, +.coverage-summary th.abs, +.coverage-summary td.pct, +.coverage-summary td.abs { text-align: right; } +.coverage-summary td.file { white-space: nowrap; } +.coverage-summary td.pic { min-width: 120px !important; } +.coverage-summary tfoot td { } + +.coverage-summary .sorter { + height: 10px; + width: 7px; + display: inline-block; + margin-left: 0.5em; + background: url(sort-arrow-sprite.png) no-repeat scroll 0 0 transparent; +} +.coverage-summary .sorted .sorter { + background-position: 0 -20px; +} +.coverage-summary .sorted-desc .sorter { + background-position: 0 -10px; +} +.status-line { height: 10px; } +/* yellow */ +.cbranch-no { background: yellow !important; color: #111; } +/* dark red */ +.red.solid, .status-line.low, .low .cover-fill { background:#C21F39 } +.low .chart { border:1px solid #C21F39 } +.highlighted, +.highlighted .cstat-no, .highlighted .fstat-no, .highlighted .cbranch-no{ + background: #C21F39 !important; +} +/* medium red */ +.cstat-no, .fstat-no, .cbranch-no, .cbranch-no { background:#F6C6CE } +/* light red */ +.low, .cline-no { background:#FCE1E5 } +/* light green */ +.high, .cline-yes { background:rgb(230,245,208) } +/* medium green */ +.cstat-yes { background:rgb(161,215,106) } +/* dark green */ +.status-line.high, .high .cover-fill { background:rgb(77,146,33) } +.high .chart { border:1px solid rgb(77,146,33) } +/* dark yellow (gold) */ +.status-line.medium, .medium .cover-fill { background: #f9cd0b; } +.medium .chart { border:1px solid #f9cd0b; } +/* light yellow */ +.medium { background: #fff4c2; } + +.cstat-skip { background: #ddd; color: #111; } +.fstat-skip { background: #ddd; color: #111 !important; } +.cbranch-skip { background: #ddd !important; color: #111; } + +span.cline-neutral { background: #eaeaea; } + +.coverage-summary td.empty { + opacity: .5; + padding-top: 4px; + padding-bottom: 4px; + line-height: 1; + color: #888; +} + +.cover-fill, .cover-empty { + display:inline-block; + height: 12px; +} +.chart { + line-height: 0; +} +.cover-empty { + background: white; +} +.cover-full { + border-right: none !important; +} +pre.prettyprint { + border: none !important; + padding: 0 !important; + margin: 0 !important; +} +.com { color: #999 !important; } +.ignore-none { color: #999; font-weight: normal; } + +.wrapper { + min-height: 100%; + height: auto !important; + height: 100%; + margin: 0 auto -48px; +} +.footer, .push { + height: 48px; +} diff --git a/jest-output/coverage/block-navigation.js b/jest-output/coverage/block-navigation.js new file mode 100644 index 0000000..530d1ed --- /dev/null +++ b/jest-output/coverage/block-navigation.js @@ -0,0 +1,87 @@ +/* eslint-disable */ +var jumpToCode = (function init() { + // Classes of code we would like to highlight in the file view + var missingCoverageClasses = ['.cbranch-no', '.cstat-no', '.fstat-no']; + + // Elements to highlight in the file listing view + var fileListingElements = ['td.pct.low']; + + // We don't want to select elements that are direct descendants of another match + var notSelector = ':not(' + missingCoverageClasses.join('):not(') + ') > '; // becomes `:not(a):not(b) > ` + + // Selector that finds elements on the page to which we can jump + var selector = + fileListingElements.join(', ') + + ', ' + + notSelector + + missingCoverageClasses.join(', ' + notSelector); // becomes `:not(a):not(b) > a, :not(a):not(b) > b` + + // The NodeList of matching elements + var missingCoverageElements = document.querySelectorAll(selector); + + var currentIndex; + + function toggleClass(index) { + missingCoverageElements + .item(currentIndex) + .classList.remove('highlighted'); + missingCoverageElements.item(index).classList.add('highlighted'); + } + + function makeCurrent(index) { + toggleClass(index); + currentIndex = index; + missingCoverageElements.item(index).scrollIntoView({ + behavior: 'smooth', + block: 'center', + inline: 'center' + }); + } + + function goToPrevious() { + var nextIndex = 0; + if (typeof currentIndex !== 'number' || currentIndex === 0) { + nextIndex = missingCoverageElements.length - 1; + } else if (missingCoverageElements.length > 1) { + nextIndex = currentIndex - 1; + } + + makeCurrent(nextIndex); + } + + function goToNext() { + var nextIndex = 0; + + if ( + typeof currentIndex === 'number' && + currentIndex < missingCoverageElements.length - 1 + ) { + nextIndex = currentIndex + 1; + } + + makeCurrent(nextIndex); + } + + return function jump(event) { + if ( + document.getElementById('fileSearch') === document.activeElement && + document.activeElement != null + ) { + // if we're currently focused on the search input, we don't want to navigate + return; + } + + switch (event.which) { + case 78: // n + case 74: // j + goToNext(); + break; + case 66: // b + case 75: // k + case 80: // p + goToPrevious(); + break; + } + }; +})(); +window.addEventListener('keydown', jumpToCode); diff --git a/jest-output/coverage/cobertura/cobertura-coverage.xml b/jest-output/coverage/cobertura/cobertura-coverage.xml new file mode 100644 index 0000000..20c1194 --- /dev/null +++ b/jest-output/coverage/cobertura/cobertura-coverage.xml @@ -0,0 +1,9 @@ + + + + + C:\Users\gorodetskiy.timur\Documents\Projects\ct-forms-portal + + + + diff --git a/jest-output/coverage/coverage-summary.json b/jest-output/coverage/coverage-summary.json new file mode 100644 index 0000000..ef1ae37 --- /dev/null +++ b/jest-output/coverage/coverage-summary.json @@ -0,0 +1,2 @@ +{"total": {"lines":{"total":0,"covered":0,"skipped":0,"pct":"Unknown"},"statements":{"total":0,"covered":0,"skipped":0,"pct":"Unknown"},"functions":{"total":0,"covered":0,"skipped":0,"pct":"Unknown"},"branches":{"total":0,"covered":0,"skipped":0,"pct":"Unknown"},"branchesTrue":{"total":0,"covered":0,"skipped":0,"pct":"Unknown"}} +} diff --git a/jest-output/coverage/favicon.png b/jest-output/coverage/favicon.png new file mode 100644 index 0000000..c1525b8 Binary files /dev/null and b/jest-output/coverage/favicon.png differ diff --git a/jest-output/coverage/index.html b/jest-output/coverage/index.html new file mode 100644 index 0000000..64145ed --- /dev/null +++ b/jest-output/coverage/index.html @@ -0,0 +1,101 @@ + + + + + + Code coverage report for All files + + + + + + + + + +
+
+

All files

+
+ +
+ Unknown% + Statements + 0/0 +
+ + +
+ Unknown% + Branches + 0/0 +
+ + +
+ Unknown% + Functions + 0/0 +
+ + +
+ Unknown% + Lines + 0/0 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+
+ + + + + + + + + + + + + + + + +
FileStatementsBranchesFunctionsLines
+
+
+
+ + + + + + + + \ No newline at end of file diff --git a/jest-output/coverage/prettify.css b/jest-output/coverage/prettify.css new file mode 100644 index 0000000..b317a7c --- /dev/null +++ b/jest-output/coverage/prettify.css @@ -0,0 +1 @@ +.pln{color:#000}@media screen{.str{color:#080}.kwd{color:#008}.com{color:#800}.typ{color:#606}.lit{color:#066}.pun,.opn,.clo{color:#660}.tag{color:#008}.atn{color:#606}.atv{color:#080}.dec,.var{color:#606}.fun{color:red}}@media print,projection{.str{color:#060}.kwd{color:#006;font-weight:bold}.com{color:#600;font-style:italic}.typ{color:#404;font-weight:bold}.lit{color:#044}.pun,.opn,.clo{color:#440}.tag{color:#006;font-weight:bold}.atn{color:#404}.atv{color:#060}}pre.prettyprint{padding:2px;border:1px solid #888}ol.linenums{margin-top:0;margin-bottom:0}li.L0,li.L1,li.L2,li.L3,li.L5,li.L6,li.L7,li.L8{list-style-type:none}li.L1,li.L3,li.L5,li.L7,li.L9{background:#eee} diff --git a/jest-output/coverage/prettify.js b/jest-output/coverage/prettify.js new file mode 100644 index 0000000..b322523 --- /dev/null +++ b/jest-output/coverage/prettify.js @@ -0,0 +1,2 @@ +/* eslint-disable */ +window.PR_SHOULD_USE_CONTINUATION=true;(function(){var h=["break,continue,do,else,for,if,return,while"];var u=[h,"auto,case,char,const,default,double,enum,extern,float,goto,int,long,register,short,signed,sizeof,static,struct,switch,typedef,union,unsigned,void,volatile"];var p=[u,"catch,class,delete,false,import,new,operator,private,protected,public,this,throw,true,try,typeof"];var l=[p,"alignof,align_union,asm,axiom,bool,concept,concept_map,const_cast,constexpr,decltype,dynamic_cast,explicit,export,friend,inline,late_check,mutable,namespace,nullptr,reinterpret_cast,static_assert,static_cast,template,typeid,typename,using,virtual,where"];var x=[p,"abstract,boolean,byte,extends,final,finally,implements,import,instanceof,null,native,package,strictfp,super,synchronized,throws,transient"];var R=[x,"as,base,by,checked,decimal,delegate,descending,dynamic,event,fixed,foreach,from,group,implicit,in,interface,internal,into,is,lock,object,out,override,orderby,params,partial,readonly,ref,sbyte,sealed,stackalloc,string,select,uint,ulong,unchecked,unsafe,ushort,var"];var r="all,and,by,catch,class,else,extends,false,finally,for,if,in,is,isnt,loop,new,no,not,null,of,off,on,or,return,super,then,true,try,unless,until,when,while,yes";var w=[p,"debugger,eval,export,function,get,null,set,undefined,var,with,Infinity,NaN"];var s="caller,delete,die,do,dump,elsif,eval,exit,foreach,for,goto,if,import,last,local,my,next,no,our,print,package,redo,require,sub,undef,unless,until,use,wantarray,while,BEGIN,END";var I=[h,"and,as,assert,class,def,del,elif,except,exec,finally,from,global,import,in,is,lambda,nonlocal,not,or,pass,print,raise,try,with,yield,False,True,None"];var f=[h,"alias,and,begin,case,class,def,defined,elsif,end,ensure,false,in,module,next,nil,not,or,redo,rescue,retry,self,super,then,true,undef,unless,until,when,yield,BEGIN,END"];var H=[h,"case,done,elif,esac,eval,fi,function,in,local,set,then,until"];var A=[l,R,w,s+I,f,H];var e=/^(DIR|FILE|vector|(de|priority_)?queue|list|stack|(const_)?iterator|(multi)?(set|map)|bitset|u?(int|float)\d*)/;var C="str";var z="kwd";var j="com";var O="typ";var G="lit";var L="pun";var F="pln";var m="tag";var E="dec";var J="src";var P="atn";var n="atv";var N="nocode";var M="(?:^^\\.?|[+-]|\\!|\\!=|\\!==|\\#|\\%|\\%=|&|&&|&&=|&=|\\(|\\*|\\*=|\\+=|\\,|\\-=|\\->|\\/|\\/=|:|::|\\;|<|<<|<<=|<=|=|==|===|>|>=|>>|>>=|>>>|>>>=|\\?|\\@|\\[|\\^|\\^=|\\^\\^|\\^\\^=|\\{|\\||\\|=|\\|\\||\\|\\|=|\\~|break|case|continue|delete|do|else|finally|instanceof|return|throw|try|typeof)\\s*";function k(Z){var ad=0;var S=false;var ac=false;for(var V=0,U=Z.length;V122)){if(!(al<65||ag>90)){af.push([Math.max(65,ag)|32,Math.min(al,90)|32])}if(!(al<97||ag>122)){af.push([Math.max(97,ag)&~32,Math.min(al,122)&~32])}}}}af.sort(function(av,au){return(av[0]-au[0])||(au[1]-av[1])});var ai=[];var ap=[NaN,NaN];for(var ar=0;arat[0]){if(at[1]+1>at[0]){an.push("-")}an.push(T(at[1]))}}an.push("]");return an.join("")}function W(al){var aj=al.source.match(new RegExp("(?:\\[(?:[^\\x5C\\x5D]|\\\\[\\s\\S])*\\]|\\\\u[A-Fa-f0-9]{4}|\\\\x[A-Fa-f0-9]{2}|\\\\[0-9]+|\\\\[^ux0-9]|\\(\\?[:!=]|[\\(\\)\\^]|[^\\x5B\\x5C\\(\\)\\^]+)","g"));var ah=aj.length;var an=[];for(var ak=0,am=0;ak=2&&ai==="["){aj[ak]=X(ag)}else{if(ai!=="\\"){aj[ak]=ag.replace(/[a-zA-Z]/g,function(ao){var ap=ao.charCodeAt(0);return"["+String.fromCharCode(ap&~32,ap|32)+"]"})}}}}return aj.join("")}var aa=[];for(var V=0,U=Z.length;V=0;){S[ac.charAt(ae)]=Y}}var af=Y[1];var aa=""+af;if(!ag.hasOwnProperty(aa)){ah.push(af);ag[aa]=null}}ah.push(/[\0-\uffff]/);V=k(ah)})();var X=T.length;var W=function(ah){var Z=ah.sourceCode,Y=ah.basePos;var ad=[Y,F];var af=0;var an=Z.match(V)||[];var aj={};for(var ae=0,aq=an.length;ae=5&&"lang-"===ap.substring(0,5);if(am&&!(ai&&typeof ai[1]==="string")){am=false;ap=J}if(!am){aj[ag]=ap}}var ab=af;af+=ag.length;if(!am){ad.push(Y+ab,ap)}else{var al=ai[1];var ak=ag.indexOf(al);var ac=ak+al.length;if(ai[2]){ac=ag.length-ai[2].length;ak=ac-al.length}var ar=ap.substring(5);B(Y+ab,ag.substring(0,ak),W,ad);B(Y+ab+ak,al,q(ar,al),ad);B(Y+ab+ac,ag.substring(ac),W,ad)}}ah.decorations=ad};return W}function i(T){var W=[],S=[];if(T.tripleQuotedStrings){W.push([C,/^(?:\'\'\'(?:[^\'\\]|\\[\s\S]|\'{1,2}(?=[^\']))*(?:\'\'\'|$)|\"\"\"(?:[^\"\\]|\\[\s\S]|\"{1,2}(?=[^\"]))*(?:\"\"\"|$)|\'(?:[^\\\']|\\[\s\S])*(?:\'|$)|\"(?:[^\\\"]|\\[\s\S])*(?:\"|$))/,null,"'\""])}else{if(T.multiLineStrings){W.push([C,/^(?:\'(?:[^\\\']|\\[\s\S])*(?:\'|$)|\"(?:[^\\\"]|\\[\s\S])*(?:\"|$)|\`(?:[^\\\`]|\\[\s\S])*(?:\`|$))/,null,"'\"`"])}else{W.push([C,/^(?:\'(?:[^\\\'\r\n]|\\.)*(?:\'|$)|\"(?:[^\\\"\r\n]|\\.)*(?:\"|$))/,null,"\"'"])}}if(T.verbatimStrings){S.push([C,/^@\"(?:[^\"]|\"\")*(?:\"|$)/,null])}var Y=T.hashComments;if(Y){if(T.cStyleComments){if(Y>1){W.push([j,/^#(?:##(?:[^#]|#(?!##))*(?:###|$)|.*)/,null,"#"])}else{W.push([j,/^#(?:(?:define|elif|else|endif|error|ifdef|include|ifndef|line|pragma|undef|warning)\b|[^\r\n]*)/,null,"#"])}S.push([C,/^<(?:(?:(?:\.\.\/)*|\/?)(?:[\w-]+(?:\/[\w-]+)+)?[\w-]+\.h|[a-z]\w*)>/,null])}else{W.push([j,/^#[^\r\n]*/,null,"#"])}}if(T.cStyleComments){S.push([j,/^\/\/[^\r\n]*/,null]);S.push([j,/^\/\*[\s\S]*?(?:\*\/|$)/,null])}if(T.regexLiterals){var X=("/(?=[^/*])(?:[^/\\x5B\\x5C]|\\x5C[\\s\\S]|\\x5B(?:[^\\x5C\\x5D]|\\x5C[\\s\\S])*(?:\\x5D|$))+/");S.push(["lang-regex",new RegExp("^"+M+"("+X+")")])}var V=T.types;if(V){S.push([O,V])}var U=(""+T.keywords).replace(/^ | $/g,"");if(U.length){S.push([z,new RegExp("^(?:"+U.replace(/[\s,]+/g,"|")+")\\b"),null])}W.push([F,/^\s+/,null," \r\n\t\xA0"]);S.push([G,/^@[a-z_$][a-z_$@0-9]*/i,null],[O,/^(?:[@_]?[A-Z]+[a-z][A-Za-z_$@0-9]*|\w+_t\b)/,null],[F,/^[a-z_$][a-z_$@0-9]*/i,null],[G,new RegExp("^(?:0x[a-f0-9]+|(?:\\d(?:_\\d+)*\\d*(?:\\.\\d*)?|\\.\\d\\+)(?:e[+\\-]?\\d+)?)[a-z]*","i"),null,"0123456789"],[F,/^\\[\s\S]?/,null],[L,/^.[^\s\w\.$@\'\"\`\/\#\\]*/,null]);return g(W,S)}var K=i({keywords:A,hashComments:true,cStyleComments:true,multiLineStrings:true,regexLiterals:true});function Q(V,ag){var U=/(?:^|\s)nocode(?:\s|$)/;var ab=/\r\n?|\n/;var ac=V.ownerDocument;var S;if(V.currentStyle){S=V.currentStyle.whiteSpace}else{if(window.getComputedStyle){S=ac.defaultView.getComputedStyle(V,null).getPropertyValue("white-space")}}var Z=S&&"pre"===S.substring(0,3);var af=ac.createElement("LI");while(V.firstChild){af.appendChild(V.firstChild)}var W=[af];function ae(al){switch(al.nodeType){case 1:if(U.test(al.className)){break}if("BR"===al.nodeName){ad(al);if(al.parentNode){al.parentNode.removeChild(al)}}else{for(var an=al.firstChild;an;an=an.nextSibling){ae(an)}}break;case 3:case 4:if(Z){var am=al.nodeValue;var aj=am.match(ab);if(aj){var ai=am.substring(0,aj.index);al.nodeValue=ai;var ah=am.substring(aj.index+aj[0].length);if(ah){var ak=al.parentNode;ak.insertBefore(ac.createTextNode(ah),al.nextSibling)}ad(al);if(!ai){al.parentNode.removeChild(al)}}}break}}function ad(ak){while(!ak.nextSibling){ak=ak.parentNode;if(!ak){return}}function ai(al,ar){var aq=ar?al.cloneNode(false):al;var ao=al.parentNode;if(ao){var ap=ai(ao,1);var an=al.nextSibling;ap.appendChild(aq);for(var am=an;am;am=an){an=am.nextSibling;ap.appendChild(am)}}return aq}var ah=ai(ak.nextSibling,0);for(var aj;(aj=ah.parentNode)&&aj.nodeType===1;){ah=aj}W.push(ah)}for(var Y=0;Y=S){ah+=2}if(V>=ap){Z+=2}}}var t={};function c(U,V){for(var S=V.length;--S>=0;){var T=V[S];if(!t.hasOwnProperty(T)){t[T]=U}else{if(window.console){console.warn("cannot override language handler %s",T)}}}}function q(T,S){if(!(T&&t.hasOwnProperty(T))){T=/^\s*]*(?:>|$)/],[j,/^<\!--[\s\S]*?(?:-\->|$)/],["lang-",/^<\?([\s\S]+?)(?:\?>|$)/],["lang-",/^<%([\s\S]+?)(?:%>|$)/],[L,/^(?:<[%?]|[%?]>)/],["lang-",/^]*>([\s\S]+?)<\/xmp\b[^>]*>/i],["lang-js",/^]*>([\s\S]*?)(<\/script\b[^>]*>)/i],["lang-css",/^]*>([\s\S]*?)(<\/style\b[^>]*>)/i],["lang-in.tag",/^(<\/?[a-z][^<>]*>)/i]]),["default-markup","htm","html","mxml","xhtml","xml","xsl"]);c(g([[F,/^[\s]+/,null," \t\r\n"],[n,/^(?:\"[^\"]*\"?|\'[^\']*\'?)/,null,"\"'"]],[[m,/^^<\/?[a-z](?:[\w.:-]*\w)?|\/?>$/i],[P,/^(?!style[\s=]|on)[a-z](?:[\w:-]*\w)?/i],["lang-uq.val",/^=\s*([^>\'\"\s]*(?:[^>\'\"\s\/]|\/(?=\s)))/],[L,/^[=<>\/]+/],["lang-js",/^on\w+\s*=\s*\"([^\"]+)\"/i],["lang-js",/^on\w+\s*=\s*\'([^\']+)\'/i],["lang-js",/^on\w+\s*=\s*([^\"\'>\s]+)/i],["lang-css",/^style\s*=\s*\"([^\"]+)\"/i],["lang-css",/^style\s*=\s*\'([^\']+)\'/i],["lang-css",/^style\s*=\s*([^\"\'>\s]+)/i]]),["in.tag"]);c(g([],[[n,/^[\s\S]+/]]),["uq.val"]);c(i({keywords:l,hashComments:true,cStyleComments:true,types:e}),["c","cc","cpp","cxx","cyc","m"]);c(i({keywords:"null,true,false"}),["json"]);c(i({keywords:R,hashComments:true,cStyleComments:true,verbatimStrings:true,types:e}),["cs"]);c(i({keywords:x,cStyleComments:true}),["java"]);c(i({keywords:H,hashComments:true,multiLineStrings:true}),["bsh","csh","sh"]);c(i({keywords:I,hashComments:true,multiLineStrings:true,tripleQuotedStrings:true}),["cv","py"]);c(i({keywords:s,hashComments:true,multiLineStrings:true,regexLiterals:true}),["perl","pl","pm"]);c(i({keywords:f,hashComments:true,multiLineStrings:true,regexLiterals:true}),["rb"]);c(i({keywords:w,cStyleComments:true,regexLiterals:true}),["js"]);c(i({keywords:r,hashComments:3,cStyleComments:true,multilineStrings:true,tripleQuotedStrings:true,regexLiterals:true}),["coffee"]);c(g([],[[C,/^[\s\S]+/]]),["regex"]);function d(V){var U=V.langExtension;try{var S=a(V.sourceNode);var T=S.sourceCode;V.sourceCode=T;V.spans=S.spans;V.basePos=0;q(U,T)(V);D(V)}catch(W){if("console" in window){console.log(W&&W.stack?W.stack:W)}}}function y(W,V,U){var S=document.createElement("PRE");S.innerHTML=W;if(U){Q(S,U)}var T={langExtension:V,numberLines:U,sourceNode:S};d(T);return S.innerHTML}function b(ad){function Y(af){return document.getElementsByTagName(af)}var ac=[Y("pre"),Y("code"),Y("xmp")];var T=[];for(var aa=0;aa=0){var ah=ai.match(ab);var am;if(!ah&&(am=o(aj))&&"CODE"===am.tagName){ah=am.className.match(ab)}if(ah){ah=ah[1]}var al=false;for(var ak=aj.parentNode;ak;ak=ak.parentNode){if((ak.tagName==="pre"||ak.tagName==="code"||ak.tagName==="xmp")&&ak.className&&ak.className.indexOf("prettyprint")>=0){al=true;break}}if(!al){var af=aj.className.match(/\blinenums\b(?::(\d+))?/);af=af?af[1]&&af[1].length?+af[1]:true:false;if(af){Q(aj,af)}S={langExtension:ah,sourceNode:aj,numberLines:af};d(S)}}}if(X]*(?:>|$)/],[PR.PR_COMMENT,/^<\!--[\s\S]*?(?:-\->|$)/],[PR.PR_PUNCTUATION,/^(?:<[%?]|[%?]>)/],["lang-",/^<\?([\s\S]+?)(?:\?>|$)/],["lang-",/^<%([\s\S]+?)(?:%>|$)/],["lang-",/^]*>([\s\S]+?)<\/xmp\b[^>]*>/i],["lang-handlebars",/^]*type\s*=\s*['"]?text\/x-handlebars-template['"]?\b[^>]*>([\s\S]*?)(<\/script\b[^>]*>)/i],["lang-js",/^]*>([\s\S]*?)(<\/script\b[^>]*>)/i],["lang-css",/^]*>([\s\S]*?)(<\/style\b[^>]*>)/i],["lang-in.tag",/^(<\/?[a-z][^<>]*>)/i],[PR.PR_DECLARATION,/^{{[#^>/]?\s*[\w.][^}]*}}/],[PR.PR_DECLARATION,/^{{&?\s*[\w.][^}]*}}/],[PR.PR_DECLARATION,/^{{{>?\s*[\w.][^}]*}}}/],[PR.PR_COMMENT,/^{{![^}]*}}/]]),["handlebars","hbs"]);PR.registerLangHandler(PR.createSimpleLexer([[PR.PR_PLAIN,/^[ \t\r\n\f]+/,null," \t\r\n\f"]],[[PR.PR_STRING,/^\"(?:[^\n\r\f\\\"]|\\(?:\r\n?|\n|\f)|\\[\s\S])*\"/,null],[PR.PR_STRING,/^\'(?:[^\n\r\f\\\']|\\(?:\r\n?|\n|\f)|\\[\s\S])*\'/,null],["lang-css-str",/^url\(([^\)\"\']*)\)/i],[PR.PR_KEYWORD,/^(?:url|rgb|\!important|@import|@page|@media|@charset|inherit)(?=[^\-\w]|$)/i,null],["lang-css-kw",/^(-?(?:[_a-z]|(?:\\[0-9a-f]+ ?))(?:[_a-z0-9\-]|\\(?:\\[0-9a-f]+ ?))*)\s*:/i],[PR.PR_COMMENT,/^\/\*[^*]*\*+(?:[^\/*][^*]*\*+)*\//],[PR.PR_COMMENT,/^(?:)/],[PR.PR_LITERAL,/^(?:\d+|\d*\.\d+)(?:%|[a-z]+)?/i],[PR.PR_LITERAL,/^#(?:[0-9a-f]{3}){1,2}/i],[PR.PR_PLAIN,/^-?(?:[_a-z]|(?:\\[\da-f]+ ?))(?:[_a-z\d\-]|\\(?:\\[\da-f]+ ?))*/i],[PR.PR_PUNCTUATION,/^[^\s\w\'\"]+/]]),["css"]);PR.registerLangHandler(PR.createSimpleLexer([],[[PR.PR_KEYWORD,/^-?(?:[_a-z]|(?:\\[\da-f]+ ?))(?:[_a-z\d\-]|\\(?:\\[\da-f]+ ?))*/i]]),["css-kw"]);PR.registerLangHandler(PR.createSimpleLexer([],[[PR.PR_STRING,/^[^\)\"\']+/]]),["css-str"]); diff --git a/jest-output/coverage/sort-arrow-sprite.png b/jest-output/coverage/sort-arrow-sprite.png new file mode 100644 index 0000000..6ed6831 Binary files /dev/null and b/jest-output/coverage/sort-arrow-sprite.png differ diff --git a/jest-output/coverage/sorter.js b/jest-output/coverage/sorter.js new file mode 100644 index 0000000..4ed70ae --- /dev/null +++ b/jest-output/coverage/sorter.js @@ -0,0 +1,210 @@ +/* eslint-disable */ +var addSorting = (function() { + 'use strict'; + var cols, + currentSort = { + index: 0, + desc: false + }; + + // returns the summary table element + function getTable() { + return document.querySelector('.coverage-summary'); + } + // returns the thead element of the summary table + function getTableHeader() { + return getTable().querySelector('thead tr'); + } + // returns the tbody element of the summary table + function getTableBody() { + return getTable().querySelector('tbody'); + } + // returns the th element for nth column + function getNthColumn(n) { + return getTableHeader().querySelectorAll('th')[n]; + } + + function onFilterInput() { + const searchValue = document.getElementById('fileSearch').value; + const rows = document.getElementsByTagName('tbody')[0].children; + + // Try to create a RegExp from the searchValue. If it fails (invalid regex), + // it will be treated as a plain text search + let searchRegex; + try { + searchRegex = new RegExp(searchValue, 'i'); // 'i' for case-insensitive + } catch (error) { + searchRegex = null; + } + + for (let i = 0; i < rows.length; i++) { + const row = rows[i]; + let isMatch = false; + + if (searchRegex) { + // If a valid regex was created, use it for matching + isMatch = searchRegex.test(row.textContent); + } else { + // Otherwise, fall back to the original plain text search + isMatch = row.textContent + .toLowerCase() + .includes(searchValue.toLowerCase()); + } + + row.style.display = isMatch ? '' : 'none'; + } + } + + // loads the search box + function addSearchBox() { + var template = document.getElementById('filterTemplate'); + var templateClone = template.content.cloneNode(true); + templateClone.getElementById('fileSearch').oninput = onFilterInput; + template.parentElement.appendChild(templateClone); + } + + // loads all columns + function loadColumns() { + var colNodes = getTableHeader().querySelectorAll('th'), + colNode, + cols = [], + col, + i; + + for (i = 0; i < colNodes.length; i += 1) { + colNode = colNodes[i]; + col = { + key: colNode.getAttribute('data-col'), + sortable: !colNode.getAttribute('data-nosort'), + type: colNode.getAttribute('data-type') || 'string' + }; + cols.push(col); + if (col.sortable) { + col.defaultDescSort = col.type === 'number'; + colNode.innerHTML = + colNode.innerHTML + ''; + } + } + return cols; + } + // attaches a data attribute to every tr element with an object + // of data values keyed by column name + function loadRowData(tableRow) { + var tableCols = tableRow.querySelectorAll('td'), + colNode, + col, + data = {}, + i, + val; + for (i = 0; i < tableCols.length; i += 1) { + colNode = tableCols[i]; + col = cols[i]; + val = colNode.getAttribute('data-value'); + if (col.type === 'number') { + val = Number(val); + } + data[col.key] = val; + } + return data; + } + // loads all row data + function loadData() { + var rows = getTableBody().querySelectorAll('tr'), + i; + + for (i = 0; i < rows.length; i += 1) { + rows[i].data = loadRowData(rows[i]); + } + } + // sorts the table using the data for the ith column + function sortByIndex(index, desc) { + var key = cols[index].key, + sorter = function(a, b) { + a = a.data[key]; + b = b.data[key]; + return a < b ? -1 : a > b ? 1 : 0; + }, + finalSorter = sorter, + tableBody = document.querySelector('.coverage-summary tbody'), + rowNodes = tableBody.querySelectorAll('tr'), + rows = [], + i; + + if (desc) { + finalSorter = function(a, b) { + return -1 * sorter(a, b); + }; + } + + for (i = 0; i < rowNodes.length; i += 1) { + rows.push(rowNodes[i]); + tableBody.removeChild(rowNodes[i]); + } + + rows.sort(finalSorter); + + for (i = 0; i < rows.length; i += 1) { + tableBody.appendChild(rows[i]); + } + } + // removes sort indicators for current column being sorted + function removeSortIndicators() { + var col = getNthColumn(currentSort.index), + cls = col.className; + + cls = cls.replace(/ sorted$/, '').replace(/ sorted-desc$/, ''); + col.className = cls; + } + // adds sort indicators for current column being sorted + function addSortIndicators() { + getNthColumn(currentSort.index).className += currentSort.desc + ? ' sorted-desc' + : ' sorted'; + } + // adds event listeners for all sorter widgets + function enableUI() { + var i, + el, + ithSorter = function ithSorter(i) { + var col = cols[i]; + + return function() { + var desc = col.defaultDescSort; + + if (currentSort.index === i) { + desc = !currentSort.desc; + } + sortByIndex(i, desc); + removeSortIndicators(); + currentSort.index = i; + currentSort.desc = desc; + addSortIndicators(); + }; + }; + for (i = 0; i < cols.length; i += 1) { + if (cols[i].sortable) { + // add the click event handler on the th so users + // dont have to click on those tiny arrows + el = getNthColumn(i).querySelector('.sorter').parentElement; + if (el.addEventListener) { + el.addEventListener('click', ithSorter(i)); + } else { + el.attachEvent('onclick', ithSorter(i)); + } + } + } + } + // adds sorting functionality to the UI + return function() { + if (!getTable()) { + return; + } + cols = loadColumns(); + loadData(); + addSearchBox(); + addSortIndicators(); + enableUI(); + }; +})(); + +window.addEventListener('load', addSorting); diff --git a/lib-commonjs/webparts/ctFormsPortal/CtFormsPortalWebPart.js b/lib-commonjs/webparts/ctFormsPortal/CtFormsPortalWebPart.js new file mode 100644 index 0000000..0b1c45c --- /dev/null +++ b/lib-commonjs/webparts/ctFormsPortal/CtFormsPortalWebPart.js @@ -0,0 +1,107 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = require("tslib"); +var React = tslib_1.__importStar(require("react")); +var ReactDom = tslib_1.__importStar(require("react-dom")); +var sp_core_library_1 = require("@microsoft/sp-core-library"); +var sp_property_pane_1 = require("@microsoft/sp-property-pane"); +var sp_webpart_base_1 = require("@microsoft/sp-webpart-base"); +var strings = tslib_1.__importStar(require("CtFormsPortalWebPartStrings")); +var CtFormsPortal_1 = tslib_1.__importDefault(require("./components/CtFormsPortal")); +var CtFormsPortalWebPart = /** @class */ (function (_super) { + tslib_1.__extends(CtFormsPortalWebPart, _super); + function CtFormsPortalWebPart() { + var _this = _super !== null && _super.apply(this, arguments) || this; + _this._isDarkTheme = false; + _this._environmentMessage = ''; + return _this; + } + CtFormsPortalWebPart.prototype.render = function () { + var element = React.createElement(CtFormsPortal_1.default, { + description: this.properties.description, + isDarkTheme: this._isDarkTheme, + environmentMessage: this._environmentMessage, + hasTeamsContext: !!this.context.sdks.microsoftTeams, + userDisplayName: this.context.pageContext.user.displayName, + context: this.context + }); + ReactDom.render(element, this.domElement); + }; + CtFormsPortalWebPart.prototype.onInit = function () { + var _this = this; + return this._getEnvironmentMessage().then(function (message) { + _this._environmentMessage = message; + }); + }; + CtFormsPortalWebPart.prototype._getEnvironmentMessage = function () { + var _this = this; + if (!!this.context.sdks.microsoftTeams) { // running in Teams, office.com or Outlook + return this.context.sdks.microsoftTeams.teamsJs.app.getContext() + .then(function (context) { + var environmentMessage = ''; + switch (context.app.host.name) { + case 'Office': // running in Office + environmentMessage = _this.context.isServedFromLocalhost ? strings.AppLocalEnvironmentOffice : strings.AppOfficeEnvironment; + break; + case 'Outlook': // running in Outlook + environmentMessage = _this.context.isServedFromLocalhost ? strings.AppLocalEnvironmentOutlook : strings.AppOutlookEnvironment; + break; + case 'Teams': // running in Teams + case 'TeamsModern': + environmentMessage = _this.context.isServedFromLocalhost ? strings.AppLocalEnvironmentTeams : strings.AppTeamsTabEnvironment; + break; + default: + environmentMessage = strings.UnknownEnvironment; + } + return environmentMessage; + }); + } + return Promise.resolve(this.context.isServedFromLocalhost ? strings.AppLocalEnvironmentSharePoint : strings.AppSharePointEnvironment); + }; + CtFormsPortalWebPart.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); + } + }; + CtFormsPortalWebPart.prototype.onDispose = function () { + ReactDom.unmountComponentAtNode(this.domElement); + }; + Object.defineProperty(CtFormsPortalWebPart.prototype, "dataVersion", { + get: function () { + return sp_core_library_1.Version.parse('1.0'); + }, + enumerable: false, + configurable: true + }); + CtFormsPortalWebPart.prototype.getPropertyPaneConfiguration = function () { + return { + pages: [ + { + header: { + description: strings.PropertyPaneDescription + }, + groups: [ + { + groupName: strings.BasicGroupName, + groupFields: [ + (0, sp_property_pane_1.PropertyPaneTextField)('description', { + label: strings.DescriptionFieldLabel + }) + ] + } + ] + } + ] + }; + }; + return CtFormsPortalWebPart; +}(sp_webpart_base_1.BaseClientSideWebPart)); +exports.default = CtFormsPortalWebPart; +//# sourceMappingURL=CtFormsPortalWebPart.js.map \ No newline at end of file diff --git a/lib-commonjs/webparts/ctFormsPortal/CtFormsPortalWebPart.js.map b/lib-commonjs/webparts/ctFormsPortal/CtFormsPortalWebPart.js.map new file mode 100644 index 0000000..ef6426a --- /dev/null +++ b/lib-commonjs/webparts/ctFormsPortal/CtFormsPortalWebPart.js.map @@ -0,0 +1 @@ +{"version":3,"file":"CtFormsPortalWebPart.js","sourceRoot":"","sources":["../../../src/webparts/ctFormsPortal/CtFormsPortalWebPart.ts"],"names":[],"mappings":"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","sourcesContent":["import * as React from 'react';\nimport * as ReactDom from 'react-dom';\nimport { Version } from '@microsoft/sp-core-library';\nimport {\n type IPropertyPaneConfiguration,\n PropertyPaneTextField\n} from '@microsoft/sp-property-pane';\nimport { BaseClientSideWebPart } from '@microsoft/sp-webpart-base';\nimport { IReadonlyTheme } from '@microsoft/sp-component-base';\n\nimport * as strings from 'CtFormsPortalWebPartStrings';\nimport CtFormsPortal from './components/CtFormsPortal';\nimport { ICtFormsPortalProps } from './components/ICtFormsPortalProps';\n\nexport interface ICtFormsPortalWebPartProps {\n description: string;\n}\n\nexport default class CtFormsPortalWebPart extends BaseClientSideWebPart {\n\n private _isDarkTheme: boolean = false;\n private _environmentMessage: string = '';\n\n public render(): void {\n const element: React.ReactElement = React.createElement(\n CtFormsPortal,\n {\n description: this.properties.description,\n isDarkTheme: this._isDarkTheme,\n environmentMessage: this._environmentMessage,\n hasTeamsContext: !!this.context.sdks.microsoftTeams,\n userDisplayName: this.context.pageContext.user.displayName,\n context: this.context\n }\n );\n\n ReactDom.render(element, this.domElement);\n }\n\n protected onInit(): Promise {\n return this._getEnvironmentMessage().then(message => {\n this._environmentMessage = message;\n });\n }\n\n\n\n private _getEnvironmentMessage(): Promise {\n if (!!this.context.sdks.microsoftTeams) { // running in Teams, office.com or Outlook\n return this.context.sdks.microsoftTeams.teamsJs.app.getContext()\n .then(context => {\n let environmentMessage: string = '';\n switch (context.app.host.name) {\n case 'Office': // running in Office\n environmentMessage = this.context.isServedFromLocalhost ? strings.AppLocalEnvironmentOffice : strings.AppOfficeEnvironment;\n break;\n case 'Outlook': // running in Outlook\n environmentMessage = this.context.isServedFromLocalhost ? strings.AppLocalEnvironmentOutlook : strings.AppOutlookEnvironment;\n break;\n case 'Teams': // running in Teams\n case 'TeamsModern':\n environmentMessage = this.context.isServedFromLocalhost ? strings.AppLocalEnvironmentTeams : strings.AppTeamsTabEnvironment;\n break;\n default:\n environmentMessage = strings.UnknownEnvironment;\n }\n\n return environmentMessage;\n });\n }\n\n return Promise.resolve(this.context.isServedFromLocalhost ? strings.AppLocalEnvironmentSharePoint : strings.AppSharePointEnvironment);\n }\n\n protected onThemeChanged(currentTheme: IReadonlyTheme | undefined): void {\n if (!currentTheme) {\n return;\n }\n\n this._isDarkTheme = !!currentTheme.isInverted;\n const {\n semanticColors\n } = currentTheme;\n\n if (semanticColors) {\n this.domElement.style.setProperty('--bodyText', semanticColors.bodyText || null);\n this.domElement.style.setProperty('--link', semanticColors.link || null);\n this.domElement.style.setProperty('--linkHovered', semanticColors.linkHovered || null);\n }\n\n }\n\n protected onDispose(): void {\n ReactDom.unmountComponentAtNode(this.domElement);\n }\n\n protected get dataVersion(): Version {\n return Version.parse('1.0');\n }\n\n protected getPropertyPaneConfiguration(): IPropertyPaneConfiguration {\n return {\n pages: [\n {\n header: {\n description: strings.PropertyPaneDescription\n },\n groups: [\n {\n groupName: strings.BasicGroupName,\n groupFields: [\n PropertyPaneTextField('description', {\n label: strings.DescriptionFieldLabel\n })\n ]\n }\n ]\n }\n ]\n };\n }\n}\n"]} \ No newline at end of file diff --git a/lib-commonjs/webparts/ctFormsPortal/CtFormsPortalWebPart.manifest.json b/lib-commonjs/webparts/ctFormsPortal/CtFormsPortalWebPart.manifest.json new file mode 100644 index 0000000..0953b2a --- /dev/null +++ b/lib-commonjs/webparts/ctFormsPortal/CtFormsPortalWebPart.manifest.json @@ -0,0 +1,29 @@ +{ + "$schema": "https://developer.microsoft.com/json-schemas/spfx/client-side-web-part-manifest.schema.json", + "id": "459579c7-954c-469e-b707-ca75d808603f", + "alias": "CtFormsPortalWebPart", + "componentType": "WebPart", + + // The "*" signifies that the version should be taken from the package.json + "version": "*", + "manifestVersion": 2, + + // If true, the component can only be installed on sites where Custom Script is allowed. + // Components that allow authors to embed arbitrary script code should set this to true. + // https://support.office.com/en-us/article/Turn-scripting-capabilities-on-or-off-1f2c515f-5d7e-448a-9fd7-835da935584f + "requiresCustomScript": false, + "supportedHosts": ["SharePointWebPart", "TeamsPersonalApp", "TeamsTab", "SharePointFullPage"], + "supportsThemeVariants": true, + "supportsFullBleed": true, + + "preconfiguredEntries": [{ + "groupId": "5c03119e-3074-46fd-976b-c60198311f70", // Advanced + "group": { "default": "Advanced" }, + "title": { "default": "ct-forms-portal" }, + "description": { "default": "ct-forms-portal description" }, + "officeFabricIconFontName": "Page", + "properties": { + "description": "ct-forms-portal" + } + }] +} diff --git a/lib-commonjs/webparts/ctFormsPortal/assets/welcome-dark.png b/lib-commonjs/webparts/ctFormsPortal/assets/welcome-dark.png new file mode 100644 index 0000000..42f0b8d Binary files /dev/null and b/lib-commonjs/webparts/ctFormsPortal/assets/welcome-dark.png differ diff --git a/lib-commonjs/webparts/ctFormsPortal/assets/welcome-light.png b/lib-commonjs/webparts/ctFormsPortal/assets/welcome-light.png new file mode 100644 index 0000000..69eb3b4 Binary files /dev/null and b/lib-commonjs/webparts/ctFormsPortal/assets/welcome-light.png differ diff --git a/lib-commonjs/webparts/ctFormsPortal/components/CtFormsPortal.js b/lib-commonjs/webparts/ctFormsPortal/components/CtFormsPortal.js new file mode 100644 index 0000000..1d9a69a --- /dev/null +++ b/lib-commonjs/webparts/ctFormsPortal/components/CtFormsPortal.js @@ -0,0 +1,15 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +var tslib_1 = require("tslib"); +var React = tslib_1.__importStar(require("react")); +require("@tnsu/ui-kit/styles.css"); +var ui_kit_1 = require("@tnsu/ui-kit"); +var CtFormsPortal_module_scss_1 = tslib_1.__importDefault(require("./CtFormsPortal.module.scss")); +var Dashboard_1 = require("./Dashboard"); +// Корень веб-парта: спрайт иконок (один раз) + дашборд-реестр. +// Импорт CtFormsPortal.module.scss обязателен — в нём :global full-width правила канвы. +var CtFormsPortal = function (props) { return (React.createElement("div", { className: CtFormsPortal_module_scss_1.default.root }, + React.createElement(ui_kit_1.IconSprite, null), + React.createElement(Dashboard_1.Dashboard, { context: props.context }))); }; +exports.default = CtFormsPortal; +//# sourceMappingURL=CtFormsPortal.js.map \ No newline at end of file diff --git a/lib-commonjs/webparts/ctFormsPortal/components/CtFormsPortal.js.map b/lib-commonjs/webparts/ctFormsPortal/components/CtFormsPortal.js.map new file mode 100644 index 0000000..6084961 --- /dev/null +++ b/lib-commonjs/webparts/ctFormsPortal/components/CtFormsPortal.js.map @@ -0,0 +1 @@ +{"version":3,"file":"CtFormsPortal.js","sourceRoot":"","sources":["../../../../src/webparts/ctFormsPortal/components/CtFormsPortal.tsx"],"names":[],"mappings":";;;AAAA,mDAA+B;AAC/B,mCAAiC;AACjC,uCAA0C;AAC1C,kGAAiD;AAEjD,yCAAwC;AAExC,+DAA+D;AAC/D,wFAAwF;AACxF,IAAM,aAAa,GAAkC,UAAC,KAAK,IAAK,OAAA,CAC9D,6BAAK,SAAS,EAAE,mCAAM,CAAC,IAAI;IACzB,oBAAC,mBAAU,OAAG;IACd,oBAAC,qBAAS,IAAC,OAAO,EAAE,KAAK,CAAC,OAAO,GAAI,CACjC,CACP,EAL+D,CAK/D,CAAC;AAEF,kBAAe,aAAa,CAAC","sourcesContent":["import * as React from 'react';\nimport '@tnsu/ui-kit/styles.css';\nimport { IconSprite } from '@tnsu/ui-kit';\nimport styles from './CtFormsPortal.module.scss';\nimport type { ICtFormsPortalProps } from './ICtFormsPortalProps';\nimport { Dashboard } from './Dashboard';\n\n// Корень веб-парта: спрайт иконок (один раз) + дашборд-реестр.\n// Импорт CtFormsPortal.module.scss обязателен — в нём :global full-width правила канвы.\nconst CtFormsPortal: React.FC = (props) => (\n
\n \n \n
\n);\n\nexport default CtFormsPortal;\n"]} \ No newline at end of file diff --git a/lib-commonjs/webparts/ctFormsPortal/components/CtFormsPortal.module.scss.css b/lib-commonjs/webparts/ctFormsPortal/components/CtFormsPortal.module.scss.css new file mode 100644 index 0000000..40019a9 --- /dev/null +++ b/lib-commonjs/webparts/ctFormsPortal/components/CtFormsPortal.module.scss.css @@ -0,0 +1,11 @@ +:global #workbenchPageContent, +:global #spPageCanvasContent, +:global .SPCanvas-canvas, +:global .CanvasComponent.LCS .CanvasZone, +:global .CanvasZone { + max-width: 100% !important; +} + +.root { + width: 100%; +} \ No newline at end of file diff --git a/lib-commonjs/webparts/ctFormsPortal/components/Dashboard.js b/lib-commonjs/webparts/ctFormsPortal/components/Dashboard.js new file mode 100644 index 0000000..bc5506a --- /dev/null +++ b/lib-commonjs/webparts/ctFormsPortal/components/Dashboard.js @@ -0,0 +1,106 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.Dashboard = void 0; +var tslib_1 = require("tslib"); +var React = tslib_1.__importStar(require("react")); +var react_1 = require("react"); +var Dashboard_module_scss_1 = tslib_1.__importDefault(require("./Dashboard.module.scss")); +var ui_kit_1 = require("@tnsu/ui-kit"); +var config_1 = require("./config"); +var useDocumentDashboard_1 = require("./registry/useDocumentDashboard"); +// Иконка сайдбара по типу (kit-иконки; Fluent-иконки из конфигов тут не используем). +var TYPE_ICON = { ct: 'clipboard', oid: 'chart', kps: 'building' }; +// Цвет статуса DashboardRow → вариант нашего Badge (error → danger). +var VARIANT_MAP = { + neutral: 'neutral', + info: 'info', + success: 'success', + error: 'danger' +}; +function formatDate(iso) { + if (!iso) + return ''; + var d = new Date(iso); + var dd = d.getDate() < 10 ? "0".concat(d.getDate()) : "".concat(d.getDate()); + var mm = d.getMonth() + 1 < 10 ? "0".concat(d.getMonth() + 1) : "".concat(d.getMonth() + 1); + return "".concat(dd, ".").concat(mm, ".").concat(d.getFullYear()); +} +// Дашборд-реестр (config-driven, данные из SharePoint). Оболочка и элементы — из @tnsu/ui-kit. +var Dashboard = function (_a) { + var _b; + var context = _a.context; + var _c = (0, useDocumentDashboard_1.useDocumentDashboard)(context, config_1.DOCUMENT_TYPES), myDocs = _c.myDocs, approvedDocs = _c.approvedDocs, pendingDocs = _c.pendingDocs, isLoading = _c.isLoading, error = _c.error; + var _d = (0, react_1.useState)(config_1.DOCUMENT_TYPES[0].typeKey), activeType = _d[0], setActiveType = _d[1]; + var _e = (0, react_1.useState)('pending'), queue = _e[0], setQueue = _e[1]; + var _f = (0, react_1.useState)(false), collapsed = _f[0], setCollapsed = _f[1]; + var _g = (0, react_1.useState)(false), mobileOpen = _g[0], setMobileOpen = _g[1]; + var _h = (0, react_1.useState)(''), search = _h[0], setSearch = _h[1]; + var _j = (0, react_1.useState)(''), status = _j[0], setStatus = _j[1]; + var cfg = config_1.DOCUMENT_TYPES.filter(function (t) { return t.typeKey === activeType; })[0]; + var byType = function (arr) { return arr.filter(function (r) { return r.typeKey === activeType; }); }; + var pendingForType = byType(pendingDocs); + var mineForType = byType(myDocs); + var approvedForType = byType(approvedDocs); + var sidebarItems = config_1.DOCUMENT_TYPES.map(function (t) { + var _a; + return ({ + id: t.typeKey, + title: t.typeLabel, + icon: (_a = TYPE_ICON[t.typeKey]) !== null && _a !== void 0 ? _a : 'document', + count: pendingDocs.filter(function (r) { return r.typeKey === t.typeKey; }).length + }); + }); + var tabs = [ + { id: 'pending', label: 'На согласовании у меня', count: pendingForType.length }, + { id: 'mine', label: 'Инициализированные мной', count: mineForType.length }, + { id: 'approved', label: 'Согласованные мной', count: approvedForType.length } + ]; + var activeRows = queue === 'pending' ? pendingForType : queue === 'mine' ? mineForType : approvedForType; + var statusOptions = Array.from(new Set(activeRows.map(function (r) { return r.statusLabel; }))).map(function (s) { return ({ + value: s, + label: s + }); }); + var rows = activeRows + .filter(function (r) { var _a; return !search || "".concat(r.title, " ").concat((_a = r.subtitle) !== null && _a !== void 0 ? _a : '').toLowerCase().indexOf(search.toLowerCase()) !== -1; }) + .filter(function (r) { return !status || r.statusLabel === status; }); + var renderCell = function (row, key) { + var _a; + if (key === 'status') { + return (React.createElement(ui_kit_1.Badge, { variant: VARIANT_MAP[row.statusVariant], size: "sm" }, row.statusLabel)); + } + if (key === 'created') + return formatDate(row.createdDate); + return String((_a = row[key]) !== null && _a !== void 0 ? _a : ''); + }; + var columns = cfg.columns.map(function (c) { return ({ + key: c.key, + title: c.label, + width: c.minWidth ? "".concat(c.minWidth, "px") : undefined, + render: function (row) { return renderCell(row, c.key); } + }); }); + var selectType = function (id) { + setActiveType(String(id)); + setQueue('pending'); + setSearch(''); + setStatus(''); + }; + return (React.createElement(ui_kit_1.AppLayout, { sidebar: React.createElement(ui_kit_1.Sidebar, { items: sidebarItems, activeId: activeType, onChange: selectType, brand: "\u0414\u043E\u0433\u043E\u0432\u043E\u0440\u0430", collapsed: collapsed, onToggleCollapsed: function () { return setCollapsed(function (c) { return !c; }); }, mobileOpen: mobileOpen, onMobileClose: function () { return setMobileOpen(false); } }), mobileToggle: React.createElement(ui_kit_1.SidebarToggle, { onClick: function () { return setMobileOpen(true); } }) }, + React.createElement("div", { className: Dashboard_module_scss_1.default.head }, + React.createElement("h2", { className: Dashboard_module_scss_1.default.title }, cfg.typeLabel), + cfg.createNewUrl && (React.createElement(ui_kit_1.Button, { variant: "accent", onClick: function () { return window.open(cfg.createNewUrl, '_blank'); } }, (_b = cfg.createNewLabel) !== null && _b !== void 0 ? _b : 'Создать'))), + React.createElement("div", { className: Dashboard_module_scss_1.default.toolbar }, + React.createElement("div", { className: Dashboard_module_scss_1.default.search }, + React.createElement(ui_kit_1.TextInput, { value: search, onChange: setSearch, placeholder: "\u041F\u043E\u0438\u0441\u043A \u043F\u043E \u043D\u043E\u043C\u0435\u0440\u0443 \u0438\u043B\u0438 \u043F\u0440\u043E\u0435\u043A\u0442\u0443" })), + React.createElement("div", { className: Dashboard_module_scss_1.default.filter }, + React.createElement(ui_kit_1.Select, { value: status, onChange: setStatus, options: statusOptions, placeholder: "\u0412\u0441\u0435 \u0441\u0442\u0430\u0442\u0443\u0441\u044B" }))), + React.createElement(ui_kit_1.Tabs, { tabs: tabs, activeId: queue, onChange: function (id) { return setQueue(id); } }), + error && (React.createElement("div", { className: Dashboard_module_scss_1.default.state }, + React.createElement(ui_kit_1.Alert, { variant: "err" }, + "\u041D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C \u0437\u0430\u0433\u0440\u0443\u0437\u0438\u0442\u044C \u0434\u0430\u043D\u043D\u044B\u0435: ", + error))), + !error && isLoading && React.createElement("div", { className: Dashboard_module_scss_1.default.state }, "\u0417\u0430\u0433\u0440\u0443\u0437\u043A\u0430\u2026"), + !error && !isLoading && (React.createElement("div", { className: Dashboard_module_scss_1.default.tableWrap }, + React.createElement(ui_kit_1.DataTable, { columns: columns, rows: rows, rowKey: function (r) { return "".concat(r.typeKey, "-").concat(r.id); }, onRowClick: function (r) { return window.open(cfg.buildLinkUrl(r), '_blank'); }, emptyText: "\u041D\u0435\u0442 \u0434\u043E\u043A\u0443\u043C\u0435\u043D\u0442\u043E\u0432" }))))); +}; +exports.Dashboard = Dashboard; +//# sourceMappingURL=Dashboard.js.map \ No newline at end of file diff --git a/lib-commonjs/webparts/ctFormsPortal/components/Dashboard.js.map b/lib-commonjs/webparts/ctFormsPortal/components/Dashboard.js.map new file mode 100644 index 0000000..cce6efb --- /dev/null +++ b/lib-commonjs/webparts/ctFormsPortal/components/Dashboard.js.map @@ -0,0 +1 @@ +{"version":3,"file":"Dashboard.js","sourceRoot":"","sources":["../../../../src/webparts/ctFormsPortal/components/Dashboard.tsx"],"names":[],"mappings":"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","sourcesContent":["import * as React from 'react';\nimport { useState } from 'react';\nimport { WebPartContext } from '@microsoft/sp-webpart-base';\nimport styles from './Dashboard.module.scss';\nimport {\n AppLayout,\n Sidebar,\n SidebarToggle,\n ISidebarItem,\n Tabs,\n ITab,\n DataTable,\n IDataTableColumn,\n Badge,\n BadgeVariant,\n Button,\n TextInput,\n Select,\n ISelectOption,\n Alert,\n IconName\n} from '@tnsu/ui-kit';\nimport { DOCUMENT_TYPES } from './config';\nimport { useDocumentDashboard } from './registry/useDocumentDashboard';\nimport { DashboardRow } from './registry/documentRegistry';\n\ntype QueueKey = 'pending' | 'mine' | 'approved';\n\n// Иконка сайдбара по типу (kit-иконки; Fluent-иконки из конфигов тут не используем).\nconst TYPE_ICON: Record = { ct: 'clipboard', oid: 'chart', kps: 'building' };\n\n// Цвет статуса DashboardRow → вариант нашего Badge (error → danger).\nconst VARIANT_MAP: Record = {\n neutral: 'neutral',\n info: 'info',\n success: 'success',\n error: 'danger'\n};\n\nfunction formatDate(iso?: string): string {\n if (!iso) return '';\n const d = new Date(iso);\n const dd = d.getDate() < 10 ? `0${d.getDate()}` : `${d.getDate()}`;\n const mm = d.getMonth() + 1 < 10 ? `0${d.getMonth() + 1}` : `${d.getMonth() + 1}`;\n return `${dd}.${mm}.${d.getFullYear()}`;\n}\n\n// Дашборд-реестр (config-driven, данные из SharePoint). Оболочка и элементы — из @tnsu/ui-kit.\nexport const Dashboard: React.FC<{ context: WebPartContext }> = ({ context }) => {\n const { myDocs, approvedDocs, pendingDocs, isLoading, error } = useDocumentDashboard(context, DOCUMENT_TYPES);\n\n const [activeType, setActiveType] = useState(DOCUMENT_TYPES[0].typeKey);\n const [queue, setQueue] = useState('pending');\n const [collapsed, setCollapsed] = useState(false);\n const [mobileOpen, setMobileOpen] = useState(false);\n const [search, setSearch] = useState('');\n const [status, setStatus] = useState('');\n\n const cfg = DOCUMENT_TYPES.filter((t) => t.typeKey === activeType)[0];\n\n const byType = (arr: DashboardRow[]): DashboardRow[] => arr.filter((r) => r.typeKey === activeType);\n const pendingForType = byType(pendingDocs);\n const mineForType = byType(myDocs);\n const approvedForType = byType(approvedDocs);\n\n const sidebarItems: ISidebarItem[] = DOCUMENT_TYPES.map((t) => ({\n id: t.typeKey,\n title: t.typeLabel,\n icon: TYPE_ICON[t.typeKey] ?? 'document',\n count: pendingDocs.filter((r) => r.typeKey === t.typeKey).length\n }));\n\n const tabs: ITab[] = [\n { id: 'pending', label: 'На согласовании у меня', count: pendingForType.length },\n { id: 'mine', label: 'Инициализированные мной', count: mineForType.length },\n { id: 'approved', label: 'Согласованные мной', count: approvedForType.length }\n ];\n\n const activeRows = queue === 'pending' ? pendingForType : queue === 'mine' ? mineForType : approvedForType;\n\n const statusOptions: ISelectOption[] = Array.from(new Set(activeRows.map((r) => r.statusLabel))).map((s) => ({\n value: s,\n label: s\n }));\n\n const rows = activeRows\n .filter((r) => !search || `${r.title} ${r.subtitle ?? ''}`.toLowerCase().indexOf(search.toLowerCase()) !== -1)\n .filter((r) => !status || r.statusLabel === status);\n\n const renderCell = (row: DashboardRow, key: string): React.ReactNode => {\n if (key === 'status') {\n return (\n \n {row.statusLabel}\n \n );\n }\n if (key === 'created') return formatDate(row.createdDate);\n return String((row as unknown as Record)[key] ?? '');\n };\n\n const columns: IDataTableColumn[] = cfg.columns.map((c) => ({\n key: c.key,\n title: c.label,\n width: c.minWidth ? `${c.minWidth}px` : undefined,\n render: (row) => renderCell(row, c.key)\n }));\n\n const selectType = (id: string | number): void => {\n setActiveType(String(id));\n setQueue('pending');\n setSearch('');\n setStatus('');\n };\n\n return (\n setCollapsed((c) => !c)}\n mobileOpen={mobileOpen}\n onMobileClose={() => setMobileOpen(false)}\n />\n }\n mobileToggle={ setMobileOpen(true)} />}\n >\n
\n

{cfg.typeLabel}

\n {cfg.createNewUrl && (\n \n )}\n
\n\n
\n
\n \n
\n
\n