From 2c18605699ecf3023fd10e6aec206635e5ff808a Mon Sep 17 00:00:00 2001 From: Maksym Sadovnychyy Date: Sun, 24 May 2026 12:07:27 +0200 Subject: [PATCH] (feature): init --- .gitignore | 7 + CHANGELOG.md | 11 + README.md | 51 + assets/docs/NPM_CONSUMPTION.md | 47 + assets/docs/NPM_PUBLISH.md | 84 + scripts/publish-npm.ps1 | 52 + src/package-lock.json | 2216 +++++++++++++++++ src/package.json | 22 + src/packages/components/package.json | 66 + .../src/components/DataTable/DataTable.tsx | 476 ++++ .../components/DataTable/DataTableFilter.tsx | 151 ++ .../components/DataTable/DataTableLabel.tsx | 57 + .../src/components/DataTable/helpers.ts | 12 + .../src/components/DataTable/index.ts | 28 + .../components/FormLayout/FormContainer.tsx | 19 + .../src/components/FormLayout/FormContent.tsx | 23 + .../src/components/FormLayout/FormFooter.tsx | 31 + .../src/components/FormLayout/FormHeader.tsx | 19 + .../src/components/FormLayout/index.ts | 11 + .../src/components/Layout/Container.tsx | 17 + .../src/components/Layout/Content.tsx | 17 + .../src/components/Layout/Footer.tsx | 16 + .../src/components/Layout/Header.tsx | 19 + .../src/components/Layout/MainContainer.tsx | 17 + .../components/Layout/SideMenu/Container.tsx | 19 + .../components/Layout/SideMenu/Content.tsx | 17 + .../src/components/Layout/SideMenu/Footer.tsx | 16 + .../src/components/Layout/SideMenu/Header.tsx | 19 + .../src/components/Layout/SideMenu/index.tsx | 32 + .../src/components/Layout/index.tsx | 40 + .../src/components/LazyLoadTable.tsx | 86 + .../components/src/components/Offcanvas.tsx | 56 + .../components/Scopes/EntityScopesSummary.tsx | 55 + .../components/src/components/Scopes/index.ts | 2 + .../src/components/Toast/addToast.ts | 13 + .../components/src/components/Toast/index.tsx | 75 + .../components/editors/ButtonComponent.tsx | 85 + .../components/editors/CheckBoxComponent.tsx | 54 + .../editors/DateTimePickerComponent.tsx | 193 ++ .../editors/DualListboxComponent.tsx | 99 + .../src/components/editors/FieldContainer.tsx | 28 + .../editors/FileUploadComponent.tsx | 182 ++ .../components/editors/ListBoxComponent.tsx | 60 + .../editors/RadioGroupComponent.tsx | 77 + .../editors/RemoteSelectBoxComponent.tsx | 100 + .../components/editors/SecretComponent.tsx | 106 + .../components/editors/SelectBoxComponent.tsx | 204 ++ .../components/editors/TextBoxComponent.tsx | 110 + .../components/editors/TreeViewComponent.tsx | 85 + .../src/components/editors/editorStyles.ts | 25 + .../src/components/editors/index.ts | 40 + .../components/list/VaultStyleDataTable.tsx | 80 + .../components/list/VaultStyleListFooter.tsx | 55 + .../components/list/VaultStyleListSection.tsx | 33 + .../components/src/components/list/index.ts | 4 + src/packages/components/src/index.ts | 34 + src/packages/components/tsconfig.json | 8 + src/packages/contracts/package.json | 43 + src/packages/contracts/src/PagedRequest.ts | 23 + src/packages/contracts/src/PagedResponse.ts | 12 + src/packages/contracts/src/PatchOperation.ts | 25 + .../contracts/src/PatchRequestModelBase.ts | 15 + src/packages/contracts/src/ProblemDetails.ts | 21 + .../contracts/src/RequestModelBase.ts | 7 + .../contracts/src/ResponseModelBase.ts | 3 + .../contracts/src/SearchResponseBase.ts | 6 + src/packages/contracts/src/TrngResponse.ts | 5 + src/packages/contracts/src/identity/Claims.ts | 239 ++ .../src/identity/login/LoginRequest.ts | 41 + .../src/identity/login/LoginResponse.ts | 9 + .../src/identity/login/RefreshTokenRequest.ts | 11 + .../src/identity/logout/LogoutRequest.ts | 12 + .../src/identity/logout/LogoutResponse.ts | 3 + src/packages/contracts/src/index.ts | 34 + .../shared/search/SearchEntityScopeEntry.ts | 10 + src/packages/contracts/tsconfig.json | 8 + src/packages/core/package.json | 52 + src/packages/core/src/functions/acl/index.ts | 2 + .../core/src/functions/acl/parseAclEntry.ts | 63 + .../functions/dataTable/dataTableFilters.ts | 14 + .../src/functions/dataTable/dataTablePaged.ts | 49 + .../core/src/functions/dataTable/index.ts | 9 + .../functions/date/dateTimeToUtcIsoSchema.ts | 25 + .../src/functions/date/formatISODateString.ts | 28 + src/packages/core/src/functions/date/index.ts | 9 + .../src/functions/date/isValidDateString.ts | 23 + .../core/src/functions/deep/deepCopy.ts | 45 + .../core/src/functions/deep/deepDelta.ts | 514 ++++ .../core/src/functions/deep/deepEqual.ts | 91 + .../core/src/functions/deep/deepMerge.ts | 47 + .../src/functions/deep/deepPatternMatch.ts | 27 + src/packages/core/src/functions/deep/index.ts | 25 + .../functions/deep/patchCollectionPolicies.ts | 72 + .../core/src/functions/enum/enumToArr.ts | 49 + .../core/src/functions/enum/enumToObj.ts | 25 + .../core/src/functions/enum/enumToString.ts | 39 + .../core/src/functions/enum/flagsToString.ts | 19 + .../core/src/functions/enum/hasAnyFlag.ts | 12 + .../core/src/functions/enum/hasFlag.ts | 12 + src/packages/core/src/functions/enum/index.ts | 37 + .../core/src/functions/enum/toggleFlag.ts | 14 + src/packages/core/src/functions/file/index.ts | 7 + .../src/functions/file/saveBinaryToDisk.ts | 27 + src/packages/core/src/functions/guid/index.ts | 5 + .../core/src/functions/guid/isGuid.ts | 14 + .../headers/extractFilenameFromHeaders.ts | 49 + .../core/src/functions/headers/index.ts | 7 + src/packages/core/src/functions/index.ts | 83 + .../src/functions/zod/applyFormBulkChange.ts | 17 + .../src/functions/zod/applyFormFieldChange.ts | 35 + .../functions/zod/createFormBulkUpdater.ts | 18 + .../functions/zod/createFormFieldUpdater.ts | 21 + .../core/src/functions/zod/emptyFormErrors.ts | 21 + .../zod/flattenFormValidationIssues.ts | 27 + .../core/src/functions/zod/formPath.ts | 30 + src/packages/core/src/functions/zod/index.ts | 17 + .../src/functions/zod/validateFormState.ts | 34 + src/packages/core/src/hooks/useFormState.tsx | 55 + .../core/src/http/authInterceptors.ts | 123 + src/packages/core/src/http/config.ts | 49 + .../core/src/http/createWebUiHttpClient.ts | 235 ++ src/packages/core/src/http/errorHandler.ts | 26 + src/packages/core/src/http/formData.ts | 17 + src/packages/core/src/http/index.ts | 14 + src/packages/core/src/http/problemDetails.ts | 12 + src/packages/core/src/http/types.ts | 44 + src/packages/core/src/index.ts | 5 + .../core/src/localStorage/identity.ts | 19 + .../core/src/types/FormValidationSchema.ts | 17 + .../core/src/types/ScopePermissions.ts | 8 + src/packages/core/src/types/index.ts | 7 + src/packages/core/tsconfig.json | 8 + src/tsconfig.base.json | 20 + .../Force-AmendTaggedCommit.bat | 3 + .../Force-AmendTaggedCommit.ps1 | 249 ++ .../scriptsettings.json | 18 + .../Generate-CoverageBadges.bat | 3 + .../Generate-CoverageBadges.ps1 | 265 ++ .../scriptsettings.json | 47 + utils/GitTools.psm1 | 268 ++ utils/Logging.psm1 | 70 + .../CorePlugins/CleanupArtifacts.psm1 | 121 + .../CorePlugins/CreateArchive.psm1 | 93 + .../CorePlugins/DockerPush.psm1 | 236 ++ .../CorePlugins/DotNetNuGet.psm1 | 70 + .../CorePlugins/DotNetPack.psm1 | 127 + .../CorePlugins/DotNetPublish.psm1 | 71 + .../CorePlugins/DotNetReleaseVersion.psm1 | 40 + .../CorePlugins/DotNetTest.psm1 | 98 + utils/Release-Package/CorePlugins/GitHub.psm1 | 232 ++ .../Release-Package/CorePlugins/HelmPush.psm1 | 180 ++ .../Release-Package/CorePlugins/NpmBuild.psm1 | 87 + .../CorePlugins/NpmPublish.psm1 | 116 + .../CorePlugins/NpmReleaseVersion.psm1 | 99 + .../CorePlugins/QualityGate.psm1 | 184 ++ .../CorePlugins/ReleasePublishGuard.psm1 | 167 ++ utils/Release-Package/CustomPlugins/.gitkeep | 1 + utils/Release-Package/EngineSupport.psm1 | 149 ++ utils/Release-Package/PluginSupport.psm1 | 376 +++ utils/Release-Package/README.md | 44 + utils/Release-Package/Release-Package.bat | 3 + utils/Release-Package/Release-Package.ps1 | 185 ++ utils/Release-Package/ReleaseContext.psm1 | 145 ++ utils/Release-Package/scriptsettings.json | 68 + utils/ScriptConfig.psm1 | 35 + utils/TestRunner.psm1 | 286 +++ utils/Update-RepoUtils/Update-RepoUtils.bat | 3 + utils/Update-RepoUtils/Update-RepoUtils.ps1 | 355 +++ utils/Update-RepoUtils/scriptsettings.json | 15 + 169 files changed, 12970 insertions(+) create mode 100644 .gitignore create mode 100644 CHANGELOG.md create mode 100644 README.md create mode 100644 assets/docs/NPM_CONSUMPTION.md create mode 100644 assets/docs/NPM_PUBLISH.md create mode 100644 scripts/publish-npm.ps1 create mode 100644 src/package-lock.json create mode 100644 src/package.json create mode 100644 src/packages/components/package.json create mode 100644 src/packages/components/src/components/DataTable/DataTable.tsx create mode 100644 src/packages/components/src/components/DataTable/DataTableFilter.tsx create mode 100644 src/packages/components/src/components/DataTable/DataTableLabel.tsx create mode 100644 src/packages/components/src/components/DataTable/helpers.ts create mode 100644 src/packages/components/src/components/DataTable/index.ts create mode 100644 src/packages/components/src/components/FormLayout/FormContainer.tsx create mode 100644 src/packages/components/src/components/FormLayout/FormContent.tsx create mode 100644 src/packages/components/src/components/FormLayout/FormFooter.tsx create mode 100644 src/packages/components/src/components/FormLayout/FormHeader.tsx create mode 100644 src/packages/components/src/components/FormLayout/index.ts create mode 100644 src/packages/components/src/components/Layout/Container.tsx create mode 100644 src/packages/components/src/components/Layout/Content.tsx create mode 100644 src/packages/components/src/components/Layout/Footer.tsx create mode 100644 src/packages/components/src/components/Layout/Header.tsx create mode 100644 src/packages/components/src/components/Layout/MainContainer.tsx create mode 100644 src/packages/components/src/components/Layout/SideMenu/Container.tsx create mode 100644 src/packages/components/src/components/Layout/SideMenu/Content.tsx create mode 100644 src/packages/components/src/components/Layout/SideMenu/Footer.tsx create mode 100644 src/packages/components/src/components/Layout/SideMenu/Header.tsx create mode 100644 src/packages/components/src/components/Layout/SideMenu/index.tsx create mode 100644 src/packages/components/src/components/Layout/index.tsx create mode 100644 src/packages/components/src/components/LazyLoadTable.tsx create mode 100644 src/packages/components/src/components/Offcanvas.tsx create mode 100644 src/packages/components/src/components/Scopes/EntityScopesSummary.tsx create mode 100644 src/packages/components/src/components/Scopes/index.ts create mode 100644 src/packages/components/src/components/Toast/addToast.ts create mode 100644 src/packages/components/src/components/Toast/index.tsx create mode 100644 src/packages/components/src/components/editors/ButtonComponent.tsx create mode 100644 src/packages/components/src/components/editors/CheckBoxComponent.tsx create mode 100644 src/packages/components/src/components/editors/DateTimePickerComponent.tsx create mode 100644 src/packages/components/src/components/editors/DualListboxComponent.tsx create mode 100644 src/packages/components/src/components/editors/FieldContainer.tsx create mode 100644 src/packages/components/src/components/editors/FileUploadComponent.tsx create mode 100644 src/packages/components/src/components/editors/ListBoxComponent.tsx create mode 100644 src/packages/components/src/components/editors/RadioGroupComponent.tsx create mode 100644 src/packages/components/src/components/editors/RemoteSelectBoxComponent.tsx create mode 100644 src/packages/components/src/components/editors/SecretComponent.tsx create mode 100644 src/packages/components/src/components/editors/SelectBoxComponent.tsx create mode 100644 src/packages/components/src/components/editors/TextBoxComponent.tsx create mode 100644 src/packages/components/src/components/editors/TreeViewComponent.tsx create mode 100644 src/packages/components/src/components/editors/editorStyles.ts create mode 100644 src/packages/components/src/components/editors/index.ts create mode 100644 src/packages/components/src/components/list/VaultStyleDataTable.tsx create mode 100644 src/packages/components/src/components/list/VaultStyleListFooter.tsx create mode 100644 src/packages/components/src/components/list/VaultStyleListSection.tsx create mode 100644 src/packages/components/src/components/list/index.ts create mode 100644 src/packages/components/src/index.ts create mode 100644 src/packages/components/tsconfig.json create mode 100644 src/packages/contracts/package.json create mode 100644 src/packages/contracts/src/PagedRequest.ts create mode 100644 src/packages/contracts/src/PagedResponse.ts create mode 100644 src/packages/contracts/src/PatchOperation.ts create mode 100644 src/packages/contracts/src/PatchRequestModelBase.ts create mode 100644 src/packages/contracts/src/ProblemDetails.ts create mode 100644 src/packages/contracts/src/RequestModelBase.ts create mode 100644 src/packages/contracts/src/ResponseModelBase.ts create mode 100644 src/packages/contracts/src/SearchResponseBase.ts create mode 100644 src/packages/contracts/src/TrngResponse.ts create mode 100644 src/packages/contracts/src/identity/Claims.ts create mode 100644 src/packages/contracts/src/identity/login/LoginRequest.ts create mode 100644 src/packages/contracts/src/identity/login/LoginResponse.ts create mode 100644 src/packages/contracts/src/identity/login/RefreshTokenRequest.ts create mode 100644 src/packages/contracts/src/identity/logout/LogoutRequest.ts create mode 100644 src/packages/contracts/src/identity/logout/LogoutResponse.ts create mode 100644 src/packages/contracts/src/index.ts create mode 100644 src/packages/contracts/src/shared/search/SearchEntityScopeEntry.ts create mode 100644 src/packages/contracts/tsconfig.json create mode 100644 src/packages/core/package.json create mode 100644 src/packages/core/src/functions/acl/index.ts create mode 100644 src/packages/core/src/functions/acl/parseAclEntry.ts create mode 100644 src/packages/core/src/functions/dataTable/dataTableFilters.ts create mode 100644 src/packages/core/src/functions/dataTable/dataTablePaged.ts create mode 100644 src/packages/core/src/functions/dataTable/index.ts create mode 100644 src/packages/core/src/functions/date/dateTimeToUtcIsoSchema.ts create mode 100644 src/packages/core/src/functions/date/formatISODateString.ts create mode 100644 src/packages/core/src/functions/date/index.ts create mode 100644 src/packages/core/src/functions/date/isValidDateString.ts create mode 100644 src/packages/core/src/functions/deep/deepCopy.ts create mode 100644 src/packages/core/src/functions/deep/deepDelta.ts create mode 100644 src/packages/core/src/functions/deep/deepEqual.ts create mode 100644 src/packages/core/src/functions/deep/deepMerge.ts create mode 100644 src/packages/core/src/functions/deep/deepPatternMatch.ts create mode 100644 src/packages/core/src/functions/deep/index.ts create mode 100644 src/packages/core/src/functions/deep/patchCollectionPolicies.ts create mode 100644 src/packages/core/src/functions/enum/enumToArr.ts create mode 100644 src/packages/core/src/functions/enum/enumToObj.ts create mode 100644 src/packages/core/src/functions/enum/enumToString.ts create mode 100644 src/packages/core/src/functions/enum/flagsToString.ts create mode 100644 src/packages/core/src/functions/enum/hasAnyFlag.ts create mode 100644 src/packages/core/src/functions/enum/hasFlag.ts create mode 100644 src/packages/core/src/functions/enum/index.ts create mode 100644 src/packages/core/src/functions/enum/toggleFlag.ts create mode 100644 src/packages/core/src/functions/file/index.ts create mode 100644 src/packages/core/src/functions/file/saveBinaryToDisk.ts create mode 100644 src/packages/core/src/functions/guid/index.ts create mode 100644 src/packages/core/src/functions/guid/isGuid.ts create mode 100644 src/packages/core/src/functions/headers/extractFilenameFromHeaders.ts create mode 100644 src/packages/core/src/functions/headers/index.ts create mode 100644 src/packages/core/src/functions/index.ts create mode 100644 src/packages/core/src/functions/zod/applyFormBulkChange.ts create mode 100644 src/packages/core/src/functions/zod/applyFormFieldChange.ts create mode 100644 src/packages/core/src/functions/zod/createFormBulkUpdater.ts create mode 100644 src/packages/core/src/functions/zod/createFormFieldUpdater.ts create mode 100644 src/packages/core/src/functions/zod/emptyFormErrors.ts create mode 100644 src/packages/core/src/functions/zod/flattenFormValidationIssues.ts create mode 100644 src/packages/core/src/functions/zod/formPath.ts create mode 100644 src/packages/core/src/functions/zod/index.ts create mode 100644 src/packages/core/src/functions/zod/validateFormState.ts create mode 100644 src/packages/core/src/hooks/useFormState.tsx create mode 100644 src/packages/core/src/http/authInterceptors.ts create mode 100644 src/packages/core/src/http/config.ts create mode 100644 src/packages/core/src/http/createWebUiHttpClient.ts create mode 100644 src/packages/core/src/http/errorHandler.ts create mode 100644 src/packages/core/src/http/formData.ts create mode 100644 src/packages/core/src/http/index.ts create mode 100644 src/packages/core/src/http/problemDetails.ts create mode 100644 src/packages/core/src/http/types.ts create mode 100644 src/packages/core/src/index.ts create mode 100644 src/packages/core/src/localStorage/identity.ts create mode 100644 src/packages/core/src/types/FormValidationSchema.ts create mode 100644 src/packages/core/src/types/ScopePermissions.ts create mode 100644 src/packages/core/src/types/index.ts create mode 100644 src/packages/core/tsconfig.json create mode 100644 src/tsconfig.base.json create mode 100644 utils/Force-AmendTaggedCommit/Force-AmendTaggedCommit.bat create mode 100644 utils/Force-AmendTaggedCommit/Force-AmendTaggedCommit.ps1 create mode 100644 utils/Force-AmendTaggedCommit/scriptsettings.json create mode 100644 utils/Generate-CoverageBadges/Generate-CoverageBadges.bat create mode 100644 utils/Generate-CoverageBadges/Generate-CoverageBadges.ps1 create mode 100644 utils/Generate-CoverageBadges/scriptsettings.json create mode 100644 utils/GitTools.psm1 create mode 100644 utils/Logging.psm1 create mode 100644 utils/Release-Package/CorePlugins/CleanupArtifacts.psm1 create mode 100644 utils/Release-Package/CorePlugins/CreateArchive.psm1 create mode 100644 utils/Release-Package/CorePlugins/DockerPush.psm1 create mode 100644 utils/Release-Package/CorePlugins/DotNetNuGet.psm1 create mode 100644 utils/Release-Package/CorePlugins/DotNetPack.psm1 create mode 100644 utils/Release-Package/CorePlugins/DotNetPublish.psm1 create mode 100644 utils/Release-Package/CorePlugins/DotNetReleaseVersion.psm1 create mode 100644 utils/Release-Package/CorePlugins/DotNetTest.psm1 create mode 100644 utils/Release-Package/CorePlugins/GitHub.psm1 create mode 100644 utils/Release-Package/CorePlugins/HelmPush.psm1 create mode 100644 utils/Release-Package/CorePlugins/NpmBuild.psm1 create mode 100644 utils/Release-Package/CorePlugins/NpmPublish.psm1 create mode 100644 utils/Release-Package/CorePlugins/NpmReleaseVersion.psm1 create mode 100644 utils/Release-Package/CorePlugins/QualityGate.psm1 create mode 100644 utils/Release-Package/CorePlugins/ReleasePublishGuard.psm1 create mode 100644 utils/Release-Package/CustomPlugins/.gitkeep create mode 100644 utils/Release-Package/EngineSupport.psm1 create mode 100644 utils/Release-Package/PluginSupport.psm1 create mode 100644 utils/Release-Package/README.md create mode 100644 utils/Release-Package/Release-Package.bat create mode 100644 utils/Release-Package/Release-Package.ps1 create mode 100644 utils/Release-Package/ReleaseContext.psm1 create mode 100644 utils/Release-Package/scriptsettings.json create mode 100644 utils/ScriptConfig.psm1 create mode 100644 utils/TestRunner.psm1 create mode 100644 utils/Update-RepoUtils/Update-RepoUtils.bat create mode 100644 utils/Update-RepoUtils/Update-RepoUtils.ps1 create mode 100644 utils/Update-RepoUtils/scriptsettings.json diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..bc55f6b --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +node_modules/ +dist/ +*.tsbuildinfo +.DS_Store +npm-debug.log* +utils/Release-Package/.npmrc.release-temp +src/.npmrc.release-temp diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..f8c9a6b --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,11 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [Unreleased] + +### Added + +- Initial `@maksit/webui-contracts`, `@maksit/webui-core`, and `@maksit/webui-components` packages extracted from Certs UI and Vault WebUI. diff --git a/README.md b/README.md new file mode 100644 index 0000000..725307f --- /dev/null +++ b/README.md @@ -0,0 +1,51 @@ +# maksit-webui + +Shared React UI library for **maksit-certs-ui** and **maksit-vault** WebUI apps. + +## Packages + +| npm package | Description | +|-------------|-------------| +| `@maksit/webui-contracts` | Shared TypeScript contracts (paging, gallery types, patch ops, scopes) | +| `@maksit/webui-core` | Utilities (`deepDelta`, enum helpers, ACL parsers) and `useFormState` | +| `@maksit/webui-components` | React components, layout, editors, DataTable, auth shell | + +Source lives under `src/` (npm workspaces). Release automation lives under `utils/` (from [maksit-repoutils](https://github.com/MAKS-IT-COM/maksit-repoutils)). + +## Local development + +```bash +cd src +npm install +npm run build +``` + +## Release to npmjs + +1. Set **`NPMJS_MAKS_IT`** to your npm automation token (same pattern as `NUGET_MAKS_IT` for NuGet). +2. Bump **`src/package.json`** `version` (and tag `vX.Y.Z` on `main` when using the publish guard). +3. Run **`utils/Release-Package/Release-Package.bat`** (or `pwsh utils/Release-Package/Release-Package.ps1`). + +Configured plugins (see `utils/Release-Package/scriptsettings.json`): + +| Plugin | Role | +|--------|------| +| `NpmReleaseVersion` | Read semver from `src/package.json`; sync `packages/*/package.json` | +| `NpmBuild` | `npm ci` + `npm run build` | +| `ReleasePublishGuard` | Branch/tag checks before publish | +| `GitHub` | GitHub release (optional; needs `GITHUB_MAKS_IT_COM`) | +| `NpmPublish` | Publish workspace packages in dependency order | + +Refresh shared utils from repoutils: **`utils/Update-RepoUtils/Update-RepoUtils.bat`**. + +## Consume in product repos + +```bash +npm install @maksit/webui-contracts @maksit/webui-core @maksit/webui-components +``` + +Wrap the app with `WebUiProvider` and pass axios/redux adapters — see [assets/docs/NPM_CONSUMPTION.md](assets/docs/NPM_CONSUMPTION.md). + +## License + +MIT diff --git a/assets/docs/NPM_CONSUMPTION.md b/assets/docs/NPM_CONSUMPTION.md new file mode 100644 index 0000000..dcbf010 --- /dev/null +++ b/assets/docs/NPM_CONSUMPTION.md @@ -0,0 +1,47 @@ +# Consuming @maksit/webui-* in Certs UI / Vault + +Install: + +```bash +npm install @maksit/webui-contracts @maksit/webui-core @maksit/webui-components +``` + +Wrap the app: + +```tsx +import { WebUiProvider, Loader, Authorization } from '@maksit/webui-components' + + getData(url), + getDataWithoutLoader: (url) => getDataWithoutLoader(url), + postData: (url, body) => postData(url, body), + postDataWithoutLoader: (url, body) => postDataWithoutLoader(url, body), + }} + loader={{ + disableLoader: () => dispatch(disableLoader()), + enableLoader: () => dispatch(enableLoader()), + }} + auth={{ + identity, + hydrated, + login: (c) => dispatch(login(c)), + logout: (r) => dispatch(logout(r)), + setIdentityFromLocalStorage: () => dispatch(setIdentityFromLocalStorage()), + showUserOffcanvas, + setShowUserOffcanvas: () => dispatch(setShowUserOffcanvas()), + setHideUserOffcanvas: () => dispatch(setHideUserOffcanvas()), + }} + loading={loading} +> + + {/* routes */} + +``` + +## API differences vs copied local folders + +- `RemoteSelectBoxComponent`: use `searchRoute` (absolute API path string) instead of `apiRoute: ApiRoutes`. +- `SecretComponent`: pass `generateSecretRoute` when `enableGenerate` is true. +- ACL: generic `parseAclEntry` / `parseAclEntries` from `@maksit/webui-core`; per-app entity maps and `parse*AclEntries` live in each WebUI project (`models/acl.ts`). +- Identity request types and Zod schemas (`LoginRequest` + `LoginRequestSchema`, `LogoutRequest` + `LogoutRequestSchema`, `RefreshTokenRequest` + `RefreshTokenRequestSchema`) live in `@maksit/webui-contracts`. diff --git a/assets/docs/NPM_PUBLISH.md b/assets/docs/NPM_PUBLISH.md new file mode 100644 index 0000000..c51f95f --- /dev/null +++ b/assets/docs/NPM_PUBLISH.md @@ -0,0 +1,84 @@ +# Publishing `@maksit/webui-*` to npm + +Packages are published under the **`@maksit` scope** to [registry.npmjs.org](https://registry.npmjs.org), managed from the [maks-it.com npm org](https://www.npmjs.com/settings/maks-it.com/packages). + +Published packages: + +| Package | npm | +|---------|-----| +| `@maksit/webui-contracts` | https://www.npmjs.com/package/@maksit/webui-contracts | +| `@maksit/webui-core` | https://www.npmjs.com/package/@maksit/webui-core | +| `@maksit/webui-components` | https://www.npmjs.com/package/@maksit/webui-components | + +## One-time npm setup + +1. Sign in at https://www.npmjs.com/ with the **maks-it.com** org account. +2. Confirm the **`@maksit` scope** exists under [Packages](https://www.npmjs.com/settings/maks-it.com/packages). Create the org/scope on npm if this is the first `@maksit/*` publish. +3. Create an **Automation** token (recommended) or Granular Access token with **Publish** on `@maksit/*`: + - https://www.npmjs.com/settings/maks-it.com/tokens +4. Store the token for release tooling: + - **CI / Release-Package:** set env var `NPMJS_MAKS_IT` to the token value (same pattern as `NUGET_MAKS_IT`). + - **Local one-off publish:** `npm login` or a user-level `~/.npmrc` entry: + ``` + //registry.npmjs.org/:_authToken=YOUR_TOKEN + ``` + +Scoped packages must use **`--access public`** (already configured in each package `publishConfig`). + +## Manual first publish (0.1.0) + +From the repo root: + +```powershell +cd src +npm ci +npm run build +npm publish -w @maksit/webui-contracts --access public +npm publish -w @maksit/webui-core --access public +npm publish -w @maksit/webui-components --access public +``` + +Order matters: **contracts → core → components**. + +Or use the helper script: + +```powershell +.\scripts\publish-npm.ps1 +``` + +Verify: + +```powershell +npm view @maksit/webui-contracts version +npm view @maksit/webui-core version +npm view @maksit/webui-components version +``` + +## Release pipeline (recommended) + +From `utils/Release-Package/`: + +1. Bump version in `src/package.json` (or tag drives `NpmReleaseVersion`). +2. Tag `HEAD` with exact semver, e.g. `git tag v0.1.0 && git push origin v0.1.0`. +3. Set `NPMJS_MAKS_IT` and run `Release-Package.ps1`. + +`scriptsettings.json` runs `NpmBuild` then `NpmPublish` in dependency order. + +## After publish — Certs UI / Vault + +In each WebUI app (`MaksIT.WebUI/package.json`): + +```json +"@maksit/webui-contracts": "^0.1.0", +"@maksit/webui-core": "^0.1.0", +"@maksit/webui-components": "^0.1.0" +``` + +Then refresh the lockfile: + +```bash +cd src/MaksIT.WebUI +npm install +``` + +Docker builds use `npm ci` from the lockfile; no sibling `maksit-webui` clone is required in the image context. diff --git a/scripts/publish-npm.ps1 b/scripts/publish-npm.ps1 new file mode 100644 index 0000000..83f58dd --- /dev/null +++ b/scripts/publish-npm.ps1 @@ -0,0 +1,52 @@ +#requires -Version 7.0 +# Publish @maksit/webui-* to registry.npmjs.org (maks-it.com org / @maksit scope). +# Requires: npm login or //registry.npmjs.org/:_authToken in ~/.npmrc, or NPMJS_MAKS_IT env var. + +$ErrorActionPreference = 'Stop' + +$workspaceRoot = Join-Path $PSScriptRoot '..' 'src' | Resolve-Path +$publishOrder = @( + '@maksit/webui-contracts', + '@maksit/webui-core', + '@maksit/webui-components' +) + +Push-Location $workspaceRoot +try { + if (-not [string]::IsNullOrWhiteSpace($env:NPMJS_MAKS_IT)) { + $tempNpmRc = Join-Path $workspaceRoot '.npmrc.publish-temp' + @" +registry=https://registry.npmjs.org +//registry.npmjs.org/:_authToken=$($env:NPMJS_MAKS_IT) +"@ | Set-Content -Path $tempNpmRc -Encoding utf8 -NoNewline + $npmUserConfig = @('--userconfig', $tempNpmRc) + } + else { + $npmUserConfig = @() + Write-Host 'NPMJS_MAKS_IT not set; using default npm auth (~/.npmrc or npm login).' -ForegroundColor Yellow + } + + Write-Host 'Installing workspace dependencies...' -ForegroundColor Cyan + npm ci @npmUserConfig + if ($LASTEXITCODE -ne 0) { throw 'npm ci failed.' } + + Write-Host 'Building packages...' -ForegroundColor Cyan + npm run build @npmUserConfig + if ($LASTEXITCODE -ne 0) { throw 'npm run build failed.' } + + foreach ($packageName in $publishOrder) { + Write-Host "Publishing $packageName..." -ForegroundColor Cyan + npm publish -w $packageName --access public @npmUserConfig + if ($LASTEXITCODE -ne 0) { throw "npm publish failed for $packageName." } + Write-Host "Published $packageName." -ForegroundColor Green + } + + Write-Host 'All @maksit/webui-* packages published.' -ForegroundColor Green +} +finally { + Pop-Location + $tempNpmRc = Join-Path $workspaceRoot '.npmrc.publish-temp' + if (Test-Path $tempNpmRc) { + Remove-Item -Path $tempNpmRc -Force -ErrorAction SilentlyContinue + } +} diff --git a/src/package-lock.json b/src/package-lock.json new file mode 100644 index 0000000..bb16b00 --- /dev/null +++ b/src/package-lock.json @@ -0,0 +1,2216 @@ +{ + "name": "maksit-webui", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "maksit-webui", + "version": "0.1.0", + "license": "MIT", + "workspaces": [ + "packages/*" + ], + "engines": { + "node": ">=20" + } + }, + "node_modules/@babel/runtime": { + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.2.tgz", + "integrity": "sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.7.tgz", + "integrity": "sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.7.tgz", + "integrity": "sha512-jbPXvB4Yj2yBV7HUfE2KHe4GJX51QplCN1pGbYjvsyCZbQmies29EoJbkEc+vYuU5o45AfQn37vZlyXy4YJ8RQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.7.tgz", + "integrity": "sha512-62dPZHpIXzvChfvfLJow3q5dDtiNMkwiRzPylSCfriLvZeq0a1bWChrGx/BbUbPwOrsWKMn8idSllklzBy+dgQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.7.tgz", + "integrity": "sha512-x5VpMODneVDb70PYV2VQOmIUUiBtY3D3mPBG8NxVk5CogneYhkR7MmM3yR/uMdITLrC1ml/NV1rj4bMJuy9MCg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.7.tgz", + "integrity": "sha512-5lckdqeuBPlKUwvoCXIgI2D9/ABmPq3Rdp7IfL70393YgaASt7tbju3Ac+ePVi3KDH6N2RqePfHnXkaDtY9fkw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.7.tgz", + "integrity": "sha512-rYnXrKcXuT7Z+WL5K980jVFdvVKhCHhUwid+dDYQpH+qu+TefcomiMAJpIiC2EM3Rjtq0sO3StMV/+3w3MyyqQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.7.tgz", + "integrity": "sha512-B48PqeCsEgOtzME2GbNM2roU29AMTuOIN91dsMO30t+Ydis3z/3Ngoj5hhnsOSSwNzS+6JppqWsuhTp6E82l2w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.7.tgz", + "integrity": "sha512-jOBDK5XEjA4m5IJK3bpAQF9/Lelu/Z9ZcdhTRLf4cajlB+8VEhFFRjWgfy3M1O4rO2GQ/b2dLwCUGpiF/eATNQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.7.tgz", + "integrity": "sha512-RkT/YXYBTSULo3+af8Ib0ykH8u2MBh57o7q/DAs3lTJlyVQkgQvlrPTnjIzzRPQyavxtPtfg0EopvDyIt0j1rA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.7.tgz", + "integrity": "sha512-RZPHBoxXuNnPQO9rvjh5jdkRmVizktkT7TCDkDmQ0W2SwHInKCAV95GRuvdSvA7w4VMwfCjUiPwDi0ZO6Nfe9A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.7.tgz", + "integrity": "sha512-GA48aKNkyQDbd3KtkplYWT102C5sn/EZTY4XROkxONgruHPU72l+gW+FfF8tf2cFjeHaRbWpOYa/uRBz/Xq1Pg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.7.tgz", + "integrity": "sha512-a4POruNM2oWsD4WKvBSEKGIiWQF8fZOAsycHOt6JBpZ+JN2n2JH9WAv56SOyu9X5IqAjqSIPTaJkqN8F7XOQ5Q==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.7.tgz", + "integrity": "sha512-KabT5I6StirGfIz0FMgl1I+R1H73Gp0ofL9A3nG3i/cYFJzKHhouBV5VWK1CSgKvVaG4q1RNpCTR2LuTVB3fIw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.7.tgz", + "integrity": "sha512-gRsL4x6wsGHGRqhtI+ifpN/vpOFTQtnbsupUF5R5YTAg+y/lKelYR1hXbnBdzDjGbMYjVJLJTd2OFmMewAgwlQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.7.tgz", + "integrity": "sha512-hL25LbxO1QOngGzu2U5xeXtxXcW+/GvMN3ejANqXkxZ/opySAZMrc+9LY/WyjAan41unrR3YrmtTsUpwT66InQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.7.tgz", + "integrity": "sha512-2k8go8Ycu1Kb46vEelhu1vqEP+UeRVj2zY1pSuPdgvbd5ykAw82Lrro28vXUrRmzEsUV0NzCf54yARIK8r0fdw==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.7.tgz", + "integrity": "sha512-hzznmADPt+OmsYzw1EE33ccA+HPdIqiCRq7cQeL1Jlq2gb1+OyWBkMCrYGBJ+sxVzve2ZJEVeePbLM2iEIZSxA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.7.tgz", + "integrity": "sha512-b6pqtrQdigZBwZxAn1UpazEisvwaIDvdbMbmrly7cDTMFnw/+3lVxxCTGOrkPVnsYIosJJXAsILG9XcQS+Yu6w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.7.tgz", + "integrity": "sha512-OfatkLojr6U+WN5EDYuoQhtM+1xco+/6FSzJJnuWiUw5eVcicbyK3dq5EeV/QHT1uy6GoDhGbFpprUiHUYggrw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.7.tgz", + "integrity": "sha512-AFuojMQTxAz75Fo8idVcqoQWEHIXFRbOc1TrVcFSgCZtQfSdc1RXgB3tjOn/krRHENUB4j00bfGjyl2mJrU37A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.7.tgz", + "integrity": "sha512-+A1NJmfM8WNDv5CLVQYJ5PshuRm/4cI6WMZRg1by1GwPIQPCTs1GLEUHwiiQGT5zDdyLiRM/l1G0Pv54gvtKIg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.7.tgz", + "integrity": "sha512-+KrvYb/C8zA9CU/g0sR6w2RBw7IGc5J2BPnc3dYc5VJxHCSF1yNMxTV5LQ7GuKteQXZtspjFbiuW5/dOj7H4Yw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.7.tgz", + "integrity": "sha512-ikktIhFBzQNt/QDyOL580ti9+5mL/YZeUPKU2ivGtGjdTYoqz6jObj6nOMfhASpS4GU4Q/Clh1QtxWAvcYKamA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.7.tgz", + "integrity": "sha512-7yRhbHvPqSpRUV7Q20VuDwbjW5kIMwTHpptuUzV+AA46kiPze5Z7qgt6CLCK3pWFrHeNfDd1VKgyP4O+ng17CA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.7.tgz", + "integrity": "sha512-SmwKXe6VHIyZYbBLJrhOoCJRB/Z1tckzmgTLfFYOfpMAx63BJEaL9ExI8x7v0oAO3Zh6D/Oi1gVxEYr5oUCFhw==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.7.tgz", + "integrity": "sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@maksit/webui-components": { + "resolved": "packages/components", + "link": true + }, + "node_modules/@maksit/webui-contracts": { + "resolved": "packages/contracts", + "link": true + }, + "node_modules/@maksit/webui-core": { + "resolved": "packages/core", + "link": true + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.60.4.tgz", + "integrity": "sha512-F5QXMSiFebS9hKZj02XhWLLnRpJ3B3AROP0tWbFBSj+6kCbg5m9j5JoHKd4mmSVy5mS/IMQloYgYxCuJC0fxEQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.60.4.tgz", + "integrity": "sha512-GxxTKApUpzRhof7poWvCJHRF51C67u1R7D6DiluBE8wKU1u5GWE8t+v81JvJYtbawoBFX1hLv5Ei4eVjkWokaw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.60.4.tgz", + "integrity": "sha512-tua0TaJxMOB1R0V0RS1jFZ/RpURFDJIOR2A6jWwQeawuFyS4gBW+rntLRaQd0EQ4bd6Vp44Z2rXW+YYDBsj6IA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.60.4.tgz", + "integrity": "sha512-CSKq7MsP+5PFIcydhAiR1K0UhEI1A2jWXVKHPCBZ151yOutENwvnPocgVHkivu2kviURtCEB6zUQw0vs8RrhMg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.60.4.tgz", + "integrity": "sha512-+O8OkVdyvXMtJEciu2wS/pzm1IxntEEQx3z5TAVy4l32G0etZn+RsA48ARRrFm6Ri8fvqPQfgrvNxSjKAbnd3g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.60.4.tgz", + "integrity": "sha512-Iw3oMskH3AfNuhU0MSN7vNbdi4me/NiYo2azqPz/Le16zHSa+3RRmliCMWWQmh4lcndccU40xcJuTYJZxNo/lw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.60.4.tgz", + "integrity": "sha512-EIPRXTVQpHyF8WOo219AD2yEltPehLTcTMz2fn6JsatLYSzQf00hj3rulF+yauOlF9/FtM2WpkT/hJh/KJFGhA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.60.4.tgz", + "integrity": "sha512-J3Yh9PzzF1Ovah2At+lHiGQdsYgArxBbXv/zHfSyaiFQEqvNv7DcW98pCrmdjCZBrqBiKrKKe2V+aaSGWuBe/w==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.60.4.tgz", + "integrity": "sha512-BFDEZMYfUvLn37ONE1yMBojPxnMlTFsdyNoqncT0qFq1mAfllL+ATMMJd8TeuVMiX84s1KbcxcZbXInmcO2mRg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.60.4.tgz", + "integrity": "sha512-pc9EYOSlOgdQ2uPl1o9PF6/kLSgaUosia7gOuS8mB69IxJvlclko1MECXysjs5ryez1/5zjYqx3+xYU0TU6R1A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.60.4.tgz", + "integrity": "sha512-NxnomyxYerDh5n4iLrNa+sH+Z+U4BMEE46V2PgQ/hoB909i8gV1M5wPojWg9fk1jWpO3IQnOs20K4wyZuFLEFQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.60.4.tgz", + "integrity": "sha512-nbJnQ8a3z1mtmrwImCYhc6BGpThAyYVRQxw9uKSKG4wR6aAYno9sVjJ0zaZcW9BPJX1GbrDPf+SvdWjgTuDmnw==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.60.4.tgz", + "integrity": "sha512-2EU6acNrQLd8tYvo/LXW535wupT3m6fo7HKo6lr7ktQoItxTyOL1ZCR/GfGCuXl2vR+zmfI6eRXkSemafv+iVg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.60.4.tgz", + "integrity": "sha512-WeBtoMuaMxiiIrO2IYP3xs6GMWkJP2C0EoT8beTLkUPmzV1i/UcOSVw1d5r9KBODtHKilG5yFxsGRnBbK3wJ4A==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.60.4.tgz", + "integrity": "sha512-FJHFfqpKUI3A10WrWKiFbBZ7yVbGT4q4B5o1qKFFojqpaYoh9LrQgqWCmmcxQzVSXYtyB5bzkXrYzlHTs21MYA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.60.4.tgz", + "integrity": "sha512-mcEl6CUT5IAUmQf1m9FYSmVqCJlpQ8r8eyftFUHG8i9OhY7BkBXSUdnLH5DOf0wCOjcP9v/QO93zpmF1SptCCw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.60.4.tgz", + "integrity": "sha512-ynt3JxVd2w2buzoKDWIyiV1pJW93xlQic1THVLXilz429oijRpSHivZAgp65KBu+cMcgf1eVVjdnTLvPxgCuoQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.60.4.tgz", + "integrity": "sha512-Boiz5+MsaROEWDf+GGEwF8VMHGhlUoQMtIPjOgA5fv4osupqTVnJteQNKJwUcnUog2G55jYXH7KZFFiJe0TEzQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.60.4.tgz", + "integrity": "sha512-+qfSY27qIrFfI/Hom04KYFw3GKZSGU4lXus51wsb5EuySfFlWRwjkKWoE9emgRw/ukoT4Udsj4W/+xxG8VbPKg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.60.4.tgz", + "integrity": "sha512-VpTfOPHgVXEBeeR8hZ2O0F3aSso+JDWqTWmTmzcQKted54IAdUVbxE+j/MVxUsKa8L20HJhv3vUezVPoquqWjA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.60.4.tgz", + "integrity": "sha512-IPOsh5aRYuLv/nkU51X10Bf75Bsf6+gZdx1X+QP5QM6lIJFHHqbHLG0uJn/hWthzo13UAc2umiUorqZy3axoZg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.60.4.tgz", + "integrity": "sha512-4QzE9E81OohJ/HKzHhsqU+zcYYojVOXlFMs1DdyMT6qXl/niOH7AVElmmEdUNHHS/oRkc++d5k6Vy85zFs0DEw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.60.4.tgz", + "integrity": "sha512-zTPgT1YuHHcd+Tmx7h8aml0FWFVelV5N54oHow9SLj+GfoDy/huQ+UV396N/C7KpMDMiPspRktzM1/0r1usYEA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.60.4.tgz", + "integrity": "sha512-DRS4G7mi9lJxqEDezIkKCaUIKCrLUUDCUaCsTPCi/rtqaC6D/jjwslMQyiDU50Ka0JKpeXeRBFBAXwArY52vBw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.60.4.tgz", + "integrity": "sha512-QVTUovf40zgTqlFVrKA1uXMVvU2QWEFWfAH8Wdc48IxLvrJMQVMBRjuQyUpzZCDkakImib9eVazbWlC6ksWtJw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@tanstack/react-table": { + "version": "8.21.3", + "resolved": "https://registry.npmjs.org/@tanstack/react-table/-/react-table-8.21.3.tgz", + "integrity": "sha512-5nNMTSETP4ykGegmVkhjcS8tTLW6Vl4axfEGQN3v0zdHYbK4UfoqfPChclTrJ4EoK9QynqAu9oUf8VEmrpZ5Ww==", + "dev": true, + "license": "MIT", + "dependencies": { + "@tanstack/table-core": "8.21.3" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8" + } + }, + "node_modules/@tanstack/table-core": { + "version": "8.21.3", + "resolved": "https://registry.npmjs.org/@tanstack/table-core/-/table-core-8.21.3.tgz", + "integrity": "sha512-ldZXEhOBb8Is7xLs01fR3YEc3DERiz5silj8tnGkFZytt1abEvl/GhUmCE0PMLaMPTa3Jk4HbKmRlHmu+gCftg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/lodash": { + "version": "4.17.24", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.24.tgz", + "integrity": "sha512-gIW7lQLZbue7lRSWEFql49QJJWThrTFFeIMJdp3eH4tKoxm1OvEPg02rm4wCCSHS0cL3/Fizimb35b7k8atwsQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/prop-types": { + "version": "15.7.15", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz", + "integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/react": { + "version": "19.2.15", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.15.tgz", + "integrity": "sha512-eRwcGNHve+E8qtEQSSRl6urh+rFop4v8gm6O8rGv25CodbvFdLjA1vVQ1KkiFE0w0UPOnb8tDiFKL5lp0rtY5Q==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "csstype": "^3.2.2" + } + }, + "node_modules/@types/react-dom": { + "version": "19.2.3", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz", + "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "^19.2.0" + } + }, + "node_modules/@types/react-virtualized": { + "version": "9.22.3", + "resolved": "https://registry.npmjs.org/@types/react-virtualized/-/react-virtualized-9.22.3.tgz", + "integrity": "sha512-UKRWeBIrECaKhE4O//TSFhlgwntMwyiEIOA7WZoVkr52Jahv0dH6YIOorqc358N2V3oKFclsq5XxPmx2PiYB5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/prop-types": "*", + "@types/react": "*" + } + }, + "node_modules/acorn": { + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", + "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/any-promise": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", + "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==", + "dev": true, + "license": "MIT" + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/axios": { + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.16.1.tgz", + "integrity": "sha512-caYkukvroVPO8KrzuJEb50Hm07KwfBZPEC3VeFHTsqWHvKTsy54hjJz9BS/cdaypROE2rH6xvm9mHX4fgWkr3A==", + "dev": true, + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.16.0", + "form-data": "^4.0.5", + "https-proxy-agent": "^5.0.1", + "proxy-from-env": "^2.1.0" + } + }, + "node_modules/bundle-require": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/bundle-require/-/bundle-require-5.1.0.tgz", + "integrity": "sha512-3WrrOuZiyaaZPWiEt4G3+IffISVC9HYlWueJEBWED4ZH4aIAC2PnkdnuRrR94M+w6yGWn4AglWtJtBI8YqvgoA==", + "dev": true, + "license": "MIT", + "dependencies": { + "load-tsconfig": "^0.2.3" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "peerDependencies": { + "esbuild": ">=0.18" + } + }, + "node_modules/cac": { + "version": "6.7.14", + "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", + "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/chokidar": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "readdirp": "^4.0.1" + }, + "engines": { + "node": ">= 14.16.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/clsx": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-1.2.1.tgz", + "integrity": "sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dev": true, + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/commander": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", + "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/confbox": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.1.8.tgz", + "integrity": "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/consola": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/consola/-/consola-3.4.2.tgz", + "integrity": "sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.18.0 || >=16.10.0" + } + }, + "node_modules/cookie": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.1.1.tgz", + "integrity": "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/date-fns": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-4.3.0.tgz", + "integrity": "sha512-OYcL+3N/jyWbYdFGqoMAhytDgxP9pbYPUUiRCOgn4Fewaadk9l/Wam4Avciiyp2BgkpfQyBV9B+ehnVJych+eQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/kossnocorp" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/dom-helpers": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz", + "integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.8.7", + "csstype": "^3.0.2" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz", + "integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/esbuild": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.7.tgz", + "integrity": "sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "peer": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.27.7", + "@esbuild/android-arm": "0.27.7", + "@esbuild/android-arm64": "0.27.7", + "@esbuild/android-x64": "0.27.7", + "@esbuild/darwin-arm64": "0.27.7", + "@esbuild/darwin-x64": "0.27.7", + "@esbuild/freebsd-arm64": "0.27.7", + "@esbuild/freebsd-x64": "0.27.7", + "@esbuild/linux-arm": "0.27.7", + "@esbuild/linux-arm64": "0.27.7", + "@esbuild/linux-ia32": "0.27.7", + "@esbuild/linux-loong64": "0.27.7", + "@esbuild/linux-mips64el": "0.27.7", + "@esbuild/linux-ppc64": "0.27.7", + "@esbuild/linux-riscv64": "0.27.7", + "@esbuild/linux-s390x": "0.27.7", + "@esbuild/linux-x64": "0.27.7", + "@esbuild/netbsd-arm64": "0.27.7", + "@esbuild/netbsd-x64": "0.27.7", + "@esbuild/openbsd-arm64": "0.27.7", + "@esbuild/openbsd-x64": "0.27.7", + "@esbuild/openharmony-arm64": "0.27.7", + "@esbuild/sunos-x64": "0.27.7", + "@esbuild/win32-arm64": "0.27.7", + "@esbuild/win32-ia32": "0.27.7", + "@esbuild/win32-x64": "0.27.7" + } + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/fix-dts-default-cjs-exports": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/fix-dts-default-cjs-exports/-/fix-dts-default-cjs-exports-1.0.1.tgz", + "integrity": "sha512-pVIECanWFC61Hzl2+oOCtoJ3F17kglZC/6N94eRWycFgBH35hHx0Li604ZIzhseh97mf2p0cv7vVrOZGoqhlEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "magic-string": "^0.30.17", + "mlly": "^1.7.4", + "rollup": "^4.34.8" + } + }, + "node_modules/follow-redirects": { + "version": "1.16.0", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.16.0.tgz", + "integrity": "sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/form-data": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", + "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", + "dev": true, + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.3.tgz", + "integrity": "sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/joycon": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/joycon/-/joycon-3.1.1.tgz", + "integrity": "sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/lilconfig": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", + "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antonk52" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true, + "license": "MIT" + }, + "node_modules/load-tsconfig": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/load-tsconfig/-/load-tsconfig-0.2.5.tgz", + "integrity": "sha512-IXO6OCs9yg8tMKzfPZ1YmheJbZCiEsnBdcB03l0OcfK9prKnJb96siuHCr5Fl37/yo9DnKU+TLpxzTUspw9shg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + } + }, + "node_modules/lodash": { + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz", + "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==", + "license": "MIT" + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/lucide-react": { + "version": "0.576.0", + "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.576.0.tgz", + "integrity": "sha512-koNxU14BXrxUfZQ9cUaP0ES1uyPZKYDjk31FQZB6dQ/x+tXk979sVAn9ppZ/pVeJJyOxVM8j1E+8QEuSc02Vug==", + "dev": true, + "license": "ISC", + "peerDependencies": { + "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mlly": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.8.2.tgz", + "integrity": "sha512-d+ObxMQFmbt10sretNDytwt85VrbkhhUA/JBGm1MPaWJ65Cl4wOgLaB1NYvJSZ0Ef03MMEU/0xpPMXUIQ29UfA==", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^8.16.0", + "pathe": "^2.0.3", + "pkg-types": "^1.3.1", + "ufo": "^1.6.3" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/mz": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", + "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "any-promise": "^1.0.0", + "object-assign": "^4.0.1", + "thenify-all": "^1.0.0" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true, + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pirates": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz", + "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/pkg-types": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.3.1.tgz", + "integrity": "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "confbox": "^0.1.8", + "mlly": "^1.7.4", + "pathe": "^2.0.1" + } + }, + "node_modules/postcss-load-config": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-6.0.1.tgz", + "integrity": "sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "lilconfig": "^3.1.1" + }, + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "jiti": ">=1.21.0", + "postcss": ">=8.0.9", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + }, + "postcss": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/prop-types": { + "version": "15.8.1", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "dev": true, + "license": "MIT", + "dependencies": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" + } + }, + "node_modules/proxy-from-env": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-2.1.0.tgz", + "integrity": "sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/react": { + "version": "19.2.6", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.6.tgz", + "integrity": "sha512-sfWGGfavi0xr8Pg0sVsyHMAOziVYKgPLNrS7ig+ivMNb3wbCBw3KxtflsGBAwD3gYQlE/AEZsTLgToRrSCjb0Q==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "19.2.6", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.6.tgz", + "integrity": "sha512-0prMI+hvBbPjsWnxDLxlCGyM8PN6UuWjEUCYmZhO67xIV9Xasa/r/vDnq+Xyq4Lo27g8QSbO5YzARu0D1Sps3g==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "scheduler": "^0.27.0" + }, + "peerDependencies": { + "react": "^19.2.6" + } + }, + "node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/react-lifecycles-compat": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz", + "integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==", + "dev": true, + "license": "MIT" + }, + "node_modules/react-router": { + "version": "7.15.1", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.15.1.tgz", + "integrity": "sha512-R8rl9HhgikFYoPJymnUtPXWbnDb3oget6lQnfIoupbt61aT9aOhRkDsY2XRhZRyX1Z/8a5sL74fXmFNm3NRK5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "cookie": "^1.0.1", + "set-cookie-parser": "^2.6.0" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + } + } + }, + "node_modules/react-router-dom": { + "version": "7.15.1", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.15.1.tgz", + "integrity": "sha512-AzF62gjY6U9rkMq4RfP/r2EVtQ7DMfNMjyOp/flLTCrtRylLiK4wT4pSq6O8rOXZ2eXdZYJPEYe+ifomiv+Igg==", + "dev": true, + "license": "MIT", + "dependencies": { + "react-router": "7.15.1" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18" + } + }, + "node_modules/react-virtualized": { + "version": "9.22.6", + "resolved": "https://registry.npmjs.org/react-virtualized/-/react-virtualized-9.22.6.tgz", + "integrity": "sha512-U5j7KuUQt3AaMatlMJ0UJddqSiX+Km0YJxSqbAzIiGw5EmNz0khMyqP2hzgu4+QUtm+QPIrxzUX4raJxmVJnHg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.7.2", + "clsx": "^1.0.4", + "dom-helpers": "^5.1.3", + "loose-envify": "^1.4.0", + "prop-types": "^15.7.2", + "react-lifecycles-compat": "^3.0.4" + }, + "peerDependencies": { + "react": "^16.3.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^16.3.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/readdirp": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", + "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14.18.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/rollup": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.60.4.tgz", + "integrity": "sha512-WHeFSbZYsPu3+bLoNRUuAO+wavNlocOPf3wSHTP7hcFKVnJeWsYlCDbr3mTS14FCizf9ccIxXA8sGL8zKeQN3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.60.4", + "@rollup/rollup-android-arm64": "4.60.4", + "@rollup/rollup-darwin-arm64": "4.60.4", + "@rollup/rollup-darwin-x64": "4.60.4", + "@rollup/rollup-freebsd-arm64": "4.60.4", + "@rollup/rollup-freebsd-x64": "4.60.4", + "@rollup/rollup-linux-arm-gnueabihf": "4.60.4", + "@rollup/rollup-linux-arm-musleabihf": "4.60.4", + "@rollup/rollup-linux-arm64-gnu": "4.60.4", + "@rollup/rollup-linux-arm64-musl": "4.60.4", + "@rollup/rollup-linux-loong64-gnu": "4.60.4", + "@rollup/rollup-linux-loong64-musl": "4.60.4", + "@rollup/rollup-linux-ppc64-gnu": "4.60.4", + "@rollup/rollup-linux-ppc64-musl": "4.60.4", + "@rollup/rollup-linux-riscv64-gnu": "4.60.4", + "@rollup/rollup-linux-riscv64-musl": "4.60.4", + "@rollup/rollup-linux-s390x-gnu": "4.60.4", + "@rollup/rollup-linux-x64-gnu": "4.60.4", + "@rollup/rollup-linux-x64-musl": "4.60.4", + "@rollup/rollup-openbsd-x64": "4.60.4", + "@rollup/rollup-openharmony-arm64": "4.60.4", + "@rollup/rollup-win32-arm64-msvc": "4.60.4", + "@rollup/rollup-win32-ia32-msvc": "4.60.4", + "@rollup/rollup-win32-x64-gnu": "4.60.4", + "@rollup/rollup-win32-x64-msvc": "4.60.4", + "fsevents": "~2.3.2" + } + }, + "node_modules/scheduler": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", + "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/set-cookie-parser": { + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.2.tgz", + "integrity": "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==", + "dev": true, + "license": "MIT" + }, + "node_modules/source-map": { + "version": "0.7.6", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.6.tgz", + "integrity": "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">= 12" + } + }, + "node_modules/sucrase": { + "version": "3.35.1", + "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.1.tgz", + "integrity": "sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.2", + "commander": "^4.0.0", + "lines-and-columns": "^1.1.6", + "mz": "^2.7.0", + "pirates": "^4.0.1", + "tinyglobby": "^0.2.11", + "ts-interface-checker": "^0.1.9" + }, + "bin": { + "sucrase": "bin/sucrase", + "sucrase-node": "bin/sucrase-node" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/thenify": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", + "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "any-promise": "^1.0.0" + } + }, + "node_modules/thenify-all": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", + "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "thenify": ">= 3.1.0 < 4" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/tinyexec": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.2.tgz", + "integrity": "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyglobby": { + "version": "0.2.16", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.16.tgz", + "integrity": "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tree-kill": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", + "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", + "dev": true, + "license": "MIT", + "bin": { + "tree-kill": "cli.js" + } + }, + "node_modules/ts-interface-checker": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", + "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/tsup": { + "version": "8.5.1", + "resolved": "https://registry.npmjs.org/tsup/-/tsup-8.5.1.tgz", + "integrity": "sha512-xtgkqwdhpKWr3tKPmCkvYmS9xnQK3m3XgxZHwSUjvfTjp7YfXe5tT3GgWi0F2N+ZSMsOeWeZFh7ZZFg5iPhing==", + "dev": true, + "license": "MIT", + "dependencies": { + "bundle-require": "^5.1.0", + "cac": "^6.7.14", + "chokidar": "^4.0.3", + "consola": "^3.4.0", + "debug": "^4.4.0", + "esbuild": "^0.27.0", + "fix-dts-default-cjs-exports": "^1.0.0", + "joycon": "^3.1.1", + "picocolors": "^1.1.1", + "postcss-load-config": "^6.0.1", + "resolve-from": "^5.0.0", + "rollup": "^4.34.8", + "source-map": "^0.7.6", + "sucrase": "^3.35.0", + "tinyexec": "^0.3.2", + "tinyglobby": "^0.2.11", + "tree-kill": "^1.2.2" + }, + "bin": { + "tsup": "dist/cli-default.js", + "tsup-node": "dist/cli-node.js" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@microsoft/api-extractor": "^7.36.0", + "@swc/core": "^1", + "postcss": "^8.4.12", + "typescript": ">=4.5.0" + }, + "peerDependenciesMeta": { + "@microsoft/api-extractor": { + "optional": true + }, + "@swc/core": { + "optional": true + }, + "postcss": { + "optional": true + }, + "typescript": { + "optional": true + } + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/ufo": { + "version": "1.6.4", + "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.6.4.tgz", + "integrity": "sha512-JFNbkD1Svwe0KvGi8GOeLcP4kAWQ609twvCdcHxq1oSL8svv39ZuSvajcD8B+5D0eL4+s1Is2D/O6KN3qcTeRA==", + "dev": true, + "license": "MIT" + }, + "node_modules/uuid": { + "version": "13.0.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-13.0.2.tgz", + "integrity": "sha512-vzi9uRZ926x4XV73S/4qQaTwPXM2JBj6/6lI/byHH1jOpCzb0zDbfytgA9LcN/hzb2l7WQSQnxITOVx5un/wGw==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist-node/bin/uuid" + } + }, + "node_modules/zod": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.4.3.tgz", + "integrity": "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "packages/components": { + "name": "@maksit/webui-components", + "version": "0.1.0", + "dependencies": { + "@maksit/webui-contracts": "^0.1.0", + "@maksit/webui-core": "^0.1.0", + "date-fns": "^4.1.0", + "lodash": "^4.17.23", + "uuid": "^13.0.0" + }, + "devDependencies": { + "@tanstack/react-table": "^8.21.3", + "@types/lodash": "^4.17.24", + "@types/react": "^19.2.14", + "@types/react-dom": "^19.2.3", + "@types/react-virtualized": "^9.22.3", + "lucide-react": "^0.576.0", + "react": "^19.2.4", + "react-dom": "^19.2.4", + "react-router-dom": "^7.13.1", + "react-virtualized": "^9.22.6", + "tsup": "^8.5.0", + "typescript": "^5.9.3", + "zod": "^4.3.6" + }, + "peerDependencies": { + "@tanstack/react-table": "^8.0.0", + "lucide-react": "^0.500.0", + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0", + "react-router-dom": "^7.0.0", + "react-virtualized": "^9.22.0", + "zod": "^4.0.0" + } + }, + "packages/contracts": { + "name": "@maksit/webui-contracts", + "version": "0.1.0", + "devDependencies": { + "tsup": "^8.5.0", + "typescript": "^5.9.3", + "zod": "^4.3.6" + }, + "peerDependencies": { + "zod": "^4.0.0" + } + }, + "packages/core": { + "name": "@maksit/webui-core", + "version": "0.1.0", + "dependencies": { + "@maksit/webui-contracts": "^0.1.0", + "date-fns": "^4.1.0" + }, + "devDependencies": { + "@types/react": "^19.2.14", + "axios": "^1.13.2", + "react": "^19.2.4", + "tsup": "^8.5.0", + "typescript": "^5.9.3", + "zod": "^4.3.6" + }, + "peerDependencies": { + "axios": "^1.7.0", + "react": "^18.0.0 || ^19.0.0", + "zod": "^4.0.0" + } + } + } +} diff --git a/src/package.json b/src/package.json new file mode 100644 index 0000000..28dd05b --- /dev/null +++ b/src/package.json @@ -0,0 +1,22 @@ +{ + "name": "maksit-webui", + "private": true, + "version": "0.1.0", + "description": "Shared React UI library for MaksIT Certs UI and Vault WebUI", + "workspaces": [ + "packages/*" + ], + "engines": { + "node": ">=20" + }, + "overrides": { + "zod": "^4.3.6" + }, + "scripts": { + "build": "npm run build -w @maksit/webui-contracts && npm run build -w @maksit/webui-core && npm run build -w @maksit/webui-components", + "typecheck": "npm run typecheck --workspaces --if-present", + "clean": "npm run clean --workspaces --if-present" + }, + "author": "MaksIT", + "license": "MIT" +} diff --git a/src/packages/components/package.json b/src/packages/components/package.json new file mode 100644 index 0000000..0aeaab8 --- /dev/null +++ b/src/packages/components/package.json @@ -0,0 +1,66 @@ +{ + "name": "@maksit/webui-components", + "version": "0.1.0", + "description": "Shared React components for MaksIT WebUI apps", + "type": "module", + "main": "./dist/index.cjs", + "module": "./dist/index.js", + "types": "./dist/index.d.ts", + "exports": { + ".": { + "types": "./dist/index.d.ts", + "import": "./dist/index.js", + "require": "./dist/index.cjs" + } + }, + "files": [ + "dist", + "README.md" + ], + "scripts": { + "build": "tsup src/index.ts --format esm,cjs --dts --clean --external react --external react-dom --external react-router-dom --external lucide-react --external @tanstack/react-table --external react-virtualized", + "typecheck": "tsc -p tsconfig.json --noEmit", + "clean": "node -e \"require('fs').rmSync('dist',{recursive:true,force:true})\"", + "prepublishOnly": "npm run build" + }, + "publishConfig": { + "access": "public", + "registry": "https://registry.npmjs.org" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/MAKS-IT-COM/maksit-webui.git", + "directory": "src/packages/components" + }, + "dependencies": { + "@maksit/webui-contracts": "^0.1.0", + "@maksit/webui-core": "^0.1.0", + "date-fns": "^4.1.0", + "lodash": "^4.17.23", + "uuid": "^13.0.0" + }, + "peerDependencies": { + "@tanstack/react-table": "^8.0.0", + "lucide-react": "^0.500.0", + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0", + "react-router-dom": "^7.0.0", + "react-virtualized": "^9.22.0", + "zod": "^4.0.0" + }, + "devDependencies": { + "@tanstack/react-table": "^8.21.3", + "@types/lodash": "^4.17.24", + "@types/react": "^19.2.14", + "@types/react-dom": "^19.2.3", + "@types/react-virtualized": "^9.22.3", + "lucide-react": "^0.576.0", + "react": "^19.2.4", + "react-dom": "^19.2.4", + "react-router-dom": "^7.13.1", + "react-virtualized": "^9.22.6", + "tsup": "^8.5.0", + "typescript": "^5.9.3", + "zod": "^4.3.6" + } +} diff --git a/src/packages/components/src/components/DataTable/DataTable.tsx b/src/packages/components/src/components/DataTable/DataTable.tsx new file mode 100644 index 0000000..f6a5639 --- /dev/null +++ b/src/packages/components/src/components/DataTable/DataTable.tsx @@ -0,0 +1,476 @@ +import React, { useState, useMemo, useRef, useEffect } from 'react' +import { AutoSizer, MultiGrid, GridCellProps } from 'react-virtualized' + +import { mapPagedToDataTable, type DataTablePageView, type PagedResponse } from '@maksit/webui-core' +import { Plus, Trash2, Edit } from 'lucide-react' +import { debounce } from 'lodash' + + +interface FilterProps { + columnId: string +} + +interface CellProps { + columnId: string + data: T + value: T[K] +} + +export interface DataTableColumn { + id: string + accessorKey: K + header: string + filter: ( + props: FilterProps, + onFilterChange: (filterId: string, columnId: string, filters: string) => void + ) => React.ReactNode + cell: (props: CellProps) => React.ReactNode +} + +interface DataTableProps { + rawd?: PagedResponse | DataTablePageView + columns: DataTableColumn[] + maxRecordsPerPage?: number + + idFields?: string[] + + allowAddRow?: () => boolean + onAddRow?: () => void + allowEditRow?: (ids: Record) => boolean + onEditRow?: (ids: Record) => void + allowDeleteRow?: (ids: Record) => boolean + onDeleteRow?: (ids: Record) => void + + onFilterChange?: (filters: Record) => void + onPreviousPage?: (pageNumber: number) => void + onNextPage?: (pageNumber: number) => void + colspan?: 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 + + storageKey?: string +} + +const DEFAULT_ACTION_WIDTH = 80 +const DEFAULT_COL_WIDTH = 150 +const HEADER_ROWS = 2 +const ROW_HEIGHT = 40 + +function toDataTableView(rawd: PagedResponse | DataTablePageView | undefined): DataTablePageView { + return mapPagedToDataTable(rawd as PagedResponse | undefined) +} + +const DataTable = ,>(props: DataTableProps) => { + const { + rawd, + columns, + idFields = ['id'], + + allowAddRow = () => false, + onAddRow, + allowEditRow = (_) => false, + onEditRow, + allowDeleteRow = (_) => false, + onDeleteRow, + + onFilterChange, + onPreviousPage, + onNextPage, + colspan = 12, + storageKey, + } = props + + const { + items, + pageNumber, + pageSize, + totalCount, + totalPages, + hasPreviousPage, + hasNextPage, + } = toDataTableView(rawd) + + const gridRef = useRef(null) + const filterMeasureRef = useRef(null) + + const [selectedRowIndex, setSelectedRowIndex] = useState(null) + const [measuredFilterRowHeight, setMeasuredFilterRowHeight] = useState(0) + const filterValues = useRef>>({}) + + const [colWidths, setColWidths] = useState(() => { + const defaultWidths = [DEFAULT_ACTION_WIDTH, ...columns.map(() => DEFAULT_COL_WIDTH)] + + if (storageKey) { + try { + const stored = localStorage.getItem(storageKey) + if (stored) { + const parsed = JSON.parse(stored) + if (Array.isArray(parsed) && parsed.length === columns.length + 1) { + return parsed + } + localStorage.removeItem(storageKey) + return defaultWidths + } + } + catch { + return defaultWidths + } + } + + return defaultWidths + }) + + useEffect(() => { + if (storageKey) { + localStorage.setItem(storageKey, JSON.stringify(colWidths)) + } + if (gridRef.current) { + gridRef.current.recomputeGridSize() + gridRef.current.forceUpdateGrids() + } + }, [colWidths, storageKey]) + + useEffect(() => { + const el = filterMeasureRef.current + if (!el || columns.length === 0) return + const padding = 12 + const updateHeight = () => { + const contentHeight = el.offsetHeight + if (contentHeight <= 0) return + const total = contentHeight + padding + setMeasuredFilterRowHeight((prev) => (prev !== total ? total : prev)) + } + const ro = new ResizeObserver(() => { + updateHeight() + gridRef.current?.recomputeGridSize() + }) + ro.observe(el) + updateHeight() + return () => ro.disconnect() + }, [columns, colWidths]) + + useEffect(() => { + if (measuredFilterRowHeight && gridRef.current) { + gridRef.current.recomputeGridSize() + } + }, [measuredFilterRowHeight]) + + const debouncedOnFilterChange = useMemo( + () => (onFilterChange ? debounce(onFilterChange, 500) : undefined), + [onFilterChange] + ) + + const handleFilterChange = ( + filterId: string, + columnId: string, + filters: string + ) => { + const prev = filterValues.current + + const newValues = { + ...prev, + [filterId]: { + ...prev[filterId], + [columnId]: filters, + }, + } + filterValues.current = newValues + + const linqQueries = Object.fromEntries( + Object.entries(newValues).map(([fid, cols]) => { + const q = Object.values(cols) + .filter((v) => v) + .map((v) => `(${v})`) + .join(' && ') + return [fid, q] + }) + ) + + debouncedOnFilterChange?.(linqQueries) + } + + const handlePreviousPage = () => onPreviousPage?.(pageNumber - 1) + const handleNextPage = () => onNextPage?.(pageNumber + 1) + + + const getRealIdsFromRow = (rowIndex: number) => { + const row = items[rowIndex] + const ids = Object.fromEntries( + idFields.map((key) => [key, `${row[key]}`]) + ) + + return ids + } + + const handleAddRow = () => onAddRow?.() + const handleEditRow = (rowIndex: number) => { + const ids = getRealIdsFromRow(rowIndex) + + onEditRow?.(ids) + } + const handleDeleteRow = (rowIndex: number) => { + const ids = getRealIdsFromRow(rowIndex) + + onDeleteRow?.(ids) + } + + const handleAllowAddRow = () => { + return allowAddRow?.() + } + + const handleAllowEditRow = (rowIndex: number) => { + const ids = getRealIdsFromRow(rowIndex) + return allowEditRow?.(ids) + } + + const handleAllowDeleteRow = (rowIndex: number) => { + const ids = getRealIdsFromRow(rowIndex) + return allowDeleteRow?.(ids) + } + + const handleRowClick = (idx: number) => + setSelectedRowIndex((prev) => (prev === idx ? null : idx)) + + const handleHeaderResize = (colIdx: number, startX: number, startWidth: number) => { + const onMouseMove = (e: MouseEvent) => { + const delta = e.clientX - startX + setColWidths(prev => { + const next = [...prev] + next[colIdx] = Math.max(40, startWidth + delta) + return next + }) + } + const onMouseUp = () => { + window.removeEventListener('mousemove', onMouseMove) + window.removeEventListener('mouseup', onMouseUp) + } + window.addEventListener('mousemove', onMouseMove) + window.addEventListener('mouseup', onMouseUp) + } + + const cellRenderer = ({ columnIndex, key, rowIndex, style }: GridCellProps) => { + const isActionCol = columnIndex === 0 + const col = columns[columnIndex - 1] + + const commonClasses = [ + 'box-border', + 'flex', + 'items-center', + 'px-2', + 'py-1', + 'border-b', + 'border-r', + 'border-gray-200', + 'overflow-hidden', + 'whitespace-nowrap', + 'truncate', + rowIndex >= HEADER_ROWS ? 'cursor-pointer' : '', + rowIndex >= HEADER_ROWS && selectedRowIndex === rowIndex - HEADER_ROWS ? 'bg-sky-100' : '', + ].filter(Boolean).join(' ') + + if (rowIndex === 0) { + const allowAddRowResult = handleAllowAddRow() + + if (isActionCol) { + return ( +
+ +
+ ) + } + return ( +
+ {col.header} +
{ + e.preventDefault() + handleHeaderResize(columnIndex, e.clientX, colWidths[columnIndex]) + }} + /> +
+ ) + } + + if (rowIndex === 1) { + return isActionCol ? ( +
+ ) : ( +
+
+ {col.filter({ columnId: col.id }, handleFilterChange)} +
+
+ ) + } + + const dataIdx = rowIndex - HEADER_ROWS + if (isActionCol) { + const allowEditRowResult = handleAllowEditRow(dataIdx) + const allowDeleteRowResult = handleAllowDeleteRow(dataIdx) + + return ( +
handleRowClick(dataIdx)}> + + +
+ ) + } + + const row = items[dataIdx] + + return ( +
setSelectedRowIndex(dataIdx)} + > + {col.cell({ + columnId: col.id, + data: row, + value: row[col.accessorKey] + })} +
+ ) + } + + const handleGridScroll = ({ + scrollTop, + clientHeight, + scrollHeight, + }: { + scrollTop: number + clientHeight: number + scrollHeight: number + }) => { + if (scrollTop + clientHeight >= scrollHeight - 2 && hasNextPage) { + handleNextPage() + } + if (scrollTop <= 2 && hasPreviousPage) { + handlePreviousPage() + } + } + + return ( +
+ {columns[0] && ( +
+ {columns[0].filter({ columnId: columns[0].id }, handleFilterChange)} +
+ )} +
+ + {({ height, width }) => ( + colWidths[index]} + fixedColumnCount={1} + fixedRowCount={HEADER_ROWS} + height={height} + rowCount={items.length + HEADER_ROWS} + rowHeight={({ index }) => index === 1 ? measuredFilterRowHeight : ROW_HEIGHT} + width={width} + onScroll={({ scrollTop, clientHeight, scrollHeight }) => + handleGridScroll({ scrollTop, clientHeight, scrollHeight }) + } + /> + )} + +
+
+
+ Page Size: {pageSize} + Total Pages: {totalPages} + Total Count: {totalCount} +
+
+ + Page {pageNumber} of {totalPages} + +
+
+
+ ) +} + +export { DataTable } diff --git a/src/packages/components/src/components/DataTable/DataTableFilter.tsx b/src/packages/components/src/components/DataTable/DataTableFilter.tsx new file mode 100644 index 0000000..a0d1198 --- /dev/null +++ b/src/packages/components/src/components/DataTable/DataTableFilter.tsx @@ -0,0 +1,151 @@ +import { useMemo, useState } from 'react' +import { debounce } from 'lodash' + +interface FilterPropsBase { + filterId?: string + columnId: string + accessorKey: string + value?: FilterState + disabled?: boolean + onFilterChange?: (filterId: string, columnId: string, filters: string) => void +} + +interface NormalFilterProps extends FilterPropsBase { + type: 'normal' +} + +export type DataTableRemoteFilterDataSource = ( + filters: string +) => Promise + +interface RemoteFilterProps extends FilterPropsBase { + type: 'remote' + dataSource: DataTableRemoteFilterDataSource +} + +type FilterProps = NormalFilterProps | RemoteFilterProps + +interface FilterState { + value: string + operator: string +} + +function toPascalCase(s: string): string { + return s.length === 0 ? s : s.charAt(0).toUpperCase() + s.slice(1) +} + +const DataTableFilter = (props: FilterProps) => { + const { + type, + filterId = 'filters', + columnId, + accessorKey, + value = { value: '', operator: 'contains' }, + disabled = false, + onFilterChange, + } = props + + const [filterState, setFilterState] = useState(value) + + const debounceOnFilterChange = useMemo(() => { + if (!onFilterChange || type !== 'remote') + return + + const { dataSource } = props as RemoteFilterProps + + return debounce((filters: string) => { + void dataSource(filters).then((rows) => { + if (!rows) + return + + const linqQuery = rows.map((item) => `${columnId} == "${item.id}"`).join(' || ') + onFilterChange(filterId, columnId, linqQuery) + }) + }, 500) + }, [filterId, columnId, onFilterChange, props, type]) + + const handleFilterChange = (nextValue: string, operator: string) => { + setFilterState({ value: nextValue, operator }) + + if (nextValue === '') { + onFilterChange?.(filterId, columnId, '') + return + } + + const propName = toPascalCase(accessorKey) + let linqQuery = '' + + switch (operator) { + case 'contains': + linqQuery = `${propName}.Contains("${nextValue}")` + break + case 'startsWith': + linqQuery = `${propName}.StartsWith("${nextValue}")` + break + case 'endsWith': + linqQuery = `${propName}.EndsWith("${nextValue}")` + break + case '=': + linqQuery = `${propName} == "${nextValue}"` + break + case '!=': + linqQuery = `${propName} != "${nextValue}"` + break + case '>': + linqQuery = `${propName} > "${nextValue}"` + break + case '<': + linqQuery = `${propName} < "${nextValue}"` + break + case '>=': + linqQuery = `${propName} >= "${nextValue}"` + break + case '<=': + linqQuery = `${propName} <= "${nextValue}"` + break + default: + linqQuery = `${propName}.Contains("${nextValue}")` + break + } + + if (type === 'normal') { + onFilterChange?.(filterId, columnId, linqQuery) + } + + if (type === 'remote' && debounceOnFilterChange) { + debounceOnFilterChange(linqQuery) + } + } + + return ( +
+ handleFilterChange(e.target.value, filterState.operator)} + disabled={disabled} + /> + +
+ ) +} + +export { DataTableFilter } +export type { FilterProps as DataTableFilterProps, FilterState as DataTableFilterState } diff --git a/src/packages/components/src/components/DataTable/DataTableLabel.tsx b/src/packages/components/src/components/DataTable/DataTableLabel.tsx new file mode 100644 index 0000000..d53c41c --- /dev/null +++ b/src/packages/components/src/components/DataTable/DataTableLabel.tsx @@ -0,0 +1,57 @@ +import { useEffect, useMemo, useState } from 'react' +import { formatISODateString } from '@maksit/webui-core' + +interface NormalLabelProps { + type: 'normal' + value?: string + dataType?: 'string' | 'date' +} + +export type DataTableRemoteLabelDataSource> = () => Promise + +interface RemoteLabelProps> { + type: 'remote' + accessorKey: keyof T & string + dataSource: DataTableRemoteLabelDataSource +} + +type LabelProps> = NormalLabelProps | RemoteLabelProps + +const DataTableLabel = >(props: LabelProps) => { + const [remoteLabel, setRemoteLabel] = useState('') + + const label = useMemo(() => { + if (props.type !== 'normal') + return remoteLabel + + const { value = '', dataType = 'string' } = props + + switch (dataType) { + case 'date': + return formatISODateString(value) + case 'string': + default: + return value + } + }, [props, remoteLabel]) + + useEffect(() => { + if (props.type !== 'remote') + return + + const { dataSource, accessorKey } = props + + void dataSource().then((payload) => { + if (!payload) + return + + const value = payload[accessorKey] + setRemoteLabel(value != null ? String(value) : '') + }) + }, [props]) + + return

{label}

+} + +export { DataTableLabel } +export type { LabelProps as DataTableLabelProps } diff --git a/src/packages/components/src/components/DataTable/helpers.ts b/src/packages/components/src/components/DataTable/helpers.ts new file mode 100644 index 0000000..b7f23c1 --- /dev/null +++ b/src/packages/components/src/components/DataTable/helpers.ts @@ -0,0 +1,12 @@ +import type { DataTableColumn } from './DataTable' + +const createColumn = (col: DataTableColumn): DataTableColumn => { + return col +} + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +const createColumns = (cols: DataTableColumn[]) => { + return cols as unknown as DataTableColumn[] +} + +export { createColumn, createColumns } diff --git a/src/packages/components/src/components/DataTable/index.ts b/src/packages/components/src/components/DataTable/index.ts new file mode 100644 index 0000000..6395abf --- /dev/null +++ b/src/packages/components/src/components/DataTable/index.ts @@ -0,0 +1,28 @@ +import { + DataTable, + DataTableColumn +} from './DataTable' + +import { + createColumn, + createColumns +} from './helpers' + +export type { + DataTableColumn +} + +export { DataTableFilter } from './DataTableFilter' +export type { + DataTableFilterProps, + DataTableFilterState, + DataTableRemoteFilterDataSource, +} from './DataTableFilter' +export { DataTableLabel } from './DataTableLabel' +export type { DataTableLabelProps, DataTableRemoteLabelDataSource } from './DataTableLabel' + +export { + DataTable, + createColumn, + createColumns +} diff --git a/src/packages/components/src/components/FormLayout/FormContainer.tsx b/src/packages/components/src/components/FormLayout/FormContainer.tsx new file mode 100644 index 0000000..61cb34b --- /dev/null +++ b/src/packages/components/src/components/FormLayout/FormContainer.tsx @@ -0,0 +1,19 @@ +import { FC, ReactNode } from 'react' + +interface FormContainerProps { + children?: ReactNode +} + +const FormContainer: FC = (props) => { + const { + children + } = props + + return
+ {children} +
+} + +export { + FormContainer +} \ No newline at end of file diff --git a/src/packages/components/src/components/FormLayout/FormContent.tsx b/src/packages/components/src/components/FormLayout/FormContent.tsx new file mode 100644 index 0000000..2860dca --- /dev/null +++ b/src/packages/components/src/components/FormLayout/FormContent.tsx @@ -0,0 +1,23 @@ +import { FC, ReactNode } from 'react' + +interface FormContentProps { + children?: ReactNode + /** Merged after base layout; use e.g. `flex flex-col overflow-hidden` when a child should fill height (iframe). */ + className?: string +} + +const FormContent: FC = (props) => { + const { + children, + className + } = props + + const base = 'bg-gray-100 w-full h-full min-h-0 p-4' + return
+ {children} +
+} + +export { + FormContent +} \ No newline at end of file diff --git a/src/packages/components/src/components/FormLayout/FormFooter.tsx b/src/packages/components/src/components/FormLayout/FormFooter.tsx new file mode 100644 index 0000000..a9e64b9 --- /dev/null +++ b/src/packages/components/src/components/FormLayout/FormFooter.tsx @@ -0,0 +1,31 @@ +import { FC, ReactNode } from 'react' + + +interface FormFooterProps { + children?: ReactNode, + leftChildren?: ReactNode, + rightChildren?: ReactNode +} + +const FormFooter: FC = (props) => { + + const { + children, + leftChildren, + rightChildren + } = props + + return
+ {children ?? <> +
{leftChildren}
+
{rightChildren}
+ } + + +
+ +} + +export { + FormFooter +} \ No newline at end of file diff --git a/src/packages/components/src/components/FormLayout/FormHeader.tsx b/src/packages/components/src/components/FormLayout/FormHeader.tsx new file mode 100644 index 0000000..9d1d53c --- /dev/null +++ b/src/packages/components/src/components/FormLayout/FormHeader.tsx @@ -0,0 +1,19 @@ +import { FC, ReactNode } from 'react' + +interface FormHeaderProps { + children?: ReactNode +} + +const FormHeader: FC = (props) => { + const { + children + } = props + + return

+ {children} +

+} + +export { + FormHeader +} \ No newline at end of file diff --git a/src/packages/components/src/components/FormLayout/index.ts b/src/packages/components/src/components/FormLayout/index.ts new file mode 100644 index 0000000..807d595 --- /dev/null +++ b/src/packages/components/src/components/FormLayout/index.ts @@ -0,0 +1,11 @@ +import { FormContainer } from './FormContainer' +import { FormHeader } from './FormHeader' +import { FormContent } from './FormContent' +import { FormFooter } from './FormFooter' + +export { + FormContainer, + FormHeader, + FormContent, + FormFooter +} \ No newline at end of file diff --git a/src/packages/components/src/components/Layout/Container.tsx b/src/packages/components/src/components/Layout/Container.tsx new file mode 100644 index 0000000..c59c335 --- /dev/null +++ b/src/packages/components/src/components/Layout/Container.tsx @@ -0,0 +1,17 @@ +import { FC, ReactNode } from 'react' + +interface ContentProps { + children: ReactNode +} + +const Container: FC = (props) => { + const { children } = props + + return
+ {children} +
+} + +export { + Container +} \ No newline at end of file diff --git a/src/packages/components/src/components/Layout/Content.tsx b/src/packages/components/src/components/Layout/Content.tsx new file mode 100644 index 0000000..62141df --- /dev/null +++ b/src/packages/components/src/components/Layout/Content.tsx @@ -0,0 +1,17 @@ +import { FC, ReactNode } from 'react' + +interface ContentProps { + children: ReactNode +} + +const Content: FC = (props) => { + const { children } = props + + return
+ {children} +
+} + +export { + Content +} \ No newline at end of file diff --git a/src/packages/components/src/components/Layout/Footer.tsx b/src/packages/components/src/components/Layout/Footer.tsx new file mode 100644 index 0000000..c0da84f --- /dev/null +++ b/src/packages/components/src/components/Layout/Footer.tsx @@ -0,0 +1,16 @@ +import { FC, ReactNode } from 'react' + +export interface FooterProps { + children: ReactNode +} + +const Footer: FC = (props) => { + const { children } = props + return
+ {children} +
+} + +export { + Footer +} \ No newline at end of file diff --git a/src/packages/components/src/components/Layout/Header.tsx b/src/packages/components/src/components/Layout/Header.tsx new file mode 100644 index 0000000..4f11e30 --- /dev/null +++ b/src/packages/components/src/components/Layout/Header.tsx @@ -0,0 +1,19 @@ +import { FC, ReactNode } from 'react' + +export interface HeaderProps { + children: ReactNode +} + +const Header: FC = (props) => { + const { children } = props + + return
+
+ {children} +
+
+} + +export { + Header +} \ No newline at end of file diff --git a/src/packages/components/src/components/Layout/MainContainer.tsx b/src/packages/components/src/components/Layout/MainContainer.tsx new file mode 100644 index 0000000..a04016e --- /dev/null +++ b/src/packages/components/src/components/Layout/MainContainer.tsx @@ -0,0 +1,17 @@ +import { FC, ReactNode } from 'react' + +interface ContainerProps { + children: ReactNode +} + +const MainContainer: FC = (props) => { + const { children } = props + + return
+ {children} +
+} + +export { + MainContainer +} \ No newline at end of file diff --git a/src/packages/components/src/components/Layout/SideMenu/Container.tsx b/src/packages/components/src/components/Layout/SideMenu/Container.tsx new file mode 100644 index 0000000..dfa4d95 --- /dev/null +++ b/src/packages/components/src/components/Layout/SideMenu/Container.tsx @@ -0,0 +1,19 @@ +import { FC, ReactNode } from 'react' + +export interface ContainerProps { + headerChildren?: ReactNode + children: ReactNode + footerChildren?: ReactNode +} + +const Container: FC = (props) => { + const { children } = props + + return +} + +export { + Container +} \ No newline at end of file diff --git a/src/packages/components/src/components/Layout/SideMenu/Content.tsx b/src/packages/components/src/components/Layout/SideMenu/Content.tsx new file mode 100644 index 0000000..816b68b --- /dev/null +++ b/src/packages/components/src/components/Layout/SideMenu/Content.tsx @@ -0,0 +1,17 @@ +import { FC, ReactNode } from 'react' + +interface ContentProps { + children: ReactNode +} + +const Content: FC = (props) => { + const { children } = props + + return
+ {children} +
+} + +export { + Content +} \ No newline at end of file diff --git a/src/packages/components/src/components/Layout/SideMenu/Footer.tsx b/src/packages/components/src/components/Layout/SideMenu/Footer.tsx new file mode 100644 index 0000000..796fc49 --- /dev/null +++ b/src/packages/components/src/components/Layout/SideMenu/Footer.tsx @@ -0,0 +1,16 @@ +import { FC, ReactNode } from 'react' + +export interface FooterProps { + children: ReactNode +} + +const Footer: FC = (props) => { + const { children } = props + return
+ {children} +
+} + +export { + Footer +} \ No newline at end of file diff --git a/src/packages/components/src/components/Layout/SideMenu/Header.tsx b/src/packages/components/src/components/Layout/SideMenu/Header.tsx new file mode 100644 index 0000000..d2f541f --- /dev/null +++ b/src/packages/components/src/components/Layout/SideMenu/Header.tsx @@ -0,0 +1,19 @@ +import { FC, ReactNode } from 'react' + +export interface HeaderProps { + children: ReactNode +} + +const Header: FC = (props) => { + const { children } = props + + return
+
+ {children} +
+
+} + +export { + Header +} \ No newline at end of file diff --git a/src/packages/components/src/components/Layout/SideMenu/index.tsx b/src/packages/components/src/components/Layout/SideMenu/index.tsx new file mode 100644 index 0000000..37eb9bd --- /dev/null +++ b/src/packages/components/src/components/Layout/SideMenu/index.tsx @@ -0,0 +1,32 @@ +import { FC, ReactNode } from 'react' +import { Container } from '../Container' +import { Header } from './Header' +import { Content } from './Content' +import { Footer } from './Footer' + + +export interface SideMenuProps { + headerChildren?: ReactNode + children: ReactNode + footerChildren?: ReactNode +} + +const SideMenu: FC = (props) => { + const { headerChildren, children, footerChildren } = props + + return +
+ {headerChildren} +
+ + {children} + +
+ {footerChildren} +
+
+} + +export { + SideMenu +} \ No newline at end of file diff --git a/src/packages/components/src/components/Layout/index.tsx b/src/packages/components/src/components/Layout/index.tsx new file mode 100644 index 0000000..74ac88f --- /dev/null +++ b/src/packages/components/src/components/Layout/index.tsx @@ -0,0 +1,40 @@ +import { FC } from 'react' +import { MainContainer } from './MainContainer' +import { SideMenu, SideMenuProps } from './SideMenu' +import { Container } from './Container' +import { Header, HeaderProps } from './Header' +import { Footer, FooterProps } from './Footer' +import { Content } from './Content' + +interface LayoutProps { + sideMenu: SideMenuProps + header: HeaderProps + children: React.ReactNode + footer: FooterProps +} + +const Layout: FC = (props) => { + const { sideMenu, header, children, footer } = props + + return + + {sideMenu.children} + + +
+ {header.children} +
+ + {children} + +
{footer.children}
+
+
+} + +export { + Layout +} \ No newline at end of file diff --git a/src/packages/components/src/components/LazyLoadTable.tsx b/src/packages/components/src/components/LazyLoadTable.tsx new file mode 100644 index 0000000..68abda5 --- /dev/null +++ b/src/packages/components/src/components/LazyLoadTable.tsx @@ -0,0 +1,86 @@ +import { FC, useEffect, useRef, useState } from 'react' + +interface LazyLoadTableColumnProps { + key: string + title: string + dataIndex: string + renderColumn?: (value: unknown) => React.ReactNode +} + +interface LazyLoadTableProps { + data: Record[] + columns: LazyLoadTableColumnProps[] + loadMore: () => void + colspan?: 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 +} + +const LazyLoadTable: FC = (props) => { + const { + data, + columns, + loadMore, + colspan = 6 + } = props + + const [selectedRowIndex, setSelectedRowIndex] = useState(null) + const observerRef = useRef(null) + + useEffect(() => { + const observer = new IntersectionObserver( + (entries) => { + if (entries[0].isIntersecting) { + loadMore() + } + }, + { threshold: 1.0 } + ) + + if (observerRef.current) { + observer.observe(observerRef.current) + } + + return () => { + if (observerRef.current) { + observer.unobserve(observerRef.current) + } + } + }, [loadMore]) + + const handleRowClick = (index: number) => { + setSelectedRowIndex(selectedRowIndex === index ? null : index) + } + + return ( +
+ + + + {columns.map((column, index) => ( + + ))} + + + + {data.map((row, index) => ( + handleRowClick(index)} + > + {columns.map((column, colIndex) => ( + + ))} + + ))} + +
{column.title}
+ {column.renderColumn + ? column.renderColumn(row[column.dataIndex]) + : String(row[column.dataIndex] ?? '')} +
+
Loading more...
+
+ ) +} + +export { LazyLoadTable } \ No newline at end of file diff --git a/src/packages/components/src/components/Offcanvas.tsx b/src/packages/components/src/components/Offcanvas.tsx new file mode 100644 index 0000000..6ca892e --- /dev/null +++ b/src/packages/components/src/components/Offcanvas.tsx @@ -0,0 +1,56 @@ +import { FC, ReactNode, useCallback, useEffect } from 'react' + +export interface OffcanvasProps { + children: ReactNode + isOpen?: boolean + onOpen?: () => void + onClose?: () => void + colspan?: 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 +} + +const Offcanvas: FC = (props) => { + const { + children, + isOpen = false, + onOpen, + onClose, + colspan = 6 + } = props + + const handleOnOpen = useCallback(() => { + onOpen?.() + }, [onOpen]) + + const handleOnClose = useCallback(() => { + onClose?.() + }, [onClose]) + + useEffect(() => { + if (isOpen) handleOnOpen() + else handleOnClose() + }, [isOpen, handleOnOpen, handleOnClose]) + + const leftSpan = 12 - colspan + + return ( +
+
+ {/* colonna di offset */} +
+ {/* area principale */} +
+ {children} +
+
+
+ ) +} + +export { Offcanvas } diff --git a/src/packages/components/src/components/Scopes/EntityScopesSummary.tsx b/src/packages/components/src/components/Scopes/EntityScopesSummary.tsx new file mode 100644 index 0000000..44ec519 --- /dev/null +++ b/src/packages/components/src/components/Scopes/EntityScopesSummary.tsx @@ -0,0 +1,55 @@ +import type { SearchEntityScopeEntry } from '@maksit/webui-contracts' + +export interface EntityScopesSummaryProps { + entries: SearchEntityScopeEntry[] + title?: string + /** Maps scope entity type enum value to a display label (e.g. app `ScopeEntityType`). */ + formatScopeEntityType?: (scopeEntityType: TScopeEntityType) => string +} + +const permChars = (e: SearchEntityScopeEntry): string => { + const parts: string[] = [] + if (e.read) parts.push('R') + if (e.write) parts.push('W') + if (e.delete) parts.push('D') + if (e.create) parts.push('C') + return parts.length ? parts.join('') : '—' +} + +export const EntityScopesSummary = ({ + entries, + title = 'Scopes', + formatScopeEntityType, +}: EntityScopesSummaryProps) => { + if (!entries?.length) { + return ( +
+

{title}

+

No scopes.

+
+ ) + } + + const scopeLabel = (type: TScopeEntityType) => + formatScopeEntityType?.(type) ?? String(type) + + return ( +
+

+ {title} +

+
    + {entries.map((entry, idx) => ( +
  • + + {scopeLabel(entry.scopeEntityType)}: {entry.entityName ?? entry.entityId} + + + {permChars(entry)} + +
  • + ))} +
+
+ ) +} diff --git a/src/packages/components/src/components/Scopes/index.ts b/src/packages/components/src/components/Scopes/index.ts new file mode 100644 index 0000000..26833da --- /dev/null +++ b/src/packages/components/src/components/Scopes/index.ts @@ -0,0 +1,2 @@ +export { EntityScopesSummary } from './EntityScopesSummary' +export type { EntityScopesSummaryProps } from './EntityScopesSummary' diff --git a/src/packages/components/src/components/Toast/addToast.ts b/src/packages/components/src/components/Toast/addToast.ts new file mode 100644 index 0000000..9c28084 --- /dev/null +++ b/src/packages/components/src/components/Toast/addToast.ts @@ -0,0 +1,13 @@ +// Define the types for the toast +interface AddToastProps { + message: string; + type: 'info' | 'success' | 'warning' | 'error'; + duration?: number; +} + +export const addToast = (message: string, type: 'info' | 'success' | 'warning' | 'error', duration?: number): void => { +const event = new CustomEvent('add-toast', { + detail: { message, type, duration }, +}) +window.dispatchEvent(event) +} \ No newline at end of file diff --git a/src/packages/components/src/components/Toast/index.tsx b/src/packages/components/src/components/Toast/index.tsx new file mode 100644 index 0000000..74260f3 --- /dev/null +++ b/src/packages/components/src/components/Toast/index.tsx @@ -0,0 +1,75 @@ +import { useState, useEffect, FC } from 'react' +import { v4 as uuidv4 } from 'uuid' + +// Define types for a toast +interface Toast { + id: string; + message: string; + type: 'info' | 'success' | 'warning' | 'error'; + duration?: number; +} + +const Toast: FC = () => { + const [toasts, setToasts] = useState([]) + + useEffect(() => { + const handleAddToast = (event: CustomEvent) => { + const { message, type, duration } = event.detail + + // Add the new toast, avoiding duplicates with same message & type + const id = uuidv4() + setToasts(prev => { + const hasDuplicate = prev.some(t => t.message === message && t.type === type) + if (hasDuplicate) return prev + + return [...prev, { id, message, type, duration }] + }) + + // Auto-remove if a duration is specified + if (duration) { + setTimeout(() => { + setToasts((prev) => prev.filter((toast) => toast.id !== id)) + }, duration) + } + } + + // Listen for the custom event + window.addEventListener('add-toast', handleAddToast as EventListener) + + return () => { + // Cleanup event listener on component unmount + window.removeEventListener('add-toast', handleAddToast as EventListener) + } + }, []) + + // Remove toast manually + const handleClose = (id: string) => { + setToasts((prev) => prev.filter((toast) => toast.id !== id)) + } + + return ( +
+ {toasts.map((toast) => ( +
+ {toast.message} + +
+ ))} +
+ ) +} + +export { + Toast +} \ No newline at end of file diff --git a/src/packages/components/src/components/editors/ButtonComponent.tsx b/src/packages/components/src/components/editors/ButtonComponent.tsx new file mode 100644 index 0000000..067d2c1 --- /dev/null +++ b/src/packages/components/src/components/editors/ButtonComponent.tsx @@ -0,0 +1,85 @@ +import { ReactNode } from 'react' +import { Link } from 'react-router-dom' + +interface CommonButtonProps { + colspan?: 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12; + route?: string; + buttonHierarchy?: 'primary' | 'secondary' | 'success' | 'error' | 'warning'; + onClick?: () => void; + disabled?: boolean; +} + +type ButtonComponentProps = + | ({ label: string; children?: never } & CommonButtonProps) + | ({ children: ReactNode; label?: never } & CommonButtonProps); + +const ButtonComponent: React.FC = (props) => { + const { + colspan, + route, + buttonHierarchy, + onClick, + disabled = false + } = props + + const isChildren = 'children' in props && props.children !== undefined + const content = 'label' in props ? props.label : props.children + + const handleClick = (e?: React.MouseEvent) => { + if (disabled) { + e?.preventDefault() + return + } + onClick?.() + } + + let buttonClass = '' + switch (buttonHierarchy) { + case 'primary': + buttonClass = 'bg-blue-500 text-white' + break + case 'secondary': + buttonClass = 'bg-gray-500 text-white' + break + case 'success': + buttonClass = 'bg-green-500 text-white' + break + case 'warning': + buttonClass = 'bg-yellow-500 text-white' + break + case 'error': + buttonClass = 'bg-red-500 text-white' + break + default: + buttonClass = 'bg-blue-500 text-white' + break + } + + const disabledClass = disabled ? 'opacity-50 cursor-default' : 'cursor-pointer' + + const centeringClass = isChildren ? 'flex justify-center items-center' : 'text-center' + + return route + ? ( + + {content} + + ) : ( + + ) +} + +export { ButtonComponent } \ No newline at end of file diff --git a/src/packages/components/src/components/editors/CheckBoxComponent.tsx b/src/packages/components/src/components/editors/CheckBoxComponent.tsx new file mode 100644 index 0000000..c97fc2c --- /dev/null +++ b/src/packages/components/src/components/editors/CheckBoxComponent.tsx @@ -0,0 +1,54 @@ +import { useEffect, useRef } from 'react' +import { FieldContainer } from './FieldContainer' + +interface CheckBoxComponentProps { + colspan?: 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12; + label: string; + value: boolean; + onChange?: (e: React.ChangeEvent) => void; + errorText?: string; + disabled?: boolean; +} + +const CheckBoxComponent: React.FC = (props) => { + + const { + colspan = 6, + label, + value, + onChange, + errorText, + disabled = false + } = props + + const prevValue = useRef(value) + + useEffect(() => { + prevValue.current = value + }, [value]) + + const handleOnChange = (e: React.ChangeEvent) => { + if (prevValue.current === e.target.checked) + return + + prevValue.current = e.target.checked + + onChange?.(e) + } + + return ( + + + + ) +} + +export { + CheckBoxComponent +} \ No newline at end of file diff --git a/src/packages/components/src/components/editors/DateTimePickerComponent.tsx b/src/packages/components/src/components/editors/DateTimePickerComponent.tsx new file mode 100644 index 0000000..a4a15eb --- /dev/null +++ b/src/packages/components/src/components/editors/DateTimePickerComponent.tsx @@ -0,0 +1,193 @@ +import { ChangeEvent, FC, useState, useEffect, useRef } from 'react' +import { parseISO, formatISO, format, getDaysInMonth, addMonths, subMonths } from 'date-fns' +import { ButtonComponent } from './ButtonComponent' +import { TextBoxComponent } from './TextBoxComponent' +import { CircleX } from 'lucide-react' +import { FieldContainer } from './FieldContainer' +import { getInputClasses } from './editorStyles' + +const DISPLAY_FORMAT = 'yyyy-MM-dd HH:mm' + +interface DateTimePickerComponentProps { + colspan?: 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 + label: string + value?: string + onChange?: (isoString?: string) => void + errorText?: string + placeholder?: string + readOnly?: boolean + disabled?: boolean +} + +const DateTimePickerComponent: FC = ({ + colspan = 6, + label, + value, + onChange, + errorText, + placeholder, + readOnly = false, + disabled = false, +}) => { + const prevValueRef = useRef(value) + const parsedValue = value ? parseISO(value) : undefined + + const [showDropdown, setShowDropdown] = useState(false) + const [currentViewDate, setCurrentViewDate] = useState(parsedValue || new Date()) + const [tempDate, setTempDate] = useState(parsedValue || new Date()) + + const dropdownRef = useRef(null) + + const formatForDisplay = (date: Date) => format(date, DISPLAY_FORMAT) + + const daysCount = getDaysInMonth(currentViewDate) + const daysArray = Array.from({ length: daysCount }, (_, i) => i + 1) + + const handlePrevMonth = () => setCurrentViewDate((prev) => subMonths(prev, 1)) + const handleNextMonth = () => setCurrentViewDate((prev) => addMonths(prev, 1)) + + const handleDayClick = (day: number) => { + const newDate = new Date(tempDate) + newDate.setFullYear(currentViewDate.getFullYear(), currentViewDate.getMonth(), day) + setTempDate(newDate) + } + + const handleTimeChange = (e: ChangeEvent) => { + const [hours, minutes] = e.target.value.split(':').map(Number) + const newDate = new Date(tempDate) + newDate.setHours(hours, minutes, 0, 0) + setTempDate(newDate) + } + + const handleClear = () => { + if (prevValueRef.current !== undefined) { + onChange?.(undefined) + prevValueRef.current = undefined + } + setShowDropdown(false) + } + + const handleConfirm = () => { + const isoString = formatISO(tempDate, { representation: 'complete' }) + if (isoString !== prevValueRef.current) { + onChange?.(isoString) + prevValueRef.current = isoString + } + setShowDropdown(false) + } + + const handleOpen = () => { + if (readOnly || disabled) { + return + } + + const newDate = parsedValue || new Date() + setCurrentViewDate(newDate) + setTempDate(newDate) + prevValueRef.current = value + setShowDropdown(true) + } + + const actionButtons = () => { + const className = 'p-1 text-gray-600 hover:text-gray-800 bg-white' + return [ + !!value && !readOnly && ( + + ), + ].filter(Boolean) + } + + // Close dropdown when clicking outside + useEffect(() => { + const handleClickOutside = (event: MouseEvent) => { + if (dropdownRef.current && !dropdownRef.current.contains(event.target as Node)) { + setShowDropdown(false) + } + } + + if (showDropdown) { + document.addEventListener('mousedown', handleClickOutside) + } + + return () => { + document.removeEventListener('mousedown', handleClickOutside) + } + }, [showDropdown]) + + return ( + +
+ + + {/* Fixed Action Buttons */} +
+ {actionButtons()} +
+ + {showDropdown && !readOnly && !disabled && ( +
+
+ + {format(currentViewDate, 'MMMM yyyy')} + +
+
+ {daysArray.map((day) => ( +
handleDayClick(day)} + className={`p-1.5 cursor-pointer text-center text-sm ${ + tempDate.getDate() === day && + tempDate.getMonth() === currentViewDate.getMonth() && + tempDate.getFullYear() === currentViewDate.getFullYear() + ? 'bg-blue-500 text-white rounded' + : 'hover:bg-gray-200 rounded' + }`} + > + {day} +
+ ))} +
+
+ +
+
+ + +
+
+ )} +
+
+ ) +} + +export { DateTimePickerComponent } diff --git a/src/packages/components/src/components/editors/DualListboxComponent.tsx b/src/packages/components/src/components/editors/DualListboxComponent.tsx new file mode 100644 index 0000000..93963c3 --- /dev/null +++ b/src/packages/components/src/components/editors/DualListboxComponent.tsx @@ -0,0 +1,99 @@ +import React, { useState } from 'react' +import { FieldContainer } from './FieldContainer' + +interface DualListboxComponentProps { + label?: string; + availableItemsLabel?: string; + selectedItemsLabel?: string; + colspan?: 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12; + + idFieldName?: string; + availableItems: string[]; + selectedItems: string[]; + onChange: (selectedItems: string[]) => void; + errorText?: string; +} + +const DualListboxComponent: React.FC = (props) => { + + const { + label, + availableItemsLabel = 'Available Items', + selectedItemsLabel = 'Selected Items', + colspan = 6, + availableItems, + selectedItems, + onChange, + errorText + } = props + + const [available, setAvailable] = useState(availableItems) + const [selected, setSelected] = useState(selectedItems) + + const moveToSelected = () => { + const movedItems = available.filter(item => selected.includes(item)) + setAvailable(available.filter(item => !movedItems.includes(item))) + setSelected([...selected, ...movedItems]) + onChange([...selected, ...movedItems]) + } + + const moveToAvailable = () => { + const movedItems = selected.filter(item => !available.includes(item)) + setSelected(selected.filter(item => !movedItems.includes(item))) + setAvailable([...available, ...movedItems]) + onChange(selected.filter(item => !movedItems.includes(item))) + } + + return ( + +
+
+

{availableItemsLabel}

+
    + {available.map(item => ( +
  • setAvailable(available.filter(i => i !== item))} + > + {item} +
  • + ))} +
+
+
+ + +
+
+

{selectedItemsLabel}

+
    + {selected.map(item => ( +
  • setSelected(selected.filter(i => i !== item))} + > + {item} +
  • + ))} +
