feat: implemented CRUD funnctioality to contractors
@@ -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<k,V>".
|
||||
'@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: {}
|
||||
}
|
||||
]
|
||||
};
|
||||
@@ -0,0 +1,7 @@
|
||||
node_modules/
|
||||
lib/
|
||||
dist/
|
||||
temp/
|
||||
*.sppkg
|
||||
.heft/
|
||||
sharepoint/solution/
|
||||
@@ -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
|
||||
@@ -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"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"@microsoft/generator-sharepoint": {
|
||||
"useGulp": false,
|
||||
"plusBeta": false,
|
||||
"isCreatingSolution": true,
|
||||
"nodeVersion": "22.22.2",
|
||||
"sdksVersions": {},
|
||||
"version": "1.22.2",
|
||||
"libraryName": "contractors-list",
|
||||
"libraryId": "58c3bf08-0f2a-41bb-a09d-f62fbf6a3502",
|
||||
"environment": "spo",
|
||||
"packageManager": "npm",
|
||||
"solutionName": "contractors-list",
|
||||
"solutionShortDescription": "contractors-list description",
|
||||
"skipFeatureDeployment": true,
|
||||
"isDomainIsolated": false,
|
||||
"componentType": "webpart"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
# contractors-list
|
||||
|
||||
## Summary
|
||||
|
||||
Short summary on functionality and used technologies.
|
||||
|
||||
[picture of the solution in action, if possible]
|
||||
|
||||
## Used SharePoint Framework Version
|
||||
|
||||