+
+
+
+ ) +} + +export { + DualListboxComponent +} \ No newline at end of file diff --git a/src/packages/components/src/components/editors/FieldContainer.tsx b/src/packages/components/src/components/editors/FieldContainer.tsx new file mode 100644 index 0000000..0459be1 --- /dev/null +++ b/src/packages/components/src/components/editors/FieldContainer.tsx @@ -0,0 +1,28 @@ +import { FC, ReactNode } from 'react' + +interface FieldContainerProps { + colspan?: 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12; + label?: string; + errorText?: string; + children: ReactNode +} + +const FieldContainer: FC = (props) => { + const { + colspan, + label, + errorText, + children + } = props + + return
+ + {children} +

{errorText || '\u00A0'}

+
+} + + +export { + FieldContainer +} \ No newline at end of file diff --git a/src/packages/components/src/components/editors/FileUploadComponent.tsx b/src/packages/components/src/components/editors/FileUploadComponent.tsx new file mode 100644 index 0000000..1ee1cb3 --- /dev/null +++ b/src/packages/components/src/components/editors/FileUploadComponent.tsx @@ -0,0 +1,182 @@ +import React, { useRef, useState } from 'react' +import { ButtonComponent } from './ButtonComponent' +import { TrashIcon } from 'lucide-react' + +interface FileUploadComponentProps { + label?: string + colspan?: 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 + multiple?: boolean + files?: File[] + onChange?: (files: File[]) => void + disabled?: boolean +} + +const FileUploadComponent: React.FC = ({ + label = 'Select files', + colspan = 6, + multiple = true, + files, + onChange, + disabled = false, +}) => { + const [selectedFiles, setSelectedFiles] = useState([]) + const [showPopup, setShowPopup] = useState(false) + const [popupPos, setPopupPos] = useState<{x: number, y: number}>({x: 0, y: 0}) + const popupRef = useRef(null) + const inputRef = useRef(null) + + // Focus popup when it opens + React.useEffect(() => { + if (showPopup && popupRef.current) { + popupRef.current.focus() + } + }, [showPopup]) + + const areFilesEqual = (left: File[], right: File[]) => + left.length === right.length && + left.every((file, index) => { + const other = right[index] + return other && + file.name === other.name && + file.size === other.size && + file.lastModified === other.lastModified && + file.type === other.type + }) + + const displayFiles = files ?? selectedFiles + + // Keep native input in sync for controlled resets. + React.useEffect(() => { + if (files !== undefined && files.length === 0 && inputRef.current) + inputRef.current.value = '' + }, [files]) + + const handleFileChange = (e: React.ChangeEvent) => { + const nextFiles = e.target.files ? Array.from(e.target.files) : [] + + if (files === undefined) { + setSelectedFiles(nextFiles) + } + + if (!areFilesEqual(nextFiles, displayFiles)) { + onChange?.(nextFiles) + } + } + + const handleClear = () => { + if (files === undefined) { + setSelectedFiles([]) + } + + if (inputRef.current) inputRef.current.value = '' + + if (displayFiles.length > 0) { + onChange?.([]) + } + } + + const handleSelectFiles = () => { + if (!disabled) inputRef.current?.click() + } + + return ( +
+ {/* File input (hidden) */} + + + {/* Files counter with hover popup */} +
{ + setShowPopup(true) + if (!showPopup) { + setPopupPos({ x: e.clientX, y: e.clientY }) + } + }} + onMouseLeave={e => { + // Only close if not moving into popup + if (!popupRef.current || !popupRef.current.contains(e.relatedTarget as Node)) { + setShowPopup(false) + } + }} + > + + {displayFiles.length} file{displayFiles.length !== 1 ? 's' : ''} + + {showPopup && displayFiles.length > 0 && ( +
{ + // Only close if focus moves outside popup and counter + if (!e.relatedTarget || (!popupRef.current?.contains(e.relatedTarget as Node) && !(e.relatedTarget as HTMLElement)?.closest('.col-span-1'))) { + setShowPopup(false) + } + }} + onMouseLeave={e => { + // Only close if not moving back to counter + const parent = (e.relatedTarget as HTMLElement)?.closest('.col-span-1') + if (!parent) setShowPopup(false) + }} + onKeyDown={e => { + if (e.key === 'Escape') setShowPopup(false) + }} + onFocus={() => {}} + > +
    + {displayFiles.map((file, idx) => ( +
  • + {file.name} +
  • + ))} +
+
+ )} + + +
+ + {/* Clear selection button */} + + + + + {/* Select files button */} + + +
+ ) +} + +export { FileUploadComponent } diff --git a/src/packages/components/src/components/editors/ListBoxComponent.tsx b/src/packages/components/src/components/editors/ListBoxComponent.tsx new file mode 100644 index 0000000..abab283 --- /dev/null +++ b/src/packages/components/src/components/editors/ListBoxComponent.tsx @@ -0,0 +1,60 @@ +import React, { useState } from 'react' +import { FieldContainer } from './FieldContainer' + +interface ListboxComponentProps { + label?: string; + itemsLabel?: string; + colspan?: 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12; + items: string[]; + onChange: (items: string[]) => void; + errorText?: string; +} + +const ListboxComponent: React.FC = (props) => { + + const { + label, + itemsLabel = 'Items', + colspan = 6, + items, + onChange, + errorText + } = props + + const [selectedItems, setSelectedItems] = useState([]) + + const toggleItemSelection = (item: string) => { + if (selectedItems.includes(item)) { + const updatedSelection = selectedItems.filter(i => i !== item) + setSelectedItems(updatedSelection) + onChange(updatedSelection) + } else { + const updatedSelection = [...selectedItems, item] + setSelectedItems(updatedSelection) + onChange(updatedSelection) + } + } + + return ( + +
+

{itemsLabel}

+
    + {items.map(item => ( +
  • toggleItemSelection(item)} + > + {item} +
  • + ))} +
+
+
+ ) +} + +export { + ListboxComponent +} diff --git a/src/packages/components/src/components/editors/RadioGroupComponent.tsx b/src/packages/components/src/components/editors/RadioGroupComponent.tsx new file mode 100644 index 0000000..5ac5d37 --- /dev/null +++ b/src/packages/components/src/components/editors/RadioGroupComponent.tsx @@ -0,0 +1,77 @@ +import React, { useEffect, useRef, useState } from 'react' +import { FieldContainer } from './FieldContainer' + +interface RadioOption { + value: string + label: string +} + +interface RadioGroupComponentProps { + options: RadioOption[] + label?: string + colspan?: 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 + value?: string + onChange?: (value: string) => void + errorText?: string + readOnly?: boolean + disabled?: boolean +} + +const RadioGroupComponent: React.FC = (props) => { + const { + options, + label, + colspan = 6, + value = '', + onChange, + errorText, + readOnly = false, + disabled = false + } = props + + const prevValue = useRef(value) + const [selectedValue, setSelectedValue] = useState(value) + + useEffect(() => { + prevValue.current = value + setSelectedValue(value) + }, [value]) + + const handleOptionChange = (val: string) => { + if (readOnly || disabled) return + if (prevValue.current === val) return + prevValue.current = val + setSelectedValue(val) + onChange?.(val) + } + + return ( + +
+ {options.map(option => { + // Use default cursor (arrow) if disabled or readOnly, else pointer + const isInactive = disabled || readOnly + return ( + + ) + })} +
+
+ ) +} + +export { RadioGroupComponent } diff --git a/src/packages/components/src/components/editors/RemoteSelectBoxComponent.tsx b/src/packages/components/src/components/editors/RemoteSelectBoxComponent.tsx new file mode 100644 index 0000000..9f37bb4 --- /dev/null +++ b/src/packages/components/src/components/editors/RemoteSelectBoxComponent.tsx @@ -0,0 +1,100 @@ +import { useState, useCallback, ChangeEvent, useEffect, useRef } from 'react' +import type { PagedRequest } from '@maksit/webui-contracts' +import type { SearchResponseBase } from '@maksit/webui-contracts' +import { deepEqual } from '@maksit/webui-core' +import { SelectBoxComponent } from './SelectBoxComponent' + +export type RemoteSelectSearchDataSource = ( + request: TRequest, + options?: { showLoader?: boolean } +) => Promise + +export interface RemoteSelectBoxProps { + dataSource: RemoteSelectSearchDataSource + additionalFilters?: TRequest + label: string + colspan?: 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 + errorText?: string + idField?: string + labelField?: string + filterFields?: string[] + value?: string | number + onChange: (e: ChangeEvent) => void + placeholder?: string + readOnly?: boolean +} + +const RemoteSelectBoxComponent = (props: RemoteSelectBoxProps) => { + const { + dataSource, + additionalFilters, + label, + colspan = 12, + errorText, + idField = 'id', + labelField = 'name', + filterFields = ['name'], + value = '', + onChange, + placeholder, + readOnly = false, + } = props + + const [options, setOptions] = useState([]) + const prevPagedRequest = useRef(null) + const dataSourceRef = useRef(dataSource) + dataSourceRef.current = dataSource + + const handleFilterChange = useCallback((filters?: string, showLoader: boolean = false) => { + const pagedRequest = { + pageSize: 10, + filters, + ...additionalFilters, + } as TRequest + + if (deepEqual(pagedRequest, prevPagedRequest.current)) + return + + prevPagedRequest.current = pagedRequest + + void dataSourceRef.current(pagedRequest, { showLoader }) + .then((items) => { + if (!items) + return + + setOptions(items) + }) + .catch((error) => { + console.error('RemoteSelectBox fetch error:', error) + }) + }, [additionalFilters]) + + useEffect(() => { + handleFilterChange(undefined, true) + }, [handleFilterChange]) + + return ( + { + const row = item as unknown as Record + const labelRaw = row[labelField] ?? row.name ?? row.id + return { + value: item.id, + label: labelRaw != null ? String(labelRaw) : item.id, + } + })} + idField={idField} + filterFields={filterFields} + onFilterChange={(text) => handleFilterChange(text, false)} + value={value} + onChange={onChange} + errorText={errorText} + readOnly={readOnly} + /> + ) +} + +export { RemoteSelectBoxComponent } diff --git a/src/packages/components/src/components/editors/SecretComponent.tsx b/src/packages/components/src/components/editors/SecretComponent.tsx new file mode 100644 index 0000000..b15db07 --- /dev/null +++ b/src/packages/components/src/components/editors/SecretComponent.tsx @@ -0,0 +1,106 @@ +import { Copy, Dices, Eye, EyeOff } from 'lucide-react' +import { ChangeEvent, FC, useRef, useState } from 'react' +import { FieldContainer } from './FieldContainer' +import { getInputClasses } from './editorStyles' + +export type SecretDataSource = () => Promise + +export interface SecretComponentProps { + label: string + colspan?: 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 + errorText?: string + value?: string + onChange?: (e: ChangeEvent) => void + placeholder?: string + readOnly?: boolean + enableCopy?: boolean + enableGenerate?: boolean + /** Fetches a new secret value when the generate button is used. */ + dataSource?: SecretDataSource +} + +const SecretComponent: FC = ({ + label, + colspan = 12, + errorText, + value = '', + onChange, + placeholder, + readOnly = false, + enableCopy = false, + enableGenerate = false, + dataSource, +}) => { + const prevValue = useRef(value) + const [showPassword, setShowPassword] = useState(false) + + const handleOnChange = (e: ChangeEvent) => { + if (prevValue.current === e.target.value) + return + + prevValue.current = e.target.value + onChange?.(e) + } + + const handleGenerateSecret = () => { + if (!dataSource) + return + + void dataSource().then((secret) => { + if (!secret) + return + + handleOnChange({ + target: { value: secret }, + } as ChangeEvent) + }) + } + + const handleCopy = async () => { + if (value) + await navigator.clipboard.writeText(value) + } + + const hasContent = String(value).length > 0 + const actionButtonClass = 'p-1 text-gray-600 hover:text-gray-800 bg-white' + + return ( + +
+ +
+ {hasContent && ( + + )} + {enableGenerate && !readOnly && dataSource && ( + + )} + {enableCopy && hasContent && ( + + )} +
+
+
+ ) +} + +export { SecretComponent } diff --git a/src/packages/components/src/components/editors/SelectBoxComponent.tsx b/src/packages/components/src/components/editors/SelectBoxComponent.tsx new file mode 100644 index 0000000..73c1064 --- /dev/null +++ b/src/packages/components/src/components/editors/SelectBoxComponent.tsx @@ -0,0 +1,204 @@ +import { debounce } from 'lodash' +import { CircleX } from 'lucide-react' +import { ChangeEvent, FC, useCallback, useEffect, useMemo, useRef, useState } from 'react' +import { FieldContainer } from './FieldContainer' +import { getInputClasses } from './editorStyles' + +export interface SelectBoxComponentOption { + value: string | number + label: string +} + +interface SelectBoxComponentProps { + label: string + colspan?: 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 + errorText?: string + options?: SelectBoxComponentOption[] + + // Field used to compare with the value + idField?: string + // Fields to search against when filtering options + filterFields?: string[] + // Callback function called with a filter string, debounced + onFilterChange?: (filters: string) => void + + value?: string | number + onChange?: (e: ChangeEvent) => void + placeholder?: string + readOnly?: boolean + disabled?: boolean +} + +const SelectBoxComponent: FC = (props) => { + + const { + label, + colspan = 12, + errorText, + options = [], + + idField = 'id', + filterFields, + onFilterChange, + + value = '', + onChange, + placeholder, + readOnly = false, + disabled = false, + } = props + + // Local state to control dropdown visibility and current filter text + const [showDropdown, setShowDropdown] = useState(false) + const [filterValue, setFilterValue] = useState('') + + // Memoized debounced callback for filter changes. + const debounceOnFilterChange = useMemo(() => { + return onFilterChange ? debounce(onFilterChange, 500) : undefined + }, [onFilterChange]) + + // Refs to store previous values to detect changes + const initRef = useRef(false) + const prevFilterValue = useRef(filterValue) + + // Update the selected value and notify parent via onValueChange callback. + const handleValueChange = useCallback( + (newValue: string | number) => { + // Simulate a ChangeEvent with the new value + onChange?.({ target: { value: newValue } } as ChangeEvent) + }, + [onChange] + ) + + // Handle input changes for filtering options. + const handleFilterChange = useCallback( + (e: ChangeEvent) => { + if (disabled) return + const newFilter = e.target.value + setFilterValue(newFilter) + + // If filter value hasn't changed, exit early. + if (prevFilterValue.current === newFilter) { + return + } + + // Build a filter query string based on the filterFields. + const query = filterFields + ?.map((field) => `${field}.Contains("${newFilter}")`) + .filter(Boolean) + .join(' || ') ?? '' + + // If debounced filter callback is provided, invoke it. + if (debounceOnFilterChange) { + debounceOnFilterChange(query) + } + + // Clear the selected value when user types in filter. + if (showDropdown) { + handleValueChange('') + } + + prevFilterValue.current = newFilter + }, + [filterFields, debounceOnFilterChange, showDropdown, handleValueChange, disabled] + ) + + const selectedOption = options.find((option) => option.value === value) + const inputValue = showDropdown ? filterValue : (selectedOption?.label ?? '') + + // Fetch the selected option when the parent provides only the id. + useEffect(() => { + if (value === '' || selectedOption) { + return + } + + if (debounceOnFilterChange && !initRef.current) { + debounceOnFilterChange(`${idField} == "${value}"`) + initRef.current = true + } + }, [value, selectedOption, idField, debounceOnFilterChange]) + + // Handle click on an option from the dropdown. + const handleOptionClick = (optionValue: string | number) => { + if (disabled) return + // Update the selected value. + handleValueChange(optionValue) + // Update the input to display the selected option's label. + const selectedOption = options.find((option) => option.value === optionValue) + setFilterValue(selectedOption?.label ?? '') + // Close the dropdown. + setShowDropdown(false) + } + + const actionButtons = () => { + const className = 'p-1 text-gray-600 hover:text-gray-800 bg-white' + if (disabled) return null + return [ + !!filterValue && !readOnly && ( + + ), + ].filter(Boolean) + } + + return ( + +
+ +
+ { if (!disabled) setShowDropdown(true) }} + // Delay closing dropdown to allow click events on options. + onBlur={() => setTimeout(() => setShowDropdown(false), 200)} + /> + + {/* Action Buttons */} +
+ {actionButtons()} +
+
+ + {showDropdown && !disabled && ( +
+ {options.length > 0 ? ( + options.map((option) => ( +
handleOptionClick(option.value)} + > + {option.label} +
+ )) + ) : ( +
No options found
+ )} +
+ )} +
+
+ ) +} + +export { SelectBoxComponent } diff --git a/src/packages/components/src/components/editors/TextBoxComponent.tsx b/src/packages/components/src/components/editors/TextBoxComponent.tsx new file mode 100644 index 0000000..9aa8c61 --- /dev/null +++ b/src/packages/components/src/components/editors/TextBoxComponent.tsx @@ -0,0 +1,110 @@ +import { Eye, EyeOff } from 'lucide-react' +import { ChangeEvent, FC, useEffect, useRef, useState } from 'react' +import { FieldContainer } from './FieldContainer' +import { getInputClasses } from './editorStyles' + +interface TextBoxComponentProps { + label: string + colspan?: 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 + errorText?: string + value?: string | number + onChange?: (e: ChangeEvent) => void + type?: 'text' | 'password' | 'textarea' | 'number' | 'email' | 'time' + placeholder?: string + readOnly?: boolean + disabled?: boolean +} + +const TextBoxComponent: FC = (props) => { + + const { + label, + colspan = 12, + errorText, + value = '', + onChange, + type = 'text', + placeholder, + readOnly = false, + disabled = false, + } = props + + const prevValue = useRef(value) + + useEffect(() => { + prevValue.current = value + }, [value]) + + // Stato locale per gestire la visibilità della password + const [showPassword, setShowPassword] = useState(false) + + const handleOnChange = (e: ChangeEvent) => { + if (prevValue.current === e.target.value) + return + + prevValue.current = e.target.value + + onChange?.(e) + } + + // Se il type è "textarea", comportamento invariato + if (type === 'textarea') { + return ( + +