|
||||
|
||||
## 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/)
|
||||
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/config.2.0.schema.json",
|
||||
"version": "2.0",
|
||||
"bundles": {
|
||||
"contractors-list-web-part": {
|
||||
"components": [
|
||||
{
|
||||
"entrypoint": "./lib/webparts/contractorsList/ContractorsListWebPart.js",
|
||||
"manifest": "./src/webparts/contractorsList/ContractorsListWebPart.manifest.json"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"externals": {},
|
||||
"localizedResources": {
|
||||
"ContractorsListWebPartStrings": "lib/webparts/contractorsList/loc/{locale}.js"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/deploy-azure-storage.schema.json",
|
||||
"workingDir": "./release/assets/",
|
||||
"account": "<!-- STORAGE ACCOUNT NAME -->",
|
||||
"container": "contractors-list",
|
||||
"accessKey": "<!-- ACCESS KEY -->"
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/package-solution.schema.json",
|
||||
"solution": {
|
||||
"name": "contractors-list-client-side-solution",
|
||||
"id": "58c3bf08-0f2a-41bb-a09d-f62fbf6a3502",
|
||||
"version": "1.0.0.0",
|
||||
"includeClientSideAssets": true,
|
||||
"skipFeatureDeployment": true,
|
||||
"isDomainIsolated": false,
|
||||
"developer": {
|
||||
"name": "",
|
||||
"websiteUrl": "",
|
||||
"privacyUrl": "",
|
||||
"termsOfUseUrl": "",
|
||||
"mpnId": "Undefined-1.22.2"
|
||||
},
|
||||
"metadata": {
|
||||
"shortDescription": {
|
||||
"default": "contractors-list description"
|
||||
},
|
||||
"longDescription": {
|
||||
"default": "contractors-list description"
|
||||
},
|
||||
"screenshotPaths": [],
|
||||
"videoUrl": "",
|
||||
"categories": []
|
||||
},
|
||||
"features": [
|
||||
{
|
||||
"title": "contractors-list Feature",
|
||||
"description": "The feature that activates elements of the contractors-list solution.",
|
||||
"id": "e4de6c52-b056-4517-81f8-9d0fb3ddc4e8",
|
||||
"version": "1.0.0.0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"paths": {
|
||||
"zippedPackage": "solution/contractors-list.sppkg"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/rig-package/rig.schema.json",
|
||||
"rigPackageName": "@microsoft/spfx-web-build-rig"
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
@@ -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"]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/write-manifests.schema.json",
|
||||
"cdnBasePath": "<!-- PATH TO CDN -->"
|
||||
}
|
||||
@@ -0,0 +1,282 @@
|
||||
# =============================================================
|
||||
# SPFx + Heft project bootstrap script (PowerShell)
|
||||
# Запустите из корня вашей ПУСТОЙ папки проекта:
|
||||
# .\setup-spfx-heft.ps1
|
||||
#
|
||||
# Если запуск скриптов заблокирован, выполните сначала:
|
||||
# Set-ExecutionPolicy -Scope CurrentUser -ExecutionPolicy RemoteSigned
|
||||
# =============================================================
|
||||
|
||||
$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 "=================================================`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 "Все зависимости установлены."
|
||||
|
||||
# ---------------------------------------------------------
|
||||
# Готово
|
||||
# ---------------------------------------------------------
|
||||
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 " Следующий шаг: " -NoNewline -ForegroundColor Yellow
|
||||
Write-Host "npm run serve" -ForegroundColor Cyan
|
||||
Write-Host ""
|
||||
@@ -0,0 +1,3 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<testsuites name="jest tests" tests="0" failures="0" errors="0" time="0.003">
|
||||
</testsuites>
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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);
|
||||
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" ?>
|
||||
<!DOCTYPE coverage SYSTEM "http://cobertura.sourceforge.net/xml/coverage-04.dtd">
|
||||
<coverage lines-valid="0" lines-covered="0" line-rate="NaN" branches-valid="0" branches-covered="0" branch-rate="NaN" timestamp="1785143259144" complexity="0" version="0.1">
|
||||
<sources>
|
||||
<source>C:\Users\gorodetskiy.timur\Documents\Projects\contractors-list</source>
|
||||
</sources>
|
||||
<packages>
|
||||
</packages>
|
||||
</coverage>
|
||||
@@ -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"}}
|
||||
}
|
||||
|
After Width: | Height: | Size: 445 B |
@@ -0,0 +1,101 @@
|
||||
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<title>Code coverage report for All files</title>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="stylesheet" href="prettify.css" />
|
||||
<link rel="stylesheet" href="base.css" />
|
||||
<link rel="shortcut icon" type="image/x-icon" href="favicon.png" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<style type='text/css'>
|
||||
.coverage-summary .sorter {
|
||||
background-image: url(sort-arrow-sprite.png);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class='wrapper'>
|
||||
<div class='pad1'>
|
||||
<h1>All files</h1>
|
||||
<div class='clearfix'>
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">Unknown% </span>
|
||||
<span class="quiet">Statements</span>
|
||||
<span class='fraction'>0/0</span>
|
||||
</div>
|
||||
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">Unknown% </span>
|
||||
<span class="quiet">Branches</span>
|
||||
<span class='fraction'>0/0</span>
|
||||
</div>
|
||||
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">Unknown% </span>
|
||||
<span class="quiet">Functions</span>
|
||||
<span class='fraction'>0/0</span>
|
||||
</div>
|
||||
|
||||
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">Unknown% </span>
|
||||
<span class="quiet">Lines</span>
|
||||
<span class='fraction'>0/0</span>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
<p class="quiet">
|
||||
Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>, <em>p</em> or <em>k</em> for the previous block.
|
||||
</p>
|
||||
<template id="filterTemplate">
|
||||
<div class="quiet">
|
||||
Filter:
|
||||
<input type="search" id="fileSearch">
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
<div class='status-line medium'></div>
|
||||
<div class="pad1">
|
||||
<table class="coverage-summary">
|
||||
<thead>
|
||||
<tr>
|
||||
<th data-col="file" data-fmt="html" data-html="true" class="file">File</th>
|
||||
<th data-col="pic" data-type="number" data-fmt="html" data-html="true" class="pic"></th>
|
||||
<th data-col="statements" data-type="number" data-fmt="pct" class="pct">Statements</th>
|
||||
<th data-col="statements_raw" data-type="number" data-fmt="html" class="abs"></th>
|
||||
<th data-col="branches" data-type="number" data-fmt="pct" class="pct">Branches</th>
|
||||
<th data-col="branches_raw" data-type="number" data-fmt="html" class="abs"></th>
|
||||
<th data-col="functions" data-type="number" data-fmt="pct" class="pct">Functions</th>
|
||||
<th data-col="functions_raw" data-type="number" data-fmt="html" class="abs"></th>
|
||||
<th data-col="lines" data-type="number" data-fmt="pct" class="pct">Lines</th>
|
||||
<th data-col="lines_raw" data-type="number" data-fmt="html" class="abs"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody></tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class='push'></div><!-- for sticky footer -->
|
||||
</div><!-- /wrapper -->
|
||||
<div class='footer quiet pad2 space-top1 center small'>
|
||||
Code coverage generated by
|
||||
<a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
|
||||
at 2026-07-27T09:07:39.198Z
|
||||
</div>
|
||||
<script src="prettify.js"></script>
|
||||
<script>
|
||||
window.onload = function () {
|
||||
prettyPrint();
|
||||
};
|
||||
</script>
|
||||
<script src="sorter.js"></script>
|
||||
<script src="block-navigation.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -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}
|
||||
|
After Width: | Height: | Size: 138 B |
@@ -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 + '<span class="sorter"></span>';
|
||||
}
|
||||
}
|
||||
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);
|
||||
@@ -0,0 +1,94 @@
|
||||
"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 ContractorsList_1 = tslib_1.__importDefault(require("./components/ContractorsList"));
|
||||
var ContractorsListWebPart = /** @class */ (function (_super) {
|
||||
tslib_1.__extends(ContractorsListWebPart, _super);
|
||||
function ContractorsListWebPart() {
|
||||
var _this = _super !== null && _super.apply(this, arguments) || this;
|
||||
_this._isDarkTheme = false;
|
||||
return _this;
|
||||
}
|
||||
ContractorsListWebPart.prototype.onInit = function () {
|
||||
return tslib_1.__awaiter(this, void 0, void 0, function () {
|
||||
var clientId, _a;
|
||||
return tslib_1.__generator(this, function (_b) {
|
||||
switch (_b.label) {
|
||||
case 0: return [4 /*yield*/, _super.prototype.onInit.call(this)];
|
||||
case 1:
|
||||
_b.sent();
|
||||
clientId = this.properties.apiClientId || '9232d9e2-9807-4753-808f-07250e5474b0';
|
||||
_a = this;
|
||||
return [4 /*yield*/, this.context.aadHttpClientFactory.getClient(clientId)];
|
||||
case 2:
|
||||
_a._httpClient = _b.sent();
|
||||
return [2 /*return*/];
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
ContractorsListWebPart.prototype.render = function () {
|
||||
var element = React.createElement(ContractorsList_1.default, {
|
||||
isDarkTheme: this._isDarkTheme,
|
||||
httpClient: this._httpClient,
|
||||
apiBaseUrl: this.properties.apiBaseUrl || 'https://api.tnsu.kz',
|
||||
});
|
||||
ReactDom.render(element, this.domElement);
|
||||
};
|
||||
ContractorsListWebPart.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);
|
||||
}
|
||||
};
|
||||
ContractorsListWebPart.prototype.onDispose = function () {
|
||||
ReactDom.unmountComponentAtNode(this.domElement);
|
||||
};
|
||||
Object.defineProperty(ContractorsListWebPart.prototype, "dataVersion", {
|
||||
get: function () {
|
||||
return sp_core_library_1.Version.parse('1.0');
|
||||
},
|
||||
enumerable: false,
|
||||
configurable: true
|
||||
});
|
||||
ContractorsListWebPart.prototype.getPropertyPaneConfiguration = function () {
|
||||
return {
|
||||
pages: [
|
||||
{
|
||||
header: {
|
||||
description: 'Настройки веб-части'
|
||||
},
|
||||
groups: [
|
||||
{
|
||||
groupName: 'API',
|
||||
groupFields: [
|
||||
(0, sp_property_pane_1.PropertyPaneTextField)('apiClientId', {
|
||||
label: 'API Client ID (Azure AD)',
|
||||
placeholder: '9232d9e2-9807-4753-808f-07250e5474b0'
|
||||
}),
|
||||
(0, sp_property_pane_1.PropertyPaneTextField)('apiBaseUrl', {
|
||||
label: 'Базовый URL API',
|
||||
placeholder: 'https://api.tnsu.kz'
|
||||
})
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
};
|
||||
};
|
||||
return ContractorsListWebPart;
|
||||
}(sp_webpart_base_1.BaseClientSideWebPart));
|
||||
exports.default = ContractorsListWebPart;
|
||||
//# sourceMappingURL=ContractorsListWebPart.js.map
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"ContractorsListWebPart.js","sourceRoot":"","sources":["../../../src/webparts/contractorsList/ContractorsListWebPart.ts"],"names":[],"mappings":";;;AAAA,mDAA+B;AAC/B,0DAAsC;AACtC,8DAAqD;AACrD,gEAGqC;AACrC,8DAAmE;AAInE,yFAA2D;AAQ3D;IAAoD,kDAAmD;IAAvG;;QACU,kBAAY,GAAY,KAAK,CAAC;;IA0ExC,CAAC;IAvEiB,uCAAM,GAAtB;;;;;4BACE,qBAAM,gBAAK,CAAC,MAAM,WAAE,EAAA;;wBAApB,SAAoB,CAAC;wBACf,QAAQ,GAAG,IAAI,CAAC,UAAU,CAAC,WAAW,IAAI,sCAAsC,CAAC;wBACvF,KAAA,IAAI,CAAA;wBAAe,qBAAM,IAAI,CAAC,OAAO,CAAC,oBAAoB,CAAC,SAAS,CAAC,QAAQ,CAAC,EAAA;;wBAA9E,GAAK,WAAW,GAAG,SAA2D,CAAC;;;;;KAChF;IAEM,uCAAM,GAAb;QACE,IAAM,OAAO,GAA8C,KAAK,CAAC,aAAa,CAC5E,yBAAe,EACf;YACE,WAAW,EAAE,IAAI,CAAC,YAAY;YAC9B,UAAU,EAAE,IAAI,CAAC,WAAW;YAC5B,UAAU,EAAE,IAAI,CAAC,UAAU,CAAC,UAAU,IAAI,qBAAqB;SAChE,CACF,CAAC;QAEF,QAAQ,CAAC,MAAM,CAAC,OAAO,EAAE,IAAI,CAAC,UAAU,CAAC,CAAC;IAC5C,CAAC;IAES,+CAAc,GAAxB,UAAyB,YAAwC;QAC/D,IAAI,CAAC,YAAY,EAAE,CAAC;YAClB,OAAO;QACT,CAAC;QAED,IAAI,CAAC,YAAY,GAAG,CAAC,CAAC,YAAY,CAAC,UAAU,CAAC;QAE5C,IAAA,cAAc,GACZ,YAAY,eADA,CACC;QAEjB,IAAI,cAAc,EAAE,CAAC;YACnB,IAAI,CAAC,UAAU,CAAC,KAAK,CAAC,WAAW,CAAC,YAAY,EAAE,cAAc,CAAC,QAAQ,IAAI,IAAI,CAAC,CAAC;YACjF,IAAI,CAAC,UAAU,CAAC,KAAK,CAAC,WAAW,CAAC,QAAQ,EAAE,cAAc,CAAC,IAAI,IAAI,IAAI,CAAC,CAAC;YACzE,IAAI,CAAC,UAAU,CAAC,KAAK,CAAC,WAAW,CAAC,eAAe,EAAE,cAAc,CAAC,WAAW,IAAI,IAAI,CAAC,CAAC;QACzF,CAAC;IAEH,CAAC;IAES,0CAAS,GAAnB;QACE,QAAQ,CAAC,sBAAsB,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;IACnD,CAAC;IAED,sBAAc,+CAAW;aAAzB;YACE,OAAO,yBAAO,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;QAC9B,CAAC;;;OAAA;IAES,6DAA4B,GAAtC;QACE,OAAO;YACL,KAAK,EAAE;gBACL;oBACE,MAAM,EAAE;wBACN,WAAW,EAAE,qBAAqB;qBACnC;oBACD,MAAM,EAAE;wBACN;4BACE,SAAS,EAAE,KAAK;4BAChB,WAAW,EAAE;gCACX,IAAA,wCAAqB,EAAC,aAAa,EAAE;oCACnC,KAAK,EAAE,0BAA0B;oCACjC,WAAW,EAAE,sCAAsC;iCACpD,CAAC;gCACF,IAAA,wCAAqB,EAAC,YAAY,EAAE;oCAClC,KAAK,EAAE,iBAAiB;oCACxB,WAAW,EAAE,qBAAqB;iCACnC,CAAC;6BACH;yBACF;qBACF;iBACF;aACF;SACF,CAAC;IACJ,CAAC;IACH,6BAAC;AAAD,CAAC,AA3ED,CAAoD,uCAAqB,GA2ExE","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';\nimport { AadHttpClient } from '@microsoft/sp-http';\n\nimport ContractorsList from './components/ContractorsList';\nimport { IContractorsListProps } from './components/IContractorsListProps';\n\nexport interface IContractorsListWebPartProps {\n apiClientId: string;\n apiBaseUrl: string;\n}\n\nexport default class ContractorsListWebPart extends BaseClientSideWebPart<IContractorsListWebPartProps> {\n private _isDarkTheme: boolean = false;\n private _httpClient!: AadHttpClient;\n\n protected async onInit(): Promise<void> {\n await super.onInit();\n const clientId = this.properties.apiClientId || '9232d9e2-9807-4753-808f-07250e5474b0';\n this._httpClient = await this.context.aadHttpClientFactory.getClient(clientId);\n }\n\n public render(): void {\n const element: React.ReactElement<IContractorsListProps> = React.createElement(\n ContractorsList,\n {\n isDarkTheme: this._isDarkTheme,\n httpClient: this._httpClient,\n apiBaseUrl: this.properties.apiBaseUrl || 'https://api.tnsu.kz',\n }\n );\n\n ReactDom.render(element, this.domElement);\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: 'Настройки веб-части'\n },\n groups: [\n {\n groupName: 'API',\n groupFields: [\n PropertyPaneTextField('apiClientId', {\n label: 'API Client ID (Azure AD)',\n placeholder: '9232d9e2-9807-4753-808f-07250e5474b0'\n }),\n PropertyPaneTextField('apiBaseUrl', {\n label: 'Базовый URL API',\n placeholder: 'https://api.tnsu.kz'\n })\n ]\n }\n ]\n }\n ]\n };\n }\n}\n"]}
|
||||
@@ -0,0 +1,29 @@
|
||||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/spfx/client-side-web-part-manifest.schema.json",
|
||||
"id": "803b4696-0af0-45a3-b621-4c9fda238fcb",
|
||||
"alias": "ContractorsListWebPart",
|
||||
"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,
|
||||
|
||||
"preconfiguredEntries": [{
|
||||
"groupId": "5c03119e-3074-46fd-976b-c60198311f70", // Advanced
|
||||
"group": { "default": "Advanced" },
|
||||
"title": { "default": "contractors-list" },
|
||||
"description": { "default": "contractors-list description" },
|
||||
"officeFabricIconFontName": "Page",
|
||||
"properties": {
|
||||
"apiClientId": "9232d9e2-9807-4753-808f-07250e5474b0",
|
||||
"apiBaseUrl": "https://api.tnsu.kz"
|
||||
}
|
||||
}]
|
||||
}
|
||||
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 12 KiB |
@@ -0,0 +1,135 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
var tslib_1 = require("tslib");
|
||||
var React = tslib_1.__importStar(require("react"));
|
||||
var ContractorsList_module_scss_1 = tslib_1.__importDefault(require("./ContractorsList.module.scss"));
|
||||
var CONTRACTOR_TYPES = ["Заказчик", "Поставщик"];
|
||||
var CloseIcon = function () { return (React.createElement("svg", { width: "16", height: "16", viewBox: "0 0 20 20", fill: "currentColor" },
|
||||
React.createElement("path", { d: "M4.293 4.293a1 1 0 0 1 1.414 0L10 8.586l4.293-4.293a1 1 0 1 1 1.414 1.414L11.414 10l4.293 4.293a1 1 0 0 1-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 0 1-1.414-1.414L8.586 10 4.293 5.707a1 1 0 0 1 0-1.414z" }))); };
|
||||
var ContractorModal = /** @class */ (function (_super) {
|
||||
tslib_1.__extends(ContractorModal, _super);
|
||||
function ContractorModal(props) {
|
||||
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k;
|
||||
var _this = _super.call(this, props) || this;
|
||||
_this._handleKeyDown = function (e) {
|
||||
if (e.key === "Escape" && !_this.state.saving)
|
||||
_this.props.onClose();
|
||||
};
|
||||
_this._handleSave = function () { return tslib_1.__awaiter(_this, void 0, void 0, function () {
|
||||
var _a, fullName, name, contractorType, bin, kbe, oked, country, address, actualAddress, phone, e_1;
|
||||
return tslib_1.__generator(this, function (_b) {
|
||||
switch (_b.label) {
|
||||
case 0:
|
||||
_a = this.state, fullName = _a.fullName, name = _a.name, contractorType = _a.contractorType, bin = _a.bin, kbe = _a.kbe, oked = _a.oked, country = _a.country, address = _a.address, actualAddress = _a.actualAddress, phone = _a.phone;
|
||||
if (!name.trim() || !fullName.trim()) {
|
||||
this.setState({ error: "Название и полное наименование обязательны для заполнения" });
|
||||
return [2 /*return*/];
|
||||
}
|
||||
this.setState({ saving: true, error: null });
|
||||
_b.label = 1;
|
||||
case 1:
|
||||
_b.trys.push([1, 3, , 4]);
|
||||
return [4 /*yield*/, this.props.onSave({
|
||||
fullName: fullName.trim(),
|
||||
name: name.trim(),
|
||||
contractorType: contractorType.trim() || null,
|
||||
bin: bin.trim() || null,
|
||||
kbe: kbe.trim() || null,
|
||||
oked: oked.trim() || null,
|
||||
country: country.trim() || null,
|
||||
address: address.trim() || null,
|
||||
actualAddress: actualAddress.trim() || null,
|
||||
phone: phone.trim() || null,
|
||||
})];
|
||||
case 2:
|
||||
_b.sent();
|
||||
return [3 /*break*/, 4];
|
||||
case 3:
|
||||
e_1 = _b.sent();
|
||||
this.setState({ saving: false, error: e_1.message });
|
||||
return [3 /*break*/, 4];
|
||||
case 4: return [2 /*return*/];
|
||||
}
|
||||
});
|
||||
}); };
|
||||
var contractor = props.contractor;
|
||||
_this.state = {
|
||||
fullName: (_a = contractor === null || contractor === void 0 ? void 0 : contractor.fullName) !== null && _a !== void 0 ? _a : "",
|
||||
name: (_b = contractor === null || contractor === void 0 ? void 0 : contractor.name) !== null && _b !== void 0 ? _b : "",
|
||||
contractorType: (_c = contractor === null || contractor === void 0 ? void 0 : contractor.contractorType) !== null && _c !== void 0 ? _c : "",
|
||||
bin: (_d = contractor === null || contractor === void 0 ? void 0 : contractor.bin) !== null && _d !== void 0 ? _d : "",
|
||||
kbe: (_e = contractor === null || contractor === void 0 ? void 0 : contractor.kbe) !== null && _e !== void 0 ? _e : "",
|
||||
oked: (_f = contractor === null || contractor === void 0 ? void 0 : contractor.oked) !== null && _f !== void 0 ? _f : "",
|
||||
country: (_g = contractor === null || contractor === void 0 ? void 0 : contractor.country) !== null && _g !== void 0 ? _g : "",
|
||||
address: (_h = contractor === null || contractor === void 0 ? void 0 : contractor.address) !== null && _h !== void 0 ? _h : "",
|
||||
actualAddress: (_j = contractor === null || contractor === void 0 ? void 0 : contractor.actualAddress) !== null && _j !== void 0 ? _j : "",
|
||||
phone: (_k = contractor === null || contractor === void 0 ? void 0 : contractor.phone) !== null && _k !== void 0 ? _k : "",
|
||||
saving: false,
|
||||
error: null,
|
||||
};
|
||||
return _this;
|
||||
}
|
||||
ContractorModal.prototype.componentDidMount = function () {
|
||||
document.addEventListener("keydown", this._handleKeyDown);
|
||||
};
|
||||
ContractorModal.prototype.componentWillUnmount = function () {
|
||||
document.removeEventListener("keydown", this._handleKeyDown);
|
||||
};
|
||||
ContractorModal.prototype.render = function () {
|
||||
var _this = this;
|
||||
var _a = this.props, contractor = _a.contractor, onClose = _a.onClose;
|
||||
var _b = this.state, fullName = _b.fullName, name = _b.name, contractorType = _b.contractorType, bin = _b.bin, kbe = _b.kbe, oked = _b.oked, country = _b.country, address = _b.address, actualAddress = _b.actualAddress, phone = _b.phone, saving = _b.saving, error = _b.error;
|
||||
var isEdit = !!contractor;
|
||||
return (React.createElement("div", { className: ContractorsList_module_scss_1.default.modalOverlay },
|
||||
React.createElement("div", { className: ContractorsList_module_scss_1.default.modal, onClick: function (e) { return e.stopPropagation(); }, role: "dialog", "aria-modal": "true" },
|
||||
React.createElement("div", { className: ContractorsList_module_scss_1.default.modalHeader },
|
||||
React.createElement("h3", { className: ContractorsList_module_scss_1.default.modalTitle }, isEdit ? "Редактировать контрагента" : "Новый контрагент"),
|
||||
React.createElement("button", { className: ContractorsList_module_scss_1.default.modalCloseBtn, onClick: onClose, disabled: saving, title: "\u0417\u0430\u043A\u0440\u044B\u0442\u044C" },
|
||||
React.createElement(CloseIcon, null))),
|
||||
React.createElement("div", { className: ContractorsList_module_scss_1.default.modalBody },
|
||||
error && React.createElement("div", { className: ContractorsList_module_scss_1.default.errorMsg }, error),
|
||||
React.createElement("div", { className: ContractorsList_module_scss_1.default.formGroup },
|
||||
React.createElement("label", { className: ContractorsList_module_scss_1.default.formLabel },
|
||||
"\u041D\u0430\u0437\u0432\u0430\u043D\u0438\u0435 ",
|
||||
React.createElement("span", { className: ContractorsList_module_scss_1.default.required }, "*")),
|
||||
React.createElement("input", { type: "text", className: ContractorsList_module_scss_1.default.formInput, value: name, onChange: function (e) { return _this.setState({ name: e.target.value, error: null }); }, placeholder: "\u041D\u0430\u043F\u0440\u0438\u043C\u0435\u0440: Arx Minerals (\u0410\u0440\u043A\u0441 \u041C\u0438\u043D\u0435\u0440\u0430\u043B\u0441) \u0422\u041E\u041E", autoFocus: true })),
|
||||
React.createElement("div", { className: ContractorsList_module_scss_1.default.formGroup },
|
||||
React.createElement("label", { className: ContractorsList_module_scss_1.default.formLabel },
|
||||
"\u041F\u043E\u043B\u043D\u043E\u0435 \u043D\u0430\u0438\u043C\u0435\u043D\u043E\u0432\u0430\u043D\u0438\u0435 ",
|
||||
React.createElement("span", { className: ContractorsList_module_scss_1.default.required }, "*")),
|
||||
React.createElement("textarea", { className: ContractorsList_module_scss_1.default.formTextarea, value: fullName, onChange: function (e) { return _this.setState({ fullName: e.target.value, error: null }); }, placeholder: '\u041D\u0430\u043F\u0440\u0438\u043C\u0435\u0440: \u0422\u041E\u041E "Arx Minerals (\u0410\u0440\u043A\u0441 \u041C\u0438\u043D\u0435\u0440\u0430\u043B\u0441)"', rows: 2 })),
|
||||
React.createElement("div", { className: ContractorsList_module_scss_1.default.formGrid },
|
||||
React.createElement("div", { className: ContractorsList_module_scss_1.default.formGroup },
|
||||
React.createElement("label", { className: ContractorsList_module_scss_1.default.formLabel }, "\u0411\u0418\u041D"),
|
||||
React.createElement("input", { type: "text", className: ContractorsList_module_scss_1.default.formInput, value: bin, onChange: function (e) { return _this.setState({ bin: e.target.value.replace(/\D/g, "").slice(0, 12) }); }, placeholder: "121140000288", inputMode: "numeric", maxLength: 12 })),
|
||||
React.createElement("div", { className: ContractorsList_module_scss_1.default.formGroup },
|
||||
React.createElement("label", { className: ContractorsList_module_scss_1.default.formLabel }, "\u0422\u0438\u043F \u043A\u043E\u043D\u0442\u0440\u0430\u0433\u0435\u043D\u0442\u0430"),
|
||||
React.createElement("select", { className: ContractorsList_module_scss_1.default.formInput, value: contractorType, onChange: function (e) { return _this.setState({ contractorType: e.target.value }); } },
|
||||
React.createElement("option", { value: "" }, "\u2014 \u041D\u0435 \u0432\u044B\u0431\u0440\u0430\u043D\u043E \u2014"),
|
||||
CONTRACTOR_TYPES.map(function (t) { return React.createElement("option", { key: t, value: t }, t); }))),
|
||||
React.createElement("div", { className: ContractorsList_module_scss_1.default.formGroup },
|
||||
React.createElement("label", { className: ContractorsList_module_scss_1.default.formLabel }, "\u041A\u0411\u0415"),
|
||||
React.createElement("input", { type: "text", className: ContractorsList_module_scss_1.default.formInput, value: kbe, onChange: function (e) { return _this.setState({ kbe: e.target.value }); } })),
|
||||
React.createElement("div", { className: ContractorsList_module_scss_1.default.formGroup },
|
||||
React.createElement("label", { className: ContractorsList_module_scss_1.default.formLabel }, "\u041E\u041A\u042D\u0414"),
|
||||
React.createElement("input", { type: "text", className: ContractorsList_module_scss_1.default.formInput, value: oked, onChange: function (e) { return _this.setState({ oked: e.target.value }); } })),
|
||||
React.createElement("div", { className: ContractorsList_module_scss_1.default.formGroup },
|
||||
React.createElement("label", { className: ContractorsList_module_scss_1.default.formLabel }, "\u0421\u0442\u0440\u0430\u043D\u0430"),
|
||||
React.createElement("input", { type: "text", className: ContractorsList_module_scss_1.default.formInput, value: country, onChange: function (e) { return _this.setState({ country: e.target.value }); } })),
|
||||
React.createElement("div", { className: ContractorsList_module_scss_1.default.formGroup },
|
||||
React.createElement("label", { className: ContractorsList_module_scss_1.default.formLabel }, "\u0422\u0435\u043B\u0435\u0444\u043E\u043D"),
|
||||
React.createElement("input", { type: "text", className: ContractorsList_module_scss_1.default.formInput, value: phone, onChange: function (e) { return _this.setState({ phone: e.target.value }); } })),
|
||||
React.createElement("div", { className: "".concat(ContractorsList_module_scss_1.default.formGroup, " ").concat(ContractorsList_module_scss_1.default.formGroupFull) },
|
||||
React.createElement("label", { className: ContractorsList_module_scss_1.default.formLabel }, "\u0410\u0434\u0440\u0435\u0441"),
|
||||
React.createElement("input", { type: "text", className: ContractorsList_module_scss_1.default.formInput, value: address, onChange: function (e) { return _this.setState({ address: e.target.value }); } })),
|
||||
React.createElement("div", { className: "".concat(ContractorsList_module_scss_1.default.formGroup, " ").concat(ContractorsList_module_scss_1.default.formGroupFull) },
|
||||
React.createElement("label", { className: ContractorsList_module_scss_1.default.formLabel }, "\u0424\u0430\u043A\u0442\u0438\u0447\u0435\u0441\u043A\u0438\u0439 \u0430\u0434\u0440\u0435\u0441"),
|
||||
React.createElement("input", { type: "text", className: ContractorsList_module_scss_1.default.formInput, value: actualAddress, onChange: function (e) { return _this.setState({ actualAddress: e.target.value }); } })))),
|
||||
React.createElement("div", { className: ContractorsList_module_scss_1.default.modalFooter },
|
||||
React.createElement("button", { className: ContractorsList_module_scss_1.default.btnSecondary, onClick: onClose, disabled: saving }, "\u041E\u0442\u043C\u0435\u043D\u0430"),
|
||||
React.createElement("button", { className: ContractorsList_module_scss_1.default.btnPrimary, onClick: this._handleSave, disabled: saving }, saving ? "Сохранение..." : isEdit ? "Сохранить" : "Создать")))));
|
||||
};
|
||||
return ContractorModal;
|
||||
}(React.Component));
|
||||
exports.default = ContractorModal;
|
||||
//# sourceMappingURL=ContractorModal.js.map
|
||||
@@ -0,0 +1,240 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
var tslib_1 = require("tslib");
|
||||
var React = tslib_1.__importStar(require("react"));
|
||||
var ContractorsList_module_scss_1 = tslib_1.__importDefault(require("./ContractorsList.module.scss"));
|
||||
var sp_http_1 = require("@microsoft/sp-http");
|
||||
var ContractorModal_1 = tslib_1.__importDefault(require("./ContractorModal"));
|
||||
var DeleteModal_1 = tslib_1.__importDefault(require("./DeleteModal"));
|
||||
var SearchIcon = function () { return (React.createElement("svg", { width: "16", height: "16", viewBox: "0 0 20 20", fill: "currentColor" },
|
||||
React.createElement("path", { d: "M12.9 14.32a8 8 0 1 1 1.41-1.41l5.35 5.33-1.42 1.42-5.33-5.34zM8 14A6 6 0 1 0 8 2a6 6 0 0 0 0 12z" }))); };
|
||||
var PlusIcon = function () { return (React.createElement("svg", { width: "14", height: "14", viewBox: "0 0 20 20", fill: "currentColor" },
|
||||
React.createElement("path", { d: "M10 3a1 1 0 0 1 1 1v5h5a1 1 0 1 1 0 2h-5v5a1 1 0 1 1-2 0v-5H4a1 1 0 1 1 0-2h5V4a1 1 0 0 1 1-1z" }))); };
|
||||
var ContractorIcon = function () { return (React.createElement("svg", { width: "48", height: "48", viewBox: "0 0 48 48", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round" },
|
||||
React.createElement("rect", { x: "8", y: "18", width: "32", height: "24", rx: "1" }),
|
||||
React.createElement("path", { d: "M4 20L24 6L44 20" }),
|
||||
React.createElement("rect", { x: "19", y: "30", width: "10", height: "12", rx: "1" }),
|
||||
React.createElement("rect", { x: "12", y: "24", width: "6", height: "6", rx: "1" }),
|
||||
React.createElement("rect", { x: "30", y: "24", width: "6", height: "6", rx: "1" }))); };
|
||||
var EditIcon = function () { return (React.createElement("svg", { width: "13", height: "13", viewBox: "0 0 20 20", fill: "currentColor" },
|
||||
React.createElement("path", { d: "M17.04 2.96a3.28 3.28 0 0 0-4.64 0L3.29 12.07a1 1 0 0 0-.27.51l-1 5a1 1 0 0 0 1.18 1.18l5-1a1 1 0 0 0 .51-.27l9.11-9.11a3.28 3.28 0 0 0 0-4.64zm-3.23 1.41a1.28 1.28 0 0 1 1.82 1.82l-1.28 1.28-1.82-1.82 1.28-1.28zM4.9 13.17l7.18-7.18 1.82 1.82-7.18 7.18-3.18.64.64-3.18z" }))); };
|
||||
var TrashIcon = function () { return (React.createElement("svg", { width: "13", height: "13", viewBox: "0 0 20 20", fill: "currentColor" },
|
||||
React.createElement("path", { d: "M8.5 3a1 1 0 0 0-1 1H5a1 1 0 0 0 0 2h10a1 1 0 1 0 0-2h-2.5a1 1 0 0 0-1-1h-3zM5 7v9a2 2 0 0 0 2 2h6a2 2 0 0 0 2-2V7H5zm3 2a.75.75 0 0 1 .75.75v5.5a.75.75 0 0 1-1.5 0v-5.5A.75.75 0 0 1 8 9zm4 0a.75.75 0 0 1 .75.75v5.5a.75.75 0 0 1-1.5 0v-5.5A.75.75 0 0 1 12 9z" }))); };
|
||||
var PAGE_SIZE = 20;
|
||||
var ContractorsList = /** @class */ (function (_super) {
|
||||
tslib_1.__extends(ContractorsList, _super);
|
||||
function ContractorsList(props) {
|
||||
var _this = _super.call(this, props) || this;
|
||||
_this._searchTimeout = null;
|
||||
_this._handleSearchInput = function (e) {
|
||||
var searchInput = e.target.value;
|
||||
_this.setState({ searchInput: searchInput, page: 1 });
|
||||
if (_this._searchTimeout)
|
||||
clearTimeout(_this._searchTimeout);
|
||||
_this._searchTimeout = setTimeout(function () {
|
||||
void _this._loadContractors(1, searchInput);
|
||||
}, 400);
|
||||
};
|
||||
_this._handlePageChange = function (newPage) {
|
||||
_this.setState({ page: newPage });
|
||||
void _this._loadContractors(newPage, _this.state.searchInput);
|
||||
};
|
||||
_this._handleModalSave = function (data) { return tslib_1.__awaiter(_this, void 0, void 0, function () {
|
||||
var _a, httpClient, apiBaseUrl, editingContractor, response, _b;
|
||||
return tslib_1.__generator(this, function (_c) {
|
||||
switch (_c.label) {
|
||||
case 0:
|
||||
_a = this.props, httpClient = _a.httpClient, apiBaseUrl = _a.apiBaseUrl;
|
||||
editingContractor = this.state.editingContractor;
|
||||
if (!editingContractor) return [3 /*break*/, 2];
|
||||
return [4 /*yield*/, httpClient.fetch("".concat(apiBaseUrl, "/Contractors/").concat(editingContractor.id), sp_http_1.AadHttpClient.configurations.v1, {
|
||||
method: "PUT",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify(data),
|
||||
})];
|
||||
case 1:
|
||||
_b = _c.sent();
|
||||
return [3 /*break*/, 4];
|
||||
case 2: return [4 /*yield*/, httpClient.fetch("".concat(apiBaseUrl, "/Contractors"), sp_http_1.AadHttpClient.configurations.v1, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify(data),
|
||||
})];
|
||||
case 3:
|
||||
_b = _c.sent();
|
||||
_c.label = 4;
|
||||
case 4:
|
||||
response = _b;
|
||||
if (!response.ok)
|
||||
throw new Error("\u041E\u0448\u0438\u0431\u043A\u0430 ".concat(response.status, ": ").concat(response.statusText));
|
||||
this.setState({ showModal: false, editingContractor: null, page: 1 });
|
||||
return [4 /*yield*/, this._loadContractors(1, this.state.searchInput)];
|
||||
case 5:
|
||||
_c.sent();
|
||||
return [2 /*return*/];
|
||||
}
|
||||
});
|
||||
}); };
|
||||
_this._handleDeleteConfirm = function () { return tslib_1.__awaiter(_this, void 0, void 0, function () {
|
||||
var _a, httpClient, apiBaseUrl, _b, deletingContractor, page, searchInput, response;
|
||||
return tslib_1.__generator(this, function (_c) {
|
||||
switch (_c.label) {
|
||||
case 0:
|
||||
_a = this.props, httpClient = _a.httpClient, apiBaseUrl = _a.apiBaseUrl;
|
||||
_b = this.state, deletingContractor = _b.deletingContractor, page = _b.page, searchInput = _b.searchInput;
|
||||
if (!deletingContractor)
|
||||
return [2 /*return*/];
|
||||
return [4 /*yield*/, httpClient.fetch("".concat(apiBaseUrl, "/Contractors/").concat(deletingContractor.id), sp_http_1.AadHttpClient.configurations.v1, { method: "DELETE" })];
|
||||
case 1:
|
||||
response = _c.sent();
|
||||
if (!response.ok)
|
||||
throw new Error("\u041E\u0448\u0438\u0431\u043A\u0430 ".concat(response.status, ": ").concat(response.statusText));
|
||||
this.setState({ deletingContractor: null });
|
||||
return [4 /*yield*/, this._loadContractors(page, searchInput)];
|
||||
case 2:
|
||||
_c.sent();
|
||||
return [2 /*return*/];
|
||||
}
|
||||
});
|
||||
}); };
|
||||
_this.state = {
|
||||
contractors: [],
|
||||
totalCount: 0,
|
||||
totalPages: 1,
|
||||
page: 1,
|
||||
loading: true,
|
||||
error: null,
|
||||
searchInput: "",
|
||||
showModal: false,
|
||||
editingContractor: null,
|
||||
deletingContractor: null,
|
||||
};
|
||||
return _this;
|
||||
}
|
||||
ContractorsList.prototype.componentDidMount = function () {
|
||||
void this._loadContractors(1, "");
|
||||
};
|
||||
ContractorsList.prototype._loadContractors = function (page, name) {
|
||||
return tslib_1.__awaiter(this, void 0, void 0, function () {
|
||||
var _a, httpClient, apiBaseUrl, nameParam, url, response, data, e_1;
|
||||
return tslib_1.__generator(this, function (_b) {
|
||||
switch (_b.label) {
|
||||
case 0:
|
||||
_a = this.props, httpClient = _a.httpClient, apiBaseUrl = _a.apiBaseUrl;
|
||||
this.setState({ loading: true, error: null });
|
||||
_b.label = 1;
|
||||
case 1:
|
||||
_b.trys.push([1, 4, , 5]);
|
||||
nameParam = name ? "&name=".concat(encodeURIComponent(name)) : "";
|
||||
url = "".concat(apiBaseUrl, "/Contractors?page=").concat(page, "&pageSize=").concat(PAGE_SIZE).concat(nameParam);
|
||||
return [4 /*yield*/, httpClient.get(url, sp_http_1.AadHttpClient.configurations.v1)];
|
||||
case 2:
|
||||
response = _b.sent();
|
||||
if (!response.ok)
|
||||
throw new Error("\u041E\u0448\u0438\u0431\u043A\u0430 ".concat(response.status, ": ").concat(response.statusText));
|
||||
return [4 /*yield*/, response.json()];
|
||||
case 3:
|
||||
data = _b.sent();
|
||||
this.setState({
|
||||
contractors: data.items,
|
||||
totalCount: data.totalCount,
|
||||
totalPages: data.totalPages,
|
||||
loading: false,
|
||||
});
|
||||
return [3 /*break*/, 5];
|
||||
case 4:
|
||||
e_1 = _b.sent();
|
||||
this.setState({ loading: false, error: e_1.message });
|
||||
return [3 /*break*/, 5];
|
||||
case 5: return [2 /*return*/];
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
ContractorsList.prototype._renderPageNumbers = function (currentPage, totalPages) {
|
||||
var _this = this;
|
||||
var btn = function (p) { return (React.createElement("button", { key: p, className: "".concat(ContractorsList_module_scss_1.default.pageBtn, " ").concat(p === currentPage ? ContractorsList_module_scss_1.default.pageBtnActive : ""), onClick: function () { return _this._handlePageChange(p); } }, p)); };
|
||||
if (totalPages <= 7) {
|
||||
return Array.from({ length: totalPages }, function (_, i) { return btn(i + 1); });
|
||||
}
|
||||
var result = [btn(1)];
|
||||
if (currentPage > 3)
|
||||
result.push(React.createElement("span", { key: "e1", className: ContractorsList_module_scss_1.default.pageEllipsis }, "\u2026"));
|
||||
var start = Math.max(2, currentPage - 1);
|
||||
var end = Math.min(totalPages - 1, currentPage + 1);
|
||||
for (var i = start; i <= end; i++)
|
||||
result.push(btn(i));
|
||||
if (currentPage < totalPages - 2)
|
||||
result.push(React.createElement("span", { key: "e2", className: ContractorsList_module_scss_1.default.pageEllipsis }, "\u2026"));
|
||||
result.push(btn(totalPages));
|
||||
return result;
|
||||
};
|
||||
ContractorsList.prototype.render = function () {
|
||||
var _this = this;
|
||||
var isDarkTheme = this.props.isDarkTheme;
|
||||
var _a = this.state, contractors = _a.contractors, totalCount = _a.totalCount, totalPages = _a.totalPages, page = _a.page, loading = _a.loading, error = _a.error, searchInput = _a.searchInput, showModal = _a.showModal, editingContractor = _a.editingContractor, deletingContractor = _a.deletingContractor;
|
||||
var currentPage = Math.min(page, totalPages);
|
||||
var from = totalCount === 0 ? 0 : (currentPage - 1) * PAGE_SIZE + 1;
|
||||
var to = Math.min(currentPage * PAGE_SIZE, totalCount);
|
||||
return (React.createElement("div", { className: "".concat(ContractorsList_module_scss_1.default.root, " ").concat(isDarkTheme ? ContractorsList_module_scss_1.default.dark : "") },
|
||||
React.createElement("div", { className: ContractorsList_module_scss_1.default.toolbar },
|
||||
React.createElement("h2", { className: ContractorsList_module_scss_1.default.pageTitle }, "\u041A\u043E\u043D\u0442\u0440\u0430\u0433\u0435\u043D\u0442\u044B"),
|
||||
React.createElement("div", { className: ContractorsList_module_scss_1.default.toolbarRight },
|
||||
React.createElement("div", { className: ContractorsList_module_scss_1.default.searchBox },
|
||||
React.createElement("span", { className: ContractorsList_module_scss_1.default.searchIcon },
|
||||
React.createElement(SearchIcon, null)),
|
||||
React.createElement("input", { type: "text", className: ContractorsList_module_scss_1.default.searchInput, placeholder: "\u041F\u043E\u0438\u0441\u043A \u043F\u043E \u043D\u0430\u0437\u0432\u0430\u043D\u0438\u044E...", value: searchInput, onChange: this._handleSearchInput })),
|
||||
React.createElement("button", { className: ContractorsList_module_scss_1.default.btnPrimary, onClick: function () { return _this.setState({ showModal: true, editingContractor: null }); } },
|
||||
React.createElement(PlusIcon, null),
|
||||
React.createElement("span", null, "\u0414\u043E\u0431\u0430\u0432\u0438\u0442\u044C \u043A\u043E\u043D\u0442\u0440\u0430\u0433\u0435\u043D\u0442\u0430")))),
|
||||
error && React.createElement("div", { className: ContractorsList_module_scss_1.default.errorMsg }, error),
|
||||
loading && (React.createElement("div", { className: ContractorsList_module_scss_1.default.loaderWrap },
|
||||
React.createElement("div", { className: ContractorsList_module_scss_1.default.spinner }),
|
||||
React.createElement("span", null, "\u0417\u0430\u0433\u0440\u0443\u0437\u043A\u0430..."))),
|
||||
!loading && !error && totalCount === 0 && (React.createElement("div", { className: ContractorsList_module_scss_1.default.emptyState },
|
||||
React.createElement("div", { className: ContractorsList_module_scss_1.default.emptyIcon },
|
||||
React.createElement(ContractorIcon, null)),
|
||||
React.createElement("p", { className: ContractorsList_module_scss_1.default.emptyTitle }, searchInput ? "Ничего не найдено" : "Контрагентов пока нет"),
|
||||
!searchInput && (React.createElement(React.Fragment, null,
|
||||
React.createElement("p", { className: ContractorsList_module_scss_1.default.emptyText }, "\u0414\u043E\u0431\u0430\u0432\u044C\u0442\u0435 \u043F\u0435\u0440\u0432\u043E\u0433\u043E \u043A\u043E\u043D\u0442\u0440\u0430\u0433\u0435\u043D\u0442\u0430, \u043D\u0430\u0436\u0430\u0432 \u043A\u043D\u043E\u043F\u043A\u0443 \u043D\u0438\u0436\u0435"),
|
||||
React.createElement("button", { className: ContractorsList_module_scss_1.default.btnPrimary, onClick: function () { return _this.setState({ showModal: true, editingContractor: null }); } },
|
||||
React.createElement(PlusIcon, null),
|
||||
React.createElement("span", null, "\u0414\u043E\u0431\u0430\u0432\u0438\u0442\u044C \u043A\u043E\u043D\u0442\u0440\u0430\u0433\u0435\u043D\u0442\u0430")))))),
|
||||
!loading && totalCount > 0 && (React.createElement("div", { className: ContractorsList_module_scss_1.default.tableWrapper },
|
||||
React.createElement("table", { className: ContractorsList_module_scss_1.default.table },
|
||||
React.createElement("thead", { className: ContractorsList_module_scss_1.default.tableHead },
|
||||
React.createElement("tr", null,
|
||||
React.createElement("th", null, "\u041D\u0430\u0437\u0432\u0430\u043D\u0438\u0435"),
|
||||
React.createElement("th", { className: ContractorsList_module_scss_1.default.binCol }, "\u0411\u0418\u041D"),
|
||||
React.createElement("th", { className: ContractorsList_module_scss_1.default.tableActionsCol }, "\u0414\u0435\u0439\u0441\u0442\u0432\u0438\u044F"))),
|
||||
React.createElement("tbody", null, contractors.map(function (c) { return (React.createElement("tr", { key: c.id, className: ContractorsList_module_scss_1.default.tableRow },
|
||||
React.createElement("td", null,
|
||||
React.createElement("div", { className: ContractorsList_module_scss_1.default.contractorName }, c.name),
|
||||
React.createElement("div", { className: ContractorsList_module_scss_1.default.contractorFullName }, c.fullName)),
|
||||
React.createElement("td", null, c.bin
|
||||
? React.createElement("span", { className: ContractorsList_module_scss_1.default.binValue }, c.bin)
|
||||
: React.createElement("span", { className: ContractorsList_module_scss_1.default.binEmpty }, "\u2014")),
|
||||
React.createElement("td", null,
|
||||
React.createElement("div", { className: ContractorsList_module_scss_1.default.tableActions },
|
||||
React.createElement("button", { className: ContractorsList_module_scss_1.default.btnEdit, title: "\u0420\u0435\u0434\u0430\u043A\u0442\u0438\u0440\u043E\u0432\u0430\u0442\u044C", onClick: function () { return _this.setState({ showModal: true, editingContractor: c }); } },
|
||||
React.createElement(EditIcon, null)),
|
||||
React.createElement("button", { className: ContractorsList_module_scss_1.default.btnDeleteCard, title: "\u0423\u0434\u0430\u043B\u0438\u0442\u044C", onClick: function () { return _this.setState({ deletingContractor: c }); } },
|
||||
React.createElement(TrashIcon, null)))))); }))))),
|
||||
!loading && totalCount > 0 && (React.createElement("div", { className: ContractorsList_module_scss_1.default.pagination },
|
||||
React.createElement("span", { className: ContractorsList_module_scss_1.default.paginationInfo },
|
||||
from,
|
||||
"\u2013",
|
||||
to,
|
||||
" \u0438\u0437 ",
|
||||
totalCount),
|
||||
totalPages > 1 && (React.createElement("div", { className: ContractorsList_module_scss_1.default.paginationControls },
|
||||
React.createElement("button", { className: ContractorsList_module_scss_1.default.pageBtn, disabled: currentPage === 1, onClick: function () { return _this._handlePageChange(currentPage - 1); } }, "\u2039"),
|
||||
this._renderPageNumbers(currentPage, totalPages),
|
||||
React.createElement("button", { className: ContractorsList_module_scss_1.default.pageBtn, disabled: currentPage === totalPages, onClick: function () { return _this._handlePageChange(currentPage + 1); } }, "\u203A"))))),
|
||||
showModal && (React.createElement(ContractorModal_1.default, { contractor: editingContractor, onSave: this._handleModalSave, onClose: function () { return _this.setState({ showModal: false, editingContractor: null }); } })),
|
||||
deletingContractor && (React.createElement(DeleteModal_1.default, { contractorName: deletingContractor.name, onConfirm: this._handleDeleteConfirm, onClose: function () { return _this.setState({ deletingContractor: null }); } }))));
|
||||
};
|
||||
return ContractorsList;
|
||||
}(React.Component));
|
||||
exports.default = ContractorsList;
|
||||
//# sourceMappingURL=ContractorsList.js.map
|
||||
@@ -0,0 +1,668 @@
|
||||
:root {
|
||||
--navy-950: #0A1B30;
|
||||
--navy-900: #0F2440;
|
||||
--navy-800: #1B3A5C;
|
||||
--navy-700: #244B73;
|
||||
--navy-600: #2D5C8A;
|
||||
--navy-100: #E8EEF4;
|
||||
--navy-50: #F4F7FA;
|
||||
--gold-600: #B8881A;
|
||||
--gold-500: #D4A01E;
|
||||
--gold-400: #E5B635;
|
||||
--gold-300: #F0CC5E;
|
||||
--gold-100: #FDF5E0;
|
||||
--gold-50: #FEFBF0;
|
||||
--gray-900: #111827;
|
||||
--gray-700: #374151;
|
||||
--gray-500: #6B7280;
|
||||
--gray-400: #9CA3AF;
|
||||
--gray-300: #D1D5DB;
|
||||
--gray-200: #E5E7EB;
|
||||
--gray-100: #F3F4F6;
|
||||
--gray-50: #F9FAFB;
|
||||
--red-500: #DC2626;
|
||||
--red-50: #FEF2F2;
|
||||
--green-600: #15803D;
|
||||
--green-50: #F0FDF4;
|
||||
--blue-600: #2563EB;
|
||||
--blue-50: #EFF6FF;
|
||||
--amber-700: #854D0E;
|
||||
--sh-1: 0 1px 2px rgba(15, 36, 64, 0.04), 0 1px 3px rgba(15, 36, 64, 0.06);
|
||||
--sh-2: 0 4px 16px rgba(15, 36, 64, 0.07);
|
||||
--sh-3: 0 12px 32px rgba(15, 36, 64, 0.1);
|
||||
--sh-gold: 0 4px 20px rgba(212, 160, 30, 0.18);
|
||||
--sh-modal: 0 24px 70px rgba(10, 27, 48, 0.25);
|
||||
--r-sm: 6px;
|
||||
--r-md: 10px;
|
||||
--r-lg: 14px;
|
||||
}
|
||||
|
||||
.root {
|
||||
font-family: "Inter", -apple-system, "Segoe UI", sans-serif;
|
||||
font-size: 14px;
|
||||
color: #111827;
|
||||
background: linear-gradient(180deg, #f6f8fa 0%, #eef2f6 100%);
|
||||
min-height: 200px;
|
||||
padding: 0 0 32px;
|
||||
}
|
||||
.root.dark {
|
||||
color: #f3f2f1;
|
||||
background: #1b1a19;
|
||||
}
|
||||
|
||||
.toolbar {
|
||||
background: linear-gradient(135deg, #0A1B30 0%, #1B3A5C 85%);
|
||||
border-radius: 14px;
|
||||
padding: 26px 30px;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
margin: 0 0 24px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.toolbar::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
height: 2px;
|
||||
background: linear-gradient(90deg, #D4A01E, transparent 60%);
|
||||
}
|
||||
|
||||
.pageTitle {
|
||||
font-family: "Lora", Georgia, serif;
|
||||
font-size: 24px;
|
||||
font-weight: 600;
|
||||
color: #fff;
|
||||
margin: 0;
|
||||
letter-spacing: 0.2px;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.toolbarRight {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
flex-wrap: wrap;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.searchBox {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
background: rgba(255, 255, 255, 0.08);
|
||||
border: 1px solid rgba(255, 255, 255, 0.22);
|
||||
border-radius: 6px;
|
||||
padding: 0 12px;
|
||||
height: 36px;
|
||||
min-width: 240px;
|
||||
transition: 0.18s;
|
||||
backdrop-filter: blur(4px);
|
||||
}
|
||||
.searchBox:focus-within {
|
||||
border-color: #E5B635;
|
||||
background: rgba(255, 255, 255, 0.14);
|
||||
}
|
||||
|
||||
.searchIcon {
|
||||
color: rgba(255, 255, 255, 0.4);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.searchInput {
|
||||
border: none;
|
||||
outline: none;
|
||||
background: transparent;
|
||||
font-size: 13px;
|
||||
color: #fff;
|
||||
width: 100%;
|
||||
font-family: inherit;
|
||||
}
|
||||
.searchInput::placeholder {
|
||||
color: rgba(255, 255, 255, 0.4);
|
||||
}
|
||||
|
||||
.errorMsg {
|
||||
margin: 0 20px 16px;
|
||||
padding: 12px 16px;
|
||||
background: #FEF2F2;
|
||||
border: 1px solid #fecaca;
|
||||
border-radius: 10px;
|
||||
color: #DC2626;
|
||||
font-size: 12.5px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.loaderWrap {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 10px;
|
||||
padding: 48px 24px;
|
||||
color: #6B7280;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.spinner {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
border: 2px solid #E5E7EB;
|
||||
border-top-color: #D4A01E;
|
||||
border-radius: 50%;
|
||||
animation: spin 0.7s linear infinite;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
.emptyState {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 64px 24px;
|
||||
text-align: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.emptyIcon {
|
||||
color: #D1D5DB;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.emptyTitle {
|
||||
font-family: "Lora", Georgia, serif;
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: #6B7280;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.emptyText {
|
||||
font-size: 13px;
|
||||
color: #9CA3AF;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.tableWrapper {
|
||||
padding: 0 20px 20px;
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
font-size: 13.5px;
|
||||
background: #fff;
|
||||
border-radius: 14px;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 1px 2px rgba(15, 36, 64, 0.04), 0 1px 3px rgba(15, 36, 64, 0.06);
|
||||
border: 1px solid #E5E7EB;
|
||||
}
|
||||
|
||||
.tableHead th {
|
||||
text-align: left;
|
||||
padding: 13px 18px;
|
||||
font-size: 10.5px;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 1.2px;
|
||||
color: #9CA3AF;
|
||||
font-weight: 600;
|
||||
background: #F9FAFB;
|
||||
border-bottom: 1px solid #E5E7EB;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.binCol {
|
||||
width: 160px;
|
||||
}
|
||||
|
||||
.tableRow {
|
||||
border-bottom: 1px solid #F3F4F6;
|
||||
transition: 0.12s;
|
||||
}
|
||||
.tableRow:last-child {
|
||||
border-bottom: 0;
|
||||
}
|
||||
.tableRow:hover {
|
||||
background: #FEFBF0;
|
||||
}
|
||||
.tableRow td {
|
||||
padding: 15px 18px;
|
||||
color: #374151;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.contractorName {
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
color: #0F2440;
|
||||
letter-spacing: 0.1px;
|
||||
}
|
||||
|
||||
.contractorFullName {
|
||||
font-size: 12px;
|
||||
color: #9CA3AF;
|
||||
margin-top: 2px;
|
||||
}
|
||||
|
||||
.binValue {
|
||||
font-variant-numeric: tabular-nums;
|
||||
color: #374151;
|
||||
}
|
||||
|
||||
.binEmpty {
|
||||
color: #9CA3AF;
|
||||
}
|
||||
|
||||
.pagination {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
padding: 12px 20px 0;
|
||||
}
|
||||
|
||||
.paginationInfo {
|
||||
font-size: 12px;
|
||||
color: #9CA3AF;
|
||||
letter-spacing: 0.3px;
|
||||
}
|
||||
|
||||
.paginationControls {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.pageBtn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-width: 30px;
|
||||
height: 30px;
|
||||
padding: 0 6px;
|
||||
background: #fff;
|
||||
color: #6B7280;
|
||||
border: 1px solid #D1D5DB;
|
||||
border-radius: 6px;
|
||||
font-size: 13px;
|
||||
font-family: inherit;
|
||||
cursor: pointer;
|
||||
transition: 0.15s;
|
||||
}
|
||||
.pageBtn:hover:not(:disabled) {
|
||||
border-color: #D4A01E;
|
||||
color: #B8881A;
|
||||
}
|
||||
.pageBtn:disabled {
|
||||
opacity: 0.35;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.pageBtnActive {
|
||||
background: #0F2440;
|
||||
color: #F0CC5E;
|
||||
border-color: #0F2440;
|
||||
font-weight: 600;
|
||||
}
|
||||
.pageBtnActive:hover {
|
||||
background: #1B3A5C;
|
||||
border-color: #1B3A5C;
|
||||
color: #F0CC5E;
|
||||
}
|
||||
|
||||
.pageEllipsis {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-width: 30px;
|
||||
height: 30px;
|
||||
color: #9CA3AF;
|
||||
font-size: 13px;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.btnPrimary {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 7px;
|
||||
padding: 9px 18px;
|
||||
background: #1B3A5C;
|
||||
color: #fff;
|
||||
border: 1px solid #1B3A5C;
|
||||
border-radius: 6px;
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
font-family: inherit;
|
||||
cursor: pointer;
|
||||
white-space: nowrap;
|
||||
letter-spacing: 0.15px;
|
||||
transition: 0.18s;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.btnPrimary:hover:not(:disabled) {
|
||||
background: #244B73;
|
||||
box-shadow: 0 4px 16px rgba(15, 36, 64, 0.07);
|
||||
}
|
||||
.btnPrimary:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.btnSecondary {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 7px;
|
||||
padding: 9px 18px;
|
||||
background: #fff;
|
||||
color: #1B3A5C;
|
||||
border: 1px solid #D1D5DB;
|
||||
border-radius: 6px;
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
font-family: inherit;
|
||||
cursor: pointer;
|
||||
transition: 0.18s;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.btnSecondary:hover:not(:disabled) {
|
||||
border-color: #D4A01E;
|
||||
color: #0F2440;
|
||||
}
|
||||
.btnSecondary:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.btnDanger {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 7px;
|
||||
padding: 9px 18px;
|
||||
background: #fff;
|
||||
color: #DC2626;
|
||||
border: 1px solid #D1D5DB;
|
||||
border-radius: 6px;
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
font-family: inherit;
|
||||
cursor: pointer;
|
||||
transition: 0.18s;
|
||||
}
|
||||
.btnDanger:hover:not(:disabled) {
|
||||
border-color: #DC2626;
|
||||
background: #FEF2F2;
|
||||
}
|
||||
.btnDanger:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.btnEdit {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 33px;
|
||||
height: 33px;
|
||||
padding: 0;
|
||||
background: transparent;
|
||||
border: 1px solid transparent;
|
||||
border-radius: 6px;
|
||||
color: #9CA3AF;
|
||||
cursor: pointer;
|
||||
transition: 0.18s;
|
||||
}
|
||||
.btnEdit:hover {
|
||||
color: #1B3A5C;
|
||||
background: #F4F7FA;
|
||||
border-color: #E5E7EB;
|
||||
}
|
||||
|
||||
.btnDeleteCard {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 33px;
|
||||
height: 33px;
|
||||
padding: 0;
|
||||
background: transparent;
|
||||
border: 1px solid transparent;
|
||||
border-radius: 6px;
|
||||
color: #9CA3AF;
|
||||
cursor: pointer;
|
||||
transition: 0.18s;
|
||||
}
|
||||
.btnDeleteCard:hover {
|
||||
color: #DC2626;
|
||||
background: #FEF2F2;
|
||||
border-color: #DC2626;
|
||||
}
|
||||
|
||||
.tableActionsCol {
|
||||
width: 100px;
|
||||
}
|
||||
|
||||
.tableActions {
|
||||
display: flex;
|
||||
gap: 4px;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.modalOverlay {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
background: rgba(10, 27, 48, 0.5);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 9999;
|
||||
padding: 16px;
|
||||
box-sizing: border-box;
|
||||
backdrop-filter: blur(6px);
|
||||
}
|
||||
|
||||
.modal {
|
||||
background: #fff;
|
||||
border-radius: 14px;
|
||||
box-shadow: 0 24px 70px rgba(10, 27, 48, 0.25);
|
||||
width: 100%;
|
||||
max-width: 640px;
|
||||
max-height: 90vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
border-top: 3px solid #D4A01E;
|
||||
}
|
||||
|
||||
.modalSm {
|
||||
max-width: 420px;
|
||||
}
|
||||
|
||||
.modalHeader {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 20px 24px 16px;
|
||||
border-bottom: 1px solid #E5E7EB;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.modalTitle {
|
||||
font-family: "Lora", Georgia, serif;
|
||||
font-size: 22px;
|
||||
font-weight: 600;
|
||||
color: #0F2440;
|
||||
margin: 0;
|
||||
letter-spacing: 0.2px;
|
||||
}
|
||||
|
||||
.modalTitleDanger {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
font-family: "Lora", Georgia, serif;
|
||||
font-size: 22px;
|
||||
font-weight: 600;
|
||||
color: #DC2626;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.warningIcon {
|
||||
color: #DC2626;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.modalCloseBtn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
background: transparent;
|
||||
border: 1px solid transparent;
|
||||
border-radius: 6px;
|
||||
color: #9CA3AF;
|
||||
cursor: pointer;
|
||||
flex-shrink: 0;
|
||||
transition: 0.18s;
|
||||
}
|
||||
.modalCloseBtn:hover:not(:disabled) {
|
||||
background: #F3F4F6;
|
||||
color: #374151;
|
||||
border-color: #E5E7EB;
|
||||
}
|
||||
.modalCloseBtn:disabled {
|
||||
opacity: 0.4;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.modalBody {
|
||||
padding: 24px;
|
||||
overflow-y: auto;
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.modalFooter {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 8px;
|
||||
padding: 16px 24px;
|
||||
border-top: 1px solid #E5E7EB;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.formGrid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 16px 20px;
|
||||
}
|
||||
@media (max-width: 480px) {
|
||||
.formGrid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
.formGroup {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.formGroupFull {
|
||||
grid-column: 1/-1;
|
||||
}
|
||||
|
||||
.formLabel {
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
color: #374151;
|
||||
letter-spacing: 0.2px;
|
||||
}
|
||||
|
||||
.required {
|
||||
color: #B8881A;
|
||||
}
|
||||
|
||||
.formInput {
|
||||
height: 38px;
|
||||
padding: 0 13px;
|
||||
border: 1px solid #D1D5DB;
|
||||
border-radius: 6px;
|
||||
font-size: 13.5px;
|
||||
font-family: inherit;
|
||||
color: #111827;
|
||||
background: #fff;
|
||||
transition: 0.18s;
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
}
|
||||
.formInput:focus {
|
||||
outline: 0;
|
||||
border-color: #D4A01E;
|
||||
box-shadow: 0 0 0 3px rgba(212, 160, 30, 0.12);
|
||||
}
|
||||
.formInput:disabled {
|
||||
background: #F9FAFB;
|
||||
color: #9CA3AF;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
.formInput::placeholder {
|
||||
color: #9CA3AF;
|
||||
}
|
||||
|
||||
.formTextarea {
|
||||
padding: 11px 13px;
|
||||
border: 1px solid #D1D5DB;
|
||||
border-radius: 6px;
|
||||
font-size: 13.5px;
|
||||
font-family: inherit;
|
||||
color: #111827;
|
||||
background: #fff;
|
||||
resize: vertical;
|
||||
transition: 0.18s;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
line-height: 1.5;
|
||||
min-height: 60px;
|
||||
}
|
||||
.formTextarea:focus {
|
||||
outline: 0;
|
||||
border-color: #D4A01E;
|
||||
box-shadow: 0 0 0 3px rgba(212, 160, 30, 0.12);
|
||||
}
|
||||
.formTextarea::placeholder {
|
||||
color: #9CA3AF;
|
||||
}
|
||||
|
||||
.deleteText {
|
||||
font-size: 14px;
|
||||
color: #374151;
|
||||
margin: 0;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.deleteHint {
|
||||
font-size: 12px;
|
||||
color: #9CA3AF;
|
||||
margin: 8px 0 0;
|
||||
line-height: 1.6;
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
var tslib_1 = require("tslib");
|
||||
var React = tslib_1.__importStar(require("react"));
|
||||
var ContractorsList_module_scss_1 = tslib_1.__importDefault(require("./ContractorsList.module.scss"));
|
||||
var WarningIcon = function () { return (React.createElement("svg", { width: "24", height: "24", viewBox: "0 0 20 20", fill: "currentColor" },
|
||||
React.createElement("path", { fillRule: "evenodd", d: "M8.485 2.495c.673-1.167 2.357-1.167 3.03 0l6.28 10.875c.673 1.167-.17 2.625-1.516 2.625H3.72c-1.347 0-2.189-1.458-1.515-2.625L8.485 2.495zM10 6a.75.75 0 0 1 .75.75v3.5a.75.75 0 0 1-1.5 0v-3.5A.75.75 0 0 1 10 6zm0 9a1 1 0 1 0 0-2 1 1 0 0 0 0 2z", clipRule: "evenodd" }))); };
|
||||
var DeleteModal = /** @class */ (function (_super) {
|
||||
tslib_1.__extends(DeleteModal, _super);
|
||||
function DeleteModal(props) {
|
||||
var _this = _super.call(this, props) || this;
|
||||
_this._handleKeyDown = function (e) {
|
||||
if (e.key === "Escape" && !_this.state.deleting)
|
||||
_this.props.onClose();
|
||||
if (e.key === "Enter" && !_this.state.deleting)
|
||||
void _this._handleConfirm();
|
||||
};
|
||||
_this._handleConfirm = function () { return tslib_1.__awaiter(_this, void 0, void 0, function () {
|
||||
var e_1;
|
||||
return tslib_1.__generator(this, function (_a) {
|
||||
switch (_a.label) {
|
||||
case 0:
|
||||
this.setState({ deleting: true, error: null });
|
||||
_a.label = 1;
|
||||
case 1:
|
||||
_a.trys.push([1, 3, , 4]);
|
||||
return [4 /*yield*/, this.props.onConfirm()];
|
||||
case 2:
|
||||
_a.sent();
|
||||
return [3 /*break*/, 4];
|
||||
case 3:
|
||||
e_1 = _a.sent();
|
||||
this.setState({ deleting: false, error: e_1.message });
|
||||
return [3 /*break*/, 4];
|
||||
case 4: return [2 /*return*/];
|
||||
}
|
||||
});
|
||||
}); };
|
||||
_this.state = { deleting: false, error: null };
|
||||
return _this;
|
||||
}
|
||||
DeleteModal.prototype.componentDidMount = function () {
|
||||
document.addEventListener("keydown", this._handleKeyDown);
|
||||
};
|
||||
DeleteModal.prototype.componentWillUnmount = function () {
|
||||
document.removeEventListener("keydown", this._handleKeyDown);
|
||||
};
|
||||
DeleteModal.prototype.render = function () {
|
||||
var _a = this.props, contractorName = _a.contractorName, onClose = _a.onClose;
|
||||
var _b = this.state, deleting = _b.deleting, error = _b.error;
|
||||
return (React.createElement("div", { className: ContractorsList_module_scss_1.default.modalOverlay, onClick: !deleting ? onClose : undefined },
|
||||
React.createElement("div", { className: "".concat(ContractorsList_module_scss_1.default.modal, " ").concat(ContractorsList_module_scss_1.default.modalSm), onClick: function (e) { return e.stopPropagation(); }, role: "dialog", "aria-modal": "true" },
|
||||
React.createElement("div", { className: ContractorsList_module_scss_1.default.modalHeader },
|
||||
React.createElement("div", { className: ContractorsList_module_scss_1.default.modalTitleDanger },
|
||||
React.createElement("span", { className: ContractorsList_module_scss_1.default.warningIcon },
|
||||
React.createElement(WarningIcon, null)),
|
||||
"\u0423\u0434\u0430\u043B\u0438\u0442\u044C \u043A\u043E\u043D\u0442\u0440\u0430\u0433\u0435\u043D\u0442\u0430")),
|
||||
React.createElement("div", { className: ContractorsList_module_scss_1.default.modalBody },
|
||||
error && React.createElement("div", { className: ContractorsList_module_scss_1.default.errorMsg }, error),
|
||||
React.createElement("p", { className: ContractorsList_module_scss_1.default.deleteText },
|
||||
"\u0412\u044B \u0443\u0432\u0435\u0440\u0435\u043D\u044B, \u0447\u0442\u043E \u0445\u043E\u0442\u0438\u0442\u0435 \u0443\u0434\u0430\u043B\u0438\u0442\u044C \u043A\u043E\u043D\u0442\u0440\u0430\u0433\u0435\u043D\u0442\u0430",
|
||||
' ',
|
||||
React.createElement("strong", null,
|
||||
"\u00AB",
|
||||
contractorName,
|
||||
"\u00BB"),
|
||||
"?"),
|
||||
React.createElement("p", { className: ContractorsList_module_scss_1.default.deleteHint }, "\u041A\u043E\u043D\u0442\u0440\u0430\u0433\u0435\u043D\u0442 \u0431\u0443\u0434\u0435\u0442 \u0443\u0434\u0430\u043B\u0451\u043D \u0438\u0437 \u0441\u043F\u0440\u0430\u0432\u043E\u0447\u043D\u0438\u043A\u0430.")),
|
||||
React.createElement("div", { className: ContractorsList_module_scss_1.default.modalFooter },
|
||||
React.createElement("button", { className: ContractorsList_module_scss_1.default.btnSecondary, onClick: onClose, disabled: deleting }, "\u041E\u0442\u043C\u0435\u043D\u0430"),
|
||||
React.createElement("button", { className: ContractorsList_module_scss_1.default.btnDanger, onClick: this._handleConfirm, disabled: deleting }, deleting ? "Удаление..." : "Удалить")))));
|
||||
};
|
||||
return DeleteModal;
|
||||
}(React.Component));
|
||||
exports.default = DeleteModal;
|
||||
//# sourceMappingURL=DeleteModal.js.map
|
||||
@@ -0,0 +1,3 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
//# sourceMappingURL=IContractorsListProps.js.map
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"IContractorsListProps.js","sourceRoot":"","sources":["../../../../src/webparts/contractorsList/components/IContractorsListProps.ts"],"names":[],"mappings":"","sourcesContent":["import { AadHttpClient } from '@microsoft/sp-http';\n\nexport interface IContractorsListProps {\n isDarkTheme: boolean;\n httpClient: AadHttpClient;\n apiBaseUrl: string;\n}\n"]}
|
||||
@@ -0,0 +1,38 @@
|
||||
:root {
|
||||
--navy-950: #0A1B30;
|
||||
--navy-900: #0F2440;
|
||||
--navy-800: #1B3A5C;
|
||||
--navy-700: #244B73;
|
||||
--navy-600: #2D5C8A;
|
||||
--navy-100: #E8EEF4;
|
||||
--navy-50: #F4F7FA;
|
||||
--gold-600: #B8881A;
|
||||
--gold-500: #D4A01E;
|
||||
--gold-400: #E5B635;
|
||||
--gold-300: #F0CC5E;
|
||||
--gold-100: #FDF5E0;
|
||||
--gold-50: #FEFBF0;
|
||||
--gray-900: #111827;
|
||||
--gray-700: #374151;
|
||||
--gray-500: #6B7280;
|
||||
--gray-400: #9CA3AF;
|
||||
--gray-300: #D1D5DB;
|
||||
--gray-200: #E5E7EB;
|
||||
--gray-100: #F3F4F6;
|
||||
--gray-50: #F9FAFB;
|
||||
--red-500: #DC2626;
|
||||
--red-50: #FEF2F2;
|
||||
--green-600: #15803D;
|
||||
--green-50: #F0FDF4;
|
||||
--blue-600: #2563EB;
|
||||
--blue-50: #EFF6FF;
|
||||
--amber-700: #854D0E;
|
||||
--sh-1: 0 1px 2px rgba(15, 36, 64, 0.04), 0 1px 3px rgba(15, 36, 64, 0.06);
|
||||
--sh-2: 0 4px 16px rgba(15, 36, 64, 0.07);
|
||||
--sh-3: 0 12px 32px rgba(15, 36, 64, 0.1);
|
||||
--sh-gold: 0 4px 20px rgba(212, 160, 30, 0.18);
|
||||
--sh-modal: 0 24px 70px rgba(10, 27, 48, 0.25);
|
||||
--r-sm: 6px;
|
||||
--r-md: 10px;
|
||||
--r-lg: 14px;
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
define([], function() {
|
||||
return {
|
||||
"PropertyPaneDescription": "Description",
|
||||
"BasicGroupName": "Group Name",
|
||||
"DescriptionFieldLabel": "Description Field",
|
||||
"AppLocalEnvironmentSharePoint": "The app is running on your local environment as SharePoint web part",
|
||||
"AppLocalEnvironmentTeams": "The app is running on your local environment as Microsoft Teams app",
|
||||
"AppLocalEnvironmentOffice": "The app is running on your local environment in office.com",
|
||||
"AppLocalEnvironmentOutlook": "The app is running on your local environment in Outlook",
|
||||
"AppSharePointEnvironment": "The app is running on SharePoint page",
|
||||
"AppTeamsTabEnvironment": "The app is running in Microsoft Teams",
|
||||
"AppOfficeEnvironment": "The app is running in office.com",
|
||||
"AppOutlookEnvironment": "The app is running in Outlook",
|
||||
"UnknownEnvironment": "The app is running in an unknown environment"
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,3 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
//# sourceMappingURL=IContractor.js.map
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"IContractor.js","sourceRoot":"","sources":["../../../../src/webparts/contractorsList/models/IContractor.ts"],"names":[],"mappings":"","sourcesContent":["export interface IContractor {\n id: string;\n fullName: string;\n name: string;\n contractorType: string | null;\n bin: string | null;\n kbe: string | null;\n oked: string | null;\n country: string | null;\n address: string | null;\n actualAddress: string | null;\n phone: string | null;\n createdAt: string;\n updatedAt: string;\n}\n\nexport interface IContractorsResponse {\n items: IContractor[];\n totalCount: number;\n page: number;\n pageSize: number;\n totalPages: number;\n}\n"]}
|
||||
@@ -0,0 +1,50 @@
|
||||
{
|
||||
"name": "contractors-list",
|
||||
"version": "0.0.1",
|
||||
"private": true,
|
||||
"engines": {
|
||||
"node": ">=22.14.0 < 23.0.0"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "heft test --clean --production && heft package-solution --production",
|
||||
"start": "heft start --clean",
|
||||
"clean": "heft clean",
|
||||
"eject-webpack": "heft eject-webpack"
|
||||
},
|
||||
"dependencies": {
|
||||
"@fluentui/react": "^8.106.4",
|
||||
"@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",
|
||||
"react": "17.0.1",
|
||||
"react-dom": "17.0.1",
|
||||
"tslib": "2.3.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@microsoft/eslint-config-spfx": "1.22.2",
|
||||
"@microsoft/eslint-plugin-spfx": "1.22.2",
|
||||
"@microsoft/sp-module-interfaces": "1.22.2",
|
||||
"@microsoft/spfx-heft-plugins": "1.22.2",
|
||||
"@microsoft/spfx-web-build-rig": "1.22.2",
|
||||
"@rushstack/eslint-config": "4.5.2",
|
||||
"@rushstack/heft": "1.1.2",
|
||||
"@types/heft-jest": "1.0.2",
|
||||
"@types/react": "17.0.45",
|
||||
"@types/react-dom": "17.0.17",
|
||||
"@types/webpack-env": "~1.15.2",
|
||||
"@typescript-eslint/parser": "8.46.2",
|
||||
"css-loader": "~7.1.2",
|
||||
"eslint": "8.57.1",
|
||||
"eslint-plugin-react-hooks": "4.3.0",
|
||||
"typescript": "~5.8.0"
|
||||
},
|
||||
"overrides": {
|
||||
"@rushstack/heft": "1.1.2"
|
||||
},
|
||||
"resolutions": {
|
||||
"@types/react": "17.0.45"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,543 @@
|
||||
{
|
||||
"version": "2.1.0",
|
||||
"$schema": "http://json.schemastore.org/sarif-2.1.0-rtm.5",
|
||||
"runs": [
|
||||
{
|
||||
"tool": {
|
||||
"driver": {
|
||||
"name": "ESLint",
|
||||
"informationUri": "https://eslint.org",
|
||||
"version": "8.57.1",
|
||||
"rules": [
|
||||
{
|
||||
"id": "@rushstack/no-new-null",
|
||||
"helpUri": "https://www.npmjs.com/package/@rushstack/eslint-plugin",
|
||||
"properties": {},
|
||||
"shortDescription": {
|
||||
"text": "Prevent usage of JavaScript's \"null\" keyword in new type declarations. To avoid hampering usage of preexisting APIs that require \"null\", the rule ignores declarations that are local variables, private members, or types that are not exported."
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "no-void",
|
||||
"helpUri": "https://eslint.org/docs/latest/rules/no-void",
|
||||
"properties": {},
|
||||
"shortDescription": {
|
||||
"text": "Disallow `void` operators"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"artifacts": [
|
||||
{
|
||||
"location": {
|
||||
"uri": "src/webparts/contractorsList/models/IContractor.ts"
|
||||
}
|
||||
},
|
||||
{
|
||||
"location": {
|
||||
"uri": "src/webparts/contractorsList/components/ContractorModal.tsx"
|
||||
}
|
||||
},
|
||||
{
|
||||
"location": {
|
||||
"uri": "src/webparts/contractorsList/components/DeleteModal.tsx"
|
||||
}
|
||||
},
|
||||
{
|
||||
"location": {
|
||||
"uri": "src/webparts/contractorsList/components/ContractorsList.tsx"
|
||||
}
|
||||
}
|
||||
],
|
||||
"results": [
|
||||
{
|
||||
"level": "warning",
|
||||
"message": {
|
||||
"text": "Usage of \"null\" is deprecated except when describing legacy APIs; use \"undefined\" instead"
|
||||
},
|
||||
"locations": [
|
||||
{
|
||||
"physicalLocation": {
|
||||
"artifactLocation": {
|
||||
"uri": "src/webparts/contractorsList/models/IContractor.ts",
|
||||
"index": 0
|
||||
},
|
||||
"region": {
|
||||
"startLine": 5,
|
||||
"startColumn": 28,
|
||||
"endLine": 5,
|
||||
"endColumn": 32,
|
||||
"snippet": {
|
||||
"text": "null"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"ruleId": "@rushstack/no-new-null",
|
||||
"ruleIndex": 0
|
||||
},
|
||||
{
|
||||
"level": "warning",
|
||||
"message": {
|
||||
"text": "Usage of \"null\" is deprecated except when describing legacy APIs; use \"undefined\" instead"
|
||||
},
|
||||
"locations": [
|
||||
{
|
||||
"physicalLocation": {
|
||||
"artifactLocation": {
|
||||
"uri": "src/webparts/contractorsList/models/IContractor.ts",
|
||||
"index": 0
|
||||
},
|
||||
"region": {
|
||||
"startLine": 6,
|
||||
"startColumn": 17,
|
||||
"endLine": 6,
|
||||
"endColumn": 21,
|
||||
"snippet": {
|
||||
"text": "null"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"ruleId": "@rushstack/no-new-null",
|
||||
"ruleIndex": 0
|
||||
},
|
||||
{
|
||||
"level": "warning",
|
||||
"message": {
|
||||
"text": "Usage of \"null\" is deprecated except when describing legacy APIs; use \"undefined\" instead"
|
||||
},
|
||||
"locations": [
|
||||
{
|
||||
"physicalLocation": {
|
||||
"artifactLocation": {
|
||||
"uri": "src/webparts/contractorsList/models/IContractor.ts",
|
||||
"index": 0
|
||||
},
|
||||
"region": {
|
||||
"startLine": 7,
|
||||
"startColumn": 17,
|
||||
"endLine": 7,
|
||||
"endColumn": 21,
|
||||
"snippet": {
|
||||
"text": "null"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"ruleId": "@rushstack/no-new-null",
|
||||
"ruleIndex": 0
|
||||
},
|
||||
{
|
||||
"level": "warning",
|
||||
"message": {
|
||||
"text": "Usage of \"null\" is deprecated except when describing legacy APIs; use \"undefined\" instead"
|
||||
},
|
||||
"locations": [
|
||||
{
|
||||
"physicalLocation": {
|
||||
"artifactLocation": {
|
||||
"uri": "src/webparts/contractorsList/models/IContractor.ts",
|
||||
"index": 0
|
||||
},
|
||||
"region": {
|
||||
"startLine": 8,
|
||||
"startColumn": 18,
|
||||
"endLine": 8,
|
||||
"endColumn": 22,
|
||||
"snippet": {
|
||||
"text": "null"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"ruleId": "@rushstack/no-new-null",
|
||||
"ruleIndex": 0
|
||||
},
|
||||
{
|
||||
"level": "warning",
|
||||
"message": {
|
||||
"text": "Usage of \"null\" is deprecated except when describing legacy APIs; use \"undefined\" instead"
|
||||
},
|
||||
"locations": [
|
||||
{
|
||||
"physicalLocation": {
|
||||
"artifactLocation": {
|
||||
"uri": "src/webparts/contractorsList/models/IContractor.ts",
|
||||
"index": 0
|
||||
},
|
||||
"region": {
|
||||
"startLine": 9,
|
||||
"startColumn": 21,
|
||||
"endLine": 9,
|
||||
"endColumn": 25,
|
||||
"snippet": {
|
||||
"text": "null"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"ruleId": "@rushstack/no-new-null",
|
||||
"ruleIndex": 0
|
||||
},
|
||||
{
|
||||
"level": "warning",
|
||||
"message": {
|
||||
"text": "Usage of \"null\" is deprecated except when describing legacy APIs; use \"undefined\" instead"
|
||||
},
|
||||
"locations": [
|
||||
{
|
||||
"physicalLocation": {
|
||||
"artifactLocation": {
|
||||
"uri": "src/webparts/contractorsList/models/IContractor.ts",
|
||||
"index": 0
|
||||
},
|
||||
"region": {
|
||||
"startLine": 10,
|
||||
"startColumn": 21,
|
||||
"endLine": 10,
|
||||
"endColumn": 25,
|
||||
"snippet": {
|
||||
"text": "null"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"ruleId": "@rushstack/no-new-null",
|
||||
"ruleIndex": 0
|
||||
},
|
||||
{
|
||||
"level": "warning",
|
||||
"message": {
|
||||
"text": "Usage of \"null\" is deprecated except when describing legacy APIs; use \"undefined\" instead"
|
||||
},
|
||||
"locations": [
|
||||
{
|
||||
"physicalLocation": {
|
||||
"artifactLocation": {
|
||||
"uri": "src/webparts/contractorsList/models/IContractor.ts",
|
||||
"index": 0
|
||||
},
|
||||
"region": {
|
||||
"startLine": 11,
|
||||
"startColumn": 27,
|
||||
"endLine": 11,
|
||||
"endColumn": 31,
|
||||
"snippet": {
|
||||
"text": "null"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"ruleId": "@rushstack/no-new-null",
|
||||
"ruleIndex": 0
|
||||
},
|
||||
{
|
||||
"level": "warning",
|
||||
"message": {
|
||||
"text": "Usage of \"null\" is deprecated except when describing legacy APIs; use \"undefined\" instead"
|
||||
},
|
||||
"locations": [
|
||||
{
|
||||
"physicalLocation": {
|
||||
"artifactLocation": {
|
||||
"uri": "src/webparts/contractorsList/models/IContractor.ts",
|
||||
"index": 0
|
||||
},
|
||||
"region": {
|
||||
"startLine": 12,
|
||||
"startColumn": 19,
|
||||
"endLine": 12,
|
||||
"endColumn": 23,
|
||||
"snippet": {
|
||||
"text": "null"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"ruleId": "@rushstack/no-new-null",
|
||||
"ruleIndex": 0
|
||||
},
|
||||
{
|
||||
"level": "warning",
|
||||
"message": {
|
||||
"text": "Usage of \"null\" is deprecated except when describing legacy APIs; use \"undefined\" instead"
|
||||
},
|
||||
"locations": [
|
||||
{
|
||||
"physicalLocation": {
|
||||
"artifactLocation": {
|
||||
"uri": "src/webparts/contractorsList/components/ContractorModal.tsx",
|
||||
"index": 1
|
||||
},
|
||||
"region": {
|
||||
"startLine": 8,
|
||||
"startColumn": 29,
|
||||
"endLine": 8,
|
||||
"endColumn": 33,
|
||||
"snippet": {
|
||||
"text": "null"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"ruleId": "@rushstack/no-new-null",
|
||||
"ruleIndex": 0
|
||||
},
|
||||
{
|
||||
"level": "warning",
|
||||
"message": {
|
||||
"text": "Usage of \"null\" is deprecated except when describing legacy APIs; use \"undefined\" instead"
|
||||
},
|
||||
"locations": [
|
||||
{
|
||||
"physicalLocation": {
|
||||
"artifactLocation": {
|
||||
"uri": "src/webparts/contractorsList/components/ContractorModal.tsx",
|
||||
"index": 1
|
||||
},
|
||||
"region": {
|
||||
"startLine": 25,
|
||||
"startColumn": 19,
|
||||
"endLine": 25,
|
||||
"endColumn": 23,
|
||||
"snippet": {
|
||||
"text": "null"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"ruleId": "@rushstack/no-new-null",
|
||||
"ruleIndex": 0
|
||||
},
|
||||
{
|
||||
"level": "warning",
|
||||
"message": {
|
||||
"text": "Usage of \"null\" is deprecated except when describing legacy APIs; use \"undefined\" instead"
|
||||
},
|
||||
"locations": [
|
||||
{
|
||||
"physicalLocation": {
|
||||
"artifactLocation": {
|
||||
"uri": "src/webparts/contractorsList/components/DeleteModal.tsx",
|
||||
"index": 2
|
||||
},
|
||||
"region": {
|
||||
"startLine": 12,
|
||||
"startColumn": 19,
|
||||
"endLine": 12,
|
||||
"endColumn": 23,
|
||||
"snippet": {
|
||||
"text": "null"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"ruleId": "@rushstack/no-new-null",
|
||||
"ruleIndex": 0
|
||||
},
|
||||
{
|
||||
"level": "warning",
|
||||
"message": {
|
||||
"text": "Expected 'undefined' and instead saw 'void'."
|
||||
},
|
||||
"locations": [
|
||||
{
|
||||
"physicalLocation": {
|
||||
"artifactLocation": {
|
||||
"uri": "src/webparts/contractorsList/components/DeleteModal.tsx",
|
||||
"index": 2
|
||||
},
|
||||
"region": {
|
||||
"startLine": 37,
|
||||
"startColumn": 52,
|
||||
"endLine": 37,
|
||||
"endColumn": 78,
|
||||
"snippet": {
|
||||
"text": "void this._handleConfirm()"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"ruleId": "no-void",
|
||||
"ruleIndex": 1
|
||||
},
|
||||
{
|
||||
"level": "warning",
|
||||
"message": {
|
||||
"text": "Usage of \"null\" is deprecated except when describing legacy APIs; use \"undefined\" instead"
|
||||
},
|
||||
"locations": [
|
||||
{
|
||||
"physicalLocation": {
|
||||
"artifactLocation": {
|
||||
"uri": "src/webparts/contractorsList/components/ContractorsList.tsx",
|
||||
"index": 3
|
||||
},
|
||||
"region": {
|
||||
"startLine": 49,
|
||||
"startColumn": 19,
|
||||
"endLine": 49,
|
||||
"endColumn": 23,
|
||||
"snippet": {
|
||||
"text": "null"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"ruleId": "@rushstack/no-new-null",
|
||||
"ruleIndex": 0
|
||||
},
|
||||
{
|
||||
"level": "warning",
|
||||
"message": {
|
||||
"text": "Usage of \"null\" is deprecated except when describing legacy APIs; use \"undefined\" instead"
|
||||
},
|
||||
"locations": [
|
||||
{
|
||||
"physicalLocation": {
|
||||
"artifactLocation": {
|
||||
"uri": "src/webparts/contractorsList/components/ContractorsList.tsx",
|
||||
"index": 3
|
||||
},
|
||||
"region": {
|
||||
"startLine": 52,
|
||||
"startColumn": 36,
|
||||
"endLine": 52,
|
||||
"endColumn": 40,
|
||||
"snippet": {
|
||||
"text": "null"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"ruleId": "@rushstack/no-new-null",
|
||||
"ruleIndex": 0
|
||||
},
|
||||
{
|
||||
"level": "warning",
|
||||
"message": {
|
||||
"text": "Usage of \"null\" is deprecated except when describing legacy APIs; use \"undefined\" instead"
|
||||
},
|
||||
"locations": [
|
||||
{
|
||||
"physicalLocation": {
|
||||
"artifactLocation": {
|
||||
"uri": "src/webparts/contractorsList/components/ContractorsList.tsx",
|
||||
"index": 3
|
||||
},
|
||||
"region": {
|
||||
"startLine": 53,
|
||||
"startColumn": 37,
|
||||
"endLine": 53,
|
||||
"endColumn": 41,
|
||||
"snippet": {
|
||||
"text": "null"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"ruleId": "@rushstack/no-new-null",
|
||||
"ruleIndex": 0
|
||||
},
|
||||
{
|
||||
"level": "warning",
|
||||
"message": {
|
||||
"text": "Expected 'undefined' and instead saw 'void'."
|
||||
},
|
||||
"locations": [
|
||||
{
|
||||
"physicalLocation": {
|
||||
"artifactLocation": {
|
||||
"uri": "src/webparts/contractorsList/components/ContractorsList.tsx",
|
||||
"index": 3
|
||||
},
|
||||
"region": {
|
||||
"startLine": 78,
|
||||
"startColumn": 5,
|
||||
"endLine": 78,
|
||||
"endColumn": 38,
|
||||
"snippet": {
|
||||
"text": "void this._loadContractors(1, \"\")"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"ruleId": "no-void",
|
||||
"ruleIndex": 1
|
||||
},
|
||||
{
|
||||
"level": "warning",
|
||||
"message": {
|
||||
"text": "Expected 'undefined' and instead saw 'void'."
|
||||
},
|
||||
"locations": [
|
||||
{
|
||||
"physicalLocation": {
|
||||
"artifactLocation": {
|
||||
"uri": "src/webparts/contractorsList/components/ContractorsList.tsx",
|
||||
"index": 3
|
||||
},
|
||||
"region": {
|
||||
"startLine": 106,
|
||||
"startColumn": 7,
|
||||
"endLine": 106,
|
||||
"endColumn": 49,
|
||||
"snippet": {
|
||||
"text": "void this._loadContractors(1, searchInput)"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"ruleId": "no-void",
|
||||
"ruleIndex": 1
|
||||
},
|
||||
{
|
||||
"level": "warning",
|
||||
"message": {
|
||||
"text": "Expected 'undefined' and instead saw 'void'."
|
||||
},
|
||||
"locations": [
|
||||
{
|
||||
"physicalLocation": {
|
||||
"artifactLocation": {
|
||||
"uri": "src/webparts/contractorsList/components/ContractorsList.tsx",
|
||||
"index": 3
|
||||
},
|
||||
"region": {
|
||||
"startLine": 112,
|
||||
"startColumn": 5,
|
||||
"endLine": 112,
|
||||
"endColumn": 64,
|
||||
"snippet": {
|
||||
"text": "void this._loadContractors(newPage, this.state.searchInput)"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"ruleId": "no-void",
|
||||
"ruleIndex": 1
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
/*! *****************************************************************************
|
||||
Copyright (c) Microsoft Corporation.
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
PERFORMANCE OF THIS SOFTWARE.
|
||||
***************************************************************************** */
|
||||
@@ -0,0 +1,237 @@
|
||||
{
|
||||
"bundles": {
|
||||
"contractors-list-web-part": {
|
||||
"dependencies": [
|
||||
{
|
||||
"componentId": "0d910c1c-13b9-4e1c-9aa4-b008c5e42d7d",
|
||||
"componentName": "react",
|
||||
"componentVersion": "17.0.1",
|
||||
"isDirectDependency": true
|
||||
},
|
||||
{
|
||||
"componentId": "aa0a46ec-1505-43cd-a44a-93f3a5aa460a",
|
||||
"componentName": "react-dom",
|
||||
"componentVersion": "17.0.1",
|
||||
"isDirectDependency": true
|
||||
},
|
||||
{
|
||||
"componentId": "7263c7d0-1d6a-45ec-8d85-d4d1d234171b",
|
||||
"componentName": "@microsoft/sp-core-library",
|
||||
"componentVersion": "1.22.1",
|
||||
"isDirectDependency": true
|
||||
},
|
||||
{
|
||||
"componentId": "01c4df03-e775-48cb-aa14-171ee5199a15",
|
||||
"componentName": "tslib",
|
||||
"componentVersion": "2.3.1",
|
||||
"isDirectDependency": false
|
||||
},
|
||||
{
|
||||
"componentId": "2e09fb9b-13bb-48f2-859f-97d6fff71176",
|
||||
"componentName": "@msinternal/odsp-core-bundle",
|
||||
"componentVersion": "1.4.517",
|
||||
"isDirectDependency": false
|
||||
},
|
||||
{
|
||||
"componentId": "f9e737b7-f0df-4597-ba8c-3060f82380db",
|
||||
"componentName": "@microsoft/sp-property-pane",
|
||||
"componentVersion": "1.22.1",
|
||||
"isDirectDependency": true
|
||||
},
|
||||
{
|
||||
"componentId": "78359e4b-07c2-43c6-8d0b-d060b4d577e8",
|
||||
"componentName": "@microsoft/sp-diagnostics",
|
||||
"componentVersion": "1.22.1",
|
||||
"isDirectDependency": false
|
||||
},
|
||||
{
|
||||
"componentId": "73e1dc6c-8441-42cc-ad47-4bd3659f8a3a",
|
||||
"componentName": "@microsoft/sp-lodash-subset",
|
||||
"componentVersion": "1.22.1",
|
||||
"isDirectDependency": false
|
||||
},
|
||||
{
|
||||
"componentId": "3173fefe-9995-49d1-a75d-a5caeaaba212",
|
||||
"componentName": "@msinternal/sp-pages-content-panel-context",
|
||||
"componentVersion": "0.1.0",
|
||||
"isDirectDependency": false
|
||||
},
|
||||
{
|
||||
"componentId": "229b8d08-79f3-438b-8c21-4613fc877abd",
|
||||
"componentName": "@microsoft/load-themed-styles",
|
||||
"componentVersion": "0.1.2",
|
||||
"isDirectDependency": false
|
||||
},
|
||||
{
|
||||
"componentId": "8494e7d7-6b99-47b2-a741-59873e42f16f",
|
||||
"componentName": "@msinternal/sp-component-utilities",
|
||||
"componentVersion": "13.12.19",
|
||||
"isDirectDependency": false
|
||||
},
|
||||
{
|
||||
"componentId": "63d75709-2785-4c91-97ad-489b171397d4",
|
||||
"componentName": "@msinternal/sp-fluentui-migration-reactdom-render-bundle",
|
||||
"componentVersion": "0.1.0",
|
||||
"isDirectDependency": false
|
||||
},
|
||||
{
|
||||
"componentId": "cc2cc925-b5be-41bb-880a-f0f8030c6aff",
|
||||
"componentName": "@msinternal/odsp-utilities-bundle",
|
||||
"componentVersion": "5.6.517",
|
||||
"isDirectDependency": false
|
||||
},
|
||||
{
|
||||
"componentId": "8217e442-8ed3-41fd-957d-b112e841286a",
|
||||
"componentName": "@msinternal/sp-telemetry",
|
||||
"componentVersion": "0.35.58",
|
||||
"isDirectDependency": false
|
||||
},
|
||||
{
|
||||
"componentId": "0ec74f52-38bc-4a51-ab82-7c91a2b399a2",
|
||||
"componentName": "@msinternal/sp-fluentui-v9-utilities-bundle",
|
||||
"componentVersion": "0.1.0",
|
||||
"isDirectDependency": false
|
||||
},
|
||||
{
|
||||
"componentId": "72ec0651-8a26-441c-87f2-77ec66884ca3",
|
||||
"componentName": "@msinternal/sp-fluentui-v9-react-button-bundle",
|
||||
"componentVersion": "0.1.0",
|
||||
"isDirectDependency": false
|
||||
},
|
||||
{
|
||||
"componentId": "f652a763-4d5f-4a08-aed3-d3377ef835ff",
|
||||
"componentName": "@msinternal/sp-fluentui-v9-react-menu-bundle",
|
||||
"componentVersion": "0.1.0",
|
||||
"isDirectDependency": false
|
||||
},
|
||||
{
|
||||
"componentId": "d3139516-bb4d-4141-9d35-a8aefe0272ec",
|
||||
"componentName": "@msinternal/sp-fluentui-v9-react-field-bundle",
|
||||
"componentVersion": "0.1.0",
|
||||
"isDirectDependency": false
|
||||
},
|
||||
{
|
||||
"componentId": "17f3a86d-33ce-4338-8594-8700ee90c494",
|
||||
"componentName": "@msinternal/sp-fluentui-v9-react-infolabel-bundle",
|
||||
"componentVersion": "0.1.0",
|
||||
"isDirectDependency": false
|
||||
},
|
||||
{
|
||||
"componentId": "33c5a798-8532-4741-888a-d0a7734036b5",
|
||||
"componentName": "@msinternal/sp-fluentui-v9-react-tooltip-bundle",
|
||||
"componentVersion": "0.1.0",
|
||||
"isDirectDependency": false
|
||||
},
|
||||
{
|
||||
"componentId": "29728f96-9823-40fa-a481-ffdf106880b1",
|
||||
"componentName": "@msinternal/sp-fluentui-v9-react-combobox-bundle",
|
||||
"componentVersion": "0.1.0",
|
||||
"isDirectDependency": false
|
||||
},
|
||||
{
|
||||
"componentId": "dbdce381-c024-42a0-ab20-4134aca49a27",
|
||||
"componentName": "@msinternal/sp-fluentui-v9-react-label-bundle",
|
||||
"componentVersion": "0.1.0",
|
||||
"isDirectDependency": false
|
||||
},
|
||||
{
|
||||
"componentId": "396bbc17-3d18-4f43-8299-d99720d88e22",
|
||||
"componentName": "@ms/office-ui-fabric-react-bundle",
|
||||
"componentVersion": "1.19.51",
|
||||
"isDirectDependency": false
|
||||
},
|
||||
{
|
||||
"componentId": "fab65e60-a04f-4772-874c-10691e4afa4b",
|
||||
"componentName": "@msinternal/sp-fluentui-migration-tooltip-bundle",
|
||||
"componentVersion": "0.1.0",
|
||||
"isDirectDependency": false
|
||||
},
|
||||
{
|
||||
"componentId": "0ad7e31a-8df9-4659-add8-a32c8e40040d",
|
||||
"componentName": "@msinternal/sp-fluentui-migration-button-bundle",
|
||||
"componentVersion": "0.1.0",
|
||||
"isDirectDependency": false
|
||||
},
|
||||
{
|
||||
"componentId": "bf211556-3cf0-412c-9618-a4f0e7069ebf",
|
||||
"componentName": "@msinternal/sp-heading-level",
|
||||
"componentVersion": "0.1.0",
|
||||
"isDirectDependency": false
|
||||
},
|
||||
{
|
||||
"componentId": "ffe5c101-373a-4bd2-bebb-13082852bab0",
|
||||
"componentName": "@msinternal/sp-fluentui-migration-label-bundle",
|
||||
"componentVersion": "0.1.0",
|
||||
"isDirectDependency": false
|
||||
},
|
||||
{
|
||||
"componentId": "1c6c9123-7aac-41f3-a376-3caea41ed83f",
|
||||
"componentName": "@microsoft/sp-loader",
|
||||
"componentVersion": "1.22.1",
|
||||
"isDirectDependency": false
|
||||
},
|
||||
{
|
||||
"componentId": "8496636c-2300-4915-abef-20de64c98d8b",
|
||||
"componentName": "@microsoft/sp-http-base",
|
||||
"componentVersion": "1.22.1",
|
||||
"isDirectDependency": false
|
||||
},
|
||||
{
|
||||
"componentId": "1c4541f7-5c31-41aa-9fa8-fbc9dc14c0a8",
|
||||
"componentName": "@microsoft/sp-page-context",
|
||||
"componentVersion": "1.22.1",
|
||||
"isDirectDependency": false
|
||||
},
|
||||
{
|
||||
"componentId": "e40f8203-b39d-425a-a957-714852e33b79",
|
||||
"componentName": "@microsoft/sp-dynamic-data",
|
||||
"componentVersion": "1.22.1",
|
||||
"isDirectDependency": false
|
||||
},
|
||||
{
|
||||
"componentId": "974a7777-0990-4136-8fa6-95d80114c2e0",
|
||||
"componentName": "@microsoft/sp-webpart-base",
|
||||
"componentVersion": "1.22.1",
|
||||
"isDirectDependency": true
|
||||
},
|
||||
{
|
||||
"componentId": "467dc675-7cc5-4709-8aac-78e3b71bd2f6",
|
||||
"componentName": "@microsoft/sp-component-base",
|
||||
"componentVersion": "1.22.1",
|
||||
"isDirectDependency": false
|
||||
},
|
||||
{
|
||||
"componentId": "c07208f0-ea3b-4c1a-9965-ac1b825211a6",
|
||||
"componentName": "@microsoft/sp-http",
|
||||
"componentVersion": "1.22.1",
|
||||
"isDirectDependency": false
|
||||
},
|
||||
{
|
||||
"componentId": "aec2bbfd-a968-4d5b-a279-12e570f378e6",
|
||||
"componentName": "@microsoft/sp-http-msgraph",
|
||||
"componentVersion": "1.22.1",
|
||||
"isDirectDependency": false
|
||||
},
|
||||
{
|
||||
"componentId": "6d3c733c-8cb7-4871-bb22-b5b37c1d518d",
|
||||
"componentName": "@msinternal/sp-safehtml",
|
||||
"componentVersion": "0.5.2",
|
||||
"isDirectDependency": false
|
||||
},
|
||||
{
|
||||
"componentId": "42647dee-1f75-4b9f-b521-5c844a6c35cc",
|
||||
"componentName": "@msinternal/sp-edit-customer-promise",
|
||||
"componentVersion": "0.1.0",
|
||||
"isDirectDependency": false
|
||||
},
|
||||
{
|
||||
"componentId": "c49ba483-7614-4c5c-b9dd-11b45a5fa280",
|
||||
"componentName": "@msinternal/sp-idle-web-task",
|
||||
"componentVersion": "0.1.2",
|
||||
"isDirectDependency": false
|
||||
}
|
||||
],
|
||||
"asyncChunks": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
{
|
||||
"id": "803b4696-0af0-45a3-b621-4c9fda238fcb",
|
||||
"alias": "ContractorsListWebPart",
|
||||
"componentType": "WebPart",
|
||||
"version": "0.0.1",
|
||||
"manifestVersion": 2,
|
||||
"requiresCustomScript": false,
|
||||
"supportedHosts": [
|
||||
"SharePointWebPart",
|
||||
"TeamsPersonalApp",
|
||||
"TeamsTab",
|
||||
"SharePointFullPage"
|
||||
],
|
||||
"supportsThemeVariants": true,
|
||||
"preconfiguredEntries": [
|
||||
{
|
||||
"groupId": "5c03119e-3074-46fd-976b-c60198311f70",
|
||||
"group": {
|
||||
"default": "Advanced"
|
||||
},
|
||||
"title": {
|
||||
"default": "contractors-list"
|
||||
},
|
||||
"description": {
|
||||
"default": "contractors-list description"
|
||||
},
|
||||
"officeFabricIconFontName": "Page",
|
||||
"properties": {
|
||||
"apiClientId": "9232d9e2-9807-4753-808f-07250e5474b0",
|
||||
"apiBaseUrl": "https://api.tnsu.kz"
|
||||
}
|
||||
}
|
||||
],
|
||||
"loaderConfig": {
|
||||
"internalModuleBaseUrls": [
|
||||
"<!-- PATH TO CDN -->"
|
||||
],
|
||||
"entryModuleId": "contractors-list-web-part",
|
||||
"scriptResources": {
|
||||
"contractors-list-web-part": {
|
||||
"type": "path",
|
||||
"path": "contractors-list-web-part_0ba42e3fa82928db811b.js"
|
||||
},
|
||||
"react": {
|
||||
"type": "component",
|
||||
"id": "0d910c1c-13b9-4e1c-9aa4-b008c5e42d7d",
|
||||
"version": "17.0.1"
|
||||
},
|
||||
"react-dom": {
|
||||
"type": "component",
|
||||
"id": "aa0a46ec-1505-43cd-a44a-93f3a5aa460a",
|
||||
"version": "17.0.1"
|
||||
},
|
||||
"@microsoft/sp-core-library": {
|
||||
"type": "component",
|
||||
"id": "7263c7d0-1d6a-45ec-8d85-d4d1d234171b",
|
||||
"version": "1.22.1"
|
||||
},
|
||||
"@microsoft/sp-property-pane": {
|
||||
"type": "component",
|
||||
"id": "f9e737b7-f0df-4597-ba8c-3060f82380db",
|
||||
"version": "1.22.1"
|
||||
},
|
||||
"@microsoft/sp-webpart-base": {
|
||||
"type": "component",
|
||||
"id": "974a7777-0990-4136-8fa6-95d80114c2e0",
|
||||
"version": "1.22.1"
|
||||
},
|
||||
"@microsoft/sp-http": {
|
||||
"type": "component",
|
||||
"id": "c07208f0-ea3b-4c1a-9965-ac1b825211a6",
|
||||
"version": "1.22.1"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/spfx/client-side-web-part-manifest.schema.json",
|
||||
"id": "803b4696-0af0-45a3-b621-4c9fda238fcb",
|
||||
"alias": "ContractorsListWebPart",
|
||||
"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,
|
||||
|
||||
"preconfiguredEntries": [{
|
||||
"groupId": "5c03119e-3074-46fd-976b-c60198311f70", // Advanced
|
||||
"group": { "default": "Advanced" },
|
||||
"title": { "default": "contractors-list" },
|
||||
"description": { "default": "contractors-list description" },
|
||||
"officeFabricIconFontName": "Page",
|
||||
"properties": {
|
||||
"apiClientId": "9232d9e2-9807-4753-808f-07250e5474b0",
|
||||
"apiBaseUrl": "https://api.tnsu.kz"
|
||||
}
|
||||
}]
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
import * as React from 'react';
|
||||
import * as ReactDom from 'react-dom';
|
||||
import { Version } from '@microsoft/sp-core-library';
|
||||
import {
|
||||
type IPropertyPaneConfiguration,
|
||||
PropertyPaneTextField
|
||||
} from '@microsoft/sp-property-pane';
|
||||
import { BaseClientSideWebPart } from '@microsoft/sp-webpart-base';
|
||||
import { IReadonlyTheme } from '@microsoft/sp-component-base';
|
||||
import { AadHttpClient } from '@microsoft/sp-http';
|
||||
|
||||
import ContractorsList from './components/ContractorsList';
|
||||
import { IContractorsListProps } from './components/IContractorsListProps';
|
||||
|
||||
export interface IContractorsListWebPartProps {
|
||||
apiClientId: string;
|
||||
apiBaseUrl: string;
|
||||
}
|
||||
|
||||
export default class ContractorsListWebPart extends BaseClientSideWebPart<IContractorsListWebPartProps> {
|
||||
private _isDarkTheme: boolean = false;
|
||||
private _httpClient!: AadHttpClient;
|
||||
|
||||
protected async onInit(): Promise<void> {
|
||||
await super.onInit();
|
||||
const clientId = this.properties.apiClientId || '9232d9e2-9807-4753-808f-07250e5474b0';
|
||||
this._httpClient = await this.context.aadHttpClientFactory.getClient(clientId);
|
||||
}
|
||||
|
||||
public render(): void {
|
||||
const element: React.ReactElement<IContractorsListProps> = React.createElement(
|
||||
ContractorsList,
|
||||
{
|
||||
isDarkTheme: this._isDarkTheme,
|
||||
httpClient: this._httpClient,
|
||||
apiBaseUrl: this.properties.apiBaseUrl || 'https://api.tnsu.kz',
|
||||
}
|
||||
);
|
||||
|
||||
ReactDom.render(element, this.domElement);
|
||||
}
|
||||
|
||||
protected onThemeChanged(currentTheme: IReadonlyTheme | undefined): void {
|
||||
if (!currentTheme) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._isDarkTheme = !!currentTheme.isInverted;
|
||||
const {
|
||||
semanticColors
|
||||
} = currentTheme;
|
||||
|
||||
if (semanticColors) {
|
||||
this.domElement.style.setProperty('--bodyText', semanticColors.bodyText || null);
|
||||
this.domElement.style.setProperty('--link', semanticColors.link || null);
|
||||
this.domElement.style.setProperty('--linkHovered', semanticColors.linkHovered || null);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
protected onDispose(): void {
|
||||
ReactDom.unmountComponentAtNode(this.domElement);
|
||||
}
|
||||
|
||||
protected get dataVersion(): Version {
|
||||
return Version.parse('1.0');
|
||||
}
|
||||
|
||||
protected getPropertyPaneConfiguration(): IPropertyPaneConfiguration {
|
||||
return {
|
||||
pages: [
|
||||
{
|
||||
header: {
|
||||
description: 'Настройки веб-части'
|
||||
},
|
||||
groups: [
|
||||
{
|
||||
groupName: 'API',
|
||||
groupFields: [
|
||||
PropertyPaneTextField('apiClientId', {
|
||||
label: 'API Client ID (Azure AD)',
|
||||
placeholder: '9232d9e2-9807-4753-808f-07250e5474b0'
|
||||
}),
|
||||
PropertyPaneTextField('apiBaseUrl', {
|
||||
label: 'Базовый URL API',
|
||||
placeholder: 'https://api.tnsu.kz'
|
||||
})
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
};
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 12 KiB |
@@ -0,0 +1,229 @@
|
||||
import * as React from "react";
|
||||
import styles from "./ContractorsList.module.scss";
|
||||
import { IContractor } from "../models/IContractor";
|
||||
|
||||
const CONTRACTOR_TYPES = ["Заказчик", "Поставщик"];
|
||||
|
||||
interface IContractorModalProps {
|
||||
contractor: IContractor | null;
|
||||
onSave: (data: Partial<IContractor>) => Promise<void>;
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
interface IContractorModalState {
|
||||
fullName: string;
|
||||
name: string;
|
||||
contractorType: string;
|
||||
bin: string;
|
||||
kbe: string;
|
||||
oked: string;
|
||||
country: string;
|
||||
address: string;
|
||||
actualAddress: string;
|
||||
phone: string;
|
||||
saving: boolean;
|
||||
error: string | null;
|
||||
}
|
||||
|
||||
const CloseIcon: React.FC = () => (
|
||||
<svg width="16" height="16" viewBox="0 0 20 20" fill="currentColor">
|
||||
<path d="M4.293 4.293a1 1 0 0 1 1.414 0L10 8.586l4.293-4.293a1 1 0 1 1 1.414 1.414L11.414 10l4.293 4.293a1 1 0 0 1-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 0 1-1.414-1.414L8.586 10 4.293 5.707a1 1 0 0 1 0-1.414z" />
|
||||
</svg>
|
||||
);
|
||||
|
||||
export default class ContractorModal extends React.Component<IContractorModalProps, IContractorModalState> {
|
||||
constructor(props: IContractorModalProps) {
|
||||
super(props);
|
||||
const { contractor } = props;
|
||||
this.state = {
|
||||
fullName: contractor?.fullName ?? "",
|
||||
name: contractor?.name ?? "",
|
||||
contractorType: contractor?.contractorType ?? "",
|
||||
bin: contractor?.bin ?? "",
|
||||
kbe: contractor?.kbe ?? "",
|
||||
oked: contractor?.oked ?? "",
|
||||
country: contractor?.country ?? "",
|
||||
address: contractor?.address ?? "",
|
||||
actualAddress: contractor?.actualAddress ?? "",
|
||||
phone: contractor?.phone ?? "",
|
||||
saving: false,
|
||||
error: null,
|
||||
};
|
||||
}
|
||||
|
||||
public componentDidMount(): void {
|
||||
document.addEventListener("keydown", this._handleKeyDown);
|
||||
}
|
||||
|
||||
public componentWillUnmount(): void {
|
||||
document.removeEventListener("keydown", this._handleKeyDown);
|
||||
}
|
||||
|
||||
private _handleKeyDown = (e: KeyboardEvent): void => {
|
||||
if (e.key === "Escape" && !this.state.saving) this.props.onClose();
|
||||
};
|
||||
|
||||
private _handleSave = async (): Promise<void> => {
|
||||
const { fullName, name, contractorType, bin, kbe, oked, country, address, actualAddress, phone } = this.state;
|
||||
if (!name.trim() || !fullName.trim()) {
|
||||
this.setState({ error: "Название и полное наименование обязательны для заполнения" });
|
||||
return;
|
||||
}
|
||||
this.setState({ saving: true, error: null });
|
||||
try {
|
||||
await this.props.onSave({
|
||||
fullName: fullName.trim(),
|
||||
name: name.trim(),
|
||||
contractorType: contractorType.trim() || null,
|
||||
bin: bin.trim() || null,
|
||||
kbe: kbe.trim() || null,
|
||||
oked: oked.trim() || null,
|
||||
country: country.trim() || null,
|
||||
address: address.trim() || null,
|
||||
actualAddress: actualAddress.trim() || null,
|
||||
phone: phone.trim() || null,
|
||||
});
|
||||
} catch (e) {
|
||||
this.setState({ saving: false, error: (e as Error).message });
|
||||
}
|
||||
};
|
||||
|
||||
public render(): React.ReactElement {
|
||||
const { contractor, onClose } = this.props;
|
||||
const { fullName, name, contractorType, bin, kbe, oked, country, address, actualAddress, phone, saving, error } = this.state;
|
||||
const isEdit = !!contractor;
|
||||
|
||||
return (
|
||||
<div className={styles.modalOverlay}>
|
||||
<div className={styles.modal} onClick={(e) => e.stopPropagation()} role="dialog" aria-modal="true">
|
||||
<div className={styles.modalHeader}>
|
||||
<h3 className={styles.modalTitle}>{isEdit ? "Редактировать контрагента" : "Новый контрагент"}</h3>
|
||||
<button className={styles.modalCloseBtn} onClick={onClose} disabled={saving} title="Закрыть">
|
||||
<CloseIcon />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className={styles.modalBody}>
|
||||
{error && <div className={styles.errorMsg}>{error}</div>}
|
||||
|
||||
<div className={styles.formGroup}>
|
||||
<label className={styles.formLabel}>
|
||||
Название <span className={styles.required}>*</span>
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
className={styles.formInput}
|
||||
value={name}
|
||||
onChange={(e) => this.setState({ name: e.target.value, error: null })}
|
||||
placeholder="Например: Arx Minerals (Аркс Минералс) ТОО"
|
||||
autoFocus
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className={styles.formGroup}>
|
||||
<label className={styles.formLabel}>
|
||||
Полное наименование <span className={styles.required}>*</span>
|
||||
</label>
|
||||
<textarea
|
||||
className={styles.formTextarea}
|
||||
value={fullName}
|
||||
onChange={(e) => this.setState({ fullName: e.target.value, error: null })}
|
||||
placeholder='Например: ТОО "Arx Minerals (Аркс Минералс)"'
|
||||
rows={2}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className={styles.formGrid}>
|
||||
<div className={styles.formGroup}>
|
||||
<label className={styles.formLabel}>БИН</label>
|
||||
<input
|
||||
type="text"
|
||||
className={styles.formInput}
|
||||
value={bin}
|
||||
onChange={(e) => this.setState({ bin: e.target.value.replace(/\D/g, "").slice(0, 12) })}
|
||||
placeholder="121140000288"
|
||||
inputMode="numeric"
|
||||
maxLength={12}
|
||||
/>
|
||||
</div>
|
||||
<div className={styles.formGroup}>
|
||||
<label className={styles.formLabel}>Тип контрагента</label>
|
||||
<select
|
||||
className={styles.formInput}
|
||||
value={contractorType}
|
||||
onChange={(e) => this.setState({ contractorType: e.target.value })}
|
||||
>
|
||||
<option value="">— Не выбрано —</option>
|
||||
{CONTRACTOR_TYPES.map((t) => <option key={t} value={t}>{t}</option>)}
|
||||
</select>
|
||||
</div>
|
||||
<div className={styles.formGroup}>
|
||||
<label className={styles.formLabel}>КБЕ</label>
|
||||
<input
|
||||
type="text"
|
||||
className={styles.formInput}
|
||||
value={kbe}
|
||||
onChange={(e) => this.setState({ kbe: e.target.value })}
|
||||
/>
|
||||
</div>
|
||||
<div className={styles.formGroup}>
|
||||
<label className={styles.formLabel}>ОКЭД</label>
|
||||
<input
|
||||
type="text"
|
||||
className={styles.formInput}
|
||||
value={oked}
|
||||
onChange={(e) => this.setState({ oked: e.target.value })}
|
||||
/>
|
||||
</div>
|
||||
<div className={styles.formGroup}>
|
||||
<label className={styles.formLabel}>Страна</label>
|
||||
<input
|
||||
type="text"
|
||||
className={styles.formInput}
|
||||
value={country}
|
||||
onChange={(e) => this.setState({ country: e.target.value })}
|
||||
/>
|
||||
</div>
|
||||
<div className={styles.formGroup}>
|
||||
<label className={styles.formLabel}>Телефон</label>
|
||||
<input
|
||||
type="text"
|
||||
className={styles.formInput}
|
||||
value={phone}
|
||||
onChange={(e) => this.setState({ phone: e.target.value })}
|
||||
/>
|
||||
</div>
|
||||
<div className={`${styles.formGroup} ${styles.formGroupFull}`}>
|
||||
<label className={styles.formLabel}>Адрес</label>
|
||||
<input
|
||||
type="text"
|
||||
className={styles.formInput}
|
||||
value={address}
|
||||
onChange={(e) => this.setState({ address: e.target.value })}
|
||||
/>
|
||||
</div>
|
||||
<div className={`${styles.formGroup} ${styles.formGroupFull}`}>
|
||||
<label className={styles.formLabel}>Фактический адрес</label>
|
||||
<input
|
||||
type="text"
|
||||
className={styles.formInput}
|
||||
value={actualAddress}
|
||||
onChange={(e) => this.setState({ actualAddress: e.target.value })}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className={styles.modalFooter}>
|
||||
<button className={styles.btnSecondary} onClick={onClose} disabled={saving}>
|
||||
Отмена
|
||||
</button>
|
||||
<button className={styles.btnPrimary} onClick={this._handleSave} disabled={saving}>
|
||||
{saving ? "Сохранение..." : isEdit ? "Сохранить" : "Создать"}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,640 @@
|
||||
@import "./design-tokens-v5";
|
||||
|
||||
// ── Root ──────────────────────────────────────────────────────────────────────
|
||||
.root {
|
||||
font-family: $font-sans;
|
||||
font-size: $fs-body;
|
||||
color: $gray-900;
|
||||
background: linear-gradient(180deg, #f6f8fa 0%, #eef2f6 100%);
|
||||
min-height: 200px;
|
||||
padding: 0 0 32px;
|
||||
|
||||
&.dark {
|
||||
color: #f3f2f1;
|
||||
background: #1b1a19;
|
||||
}
|
||||
}
|
||||
|
||||
// ── Toolbar / Page Header ─────────────────────────────────────────────────────
|
||||
.toolbar {
|
||||
@include page-header;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
margin: 0 0 24px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.pageTitle {
|
||||
font-family: $font-serif;
|
||||
font-size: $fs-page-title;
|
||||
font-weight: 600;
|
||||
color: #fff;
|
||||
margin: 0;
|
||||
letter-spacing: 0.2px;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.toolbarRight {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
flex-wrap: wrap;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
// ── Search ────────────────────────────────────────────────────────────────────
|
||||
.searchBox {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
background: rgba(255, 255, 255, 0.08);
|
||||
border: 1px solid rgba(255, 255, 255, 0.22);
|
||||
border-radius: $r-sm;
|
||||
padding: 0 12px;
|
||||
height: 36px;
|
||||
min-width: 240px;
|
||||
transition: 0.18s;
|
||||
backdrop-filter: blur(4px);
|
||||
|
||||
&:focus-within {
|
||||
border-color: $gold-400;
|
||||
background: rgba(255, 255, 255, 0.14);
|
||||
}
|
||||
}
|
||||
|
||||
.searchIcon {
|
||||
color: rgba(255, 255, 255, 0.4);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.searchInput {
|
||||
border: none;
|
||||
outline: none;
|
||||
background: transparent;
|
||||
font-size: $fs-sm;
|
||||
color: #fff;
|
||||
width: 100%;
|
||||
font-family: inherit;
|
||||
|
||||
&::placeholder {
|
||||
color: rgba(255, 255, 255, 0.4);
|
||||
}
|
||||
}
|
||||
|
||||
// ── Messages ──────────────────────────────────────────────────────────────────
|
||||
.errorMsg {
|
||||
margin: 0 20px 16px;
|
||||
padding: 12px 16px;
|
||||
background: $red-50;
|
||||
border: 1px solid #fecaca;
|
||||
border-radius: $r-md;
|
||||
color: $red-500;
|
||||
font-size: 12.5px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
// ── Loader ────────────────────────────────────────────────────────────────────
|
||||
.loaderWrap {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 10px;
|
||||
padding: 48px 24px;
|
||||
color: $gray-500;
|
||||
font-size: $fs-sm;
|
||||
}
|
||||
|
||||
.spinner {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
border: 2px solid $gray-200;
|
||||
border-top-color: $gold-500;
|
||||
border-radius: 50%;
|
||||
animation: spin 0.7s linear infinite;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
// ── Empty state ───────────────────────────────────────────────────────────────
|
||||
.emptyState {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 64px 24px;
|
||||
text-align: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.emptyIcon {
|
||||
color: $gray-300;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.emptyTitle {
|
||||
font-family: $font-serif;
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: $gray-500;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.emptyText {
|
||||
font-size: $fs-sm;
|
||||
color: $gray-400;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
// ── Table ─────────────────────────────────────────────────────────────────────
|
||||
.tableWrapper {
|
||||
padding: 0 20px 20px;
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
font-size: 13.5px;
|
||||
background: #fff;
|
||||
border-radius: $r-lg;
|
||||
overflow: hidden;
|
||||
box-shadow: $sh-1;
|
||||
border: 1px solid $gray-200;
|
||||
}
|
||||
|
||||
.tableHead {
|
||||
th {
|
||||
text-align: left;
|
||||
padding: 13px 18px;
|
||||
font-size: $fs-overline;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 1.2px;
|
||||
color: $gray-400;
|
||||
font-weight: 600;
|
||||
background: $gray-50;
|
||||
border-bottom: 1px solid $gray-200;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
|
||||
.binCol {
|
||||
width: 160px;
|
||||
}
|
||||
|
||||
.tableRow {
|
||||
border-bottom: 1px solid $gray-100;
|
||||
transition: 0.12s;
|
||||
|
||||
&:last-child {
|
||||
border-bottom: 0;
|
||||
}
|
||||
&:hover {
|
||||
background: $gold-50;
|
||||
}
|
||||
|
||||
td {
|
||||
padding: 15px 18px;
|
||||
color: $gray-700;
|
||||
vertical-align: middle;
|
||||
}
|
||||
}
|
||||
|
||||
.contractorName {
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
color: $navy-900;
|
||||
letter-spacing: 0.1px;
|
||||
}
|
||||
|
||||
.contractorFullName {
|
||||
font-size: 12px;
|
||||
color: $gray-400;
|
||||
margin-top: 2px;
|
||||
}
|
||||
|
||||
.binValue {
|
||||
font-variant-numeric: tabular-nums;
|
||||
color: $gray-700;
|
||||
}
|
||||
|
||||
.binEmpty {
|
||||
color: $gray-400;
|
||||
}
|
||||
|
||||
// ── Pagination ────────────────────────────────────────────────────────────────
|
||||
.pagination {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
padding: 12px 20px 0;
|
||||
}
|
||||
|
||||
.paginationInfo {
|
||||
font-size: 12px;
|
||||
color: $gray-400;
|
||||
letter-spacing: 0.3px;
|
||||
}
|
||||
|
||||
.paginationControls {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.pageBtn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-width: 30px;
|
||||
height: 30px;
|
||||
padding: 0 6px;
|
||||
background: #fff;
|
||||
color: $gray-500;
|
||||
border: 1px solid $gray-300;
|
||||
border-radius: $r-sm;
|
||||
font-size: $fs-sm;
|
||||
font-family: inherit;
|
||||
cursor: pointer;
|
||||
transition: 0.15s;
|
||||
|
||||
&:hover:not(:disabled) {
|
||||
border-color: $gold-500;
|
||||
color: $gold-600;
|
||||
}
|
||||
&:disabled {
|
||||
opacity: 0.35;
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
|
||||
.pageBtnActive {
|
||||
background: $navy-900;
|
||||
color: $gold-300;
|
||||
border-color: $navy-900;
|
||||
font-weight: 600;
|
||||
|
||||
&:hover {
|
||||
background: $navy-800;
|
||||
border-color: $navy-800;
|
||||
color: $gold-300;
|
||||
}
|
||||
}
|
||||
|
||||
.pageEllipsis {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-width: 30px;
|
||||
height: 30px;
|
||||
color: $gray-400;
|
||||
font-size: $fs-sm;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
// ── Buttons ───────────────────────────────────────────────────────────────────
|
||||
.btnPrimary {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 7px;
|
||||
padding: 9px 18px;
|
||||
background: $navy-800;
|
||||
color: #fff;
|
||||
border: 1px solid $navy-800;
|
||||
border-radius: $r-sm;
|
||||
font-size: $fs-sm;
|
||||
font-weight: 500;
|
||||
font-family: inherit;
|
||||
cursor: pointer;
|
||||
white-space: nowrap;
|
||||
letter-spacing: 0.15px;
|
||||
transition: 0.18s;
|
||||
flex-shrink: 0;
|
||||
|
||||
&:hover:not(:disabled) {
|
||||
background: $navy-700;
|
||||
box-shadow: $sh-2;
|
||||
}
|
||||
&:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
}
|
||||
|
||||
.btnSecondary {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 7px;
|
||||
padding: 9px 18px;
|
||||
background: #fff;
|
||||
color: $navy-800;
|
||||
border: 1px solid $gray-300;
|
||||
border-radius: $r-sm;
|
||||
font-size: $fs-sm;
|
||||
font-weight: 500;
|
||||
font-family: inherit;
|
||||
cursor: pointer;
|
||||
transition: 0.18s;
|
||||
white-space: nowrap;
|
||||
|
||||
&:hover:not(:disabled) {
|
||||
border-color: $gold-500;
|
||||
color: $navy-900;
|
||||
}
|
||||
&:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
}
|
||||
|
||||
.btnDanger {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 7px;
|
||||
padding: 9px 18px;
|
||||
background: #fff;
|
||||
color: $red-500;
|
||||
border: 1px solid $gray-300;
|
||||
border-radius: $r-sm;
|
||||
font-size: $fs-sm;
|
||||
font-weight: 500;
|
||||
font-family: inherit;
|
||||
cursor: pointer;
|
||||
transition: 0.18s;
|
||||
|
||||
&:hover:not(:disabled) {
|
||||
border-color: $red-500;
|
||||
background: $red-50;
|
||||
}
|
||||
&:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
}
|
||||
|
||||
.btnEdit {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 33px;
|
||||
height: 33px;
|
||||
padding: 0;
|
||||
background: transparent;
|
||||
border: 1px solid transparent;
|
||||
border-radius: $r-sm;
|
||||
color: $gray-400;
|
||||
cursor: pointer;
|
||||
transition: 0.18s;
|
||||
|
||||
&:hover {
|
||||
color: $navy-800;
|
||||
background: $navy-50;
|
||||
border-color: $gray-200;
|
||||
}
|
||||
}
|
||||
|
||||
.btnDeleteCard {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 33px;
|
||||
height: 33px;
|
||||
padding: 0;
|
||||
background: transparent;
|
||||
border: 1px solid transparent;
|
||||
border-radius: $r-sm;
|
||||
color: $gray-400;
|
||||
cursor: pointer;
|
||||
transition: 0.18s;
|
||||
|
||||
&:hover {
|
||||
color: $red-500;
|
||||
background: $red-50;
|
||||
border-color: $red-500;
|
||||
}
|
||||
}
|
||||
|
||||
// ── Table actions column ──────────────────────────────────────────────────────
|
||||
.tableActionsCol {
|
||||
width: 100px;
|
||||
}
|
||||
|
||||
.tableActions {
|
||||
display: flex;
|
||||
gap: 4px;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
// ── Modal overlay ─────────────────────────────────────────────────────────────
|
||||
.modalOverlay {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
background: rgba(10, 27, 48, 0.5);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 9999;
|
||||
padding: 16px;
|
||||
box-sizing: border-box;
|
||||
backdrop-filter: blur(6px);
|
||||
}
|
||||
|
||||
.modal {
|
||||
background: #fff;
|
||||
border-radius: $r-lg;
|
||||
box-shadow: $sh-modal;
|
||||
width: 100%;
|
||||
max-width: 640px;
|
||||
max-height: 90vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
border-top: 3px solid $gold-500;
|
||||
}
|
||||
|
||||
.modalSm {
|
||||
max-width: 420px;
|
||||
}
|
||||
|
||||
.modalHeader {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 20px 24px 16px;
|
||||
border-bottom: 1px solid $gray-200;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.modalTitle {
|
||||
font-family: $font-serif;
|
||||
font-size: $fs-form-title;
|
||||
font-weight: 600;
|
||||
color: $navy-900;
|
||||
margin: 0;
|
||||
letter-spacing: 0.2px;
|
||||
}
|
||||
|
||||
.modalTitleDanger {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
font-family: $font-serif;
|
||||
font-size: $fs-form-title;
|
||||
font-weight: 600;
|
||||
color: $red-500;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.warningIcon {
|
||||
color: $red-500;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.modalCloseBtn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
background: transparent;
|
||||
border: 1px solid transparent;
|
||||
border-radius: $r-sm;
|
||||
color: $gray-400;
|
||||
cursor: pointer;
|
||||
flex-shrink: 0;
|
||||
transition: 0.18s;
|
||||
|
||||
&:hover:not(:disabled) {
|
||||
background: $gray-100;
|
||||
color: $gray-700;
|
||||
border-color: $gray-200;
|
||||
}
|
||||
&:disabled {
|
||||
opacity: 0.4;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
}
|
||||
|
||||
.modalBody {
|
||||
padding: 24px;
|
||||
overflow-y: auto;
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.modalFooter {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 8px;
|
||||
padding: 16px 24px;
|
||||
border-top: 1px solid $gray-200;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
// ── Form ──────────────────────────────────────────────────────────────────────
|
||||
.formGrid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 16px 20px;
|
||||
|
||||
@media (max-width: 480px) {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
.formGroup {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.formGroupFull {
|
||||
grid-column: 1 / -1;
|
||||
}
|
||||
|
||||
.formLabel {
|
||||
font-size: $fs-label;
|
||||
font-weight: 500;
|
||||
color: $gray-700;
|
||||
letter-spacing: 0.2px;
|
||||
}
|
||||
|
||||
.required {
|
||||
color: $gold-600;
|
||||
}
|
||||
|
||||
.formInput {
|
||||
height: 38px;
|
||||
padding: 0 13px;
|
||||
border: 1px solid $gray-300;
|
||||
border-radius: $r-sm;
|
||||
font-size: 13.5px;
|
||||
font-family: inherit;
|
||||
color: $gray-900;
|
||||
background: #fff;
|
||||
transition: 0.18s;
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
@include input-focus-gold;
|
||||
|
||||
&:disabled {
|
||||
background: $gray-50;
|
||||
color: $gray-400;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
&::placeholder {
|
||||
color: $gray-400;
|
||||
}
|
||||
}
|
||||
|
||||
.formTextarea {
|
||||
padding: 11px 13px;
|
||||
border: 1px solid $gray-300;
|
||||
border-radius: $r-sm;
|
||||
font-size: 13.5px;
|
||||
font-family: inherit;
|
||||
color: $gray-900;
|
||||
background: #fff;
|
||||
resize: vertical;
|
||||
transition: 0.18s;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
line-height: 1.5;
|
||||
min-height: 60px;
|
||||
@include input-focus-gold;
|
||||
|
||||
&::placeholder {
|
||||
color: $gray-400;
|
||||
}
|
||||
}
|
||||
|
||||
// ── Delete modal ──────────────────────────────────────────────────────────────
|
||||
.deleteText {
|
||||
font-size: $fs-body;
|
||||
color: $gray-700;
|
||||
margin: 0;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.deleteHint {
|
||||
font-size: $fs-label;
|
||||
color: $gray-400;
|
||||
margin: 8px 0 0;
|
||||
line-height: 1.6;
|
||||
}
|
||||
@@ -0,0 +1,316 @@
|
||||
import * as React from "react";
|
||||
import styles from "./ContractorsList.module.scss";
|
||||
import type { IContractorsListProps } from "./IContractorsListProps";
|
||||
import { IContractor, IContractorsResponse } from "../models/IContractor";
|
||||
import { AadHttpClient, HttpClientResponse } from "@microsoft/sp-http";
|
||||
import ContractorModal from "./ContractorModal";
|
||||
import DeleteModal from "./DeleteModal";
|
||||
|
||||
const SearchIcon: React.FC = () => (
|
||||
<svg width="16" height="16" viewBox="0 0 20 20" fill="currentColor">
|
||||
<path d="M12.9 14.32a8 8 0 1 1 1.41-1.41l5.35 5.33-1.42 1.42-5.33-5.34zM8 14A6 6 0 1 0 8 2a6 6 0 0 0 0 12z" />
|
||||
</svg>
|
||||
);
|
||||
|
||||
const PlusIcon: React.FC = () => (
|
||||
<svg width="14" height="14" viewBox="0 0 20 20" fill="currentColor">
|
||||
<path d="M10 3a1 1 0 0 1 1 1v5h5a1 1 0 1 1 0 2h-5v5a1 1 0 1 1-2 0v-5H4a1 1 0 1 1 0-2h5V4a1 1 0 0 1 1-1z" />
|
||||
</svg>
|
||||
);
|
||||
|
||||
const ContractorIcon: React.FC = () => (
|
||||
<svg width="48" height="48" viewBox="0 0 48 48" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
||||
<rect x="8" y="18" width="32" height="24" rx="1" />
|
||||
<path d="M4 20L24 6L44 20" />
|
||||
<rect x="19" y="30" width="10" height="12" rx="1" />
|
||||
<rect x="12" y="24" width="6" height="6" rx="1" />
|
||||
<rect x="30" y="24" width="6" height="6" rx="1" />
|
||||
</svg>
|
||||
);
|
||||
|
||||
const EditIcon: React.FC = () => (
|
||||
<svg width="13" height="13" viewBox="0 0 20 20" fill="currentColor">
|
||||
<path d="M17.04 2.96a3.28 3.28 0 0 0-4.64 0L3.29 12.07a1 1 0 0 0-.27.51l-1 5a1 1 0 0 0 1.18 1.18l5-1a1 1 0 0 0 .51-.27l9.11-9.11a3.28 3.28 0 0 0 0-4.64zm-3.23 1.41a1.28 1.28 0 0 1 1.82 1.82l-1.28 1.28-1.82-1.82 1.28-1.28zM4.9 13.17l7.18-7.18 1.82 1.82-7.18 7.18-3.18.64.64-3.18z" />
|
||||
</svg>
|
||||
);
|
||||
|
||||
const TrashIcon: React.FC = () => (
|
||||
<svg width="13" height="13" viewBox="0 0 20 20" fill="currentColor">
|
||||
<path d="M8.5 3a1 1 0 0 0-1 1H5a1 1 0 0 0 0 2h10a1 1 0 1 0 0-2h-2.5a1 1 0 0 0-1-1h-3zM5 7v9a2 2 0 0 0 2 2h6a2 2 0 0 0 2-2V7H5zm3 2a.75.75 0 0 1 .75.75v5.5a.75.75 0 0 1-1.5 0v-5.5A.75.75 0 0 1 8 9zm4 0a.75.75 0 0 1 .75.75v5.5a.75.75 0 0 1-1.5 0v-5.5A.75.75 0 0 1 12 9z" />
|
||||
</svg>
|
||||
);
|
||||
|
||||
interface IState {
|
||||
contractors: IContractor[];
|
||||
totalCount: number;
|
||||
totalPages: number;
|
||||
page: number;
|
||||
loading: boolean;
|
||||
error: string | null;
|
||||
searchInput: string;
|
||||
showModal: boolean;
|
||||
editingContractor: IContractor | null;
|
||||
deletingContractor: IContractor | null;
|
||||
}
|
||||
|
||||
const PAGE_SIZE = 20;
|
||||
|
||||
export default class ContractorsList extends React.Component<IContractorsListProps, IState> {
|
||||
private _searchTimeout: ReturnType<typeof setTimeout> | null = null;
|
||||
|
||||
constructor(props: IContractorsListProps) {
|
||||
super(props);
|
||||
this.state = {
|
||||
contractors: [],
|
||||
totalCount: 0,
|
||||
totalPages: 1,
|
||||
page: 1,
|
||||
loading: true,
|
||||
error: null,
|
||||
searchInput: "",
|
||||
showModal: false,
|
||||
editingContractor: null,
|
||||
deletingContractor: null,
|
||||
};
|
||||
}
|
||||
|
||||
public componentDidMount(): void {
|
||||
void this._loadContractors(1, "");
|
||||
}
|
||||
|
||||
private async _loadContractors(page: number, name: string): Promise<void> {
|
||||
const { httpClient, apiBaseUrl } = this.props;
|
||||
this.setState({ loading: true, error: null });
|
||||
try {
|
||||
const nameParam = name ? `&name=${encodeURIComponent(name)}` : "";
|
||||
const url = `${apiBaseUrl}/Contractors?page=${page}&pageSize=${PAGE_SIZE}${nameParam}`;
|
||||
const response: HttpClientResponse = await httpClient.get(url, AadHttpClient.configurations.v1);
|
||||
if (!response.ok) throw new Error(`Ошибка ${response.status}: ${response.statusText}`);
|
||||
const data: IContractorsResponse = await response.json();
|
||||
this.setState({
|
||||
contractors: data.items,
|
||||
totalCount: data.totalCount,
|
||||
totalPages: data.totalPages,
|
||||
loading: false,
|
||||
});
|
||||
} catch (e) {
|
||||
this.setState({ loading: false, error: (e as Error).message });
|
||||
}
|
||||
}
|
||||
|
||||
private _handleSearchInput = (e: React.ChangeEvent<HTMLInputElement>): void => {
|
||||
const searchInput = e.target.value;
|
||||
this.setState({ searchInput, page: 1 });
|
||||
if (this._searchTimeout) clearTimeout(this._searchTimeout);
|
||||
this._searchTimeout = setTimeout(() => {
|
||||
void this._loadContractors(1, searchInput);
|
||||
}, 400);
|
||||
};
|
||||
|
||||
private _handlePageChange = (newPage: number): void => {
|
||||
this.setState({ page: newPage });
|
||||
void this._loadContractors(newPage, this.state.searchInput);
|
||||
};
|
||||
|
||||
private _handleModalSave = async (data: Partial<IContractor>): Promise<void> => {
|
||||
const { httpClient, apiBaseUrl } = this.props;
|
||||
const { editingContractor } = this.state;
|
||||
|
||||
const response: HttpClientResponse = editingContractor
|
||||
? await httpClient.fetch(`${apiBaseUrl}/Contractors/${editingContractor.id}`, AadHttpClient.configurations.v1, {
|
||||
method: "PUT",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify(data),
|
||||
})
|
||||
: await httpClient.fetch(`${apiBaseUrl}/Contractors`, AadHttpClient.configurations.v1, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify(data),
|
||||
});
|
||||
|
||||
if (!response.ok) throw new Error(`Ошибка ${response.status}: ${response.statusText}`);
|
||||
|
||||
this.setState({ showModal: false, editingContractor: null, page: 1 });
|
||||
await this._loadContractors(1, this.state.searchInput);
|
||||
};
|
||||
|
||||
private _handleDeleteConfirm = async (): Promise<void> => {
|
||||
const { httpClient, apiBaseUrl } = this.props;
|
||||
const { deletingContractor, page, searchInput } = this.state;
|
||||
if (!deletingContractor) return;
|
||||
const response = await httpClient.fetch(
|
||||
`${apiBaseUrl}/Contractors/${deletingContractor.id}`,
|
||||
AadHttpClient.configurations.v1,
|
||||
{ method: "DELETE" }
|
||||
);
|
||||
if (!response.ok) throw new Error(`Ошибка ${response.status}: ${response.statusText}`);
|
||||
this.setState({ deletingContractor: null });
|
||||
await this._loadContractors(page, searchInput);
|
||||
};
|
||||
|
||||
private _renderPageNumbers(currentPage: number, totalPages: number): React.ReactElement[] {
|
||||
const btn = (p: number): React.ReactElement => (
|
||||
<button
|
||||
key={p}
|
||||
className={`${styles.pageBtn} ${p === currentPage ? styles.pageBtnActive : ""}`}
|
||||
onClick={() => this._handlePageChange(p)}
|
||||
>{p}</button>
|
||||
);
|
||||
if (totalPages <= 7) {
|
||||
return Array.from({ length: totalPages }, (_, i) => btn(i + 1));
|
||||
}
|
||||
const result: React.ReactElement[] = [btn(1)];
|
||||
if (currentPage > 3) result.push(<span key="e1" className={styles.pageEllipsis}>…</span>);
|
||||
const start = Math.max(2, currentPage - 1);
|
||||
const end = Math.min(totalPages - 1, currentPage + 1);
|
||||
for (let i = start; i <= end; i++) result.push(btn(i));
|
||||
if (currentPage < totalPages - 2) result.push(<span key="e2" className={styles.pageEllipsis}>…</span>);
|
||||
result.push(btn(totalPages));
|
||||
return result;
|
||||
}
|
||||
|
||||
public render(): React.ReactElement<IContractorsListProps> {
|
||||
const { isDarkTheme } = this.props;
|
||||
const {
|
||||
contractors, totalCount, totalPages, page, loading, error, searchInput,
|
||||
showModal, editingContractor, deletingContractor,
|
||||
} = this.state;
|
||||
|
||||
const currentPage = Math.min(page, totalPages);
|
||||
const from = totalCount === 0 ? 0 : (currentPage - 1) * PAGE_SIZE + 1;
|
||||
const to = Math.min(currentPage * PAGE_SIZE, totalCount);
|
||||
|
||||
return (
|
||||
<div className={`${styles.root} ${isDarkTheme ? styles.dark : ""}`}>
|
||||
<div className={styles.toolbar}>
|
||||
<h2 className={styles.pageTitle}>Контрагенты</h2>
|
||||
<div className={styles.toolbarRight}>
|
||||
<div className={styles.searchBox}>
|
||||
<span className={styles.searchIcon}><SearchIcon /></span>
|
||||
<input
|
||||
type="text"
|
||||
className={styles.searchInput}
|
||||
placeholder="Поиск по названию..."
|
||||
value={searchInput}
|
||||
onChange={this._handleSearchInput}
|
||||
/>
|
||||
</div>
|
||||
<button
|
||||
className={styles.btnPrimary}
|
||||
onClick={() => this.setState({ showModal: true, editingContractor: null })}
|
||||
>
|
||||
<PlusIcon />
|
||||
<span>Добавить контрагента</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{error && <div className={styles.errorMsg}>{error}</div>}
|
||||
|
||||
{loading && (
|
||||
<div className={styles.loaderWrap}>
|
||||
<div className={styles.spinner} />
|
||||
<span>Загрузка...</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{!loading && !error && totalCount === 0 && (
|
||||
<div className={styles.emptyState}>
|
||||
<div className={styles.emptyIcon}><ContractorIcon /></div>
|
||||
<p className={styles.emptyTitle}>
|
||||
{searchInput ? "Ничего не найдено" : "Контрагентов пока нет"}
|
||||
</p>
|
||||
{!searchInput && (
|
||||
<>
|
||||
<p className={styles.emptyText}>Добавьте первого контрагента, нажав кнопку ниже</p>
|
||||
<button
|
||||
className={styles.btnPrimary}
|
||||
onClick={() => this.setState({ showModal: true, editingContractor: null })}
|
||||
>
|
||||
<PlusIcon />
|
||||
<span>Добавить контрагента</span>
|
||||
</button>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{!loading && totalCount > 0 && (
|
||||
<div className={styles.tableWrapper}>
|
||||
<table className={styles.table}>
|
||||
<thead className={styles.tableHead}>
|
||||
<tr>
|
||||
<th>Название</th>
|
||||
<th className={styles.binCol}>БИН</th>
|
||||
<th className={styles.tableActionsCol}>Действия</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{contractors.map((c) => (
|
||||
<tr key={c.id} className={styles.tableRow}>
|
||||
<td>
|
||||
<div className={styles.contractorName}>{c.name}</div>
|
||||
<div className={styles.contractorFullName}>{c.fullName}</div>
|
||||
</td>
|
||||
<td>
|
||||
{c.bin
|
||||
? <span className={styles.binValue}>{c.bin}</span>
|
||||
: <span className={styles.binEmpty}>—</span>}
|
||||
</td>
|
||||
<td>
|
||||
<div className={styles.tableActions}>
|
||||
<button
|
||||
className={styles.btnEdit}
|
||||
title="Редактировать"
|
||||
onClick={() => this.setState({ showModal: true, editingContractor: c })}
|
||||
>
|
||||
<EditIcon />
|
||||
</button>
|
||||
<button
|
||||
className={styles.btnDeleteCard}
|
||||
title="Удалить"
|
||||
onClick={() => this.setState({ deletingContractor: c })}
|
||||
>
|
||||
<TrashIcon />
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{!loading && totalCount > 0 && (
|
||||
<div className={styles.pagination}>
|
||||
<span className={styles.paginationInfo}>{from}–{to} из {totalCount}</span>
|
||||
{totalPages > 1 && (
|
||||
<div className={styles.paginationControls}>
|
||||
<button className={styles.pageBtn} disabled={currentPage === 1} onClick={() => this._handlePageChange(currentPage - 1)}>‹</button>
|
||||
{this._renderPageNumbers(currentPage, totalPages)}
|
||||
<button className={styles.pageBtn} disabled={currentPage === totalPages} onClick={() => this._handlePageChange(currentPage + 1)}>›</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{showModal && (
|
||||
<ContractorModal
|
||||
contractor={editingContractor}
|
||||
onSave={this._handleModalSave}
|
||||
onClose={() => this.setState({ showModal: false, editingContractor: null })}
|
||||
/>
|
||||
)}
|
||||
|
||||
{deletingContractor && (
|
||||
<DeleteModal
|
||||
contractorName={deletingContractor.name}
|
||||
onConfirm={this._handleDeleteConfirm}
|
||||
onClose={() => this.setState({ deletingContractor: null })}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
import * as React from "react";
|
||||
import styles from "./ContractorsList.module.scss";
|
||||
|
||||
interface IDeleteModalProps {
|
||||
contractorName: string;
|
||||
onConfirm: () => Promise<void>;
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
interface IDeleteModalState {
|
||||
deleting: boolean;
|
||||
error: string | null;
|
||||
}
|
||||
|
||||
const WarningIcon: React.FC = () => (
|
||||
<svg width="24" height="24" viewBox="0 0 20 20" fill="currentColor">
|
||||
<path fillRule="evenodd" d="M8.485 2.495c.673-1.167 2.357-1.167 3.03 0l6.28 10.875c.673 1.167-.17 2.625-1.516 2.625H3.72c-1.347 0-2.189-1.458-1.515-2.625L8.485 2.495zM10 6a.75.75 0 0 1 .75.75v3.5a.75.75 0 0 1-1.5 0v-3.5A.75.75 0 0 1 10 6zm0 9a1 1 0 1 0 0-2 1 1 0 0 0 0 2z" clipRule="evenodd" />
|
||||
</svg>
|
||||
);
|
||||
|
||||
export default class DeleteModal extends React.Component<IDeleteModalProps, IDeleteModalState> {
|
||||
constructor(props: IDeleteModalProps) {
|
||||
super(props);
|
||||
this.state = { deleting: false, error: null };
|
||||
}
|
||||
|
||||
public componentDidMount(): void {
|
||||
document.addEventListener("keydown", this._handleKeyDown);
|
||||
}
|
||||
|
||||
public componentWillUnmount(): void {
|
||||
document.removeEventListener("keydown", this._handleKeyDown);
|
||||
}
|
||||
|
||||
private _handleKeyDown = (e: KeyboardEvent): void => {
|
||||
if (e.key === "Escape" && !this.state.deleting) this.props.onClose();
|
||||
if (e.key === "Enter" && !this.state.deleting) void this._handleConfirm();
|
||||
};
|
||||
|
||||
private _handleConfirm = async (): Promise<void> => {
|
||||
this.setState({ deleting: true, error: null });
|
||||
try {
|
||||
await this.props.onConfirm();
|
||||
} catch (e) {
|
||||
this.setState({ deleting: false, error: (e as Error).message });
|
||||
}
|
||||
};
|
||||
|
||||
public render(): React.ReactElement {
|
||||
const { contractorName, onClose } = this.props;
|
||||
const { deleting, error } = this.state;
|
||||
|
||||
return (
|
||||
<div className={styles.modalOverlay} onClick={!deleting ? onClose : undefined}>
|
||||
<div className={`${styles.modal} ${styles.modalSm}`} onClick={(e) => e.stopPropagation()} role="dialog" aria-modal="true">
|
||||
<div className={styles.modalHeader}>
|
||||
<div className={styles.modalTitleDanger}>
|
||||
<span className={styles.warningIcon}><WarningIcon /></span>
|
||||
Удалить контрагента
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles.modalBody}>
|
||||
{error && <div className={styles.errorMsg}>{error}</div>}
|
||||
<p className={styles.deleteText}>
|
||||
Вы уверены, что хотите удалить контрагента{' '}
|
||||
<strong>«{contractorName}»</strong>?
|
||||
</p>
|
||||
<p className={styles.deleteHint}>
|
||||
Контрагент будет удалён из справочника.
|
||||
</p>
|
||||
</div>
|
||||
<div className={styles.modalFooter}>
|
||||
<button className={styles.btnSecondary} onClick={onClose} disabled={deleting}>
|
||||
Отмена
|
||||
</button>
|
||||
<button className={styles.btnDanger} onClick={this._handleConfirm} disabled={deleting}>
|
||||
{deleting ? "Удаление..." : "Удалить"}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
import { AadHttpClient } from '@microsoft/sp-http';
|
||||
|
||||
export interface IContractorsListProps {
|
||||
isDarkTheme: boolean;
|
||||
httpClient: AadHttpClient;
|
||||
apiBaseUrl: string;
|
||||
}
|
||||
@@ -0,0 +1,154 @@
|
||||
// ============================================
|
||||
// Tansu Construction — Design Tokens
|
||||
// Дизайн-система V5: Executive Premium
|
||||
// Скопируйте этот файл в каждый SPFx-проект:
|
||||
// src/styles/design-tokens.scss
|
||||
// и импортируйте в каждом .module.scss:
|
||||
// @import '../../styles/design-tokens';
|
||||
// ============================================
|
||||
|
||||
// ---- Navy (Primary) ----
|
||||
$navy-950: #0A1B30;
|
||||
$navy-900: #0F2440;
|
||||
$navy-800: #1B3A5C;
|
||||
$navy-700: #244B73;
|
||||
$navy-600: #2D5C8A;
|
||||
$navy-100: #E8EEF4;
|
||||
$navy-50: #F4F7FA;
|
||||
|
||||
// ---- Gold (Accent) ----
|
||||
$gold-600: #B8881A;
|
||||
$gold-500: #D4A01E;
|
||||
$gold-400: #E5B635;
|
||||
$gold-300: #F0CC5E;
|
||||
$gold-100: #FDF5E0;
|
||||
$gold-50: #FEFBF0;
|
||||
|
||||
// ---- Gray (Neutral) ----
|
||||
$gray-900: #111827;
|
||||
$gray-700: #374151;
|
||||
$gray-500: #6B7280;
|
||||
$gray-400: #9CA3AF;
|
||||
$gray-300: #D1D5DB;
|
||||
$gray-200: #E5E7EB;
|
||||
$gray-100: #F3F4F6;
|
||||
$gray-50: #F9FAFB;
|
||||
|
||||
// ---- Semantic ----
|
||||
$red-500: #DC2626;
|
||||
$red-50: #FEF2F2;
|
||||
$green-600: #15803D;
|
||||
$green-50: #F0FDF4;
|
||||
$blue-600: #2563EB;
|
||||
$blue-50: #EFF6FF;
|
||||
$amber-700: #854D0E;
|
||||
|
||||
// ---- Typography ----
|
||||
// Заголовки страниц и секций — Lora (serif)
|
||||
// Весь остальной текст — Inter (sans)
|
||||
$font-serif: 'Lora', Georgia, serif;
|
||||
$font-sans: 'Inter', -apple-system, 'Segoe UI', sans-serif;
|
||||
|
||||
$fs-page-title: 24px; // Lora 600
|
||||
$fs-form-title: 22px; // Lora 600
|
||||
$fs-section-title: 15px; // Lora 600
|
||||
$fs-body: 14px; // Inter 400
|
||||
$fs-sm: 13px;
|
||||
$fs-label: 12px; // Inter 500
|
||||
$fs-caption: 11px;
|
||||
$fs-overline: 10.5px; // uppercase, letter-spacing 1.4-1.8px
|
||||
|
||||
// ---- Radius ----
|
||||
$r-sm: 6px;
|
||||
$r-md: 10px;
|
||||
$r-lg: 14px;
|
||||
$r-full: 100px;
|
||||
|
||||
// ---- Shadows ----
|
||||
$sh-1: 0 1px 2px rgba(15,36,64,.04), 0 1px 3px rgba(15,36,64,.06);
|
||||
$sh-2: 0 4px 16px rgba(15,36,64,.07);
|
||||
$sh-3: 0 12px 32px rgba(15,36,64,.10);
|
||||
$sh-gold: 0 4px 20px rgba(212,160,30,.18);
|
||||
$sh-modal: 0 24px 70px rgba(10,27,48,.25);
|
||||
|
||||
// ---- Gradients (фирменные) ----
|
||||
$grad-header: linear-gradient(135deg, $navy-950 0%, $navy-800 85%);
|
||||
$grad-gold: linear-gradient(135deg, $gold-400, $gold-500);
|
||||
$grad-gold-v: linear-gradient(180deg, $gold-400, $gold-600);
|
||||
$grad-hairline: linear-gradient(90deg, $gold-400, transparent 70%);
|
||||
|
||||
// ---- Spacing ----
|
||||
$sp-1: 4px; $sp-2: 8px; $sp-3: 12px;
|
||||
$sp-4: 16px; $sp-5: 20px; $sp-6: 24px;
|
||||
$sp-7: 28px; $sp-8: 32px;
|
||||
|
||||
// ---- CSS Custom Properties ----
|
||||
:root {
|
||||
--navy-950: #{$navy-950}; --navy-900: #{$navy-900}; --navy-800: #{$navy-800};
|
||||
--navy-700: #{$navy-700}; --navy-600: #{$navy-600};
|
||||
--navy-100: #{$navy-100}; --navy-50: #{$navy-50};
|
||||
|
||||
--gold-600: #{$gold-600}; --gold-500: #{$gold-500}; --gold-400: #{$gold-400};
|
||||
--gold-300: #{$gold-300}; --gold-100: #{$gold-100}; --gold-50: #{$gold-50};
|
||||
|
||||
--gray-900: #{$gray-900}; --gray-700: #{$gray-700}; --gray-500: #{$gray-500};
|
||||
--gray-400: #{$gray-400}; --gray-300: #{$gray-300}; --gray-200: #{$gray-200};
|
||||
--gray-100: #{$gray-100}; --gray-50: #{$gray-50};
|
||||
|
||||
--red-500: #{$red-500}; --red-50: #{$red-50};
|
||||
--green-600: #{$green-600}; --green-50: #{$green-50};
|
||||
--blue-600: #{$blue-600}; --blue-50: #{$blue-50};
|
||||
--amber-700: #{$amber-700};
|
||||
|
||||
--sh-1: #{$sh-1}; --sh-2: #{$sh-2}; --sh-3: #{$sh-3};
|
||||
--sh-gold: #{$sh-gold}; --sh-modal: #{$sh-modal};
|
||||
|
||||
--r-sm: #{$r-sm}; --r-md: #{$r-md}; --r-lg: #{$r-lg};
|
||||
}
|
||||
|
||||
// ---- Готовые миксины ----
|
||||
@mixin page-header {
|
||||
background: $grad-header;
|
||||
border-radius: $r-lg;
|
||||
padding: 26px 30px;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
&::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
left: 0; right: 0; bottom: 0;
|
||||
height: 2px;
|
||||
background: linear-gradient(90deg, $gold-500, transparent 60%);
|
||||
}
|
||||
}
|
||||
|
||||
@mixin card-hover-gold {
|
||||
transition: .22s;
|
||||
&:hover {
|
||||
box-shadow: $sh-3;
|
||||
transform: translateY(-2px);
|
||||
border-color: $gold-300;
|
||||
}
|
||||
}
|
||||
|
||||
@mixin btn-accent {
|
||||
background: $grad-gold;
|
||||
color: $navy-950;
|
||||
font-weight: 600;
|
||||
border: 1px solid $gold-500;
|
||||
&:hover { box-shadow: $sh-gold; transform: translateY(-1px); }
|
||||
}
|
||||
|
||||
@mixin input-focus-gold {
|
||||
&:focus {
|
||||
outline: 0;
|
||||
border-color: $gold-500;
|
||||
box-shadow: 0 0 0 3px rgba(212,160,30,.12);
|
||||
}
|
||||
}
|
||||
|
||||
@mixin hairline {
|
||||
height: 1px;
|
||||
background: $grad-hairline;
|
||||
border: 0;
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
define([], function() {
|
||||
return {
|
||||
"PropertyPaneDescription": "Description",
|
||||
"BasicGroupName": "Group Name",
|
||||
"DescriptionFieldLabel": "Description Field",
|
||||
"AppLocalEnvironmentSharePoint": "The app is running on your local environment as SharePoint web part",
|
||||
"AppLocalEnvironmentTeams": "The app is running on your local environment as Microsoft Teams app",
|
||||
"AppLocalEnvironmentOffice": "The app is running on your local environment in office.com",
|
||||
"AppLocalEnvironmentOutlook": "The app is running on your local environment in Outlook",
|
||||
"AppSharePointEnvironment": "The app is running on SharePoint page",
|
||||
"AppTeamsTabEnvironment": "The app is running in Microsoft Teams",
|
||||
"AppOfficeEnvironment": "The app is running in office.com",
|
||||
"AppOutlookEnvironment": "The app is running in Outlook",
|
||||
"UnknownEnvironment": "The app is running in an unknown environment"
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,19 @@
|
||||
declare interface IContractorsListWebPartStrings {
|
||||
PropertyPaneDescription: string;
|
||||
BasicGroupName: string;
|
||||
DescriptionFieldLabel: string;
|
||||
AppLocalEnvironmentSharePoint: string;
|
||||
AppLocalEnvironmentTeams: string;
|
||||
AppLocalEnvironmentOffice: string;
|
||||
AppLocalEnvironmentOutlook: string;
|
||||
AppSharePointEnvironment: string;
|
||||
AppTeamsTabEnvironment: string;
|
||||
AppOfficeEnvironment: string;
|
||||
AppOutlookEnvironment: string;
|
||||
UnknownEnvironment: string;
|
||||
}
|
||||
|
||||
declare module 'ContractorsListWebPartStrings' {
|
||||
const strings: IContractorsListWebPartStrings;
|
||||
export = strings;
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
export interface IContractor {
|
||||
id: string;
|
||||
fullName: string;
|
||||
name: string;
|
||||
contractorType: string | null;
|
||||
bin: string | null;
|
||||
kbe: string | null;
|
||||
oked: string | null;
|
||||
country: string | null;
|
||||
address: string | null;
|
||||
actualAddress: string | null;
|
||||
phone: string | null;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
}
|
||||
|
||||
export interface IContractorsResponse {
|
||||
items: IContractor[];
|
||||
totalCount: number;
|
||||
page: number;
|
||||
pageSize: number;
|
||||
totalPages: number;
|
||||
}
|
||||
|
After Width: | Height: | Size: 10 KiB |
|
After Width: | Height: | Size: 249 B |
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"extends": "./node_modules/@microsoft/spfx-web-build-rig/profiles/default/tsconfig-base.json"
|
||||
}
|
||||