diff --git a/.aspire/settings.json b/.aspire/settings.json new file mode 100644 index 000000000..deede96df --- /dev/null +++ b/.aspire/settings.json @@ -0,0 +1,3 @@ +{ + "appHostPath": "../src/src.AppHost/src.AppHost.csproj" +} \ No newline at end of file diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json deleted file mode 100644 index 45434e736..000000000 --- a/.devcontainer/devcontainer.json +++ /dev/null @@ -1,38 +0,0 @@ -// For format details, see https://aka.ms/devcontainer.json. For config options, see the -// README at: https://github.com/devcontainers/templates/tree/main/src/dotnet -{ - "name": "AccountGo (.NET)", - // Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile - "image": "mcr.microsoft.com/devcontainers/dotnet:0-7.0-bullseye", - "features": { - "ghcr.io/devcontainers/features/azure-cli:1": {}, - "ghcr.io/devcontainers/features/git:1": {}, - "ghcr.io/dhoeric/features/google-cloud-cli:1": {}, - "ghcr.io/warrenbuckley/codespace-features/sqlite:1": {}, - "ghcr.io/devcontainers/features/docker-in-docker:1": { - "version": "latest", - "moby": true - }, - "ghcr.io/devcontainers/features/node:1": {} - } - - // Features to add to the dev container. More info: https://containers.dev/features. - // "features": {}, - - // Use 'forwardPorts' to make a list of ports inside the container available locally. - // "forwardPorts": [5000, 5001], - // "portsAttributes": { - // "5001": { - // "protocol": "https" - // } - // } - - // Use 'postCreateCommand' to run commands after the container is created. - // "postCreateCommand": "dotnet restore", - - // Configure tool-specific properties. - // "customizations": {}, - - // Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root. - // "remoteUser": "root" -} diff --git a/.github/workflows/build-deploy-azure.yml b/.github/workflows/build-deploy-azure.yml deleted file mode 100644 index eb332b2cd..000000000 --- a/.github/workflows/build-deploy-azure.yml +++ /dev/null @@ -1,19 +0,0 @@ -name: Docker Image CI - -on: - push: - branches: [ "main" ] - pull_request: - branches: [ "main" ] - workflow_dispatch: - -jobs: - - build: - - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v3 - - name: Build the Docker image - run: docker-compose build diff --git a/.github/workflows/gdbapi.yml b/.github/workflows/gdbapi.yml new file mode 100644 index 000000000..30eb05c91 --- /dev/null +++ b/.github/workflows/gdbapi.yml @@ -0,0 +1,94 @@ +# Docs for the Azure Web Apps Deploy action: https://github.com/Azure/webapps-deploy +# More GitHub Actions for Azure: https://github.com/Azure/actions + +name: Build and deploy Good Deed Books API to Azure Web App - gdbapi + +on: + push: + branches: + - main + workflow_dispatch: + +jobs: + build: + runs-on: windows-latest + + steps: + - uses: actions/checkout@v4 + + - name: Set up .NET Core + uses: actions/setup-dotnet@v4 + with: + dotnet-version: '9.x' + + - name: Install dotnet-ef tool + run: | + dotnet tool install --global dotnet-ef + echo "++++ dotnet-ef version" + dotnet ef --version + + - name: Install dotnet aspire workload + run: | + dotnet workload install aspire + + - name: Build with dotnet + run: | + echo "++++ dotnet build" + dotnet build --configuration Release + + - name: Run unit tests + run: dotnet test ./test/GoodBooks.BackendTests/GoodBooks.BackendTests.csproj --configuration Release + + - name: Add migrations + run: | + echo "++++ current directory" + pwd + echo "++++ add ApplicationIdentityDbContext migration IdentityMig" + dotnet ef migrations add IdentityMig --project ./src/Api/ --startup-project ./src/Api/Api.csproj --msbuildprojectextensionspath .build/obj/Api/ --context ApplicationIdentityDbContext --output-dir Data/Migrations/IdentityDb + echo "++++ add ApiDbContext migration ApiMig" + dotnet ef migrations add ApiMig --project ./src/Api/ --startup-project ./src/Api/Api.csproj --msbuildprojectextensionspath .build/obj/Api/ --context ApiDbContext --output-dir Data/Migrations/ApiDb + echo "++++ contents of ./src/Api/Data/Migrations/IdentityDb" + ls ./src/Api/Data/Migrations/IdentityDb + echo "++++ contents of ./src/Api/Data/Migrations/ApiDb" + ls ./src/Api/Data/Migrations/ApiDb + + - name: dotnet publish + run: | + echo "++++ contents of dotnet publish ./src/Api/Api.csproj" + dotnet publish ./src/Api/Api.csproj -f net9.0 -c Release -o "${{runner.temp}}/myapp" + + - name: Upload artifact for deployment job + uses: actions/upload-artifact@v4 + with: + name: .net-app + path: ${{runner.temp}}/myapp + + deploy: + runs-on: windows-latest + needs: build + environment: + name: 'Production' + url: ${{ steps.deploy-to-webapp.outputs.webapp-url }} + permissions: + id-token: write #This is required for requesting the JWT + + steps: + - name: Download artifact from build job + uses: actions/download-artifact@v4 + with: + name: .net-app + + - name: Login to Azure + uses: azure/login@v2 + with: + client-id: ${{ secrets.AZUREAPPSERVICE_CLIENTID_A17E281C175C4E629A76134AA823BAC5 }} + tenant-id: ${{ secrets.AZUREAPPSERVICE_TENANTID_258CF23452C24D9795BD94B25EF50B73 }} + subscription-id: ${{ secrets.AZUREAPPSERVICE_SUBSCRIPTIONID_9375B274C69740D39F4770D5D433E8B1 }} + + - name: Deploy to Azure Web App + id: deploy-to-webapp + uses: azure/webapps-deploy@v3 + with: + app-name: 'gdbapi' + slot-name: 'Production' + package: . diff --git a/.github/workflows/gdbmvc_tar.yml b/.github/workflows/gdbmvc_tar.yml new file mode 100644 index 000000000..4939fb8e4 --- /dev/null +++ b/.github/workflows/gdbmvc_tar.yml @@ -0,0 +1,99 @@ +# Docs for the Azure Web Apps Deploy action: https://github.com/Azure/webapps-deploy +# More GitHub Actions for Azure: https://github.com/Azure/actions +name: Build and deploy Good Deed Books MVC project to Azure +on: + push: + branches: + - main + workflow_dispatch: +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Set up .NET Core + uses: actions/setup-dotnet@v4 + with: + dotnet-version: '9.x' + include-prerelease: true + + - name: Install dotnet aspire workload + run: | + dotnet workload install aspire + + - name: Build with dotnet + run: dotnet build --configuration Release + + - name: Run unit tests + run: dotnet test ./test/GoodBooks.BackendTests/GoodBooks.BackendTests.csproj --configuration Release + + - name: dotnet publish + run: dotnet publish ./src/AccountGoWeb/AccountGoWeb.csproj -c Release -o ${{runner.temp}}/myapp + + - name: Archive production artifacts + run: | + echo "+++++++++++++++++++++++++ where am I? ++++++++++++++++++++++++" + pwd + echo "+++++++++++++++++++++++++ save current directory into a variable dir ++++" + dir=$(pwd) + echo "+++++++++ what is in variable dir ++++++++++++++" + echo $dir + echo "++++++++++++++++++++++++ what's in current directory? ++++++++" + ls -al + echo "+++++ what's in the ${{runner.temp}}/myapp directory? ++++" + ls -al ${{runner.temp}}/myapp + echo "+++++ change directory to ${{runner.temp}}/myapp ++++" + cd ${{runner.temp}}/myapp + echo "+++++++++++++++++++++++++ where am I? ++++++++++++++++++++++++" + pwd + echo "+++++++++++++++++++++++++ compress current directory and save in $dir/my_artifact.tar.gz ++++" + tar -czvf $dir/my_artifact.tar.gz . + echo "+++++++++++++++++++++++++ change back to $dir directory ++++" + cd $dir + echo "++++++++++++++++++++++++ what's in $dir directory? ++++++++" + ls -al + + - name: Upload artifact for deployment job + uses: actions/upload-artifact@v4 + with: + name: .net-app + path: my_artifact.tar.gz + + deploy: + runs-on: ubuntu-latest + needs: build + environment: + name: 'Production' + url: ${{ steps.deploy-to-webapp.outputs.webapp-url }} + permissions: + id-token: write #This is required for requesting the JWT + + steps: + - name: Download artifact from build job + uses: actions/download-artifact@v4 + with: + name: .net-app + + - name: Login to Azure + uses: azure/login@v2 + with: + client-id: ${{ secrets.AZUREAPPSERVICE_CLIENTID_8B6389BB3F37413FB2483AC2574C3BCB }} + tenant-id: ${{ secrets.AZUREAPPSERVICE_TENANTID_FD62C59DE5DC42C2A07DB8191A522348 }} + subscription-id: ${{ secrets.AZUREAPPSERVICE_SUBSCRIPTIONID_7076EF307FDA4C11BC99A0A7A0943794 }} + + - name: Extract artifacts + run: | + tar -xzvf my_artifact.tar.gz -C . + + - name: Set startup command + run: | + az webapp config set --resource-group goodbooks-RG --name gdbmvc --startup-file "dotnet /home/site/wwwroot/GoodBooks.dll" + + - name: Deploy to Azure Web App + id: deploy-to-webapp + uses: azure/webapps-deploy@v3 + with: + app-name: 'gdbmvc' + slot-name: 'Production' + package: . diff --git a/.gitignore b/.gitignore index fbb3738a8..6272404dc 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,9 @@ ## Ignore Visual Studio temporary files, build results, and ## files generated by popular Visual Studio add-ons. +# custom 2024/04/29 +.idea + # User-specific files *.suo *.user @@ -18,6 +21,7 @@ build/ bld/ [Bb]in/ [Oo]bj/ +node_modules/ # Roslyn cache directories *.ide/ @@ -186,6 +190,10 @@ FakesAssemblies/ # Lib folder generated by gulpfile.js **/src/[Ww]eb[Aa]ngular/wwwroot/[Ll]ib/* + + +**/src/[Rr]eact[Ff]ront[Ee]nd/wwwroot/* + **/src/[Ww]eb[Aa]pp/wwwroot/app/scripts/* **/src/[Ww]eb[Aa]pp/wwwroot/app/compiledscripts/* **/src/[Ww]eb[Aa]pp/wwwroot/app/typescripts/compiledscripts/* @@ -215,7 +223,8 @@ FakesAssemblies/ /src/Api/Plugins/* /src/AccountGoWeb/Modules/* /src/AccountGoWeb/Plugins/* -/src/Api/Data/Migrations +# /src/Api/Data/Migrations .vscode -exclude \ No newline at end of file +exclude +/src/Api/appsettings.Development.json diff --git a/accountgo.sln b/accountgo.sln index 4eaa3f47d..29ef7bb4d 100644 --- a/accountgo.sln +++ b/accountgo.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 15 -VisualStudioVersion = 15.0.26228.4 +# Visual Studio Version 17 +VisualStudioVersion = 17.8.34322.80 MinimumVisualStudioVersion = 10.0.40219.1 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Presentation", "Presentation", "{0295DFAC-BF6E-46C0-A63D-FBE9AF3C04E5}" EndProject @@ -19,62 +19,234 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AccountGoWeb", "src\Account EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Dto", "src\Dto\Dto.csproj", "{1E610F55-2D74-4856-818B-0D0B47601B75}" EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{E1B45442-3F2D-491A-9D8A-0DDA50309A1A}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Infrastructure", "src\Infrastructure\Infrastructure.csproj", "{EBFAFB5B-494F-48D5-A70D-AF1490B9260A}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{0EAC5155-A5EA-49C1-8E0C-19DD36D2C21C}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Module.Tests", "test\Module.Tests\Module.Tests.csproj", "{54631590-2A41-45F4-B057-92C840ED08C1}" EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Modules", "Modules", "{E0861852-0F5B-4810-8586-A59038BC4034}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GoodBooks.BackendTests", "test\GoodBooks.BackendTests\GoodBooks.BackendTests.csproj", "{C59F300E-4BAC-4329-9A41-8F1D75A7E197}" +EndProject +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{EF0BD6F1-00D6-41E5-91AB-8B606D35D448}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BlazorGDB", "src\BlazorGDB\BlazorGDB\BlazorGDB.csproj", "{AB5F238F-AB78-4A85-8D8D-17E211015FD3}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BlazorGDB.Client", "src\BlazorGDB\BlazorGDB.Client\BlazorGDB.Client.csproj", "{12BE663C-C0DD-4343-93DF-6B2D853B6B79}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LibraryGDB", "src\LibraryGDB\LibraryGDB.csproj", "{F64790E0-86AD-4562-9AC5-F4DD3F4881BA}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Aspire", "Aspire", "{64880D93-BAB4-FF83-898C-B934B68C31A9}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "src.ServiceDefaults", "src\src.ServiceDefaults\src.ServiceDefaults.csproj", "{949C95E9-4261-416E-8D2A-F05E3D4640CA}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "src.AppHost", "src\src.AppHost\src.AppHost.csproj", "{ADDBCE30-FE7F-4198-8A37-772EF6BF3676}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SampleModule", "src\Modules\SampleModule\SampleModule.csproj", "{B296277A-C822-444E-8CFA-4CC4C1C1F737}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MigrationService", "src\MigrationService\MigrationService.csproj", "{DF084D96-707B-47C2-9493-85FA84631ACE}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SampleNetStandard20", "test\SampleModules\SampleNetStandard20\SampleNetStandard20.csproj", "{B0AB6EA7-7D53-4457-9482-F0613F99E3BB}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GoodBooks.ServicesTests", "test\GoodBooks.ServicesTests\GoodBooks.ServicesTests.csproj", "{1764E3C7-CAAF-4FE9-9104-CF26D8121FC9}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 Release|Any CPU = Release|Any CPU + Release|x64 = Release|x64 + Release|x86 = Release|x86 EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {9B652491-4E9C-45E0-BE5B-EA6AF892F380}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {9B652491-4E9C-45E0-BE5B-EA6AF892F380}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9B652491-4E9C-45E0-BE5B-EA6AF892F380}.Debug|x64.ActiveCfg = Debug|Any CPU + {9B652491-4E9C-45E0-BE5B-EA6AF892F380}.Debug|x64.Build.0 = Debug|Any CPU + {9B652491-4E9C-45E0-BE5B-EA6AF892F380}.Debug|x86.ActiveCfg = Debug|Any CPU + {9B652491-4E9C-45E0-BE5B-EA6AF892F380}.Debug|x86.Build.0 = Debug|Any CPU {9B652491-4E9C-45E0-BE5B-EA6AF892F380}.Release|Any CPU.ActiveCfg = Release|Any CPU {9B652491-4E9C-45E0-BE5B-EA6AF892F380}.Release|Any CPU.Build.0 = Release|Any CPU + {9B652491-4E9C-45E0-BE5B-EA6AF892F380}.Release|x64.ActiveCfg = Release|Any CPU + {9B652491-4E9C-45E0-BE5B-EA6AF892F380}.Release|x64.Build.0 = Release|Any CPU + {9B652491-4E9C-45E0-BE5B-EA6AF892F380}.Release|x86.ActiveCfg = Release|Any CPU + {9B652491-4E9C-45E0-BE5B-EA6AF892F380}.Release|x86.Build.0 = Release|Any CPU {C02DECC9-2A82-42C0-8F26-D0AE6559AC5E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {C02DECC9-2A82-42C0-8F26-D0AE6559AC5E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C02DECC9-2A82-42C0-8F26-D0AE6559AC5E}.Debug|x64.ActiveCfg = Debug|Any CPU + {C02DECC9-2A82-42C0-8F26-D0AE6559AC5E}.Debug|x64.Build.0 = Debug|Any CPU + {C02DECC9-2A82-42C0-8F26-D0AE6559AC5E}.Debug|x86.ActiveCfg = Debug|Any CPU + {C02DECC9-2A82-42C0-8F26-D0AE6559AC5E}.Debug|x86.Build.0 = Debug|Any CPU {C02DECC9-2A82-42C0-8F26-D0AE6559AC5E}.Release|Any CPU.ActiveCfg = Release|Any CPU {C02DECC9-2A82-42C0-8F26-D0AE6559AC5E}.Release|Any CPU.Build.0 = Release|Any CPU + {C02DECC9-2A82-42C0-8F26-D0AE6559AC5E}.Release|x64.ActiveCfg = Release|Any CPU + {C02DECC9-2A82-42C0-8F26-D0AE6559AC5E}.Release|x64.Build.0 = Release|Any CPU + {C02DECC9-2A82-42C0-8F26-D0AE6559AC5E}.Release|x86.ActiveCfg = Release|Any CPU + {C02DECC9-2A82-42C0-8F26-D0AE6559AC5E}.Release|x86.Build.0 = Release|Any CPU {09096FEC-DA29-4914-B046-CD280220C52A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {09096FEC-DA29-4914-B046-CD280220C52A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {09096FEC-DA29-4914-B046-CD280220C52A}.Debug|x64.ActiveCfg = Debug|Any CPU + {09096FEC-DA29-4914-B046-CD280220C52A}.Debug|x64.Build.0 = Debug|Any CPU + {09096FEC-DA29-4914-B046-CD280220C52A}.Debug|x86.ActiveCfg = Debug|Any CPU + {09096FEC-DA29-4914-B046-CD280220C52A}.Debug|x86.Build.0 = Debug|Any CPU {09096FEC-DA29-4914-B046-CD280220C52A}.Release|Any CPU.ActiveCfg = Release|Any CPU {09096FEC-DA29-4914-B046-CD280220C52A}.Release|Any CPU.Build.0 = Release|Any CPU + {09096FEC-DA29-4914-B046-CD280220C52A}.Release|x64.ActiveCfg = Release|Any CPU + {09096FEC-DA29-4914-B046-CD280220C52A}.Release|x64.Build.0 = Release|Any CPU + {09096FEC-DA29-4914-B046-CD280220C52A}.Release|x86.ActiveCfg = Release|Any CPU + {09096FEC-DA29-4914-B046-CD280220C52A}.Release|x86.Build.0 = Release|Any CPU {9CA13D2D-D6E2-4201-946C-81D1E6093404}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {9CA13D2D-D6E2-4201-946C-81D1E6093404}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9CA13D2D-D6E2-4201-946C-81D1E6093404}.Debug|x64.ActiveCfg = Debug|Any CPU + {9CA13D2D-D6E2-4201-946C-81D1E6093404}.Debug|x64.Build.0 = Debug|Any CPU + {9CA13D2D-D6E2-4201-946C-81D1E6093404}.Debug|x86.ActiveCfg = Debug|Any CPU + {9CA13D2D-D6E2-4201-946C-81D1E6093404}.Debug|x86.Build.0 = Debug|Any CPU {9CA13D2D-D6E2-4201-946C-81D1E6093404}.Release|Any CPU.ActiveCfg = Release|Any CPU {9CA13D2D-D6E2-4201-946C-81D1E6093404}.Release|Any CPU.Build.0 = Release|Any CPU + {9CA13D2D-D6E2-4201-946C-81D1E6093404}.Release|x64.ActiveCfg = Release|Any CPU + {9CA13D2D-D6E2-4201-946C-81D1E6093404}.Release|x64.Build.0 = Release|Any CPU + {9CA13D2D-D6E2-4201-946C-81D1E6093404}.Release|x86.ActiveCfg = Release|Any CPU + {9CA13D2D-D6E2-4201-946C-81D1E6093404}.Release|x86.Build.0 = Release|Any CPU {1E610F55-2D74-4856-818B-0D0B47601B75}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {1E610F55-2D74-4856-818B-0D0B47601B75}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1E610F55-2D74-4856-818B-0D0B47601B75}.Debug|x64.ActiveCfg = Debug|Any CPU + {1E610F55-2D74-4856-818B-0D0B47601B75}.Debug|x64.Build.0 = Debug|Any CPU + {1E610F55-2D74-4856-818B-0D0B47601B75}.Debug|x86.ActiveCfg = Debug|Any CPU + {1E610F55-2D74-4856-818B-0D0B47601B75}.Debug|x86.Build.0 = Debug|Any CPU {1E610F55-2D74-4856-818B-0D0B47601B75}.Release|Any CPU.ActiveCfg = Release|Any CPU {1E610F55-2D74-4856-818B-0D0B47601B75}.Release|Any CPU.Build.0 = Release|Any CPU + {1E610F55-2D74-4856-818B-0D0B47601B75}.Release|x64.ActiveCfg = Release|Any CPU + {1E610F55-2D74-4856-818B-0D0B47601B75}.Release|x64.Build.0 = Release|Any CPU + {1E610F55-2D74-4856-818B-0D0B47601B75}.Release|x86.ActiveCfg = Release|Any CPU + {1E610F55-2D74-4856-818B-0D0B47601B75}.Release|x86.Build.0 = Release|Any CPU {EBFAFB5B-494F-48D5-A70D-AF1490B9260A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {EBFAFB5B-494F-48D5-A70D-AF1490B9260A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {EBFAFB5B-494F-48D5-A70D-AF1490B9260A}.Debug|x64.ActiveCfg = Debug|Any CPU + {EBFAFB5B-494F-48D5-A70D-AF1490B9260A}.Debug|x64.Build.0 = Debug|Any CPU + {EBFAFB5B-494F-48D5-A70D-AF1490B9260A}.Debug|x86.ActiveCfg = Debug|Any CPU + {EBFAFB5B-494F-48D5-A70D-AF1490B9260A}.Debug|x86.Build.0 = Debug|Any CPU {EBFAFB5B-494F-48D5-A70D-AF1490B9260A}.Release|Any CPU.ActiveCfg = Release|Any CPU {EBFAFB5B-494F-48D5-A70D-AF1490B9260A}.Release|Any CPU.Build.0 = Release|Any CPU + {EBFAFB5B-494F-48D5-A70D-AF1490B9260A}.Release|x64.ActiveCfg = Release|Any CPU + {EBFAFB5B-494F-48D5-A70D-AF1490B9260A}.Release|x64.Build.0 = Release|Any CPU + {EBFAFB5B-494F-48D5-A70D-AF1490B9260A}.Release|x86.ActiveCfg = Release|Any CPU + {EBFAFB5B-494F-48D5-A70D-AF1490B9260A}.Release|x86.Build.0 = Release|Any CPU {54631590-2A41-45F4-B057-92C840ED08C1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {54631590-2A41-45F4-B057-92C840ED08C1}.Debug|Any CPU.Build.0 = Debug|Any CPU + {54631590-2A41-45F4-B057-92C840ED08C1}.Debug|x64.ActiveCfg = Debug|Any CPU + {54631590-2A41-45F4-B057-92C840ED08C1}.Debug|x64.Build.0 = Debug|Any CPU + {54631590-2A41-45F4-B057-92C840ED08C1}.Debug|x86.ActiveCfg = Debug|Any CPU + {54631590-2A41-45F4-B057-92C840ED08C1}.Debug|x86.Build.0 = Debug|Any CPU {54631590-2A41-45F4-B057-92C840ED08C1}.Release|Any CPU.ActiveCfg = Release|Any CPU {54631590-2A41-45F4-B057-92C840ED08C1}.Release|Any CPU.Build.0 = Release|Any CPU - {B296277A-C822-444E-8CFA-4CC4C1C1F737}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {B296277A-C822-444E-8CFA-4CC4C1C1F737}.Debug|Any CPU.Build.0 = Debug|Any CPU - {B296277A-C822-444E-8CFA-4CC4C1C1F737}.Release|Any CPU.ActiveCfg = Release|Any CPU - {B296277A-C822-444E-8CFA-4CC4C1C1F737}.Release|Any CPU.Build.0 = Release|Any CPU - {B0AB6EA7-7D53-4457-9482-F0613F99E3BB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C59F300E-4BAC-4329-9A41-8F1D75A7E197}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C59F300E-4BAC-4329-9A41-8F1D75A7E197}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C59F300E-4BAC-4329-9A41-8F1D75A7E197}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C59F300E-4BAC-4329-9A41-8F1D75A7E197}.Release|Any CPU.Build.0 = Release|Any CPU {B0AB6EA7-7D53-4457-9482-F0613F99E3BB}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B0AB6EA7-7D53-4457-9482-F0613F99E3BB}.Debug|x64.ActiveCfg = Debug|Any CPU + {B0AB6EA7-7D53-4457-9482-F0613F99E3BB}.Debug|x64.Build.0 = Debug|Any CPU + {B0AB6EA7-7D53-4457-9482-F0613F99E3BB}.Debug|x86.ActiveCfg = Debug|Any CPU + {B0AB6EA7-7D53-4457-9482-F0613F99E3BB}.Debug|x86.Build.0 = Debug|Any CPU {B0AB6EA7-7D53-4457-9482-F0613F99E3BB}.Release|Any CPU.ActiveCfg = Release|Any CPU {B0AB6EA7-7D53-4457-9482-F0613F99E3BB}.Release|Any CPU.Build.0 = Release|Any CPU + {B0AB6EA7-7D53-4457-9482-F0613F99E3BB}.Release|x64.ActiveCfg = Release|Any CPU + {B0AB6EA7-7D53-4457-9482-F0613F99E3BB}.Release|x64.Build.0 = Release|Any CPU + {B0AB6EA7-7D53-4457-9482-F0613F99E3BB}.Release|x86.ActiveCfg = Release|Any CPU + {B0AB6EA7-7D53-4457-9482-F0613F99E3BB}.Release|x86.Build.0 = Release|Any CPU + {ABD1EE97-DD84-4C6A-8F3F-28E7D3D898B4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {ABD1EE97-DD84-4C6A-8F3F-28E7D3D898B4}.Debug|Any CPU.Build.0 = Debug|Any CPU + {ABD1EE97-DD84-4C6A-8F3F-28E7D3D898B4}.Debug|x64.ActiveCfg = Debug|Any CPU + {ABD1EE97-DD84-4C6A-8F3F-28E7D3D898B4}.Debug|x64.Build.0 = Debug|Any CPU + {ABD1EE97-DD84-4C6A-8F3F-28E7D3D898B4}.Debug|x86.ActiveCfg = Debug|Any CPU + {ABD1EE97-DD84-4C6A-8F3F-28E7D3D898B4}.Debug|x86.Build.0 = Debug|Any CPU + {ABD1EE97-DD84-4C6A-8F3F-28E7D3D898B4}.Release|Any CPU.ActiveCfg = Release|Any CPU + {ABD1EE97-DD84-4C6A-8F3F-28E7D3D898B4}.Release|Any CPU.Build.0 = Release|Any CPU + {ABD1EE97-DD84-4C6A-8F3F-28E7D3D898B4}.Release|x64.ActiveCfg = Release|Any CPU + {ABD1EE97-DD84-4C6A-8F3F-28E7D3D898B4}.Release|x64.Build.0 = Release|Any CPU + {ABD1EE97-DD84-4C6A-8F3F-28E7D3D898B4}.Release|x86.ActiveCfg = Release|Any CPU + {ABD1EE97-DD84-4C6A-8F3F-28E7D3D898B4}.Release|x86.Build.0 = Release|Any CPU + {AB5F238F-AB78-4A85-8D8D-17E211015FD3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {AB5F238F-AB78-4A85-8D8D-17E211015FD3}.Debug|Any CPU.Build.0 = Debug|Any CPU + {AB5F238F-AB78-4A85-8D8D-17E211015FD3}.Debug|x64.ActiveCfg = Debug|Any CPU + {AB5F238F-AB78-4A85-8D8D-17E211015FD3}.Debug|x64.Build.0 = Debug|Any CPU + {AB5F238F-AB78-4A85-8D8D-17E211015FD3}.Debug|x86.ActiveCfg = Debug|Any CPU + {AB5F238F-AB78-4A85-8D8D-17E211015FD3}.Debug|x86.Build.0 = Debug|Any CPU + {AB5F238F-AB78-4A85-8D8D-17E211015FD3}.Release|Any CPU.ActiveCfg = Release|Any CPU + {AB5F238F-AB78-4A85-8D8D-17E211015FD3}.Release|Any CPU.Build.0 = Release|Any CPU + {AB5F238F-AB78-4A85-8D8D-17E211015FD3}.Release|x64.ActiveCfg = Release|Any CPU + {AB5F238F-AB78-4A85-8D8D-17E211015FD3}.Release|x64.Build.0 = Release|Any CPU + {AB5F238F-AB78-4A85-8D8D-17E211015FD3}.Release|x86.ActiveCfg = Release|Any CPU + {AB5F238F-AB78-4A85-8D8D-17E211015FD3}.Release|x86.Build.0 = Release|Any CPU + {12BE663C-C0DD-4343-93DF-6B2D853B6B79}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {12BE663C-C0DD-4343-93DF-6B2D853B6B79}.Debug|Any CPU.Build.0 = Debug|Any CPU + {12BE663C-C0DD-4343-93DF-6B2D853B6B79}.Debug|x64.ActiveCfg = Debug|Any CPU + {12BE663C-C0DD-4343-93DF-6B2D853B6B79}.Debug|x64.Build.0 = Debug|Any CPU + {12BE663C-C0DD-4343-93DF-6B2D853B6B79}.Debug|x86.ActiveCfg = Debug|Any CPU + {12BE663C-C0DD-4343-93DF-6B2D853B6B79}.Debug|x86.Build.0 = Debug|Any CPU + {12BE663C-C0DD-4343-93DF-6B2D853B6B79}.Release|Any CPU.ActiveCfg = Release|Any CPU + {12BE663C-C0DD-4343-93DF-6B2D853B6B79}.Release|Any CPU.Build.0 = Release|Any CPU + {12BE663C-C0DD-4343-93DF-6B2D853B6B79}.Release|x64.ActiveCfg = Release|Any CPU + {12BE663C-C0DD-4343-93DF-6B2D853B6B79}.Release|x64.Build.0 = Release|Any CPU + {12BE663C-C0DD-4343-93DF-6B2D853B6B79}.Release|x86.ActiveCfg = Release|Any CPU + {12BE663C-C0DD-4343-93DF-6B2D853B6B79}.Release|x86.Build.0 = Release|Any CPU + {F64790E0-86AD-4562-9AC5-F4DD3F4881BA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F64790E0-86AD-4562-9AC5-F4DD3F4881BA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F64790E0-86AD-4562-9AC5-F4DD3F4881BA}.Debug|x64.ActiveCfg = Debug|Any CPU + {F64790E0-86AD-4562-9AC5-F4DD3F4881BA}.Debug|x64.Build.0 = Debug|Any CPU + {F64790E0-86AD-4562-9AC5-F4DD3F4881BA}.Debug|x86.ActiveCfg = Debug|Any CPU + {F64790E0-86AD-4562-9AC5-F4DD3F4881BA}.Debug|x86.Build.0 = Debug|Any CPU + {F64790E0-86AD-4562-9AC5-F4DD3F4881BA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F64790E0-86AD-4562-9AC5-F4DD3F4881BA}.Release|Any CPU.Build.0 = Release|Any CPU + {F64790E0-86AD-4562-9AC5-F4DD3F4881BA}.Release|x64.ActiveCfg = Release|Any CPU + {F64790E0-86AD-4562-9AC5-F4DD3F4881BA}.Release|x64.Build.0 = Release|Any CPU + {F64790E0-86AD-4562-9AC5-F4DD3F4881BA}.Release|x86.ActiveCfg = Release|Any CPU + {F64790E0-86AD-4562-9AC5-F4DD3F4881BA}.Release|x86.Build.0 = Release|Any CPU + {949C95E9-4261-416E-8D2A-F05E3D4640CA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {949C95E9-4261-416E-8D2A-F05E3D4640CA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {949C95E9-4261-416E-8D2A-F05E3D4640CA}.Debug|x64.ActiveCfg = Debug|Any CPU + {949C95E9-4261-416E-8D2A-F05E3D4640CA}.Debug|x64.Build.0 = Debug|Any CPU + {949C95E9-4261-416E-8D2A-F05E3D4640CA}.Debug|x86.ActiveCfg = Debug|Any CPU + {949C95E9-4261-416E-8D2A-F05E3D4640CA}.Debug|x86.Build.0 = Debug|Any CPU + {949C95E9-4261-416E-8D2A-F05E3D4640CA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {949C95E9-4261-416E-8D2A-F05E3D4640CA}.Release|Any CPU.Build.0 = Release|Any CPU + {949C95E9-4261-416E-8D2A-F05E3D4640CA}.Release|x64.ActiveCfg = Release|Any CPU + {949C95E9-4261-416E-8D2A-F05E3D4640CA}.Release|x64.Build.0 = Release|Any CPU + {949C95E9-4261-416E-8D2A-F05E3D4640CA}.Release|x86.ActiveCfg = Release|Any CPU + {949C95E9-4261-416E-8D2A-F05E3D4640CA}.Release|x86.Build.0 = Release|Any CPU + {ADDBCE30-FE7F-4198-8A37-772EF6BF3676}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {ADDBCE30-FE7F-4198-8A37-772EF6BF3676}.Debug|Any CPU.Build.0 = Debug|Any CPU + {ADDBCE30-FE7F-4198-8A37-772EF6BF3676}.Debug|x64.ActiveCfg = Debug|Any CPU + {ADDBCE30-FE7F-4198-8A37-772EF6BF3676}.Debug|x64.Build.0 = Debug|Any CPU + {ADDBCE30-FE7F-4198-8A37-772EF6BF3676}.Debug|x86.ActiveCfg = Debug|Any CPU + {ADDBCE30-FE7F-4198-8A37-772EF6BF3676}.Debug|x86.Build.0 = Debug|Any CPU + {ADDBCE30-FE7F-4198-8A37-772EF6BF3676}.Release|Any CPU.ActiveCfg = Release|Any CPU + {ADDBCE30-FE7F-4198-8A37-772EF6BF3676}.Release|Any CPU.Build.0 = Release|Any CPU + {ADDBCE30-FE7F-4198-8A37-772EF6BF3676}.Release|x64.ActiveCfg = Release|Any CPU + {ADDBCE30-FE7F-4198-8A37-772EF6BF3676}.Release|x64.Build.0 = Release|Any CPU + {ADDBCE30-FE7F-4198-8A37-772EF6BF3676}.Release|x86.ActiveCfg = Release|Any CPU + {ADDBCE30-FE7F-4198-8A37-772EF6BF3676}.Release|x86.Build.0 = Release|Any CPU + {DF084D96-707B-47C2-9493-85FA84631ACE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {DF084D96-707B-47C2-9493-85FA84631ACE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {DF084D96-707B-47C2-9493-85FA84631ACE}.Debug|x64.ActiveCfg = Debug|Any CPU + {DF084D96-707B-47C2-9493-85FA84631ACE}.Debug|x64.Build.0 = Debug|Any CPU + {DF084D96-707B-47C2-9493-85FA84631ACE}.Debug|x86.ActiveCfg = Debug|Any CPU + {DF084D96-707B-47C2-9493-85FA84631ACE}.Debug|x86.Build.0 = Debug|Any CPU + {DF084D96-707B-47C2-9493-85FA84631ACE}.Release|Any CPU.ActiveCfg = Release|Any CPU + {DF084D96-707B-47C2-9493-85FA84631ACE}.Release|Any CPU.Build.0 = Release|Any CPU + {DF084D96-707B-47C2-9493-85FA84631ACE}.Release|x64.ActiveCfg = Release|Any CPU + {DF084D96-707B-47C2-9493-85FA84631ACE}.Release|x64.Build.0 = Release|Any CPU + {DF084D96-707B-47C2-9493-85FA84631ACE}.Release|x86.ActiveCfg = Release|Any CPU + {DF084D96-707B-47C2-9493-85FA84631ACE}.Release|x86.Build.0 = Release|Any CPU + {1764E3C7-CAAF-4FE9-9104-CF26D8121FC9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1764E3C7-CAAF-4FE9-9104-CF26D8121FC9}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1764E3C7-CAAF-4FE9-9104-CF26D8121FC9}.Debug|x64.ActiveCfg = Debug|Any CPU + {1764E3C7-CAAF-4FE9-9104-CF26D8121FC9}.Debug|x64.Build.0 = Debug|Any CPU + {1764E3C7-CAAF-4FE9-9104-CF26D8121FC9}.Debug|x86.ActiveCfg = Debug|Any CPU + {1764E3C7-CAAF-4FE9-9104-CF26D8121FC9}.Debug|x86.Build.0 = Debug|Any CPU + {1764E3C7-CAAF-4FE9-9104-CF26D8121FC9}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1764E3C7-CAAF-4FE9-9104-CF26D8121FC9}.Release|Any CPU.Build.0 = Release|Any CPU + {1764E3C7-CAAF-4FE9-9104-CF26D8121FC9}.Release|x64.ActiveCfg = Release|Any CPU + {1764E3C7-CAAF-4FE9-9104-CF26D8121FC9}.Release|x64.Build.0 = Release|Any CPU + {1764E3C7-CAAF-4FE9-9104-CF26D8121FC9}.Release|x86.ActiveCfg = Release|Any CPU + {1764E3C7-CAAF-4FE9-9104-CF26D8121FC9}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -84,11 +256,14 @@ Global {C02DECC9-2A82-42C0-8F26-D0AE6559AC5E} = {B4CE3CD4-74AA-4A22-B514-BC9B380AAFD7} {09096FEC-DA29-4914-B046-CD280220C52A} = {0295DFAC-BF6E-46C0-A63D-FBE9AF3C04E5} {9CA13D2D-D6E2-4201-946C-81D1E6093404} = {0295DFAC-BF6E-46C0-A63D-FBE9AF3C04E5} - {1E610F55-2D74-4856-818B-0D0B47601B75} = {0295DFAC-BF6E-46C0-A63D-FBE9AF3C04E5} {EBFAFB5B-494F-48D5-A70D-AF1490B9260A} = {B5D35D0C-387C-44FA-9A70-6FE24DAE5728} - {54631590-2A41-45F4-B057-92C840ED08C1} = {0EAC5155-A5EA-49C1-8E0C-19DD36D2C21C} - {B296277A-C822-444E-8CFA-4CC4C1C1F737} = {E0861852-0F5B-4810-8586-A59038BC4034} - {B0AB6EA7-7D53-4457-9482-F0613F99E3BB} = {0EAC5155-A5EA-49C1-8E0C-19DD36D2C21C} + {AB5F238F-AB78-4A85-8D8D-17E211015FD3} = {0295DFAC-BF6E-46C0-A63D-FBE9AF3C04E5} + {12BE663C-C0DD-4343-93DF-6B2D853B6B79} = {0295DFAC-BF6E-46C0-A63D-FBE9AF3C04E5} + {F64790E0-86AD-4562-9AC5-F4DD3F4881BA} = {B5D35D0C-387C-44FA-9A70-6FE24DAE5728} + {949C95E9-4261-416E-8D2A-F05E3D4640CA} = {64880D93-BAB4-FF83-898C-B934B68C31A9} + {ADDBCE30-FE7F-4198-8A37-772EF6BF3676} = {64880D93-BAB4-FF83-898C-B934B68C31A9} + {DF084D96-707B-47C2-9493-85FA84631ACE} = {B4CE3CD4-74AA-4A22-B514-BC9B380AAFD7} + {1764E3C7-CAAF-4FE9-9104-CF26D8121FC9} = {0EAC5155-A5EA-49C1-8E0C-19DD36D2C21C} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {AD284F35-E81F-4678-B737-A5DC8CB883CB} diff --git a/actions/endpoint_sahil_gdbapi.yml.20241204 b/actions/endpoint_sahil_gdbapi.yml.20241204 new file mode 100644 index 000000000..94f4b0fbd --- /dev/null +++ b/actions/endpoint_sahil_gdbapi.yml.20241204 @@ -0,0 +1,87 @@ +# Docs for the Azure Web Apps Deploy action: https://github.com/Azure/webapps-deploy +# More GitHub Actions for Azure: https://github.com/Azure/actions + +name: Build and deploy Good Deed Books API to Azure Web App - gdbapi + +on: + push: + branches: + - endpoint_sahil + workflow_dispatch: + +jobs: + build: + runs-on: windows-latest + + steps: + - uses: actions/checkout@v4 + + - name: Set up .NET Core + uses: actions/setup-dotnet@v4 + with: + dotnet-version: '8.x' + + - name: Install dotnet-ef tool + run: | + dotnet tool install --global dotnet-ef + echo "++++ dotnet-ef version" + dotnet ef --version + + - name: Build with dotnet + run: | + echo "++++ dotnet build" + dotnet build --configuration Release + + - name: Add migrations + run: | + echo "++++ current directory" + pwd + echo "++++ add ApplicationIdentityDbContext migration M1" + dotnet ef migrations add M1 --project ./src/Api/ --startup-project ./src/Api/Api.csproj --msbuildprojectextensionspath .build/obj/Api/ --context ApplicationIdentityDbContext --output-dir Data/Migrations/IdentityDb + echo "++++ add ApiDbContext migration M2" + dotnet ef migrations add M2 --project ./src/Api/ --startup-project ./src/Api/Api.csproj --msbuildprojectextensionspath .build/obj/Api/ --context ApiDbContext --output-dir Data/Migrations/ApiDb + echo "++++ contents of ./src/Api/Data/Migrations/IdentityDb" + ls ./src/Api/Data/Migrations/IdentityDb + echo "++++ contents of ./src/Api/Data/Migrations/ApiDb" + ls ./src/Api/Data/Migrations/ApiDb + + - name: dotnet publish + run: | + echo "++++ contents of dotnet publish ./src/Api/Api.csproj" + dotnet publish ./src/Api/Api.csproj -f net8.0 -c Release -o "${{runner.temp}}/myapp" + + - name: Upload artifact for deployment job + uses: actions/upload-artifact@v4 + with: + name: .net-app + path: ${{runner.temp}}/myapp + + deploy: + runs-on: windows-latest + needs: build + environment: + name: 'Production' + url: ${{ steps.deploy-to-webapp.outputs.webapp-url }} + permissions: + id-token: write #This is required for requesting the JWT + + steps: + - name: Download artifact from build job + uses: actions/download-artifact@v4 + with: + name: .net-app + + - name: Login to Azure + uses: azure/login@v2 + with: + client-id: ${{ secrets.AZUREAPPSERVICE_CLIENTID_A17E281C175C4E629A76134AA823BAC5 }} + tenant-id: ${{ secrets.AZUREAPPSERVICE_TENANTID_258CF23452C24D9795BD94B25EF50B73 }} + subscription-id: ${{ secrets.AZUREAPPSERVICE_SUBSCRIPTIONID_9375B274C69740D39F4770D5D433E8B1 }} + + - name: Deploy to Azure Web App + id: deploy-to-webapp + uses: azure/webapps-deploy@v3 + with: + app-name: 'gdbapi' + slot-name: 'Production' + package: . diff --git a/actions/endpoint_sahil_gdbmvc.yml.20241204 b/actions/endpoint_sahil_gdbmvc.yml.20241204 new file mode 100644 index 000000000..330b665ab --- /dev/null +++ b/actions/endpoint_sahil_gdbmvc.yml.20241204 @@ -0,0 +1,101 @@ +# Docs for the Azure Web Apps Deploy action: https://github.com/Azure/webapps-deploy +# More GitHub Actions for Azure: https://github.com/Azure/actions + +name: Build and deploy Good Deed Books MVC project to Azure + +on: + push: + branches: + - endpoint_sahil + workflow_dispatch: + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - name: Set up .NET Core + uses: actions/setup-dotnet@v4 + with: + dotnet-version: '8.x' + include-prerelease: true + + - name: Build with dotnet + run: dotnet build --configuration Release + + - name: dotnet publish + run: dotnet publish ./src/AccountGoWeb/AccountGoWeb.csproj -c Release -o ${{env.DOTNET_ROOT}}/myapp + + - name: Archive production artifacts + run: | + echo "+++++++++++++++++++++++++ where am I? ++++++++++++++++++++++++" + pwd + echo "+++++++++++++++++++++++++ save current directory into a variable dir ++++" + dir=$(pwd) + echo "+++++++++ what is in variable dir ++++++++++++++" + echo $dir + echo "++++++++++++++++++++++++ what's in current directory? ++++++++" + ls -al + echo "+++++ what's in the ${{env.DOTNET_ROOT}}/myapp directory? ++++" + ls -al ${{env.DOTNET_ROOT}}/myapp + echo "+++++ change directoiry to ${{env.DOTNET_ROOT}}/myapp ++++" + cd ${{env.DOTNET_ROOT}}/myapp + echo "+++++++++++++++++++++++++ where am I? ++++++++++++++++++++++++" + pwd + echo "+++++++++++++++++++++++++ compress current directory and save in $dir/my_artifact.tar.gz ++++" + tar -czvf $dir/my_artifact.tar.gz . + echo "+++++++++++++++++++++++++ change dir to to $dir directory ++++" + cd $dir + echo "+++++++++++++++++++++++++ where am I? ++++++++++++++++++++++++" + pwd + echo "++++++++++++++++++++++++ what's in $dir directory? ++++++++" + ls -al + + - name: Upload artifact for deployment job + uses: actions/upload-artifact@v4 + with: + name: .net-app + path: my_artifact.tar.gz + + deploy: + runs-on: ubuntu-latest + needs: build + environment: + name: 'Production' + url: ${{ steps.deploy-to-webapp.outputs.webapp-url }} + permissions: + id-token: write #This is required for requesting the JWT + + steps: + - name: Download artifact from build job + uses: actions/download-artifact@v4 + with: + name: .net-app + + - name: Login to Azure + uses: azure/login@v2 + with: + client-id: ${{ secrets.AZUREAPPSERVICE_CLIENTID_8B6389BB3F37413FB2483AC2574C3BCB }} + tenant-id: ${{ secrets.AZUREAPPSERVICE_TENANTID_FD62C59DE5DC42C2A07DB8191A522348 }} + subscription-id: ${{ secrets.AZUREAPPSERVICE_SUBSCRIPTIONID_7076EF307FDA4C11BC99A0A7A0943794 }} + + - name: Extract artifacts + run: | + tar -xzvf my_artifact.tar.gz -C . + + - name: Print working directory + run: pwd + + - name: List directory contents + run: ls -l /home/runner/.dotnet/ + + - name: Deploy to Azure Web App + id: deploy-to-webapp + uses: azure/webapps-deploy@v3 + with: + app-name: 'gdbmvc' + slot-name: 'Production' + package: . + \ No newline at end of file diff --git a/actions/gdb-blazor.yml.gold b/actions/gdb-blazor.yml.gold new file mode 100644 index 000000000..7691040f1 --- /dev/null +++ b/actions/gdb-blazor.yml.gold @@ -0,0 +1,76 @@ +# Docs for the Azure Web Apps Deploy action: https://github.com/Azure/webapps-deploy +# More GitHub Actions for Azure: https://github.com/Azure/actions + +name: Build and deploy Good Deed Books BLAZOR project to Azure + +on: + push: + branches: + - endpoint_sahil + workflow_dispatch: + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - name: Set up .NET Core + uses: actions/setup-dotnet@v4 + with: + dotnet-version: '8.x' + + - name: Build with dotnet + working-directory: ./src/BlazorGDB/BlazorGDB + run: dotnet build --configuration Release + + - name: dotnet publish + working-directory: ./src/BlazorGDB/BlazorGDB + run: dotnet publish BlazorGDB.csproj -c Release -o ${{env.DOTNET_ROOT}}/myapp + + - name: sanity check + run: | + echo "+++++++++++++++++++++++++ where am I? ++++++++++++++++++++++++" + pwd + echo "++++++++++++++++++++++++ what's in current directory? ++++++++" + ls -al + echo "+++++ what's in the ${{env.DOTNET_ROOT}}/myapp directory? ++++" + ls -al ${{env.DOTNET_ROOT}}/myapp + + - name: Upload artifact for deployment job + uses: actions/upload-artifact@v4 + with: + name: .net-app + path: ${{env.DOTNET_ROOT}}/myapp + + deploy: + runs-on: ubuntu-latest + needs: build + environment: + name: 'Production' + url: ${{ steps.deploy-to-webapp.outputs.webapp-url }} + permissions: + id-token: write #This is required for requesting the JWT + + steps: + - name: Download artifact from build job + uses: actions/download-artifact@v4 + with: + name: .net-app + + - name: Login to Azure + uses: azure/login@v2 + with: + client-id: ${{ secrets.AZUREAPPSERVICE_CLIENTID_6A854C1CD0C74473AD2E3B9F843CC396 }} + tenant-id: ${{ secrets.AZUREAPPSERVICE_TENANTID_224A065E650B4D5F9EB2329B6B2F1716 }} + subscription-id: ${{ secrets.AZUREAPPSERVICE_SUBSCRIPTIONID_570B031F0942445C8E479905EE706F43 }} + + - name: Deploy to Azure Web App + id: deploy-to-webapp + uses: azure/webapps-deploy@v3 + with: + app-name: 'gdb-blazor' + slot-name: 'Production' + package: . + \ No newline at end of file diff --git a/actions/gdb_api.yml.flat b/actions/gdb_api.yml.flat new file mode 100644 index 000000000..9218db27d --- /dev/null +++ b/actions/gdb_api.yml.flat @@ -0,0 +1,91 @@ +# Docs for the Azure Web Apps Deploy action: https://github.com/Azure/webapps-deploy +# More GitHub Actions for Azure: https://github.com/Azure/actions + +name: Build and deploy Good Deed Books API project to Azure + +on: + push: + branches: + - endpoint_sahil + workflow_dispatch: + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - name: Set up .NET Core + uses: actions/setup-dotnet@v1 + with: + dotnet-version: '8.x' + include-prerelease: true + + - name: Install dotnet-ef tool + run: | + dotnet tool install --global dotnet-ef + echo "++++ dotnet-ef version" + dotnet ef --version + + - name: Build with dotnet + run: | + echo "++++ dotnet restore" + dotnet restore + echo "++++ dotnet build" + dotnet build --configuration Release + + - name: Add migrations + run: | + echo "++++ current directory" + pwd + echo "++++ add ApplicationIdentityDbContext migration M1" + dotnet ef migrations add M1 --project ./src/Api/ --startup-project ./src/Api/Api.csproj --msbuildprojectextensionspath .build/obj/Api/ --context ApplicationIdentityDbContext --output-dir Data/Migrations/IdentityDb + echo "++++ add ApiDbContext migration M2" + dotnet ef migrations add M2 --project ./src/Api/ --startup-project ./src/Api/Api.csproj --msbuildprojectextensionspath .build/obj/Api/ --context ApiDbContext --output-dir Data/Migrations/ApiDb + echo "++++ contents of ./src/Api/Data/Migrations/IdentityDb" + ls ./src/Api/Data/Migrations/IdentityDb + echo "++++ contents of ./src/Api/Data/Migrations/ApiDb" + ls ./src/Api/Data/Migrations/ApiDb + + - name: dotnet publish + run: | + echo "++++ contents of dotnet publish ./src/Api/Api.csproj" + dotnet publish ./src/Api/Api.csproj -c Release -o ${{env.DOTNET_ROOT}}/myapp + + - name: Upload artifact for deployment job + uses: actions/upload-artifact@v3 + with: + name: .net-app + path: ${{env.DOTNET_ROOT}}/myapp + + deploy: + runs-on: ubuntu-latest + needs: build + environment: + name: 'Production' + url: ${{ steps.deploy-to-webapp.outputs.webapp-url }} + permissions: + id-token: write #This is required for requesting the JWT + + steps: + - name: Download artifact from build job + uses: actions/download-artifact@v3 + with: + name: .net-app + + - name: Login to Azure + uses: azure/login@v1 + with: + client-id: ${{ secrets.AZUREAPPSERVICE_CLIENTID_543326D87AEF459D91E15D756166A5AC }} + tenant-id: ${{ secrets.AZUREAPPSERVICE_TENANTID_D57EB2BACAA54EE2AB97F696E8E99A4B }} + subscription-id: ${{ secrets.AZUREAPPSERVICE_SUBSCRIPTIONID_3C797712E9A047958FF5C9BB540F0543 }} + + - name: Deploy to Azure Web App + id: deploy-to-webapp + uses: azure/webapps-deploy@v2 + with: + app-name: 'goodbooksapi' + slot-name: 'Production' + package: . + diff --git a/actions/gdb_mvc_tar.yml.flat b/actions/gdb_mvc_tar.yml.flat new file mode 100644 index 000000000..0b749010a --- /dev/null +++ b/actions/gdb_mvc_tar.yml.flat @@ -0,0 +1,101 @@ +# Docs for the Azure Web Apps Deploy action: https://github.com/Azure/webapps-deploy +# More GitHub Actions for Azure: https://github.com/Azure/actions + +name: Build and deploy Good Deed Books MVC project to Azure + +on: + push: + branches: + - endpoint_sahil + workflow_dispatch: + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - name: Set up .NET Core + uses: actions/setup-dotnet@v4 + with: + dotnet-version: '8.x' + include-prerelease: true + + - name: Build with dotnet + run: dotnet build --configuration Release + + - name: dotnet publish + run: dotnet publish ./src/AccountGoWeb/AccountGoWeb.csproj -c Release -o ${{env.DOTNET_ROOT}}/myapp + + - name: Archive production artifacts + run: | + echo "+++++++++++++++++++++++++ where am I? ++++++++++++++++++++++++" + pwd + echo "+++++++++++++++++++++++++ save current directory into a variable dir ++++" + dir=$(pwd) + echo "+++++++++ what is in variable dir ++++++++++++++" + echo $dir + echo "++++++++++++++++++++++++ what's in current directory? ++++++++" + ls -al + echo "+++++ what's in the ${{env.DOTNET_ROOT}}/myapp directory? ++++" + ls -al ${{env.DOTNET_ROOT}}/myapp + echo "+++++ change directoiry to ${{env.DOTNET_ROOT}}/myapp ++++" + cd ${{env.DOTNET_ROOT}}/myapp + echo "+++++++++++++++++++++++++ where am I? ++++++++++++++++++++++++" + pwd + echo "+++++++++++++++++++++++++ compress current directory and save in $dir/my_artifact.tar.gz ++++" + tar -czvf $dir/my_artifact.tar.gz . + echo "+++++++++++++++++++++++++ change dir to to $dir directory ++++" + cd $dir + echo "+++++++++++++++++++++++++ where am I? ++++++++++++++++++++++++" + pwd + echo "++++++++++++++++++++++++ what's in $dir directory? ++++++++" + ls -al + + - name: Upload artifact for deployment job + uses: actions/upload-artifact@v4 + with: + name: .net-app + path: my_artifact.tar.gz + + deploy: + runs-on: ubuntu-latest + needs: build + environment: + name: 'Production' + url: ${{ steps.deploy-to-webapp.outputs.webapp-url }} + permissions: + id-token: write #This is required for requesting the JWT + + steps: + - name: Download artifact from build job + uses: actions/download-artifact@v4 + with: + name: .net-app + + - name: Login to Azure + uses: azure/login@v2 + with: + client-id: ${{ secrets.AZUREAPPSERVICE_CLIENTID_28108B2CCE81480BB0295B2554B37231 }} + tenant-id: ${{ secrets.AZUREAPPSERVICE_TENANTID_9ED1B649A03F45E7B34C3BE1217B6BDE }} + subscription-id: ${{ secrets.AZUREAPPSERVICE_SUBSCRIPTIONID_A41842A963384E4BAB26580EEFE65E92 }} + + - name: Extract artifacts + run: | + tar -xzvf my_artifact.tar.gz -C . + + - name: Print working directory + run: pwd + + - name: List directory contents + run: ls -l /home/runner/.dotnet/ + + - name: Deploy to Azure Web App + id: deploy-to-webapp + uses: azure/webapps-deploy@v3 + with: + app-name: 'good-books' + slot-name: 'Production' + package: . + diff --git a/actions/gdbblazor.yml b/actions/gdbblazor.yml new file mode 100644 index 000000000..a372a08c3 --- /dev/null +++ b/actions/gdbblazor.yml @@ -0,0 +1,67 @@ +# Docs for the Azure Web Apps Deploy action: https://github.com/Azure/webapps-deploy +# More GitHub Actions for Azure: https://github.com/Azure/actions + +name: Build and deploy Good Deed Books BLAZOR project to Azure + +on: + push: + branches: + - endpoint_sahil + +jobs: + build: + runs-on: windows-latest + + steps: + - uses: actions/checkout@v2 + + - name: Set up .NET Core + uses: actions/setup-dotnet@v2 + with: + dotnet-version: '8.0.x' + + - name: Restore dependencies + run: dotnet restore ./src/BlazorGDB/BlazorGDB/BlazorGDB.csproj + + - name: Build + run: dotnet build ./src/BlazorGDB/BlazorGDB/BlazorGDB.csproj --configuration Release + + - name: Publish + run: dotnet publish ./src/BlazorGDB/BlazorGDB/BlazorGDB.csproj --configuration Release --output ${{ github.workspace }}/myapp + + - name: Upload artifact for deployment job + uses: actions/upload-artifact@v3 + with: + name: .net-app + path: ${{ github.workspace }}/myapp + + deploy: + runs-on: windows-latest + needs: build + environment: + name: 'Production' + url: ${{ steps.deploy-to-webapp.outputs.webapp-url }} + permissions: + id-token: write #This is required for requesting the JWT + + steps: + - name: Download artifact from build job + uses: actions/download-artifact@v3 + with: + name: .net-app + + - name: Login to Azure + uses: azure/login@v2 + with: + client-id: ${{ secrets.AZUREAPPSERVICE_CLIENTID_C7C01847F7FC4BBFB72DEAC64242E5A4 }} + tenant-id: ${{ secrets.AZUREAPPSERVICE_TENANTID_21069DC407434A3591399953BE45ED78 }} + subscription-id: ${{ secrets.AZUREAPPSERVICE_SUBSCRIPTIONID_CC5D4E473B8345BA854EA230A48D8D20 }} + + - name: Deploy to Azure Web App + id: deploy-to-webapp + uses: azure/webapps-deploy@v3 + with: + app-name: 'gdbblazor' + slot-name: 'Production' + package: . + diff --git a/actions/good-books_mvc.yml.disable b/actions/good-books_mvc.yml.disable new file mode 100644 index 000000000..659083c9a --- /dev/null +++ b/actions/good-books_mvc.yml.disable @@ -0,0 +1,75 @@ +# Docs for the Azure Web Apps Deploy action: https://github.com/Azure/webapps-deploy +# More GitHub Actions for Azure: https://github.com/Azure/actions + +name: Build and deploy GoodBooks MVC project to Azure + +on: + push: + branches: + - endpoint_sahil + workflow_dispatch: + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - name: Set up .NET Core + uses: actions/setup-dotnet@v1 + with: + dotnet-version: '8.x' + include-prerelease: true + + - name: Build with dotnet + run: dotnet build --configuration Release + + - name: dotnet publish + run: dotnet publish ./src/AccountGoWeb/AccountGoWeb.csproj -c Release -o ${{env.DOTNET_ROOT}}/myapp + + # - name: Archive production artifacts + # run: | + # tar -czvf my_artifact.tar.gz ${{env.DOTNET_ROOT}}/myapp + + - name: Upload artifact for deployment job + uses: actions/upload-artifact@v3 + with: + name: .net-app + # path: my_artifact.tar.gz + path: ${{env.DOTNET_ROOT}}/myapp + + deploy: + runs-on: ubuntu-latest + needs: build + environment: + name: 'Production' + url: ${{ steps.deploy-to-webapp.outputs.webapp-url }} + permissions: + id-token: write #This is required for requesting the JWT + + steps: + - name: Download artifact from build job + uses: actions/download-artifact@v3 + with: + name: .net-app + + - name: Login to Azure + uses: azure/login@v1 + with: + client-id: ${{ secrets.AZUREAPPSERVICE_CLIENTID_28108B2CCE81480BB0295B2554B37231 }} + tenant-id: ${{ secrets.AZUREAPPSERVICE_TENANTID_9ED1B649A03F45E7B34C3BE1217B6BDE }} + subscription-id: ${{ secrets.AZUREAPPSERVICE_SUBSCRIPTIONID_A41842A963384E4BAB26580EEFE65E92 }} + + # - name: Extract artifacts + # run: | + # tar -xzvf my_artifact.tar.gz -C . + + - name: Deploy to Azure Web App + id: deploy-to-webapp + uses: azure/webapps-deploy@v2 + with: + app-name: 'good-books' + slot-name: 'Production' + package: . + diff --git a/actions/good-books_react.yml.disable b/actions/good-books_react.yml.disable new file mode 100644 index 000000000..99103e5e5 --- /dev/null +++ b/actions/good-books_react.yml.disable @@ -0,0 +1,54 @@ +name: Azure Static Web Apps CI/CD + +on: + push: + branches: + - endpoint_sahil + pull_request: + types: [opened, synchronize, reopened, closed] + branches: + - endpoint_sahil + +jobs: + build_and_deploy_job: + if: github.event_name == 'push' || (github.event_name == 'pull_request' && github.event.action != 'closed') + runs-on: ubuntu-latest + name: Build and Deploy Job + steps: + - uses: actions/checkout@v3 + with: + submodules: true + lfs: false + + - name: Replace API URL + run: | + echo "++++ search & replace API URL from http://localhost:8001 to https://goodbooksapi.azurewebsites.net" + sed -i 's|http://localhost:8001|https://goodbooksapi.azurewebsites.net|g' ./src/GoodBooksReact/src/components/Shared/Config/index.tsx + echo "++++ display contents of index.tsx after search & replace" + cat ./src/GoodBooksReact/src/components/Shared/Config/index.tsx + + - name: Build And Deploy + id: builddeploy + uses: Azure/static-web-apps-deploy@v1 + with: + azure_static_web_apps_api_token: ${{ secrets.AZURE_STATIC_WEB_APPS_API_TOKEN_MANGO_GLACIER_0EDFEC41E }} + repo_token: ${{ secrets.GITHUB_TOKEN }} # Used for Github integrations (i.e. PR comments) + action: "upload" + ###### Repository/Build Configurations - These values can be configured to match your app requirements. ###### + # For more information regarding Static Web App workflow configurations, please visit: https://aka.ms/swaworkflowconfig + app_location: "/src/GoodBooksReact/" # App source code path + api_location: "" # Api source code path - optional + output_location: "/dist" # Built app content directory - optional + ###### End of Repository/Build Configurations ###### + + close_pull_request_job: + if: github.event_name == 'pull_request' && github.event.action == 'closed' + runs-on: ubuntu-latest + name: Close Pull Request Job + steps: + - name: Close Pull Request + id: closepullrequest + uses: Azure/static-web-apps-deploy@v1 + with: + azure_static_web_apps_api_token: ${{ secrets.AZURE_STATIC_WEB_APPS_API_TOKEN_MANGO_GLACIER_0EDFEC41E }} + action: "close" diff --git a/actions/mvc_tar.yml.works b/actions/mvc_tar.yml.works new file mode 100644 index 000000000..0e57f5cfc --- /dev/null +++ b/actions/mvc_tar.yml.works @@ -0,0 +1,92 @@ +# Docs for the Azure Web Apps Deploy action: https://github.com/Azure/webapps-deploy +# More GitHub Actions for Azure: https://github.com/Azure/actions +name: Build and deploy Good Deed Books MVC project to Azure +on: + push: + branches: + - endpoint_sahil + workflow_dispatch: +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Set up .NET Core + uses: actions/setup-dotnet@v4 + with: + dotnet-version: '8.x' + include-prerelease: true + + - name: Build with dotnet + run: dotnet build --configuration Release + + - name: dotnet publish + run: dotnet publish ./src/AccountGoWeb/AccountGoWeb.csproj -c Release -o ${{runner.temp}}/myapp + + - name: Archive production artifacts + run: | + echo "+++++++++++++++++++++++++ where am I? ++++++++++++++++++++++++" + pwd + echo "+++++++++++++++++++++++++ save current directory into a variable dir ++++" + dir=$(pwd) + echo "+++++++++ what is in variable dir ++++++++++++++" + echo $dir + echo "++++++++++++++++++++++++ what's in current directory? ++++++++" + ls -al + echo "+++++ what's in the ${{runner.temp}}/myapp directory? ++++" + ls -al ${{runner.temp}}/myapp + echo "+++++ change directory to ${{runner.temp}}/myapp ++++" + cd ${{runner.temp}}/myapp + echo "+++++++++++++++++++++++++ where am I? ++++++++++++++++++++++++" + pwd + echo "+++++++++++++++++++++++++ compress current directory and save in $dir/my_artifact.tar.gz ++++" + tar -czvf $dir/my_artifact.tar.gz . + echo "+++++++++++++++++++++++++ change back to $dir directory ++++" + cd $dir + echo "++++++++++++++++++++++++ what's in $dir directory? ++++++++" + ls -al + + - name: Upload artifact for deployment job + uses: actions/upload-artifact@v4 + with: + name: .net-app + path: my_artifact.tar.gz + + deploy: + runs-on: ubuntu-latest + needs: build + environment: + name: 'Production' + url: ${{ steps.deploy-to-webapp.outputs.webapp-url }} + permissions: + id-token: write #This is required for requesting the JWT + + steps: + - name: Download artifact from build job + uses: actions/download-artifact@v4 + with: + name: .net-app + + - name: Login to Azure + uses: azure/login@v2 + with: + client-id: ${{ secrets.AZUREAPPSERVICE_CLIENTID_8B6389BB3F37413FB2483AC2574C3BCB }} + tenant-id: ${{ secrets.AZUREAPPSERVICE_TENANTID_FD62C59DE5DC42C2A07DB8191A522348 }} + subscription-id: ${{ secrets.AZUREAPPSERVICE_SUBSCRIPTIONID_7076EF307FDA4C11BC99A0A7A0943794 }} + + - name: Extract artifacts + run: | + tar -xzvf my_artifact.tar.gz -C . + + - name: Set startup command + run: | + az webapp config set --resource-group goodbooks-RG --name gdbmvc --startup-file "dotnet /home/site/wwwroot/GoodBooks.dll" + + - name: Deploy to Azure Web App + id: deploy-to-webapp + uses: azure/webapps-deploy@v2 + with: + app-name: 'gdbmvc' + slot-name: 'Production' + package: . diff --git a/db/scripts/initial_data/3_InitialData-0001-Audit.sql b/db/scripts/initial_data/3_InitialData-0001-Audit.sql new file mode 100644 index 000000000..b16be7c0d --- /dev/null +++ b/db/scripts/initial_data/3_InitialData-0001-Audit.sql @@ -0,0 +1,13 @@ +-- Add audit data for the Company table +INSERT INTO [dbo].[AuditableEntity] ([EntityName], [EnableAudit]) VALUES ('Company', 1); + +DECLARE @auditableEntityId INT; +SELECT @auditableEntityId = [Id] FROM [dbo].[AuditableEntity] WHERE [EntityName] = 'Company'; + +-- Add attributes for the Company table +INSERT INTO [dbo].[AuditableAttribute] ([AuditableEntityId], [AttributeName], [EnableAudit]) +VALUES + (@auditableEntityId, 'CompanyCode', 1), + (@auditableEntityId, 'Name', 1), + (@auditableEntityId, 'ShortName', 1), + (@auditableEntityId, 'CRA', 1); \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml index c7fef8216..75fe7a32d 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,5 +1,5 @@ version: "3" -services: +services: api: image: accountgo/accountgoapi build: @@ -8,14 +8,7 @@ services: ports: - "8001:8001" environment: - # - ASPNETCORE_ENVIRONMENT=Production - ASPNETCORE_URLS=http://+:8001 - # - DBSERVER=localhost - # - DBUSERID=dbuser - # - DBPASSWORD=Str0ngPassword - # - DBNAME=accountgodb - # depends_on: - # - db web: image: accountgo/accountgoweb build: @@ -24,13 +17,18 @@ services: ports: - "8000:8000" environment: - # - ASPNETCORE_ENVIRONMENT=Production - ASPNETCORE_URLS=http://+:8000 - APIHOST=api - # db: - # image: microsoft/mssql-server-linux - # ports: - # - "1433:1433" - # environment: - # SA_PASSWORD: "Str0ngPassword" - # ACCEPT_EULA: "Y" \ No newline at end of file + sqlserver: + image: mcr.microsoft.com/mssql/server:2022-latest + container_name: gdb-sql-server + environment: + - ACCEPT_EULA=Y + - MSSQL_SA_PASSWORD=YourStrong!Passw0rd + ports: + - "1433:1433" + volumes: + - sqlserver_data:/var/opt/mssql + +volumes: + sqlserver_data: \ No newline at end of file diff --git a/docs/Bootstrap Blazor.txt b/docs/Bootstrap Blazor.txt new file mode 100644 index 000000000..97341be99 --- /dev/null +++ b/docs/Bootstrap Blazor.txt @@ -0,0 +1,93 @@ +https://github.com/vikramlearning/blazorbootstrap-starter-templates/tree/master + + +dotnet add package Blazor.Bootstrap -v 3.0.0-preview.2 + +Program.cs + + builder.Services.AddBlazorBootstrap(); // Add this line + +_Imports.razor + + @using BlazorBootstrap; + +Delete wwwroot/bootstrap folder + +Replace MainLayout.razor with: + + @inherits LayoutComponentBase + +
+ + + +
+
+ About +
+ +
+
@Body
+
+
+ +
+ + @code { + Sidebar sidebar; + IEnumerable navItems; + + private async Task SidebarDataProvider(SidebarDataProviderRequest request) + { + if (navItems is null) + navItems = GetNavItems(); + + return await Task.FromResult(request.ApplyTo(navItems)); + } + + private IEnumerable GetNavItems() + { + navItems = new List + { + new NavItem { Id = "1", Href = "/", IconName = IconName.HouseDoorFill, Text = "Home", Match=NavLinkMatch.All}, + new NavItem { Id = "2", Href = "/counter", IconName = IconName.PlusSquareFill, Text = "Counter"}, + new NavItem { Id = "3", Href = "/weather", IconName = IconName.Table, Text = "Fetch Data"}, + }; + + return navItems; + } + } + + +
+ An unhandled error has occurred. + Reload + 🗙 +
+ +App.razor + + 1) Delete >> + 2) Add these lines at top of file under + + + + + + 3) Add these lines at bottom of file under + + + + + + + + + 4) Change to: + + + + \ No newline at end of file diff --git a/docs/GoodDeedBooks.docx b/docs/GoodDeedBooks.docx new file mode 100644 index 000000000..30ae3bee2 Binary files /dev/null and b/docs/GoodDeedBooks.docx differ diff --git a/docs/README.md b/docs/README.md index 74552fce9..c4965d29f 100644 --- a/docs/README.md +++ b/docs/README.md @@ -128,7 +128,7 @@ At this point, your database has no data on it. But there is already an initial - Items - Banks -To initialize a company, call the api endpoint directly http://localhost:8001/api/administration/initializedcompany from the browser or by using curl e.g. `curl http://localhost:8001/api/administration/initializedcompany`. If you encounter some issues, the easy way for now is recreate your database and repeat the `Publish Database` section. +To initialize a company, call the api endpoint directly http://localhost:8001/api/administration/setup from the browser or by using curl e.g. `curl http://localhost:8001/api/administration/setup`. If you encounter some issues, the easy way for now is recreate your database and repeat the `Publish Database` section. ## Build and Run "Api" (Back-end) 1. Navigate directory to `src/Api` project @@ -181,7 +181,7 @@ To run everything (database, api, web) in docker container you can use docker-co 1. Database instance running in docker container and you can connect to it 1. You should have a running "Api" and can test it by getting the list of customers e.g. http://localhost:8001/api/sales customers 1. You can browse the UI from http://localhost:8000 and able to login to the system using initial username/password: admin@accountgo.ph/P@ssword1 -1. Initialize data by calling a special api endpoint directly. http://localhost:8001/api/administration/initializedcompany +1. Initialize data by calling a special api endpoint directly: http://localhost:8001/api/administration/setup # Technology Stack - ASP.NET Core 3.1 @@ -207,4 +207,4 @@ If you are a developer and wanted to take part as contributor/collaborator we ar So go ahead, add your code and make your first pull request. # Contact Support -Feel free to email mvpsolution@gmail.com of any questions. \ No newline at end of file +Feel free to email mvpsolution@gmail.com of any questions. diff --git a/docs/azure.txt b/docs/azure.txt new file mode 100644 index 000000000..1e437cd3d --- /dev/null +++ b/docs/azure.txt @@ -0,0 +1,8 @@ +API: +https://goodbooksapi.azurewebsites.net + +MVC: +https://good-books.azurewebsites.net + +React: +https://mango-glacier-0edfec41e.5.azurestaticapps.net diff --git a/docs/background.txt b/docs/background.txt new file mode 100644 index 000000000..dfd5d92f3 --- /dev/null +++ b/docs/background.txt @@ -0,0 +1,41 @@ +I was asked by a non-profit organization to help them find a cheap accounting system instead of paying high subscription fees from a current vendor. I stumbled upon this open source project on GitHub: + +https://github.com/AccountGo/accountgo + +It is based on the following technologies: + +Backend: ASP.NET WebAPI and MVC +Frontend: React with TypeScript +Database: SQL Server + +It seems that development on this app stopped about seven years ago. When I looked at it, I figured that it has most of what is needed and could be brought up to snuff by upgrading the application to the latest state of .NET and React. Therefore, I forked it and updated it to the latest versions of .NET, React, and TypeScript. + +The forked app is at https://github.com/medhatelmasry/GoodBooks + +You can run it by following these steps: + +Clone the repo +Start SQL Server in a docker container with: + + docker run --cap-add SYS_PTRACE -e ACCEPT_EULA=1 -e MSSQL_SA_PASSWORD=SqlPassword! -p 1444:1433 --name azsql -d mcr.microsoft.com/azure-sql-edge + +In root directory of the code, run the following commands: + + dotnet ef migrations add M1 --project ./src/Api/ --startup-project ./src/Api/Api.csproj --msbuildprojectextensionspath .build/obj/Api/ --context ApplicationIdentityDbContext --output-dir Data/Migrations/IdentityDb + + dotnet ef migrations add M2 --project ./src/Api/ --startup-project ./src/Api/Api.csproj --msbuildprojectextensionspath .build/obj/Api/ --context ApiDbContext --output-dir Data/Migrations/ApiDb + + dotnet ef database update --project ./src/Api/ --msbuildprojectextensionspath .build/obj/Api/ --context ApplicationIdentityDbContext + + dotnet ef database update --project ./src/Api/ --msbuildprojectextensionspath .build/obj/Api/ --context ApiDbContext + +Update to the latest versions of Node & Npm +Go to the src/Api folder and start the WebAPI app with: dotnet watch +Hit this endpoint in order to populate the database with some sample data: http://localhost:8001/api/administration/setup +In a separate terminal window, go to the src/GoodBooksReact folder run these commands: + + npm install + npm run dev + +The React app will run. It is a rudimentary frontend menu system and is a work in progress. + diff --git a/docs/expand-chart-of-accounts.docx b/docs/expand-chart-of-accounts.docx new file mode 100644 index 000000000..0ebf39cf4 Binary files /dev/null and b/docs/expand-chart-of-accounts.docx differ diff --git a/docs/medhat.txt b/docs/medhat.txt new file mode 100644 index 000000000..b66e10c56 --- /dev/null +++ b/docs/medhat.txt @@ -0,0 +1,28 @@ +docker run --cap-add SYS_PTRACE -e ACCEPT_EULA=1 -e MSSQL_SA_PASSWORD=SqlPassword! -p 1444:1433 --name azsql -d mcr.microsoft.com/azure-sql-edge + +Data Source=localhost,1444;Database=Northwind;Persist Security Info=True;User ID=sa;Password=SqlPassword!;TrustServerCertificate=True; + +==================== + +dotnet ef migrations add M1 --project ./src/Api/ --startup-project ./src/Api/Api.csproj --msbuildprojectextensionspath .build/obj/Api/ --context ApplicationIdentityDbContext --output-dir Data/Migrations/IdentityDb + +dotnet ef migrations add M2 --project ./src/Api/ --startup-project ./src/Api/Api.csproj --msbuildprojectextensionspath .build/obj/Api/ --context ApiDbContext --output-dir Data/Migrations/ApiDb + +==================== + +dotnet ef database update --project ./src/Api/ --msbuildprojectextensionspath .build/obj/Api/ --context ApplicationIdentityDbContext + +dotnet ef database update --project ./src/Api/ --msbuildprojectextensionspath .build/obj/Api/ --context ApiDbContext + +==================== + +Update to the latest versions of node & npm + +==================== + +Start the API .NET application then hit this endpoint in a browser to create seed data: +http://localhost:8001/api/administration/setup + + + + diff --git a/docs/open-source.txt b/docs/open-source.txt new file mode 100644 index 000000000..38db29dbc --- /dev/null +++ b/docs/open-source.txt @@ -0,0 +1,3 @@ +Open Source Accounting System + +https://github.com/AccountGo/accountgo diff --git a/docs/pr.txt b/docs/pr.txt new file mode 100644 index 000000000..b5b7b5215 --- /dev/null +++ b/docs/pr.txt @@ -0,0 +1,19 @@ +git checkout -b dotnet_9 origin/dotnet_9 + +docker run --cap-add SYS_PTRACE -e ACCEPT_EULA=1 -e MSSQL_SA_PASSWORD=SqlPassword! -p 1444:1433 --name sql -d mcr.microsoft.com/mssql/server:2022-latest + +--------- + +dotnet ef migrations add M1 --project ./src/Api/ --startup-project ./src/Api/Api.csproj --msbuildprojectextensionspath .build/obj/Api/ --context ApplicationIdentityDbContext --output-dir Data/Migrations/IdentityDb + +dotnet ef migrations add M2 --project ./src/Api/ --startup-project ./src/Api/Api.csproj --msbuildprojectextensionspath .build/obj/Api/ --context ApiDbContext --output-dir Data/Migrations/ApiDb + +dotnet ef database update --project ./src/Api/ --msbuildprojectextensionspath .build/obj/Api/ --context ApplicationIdentityDbContext + +dotnet ef database update --project ./src/Api/ --msbuildprojectextensionspath .build/obj/Api/ --context ApiDbContext + +========== + +Apply Entity Framework Core migrations in .NET Aspire + https://learn.microsoft.com/en-us/dotnet/aspire/database/ef-core-migrations + diff --git a/docs/react.txt b/docs/react.txt new file mode 100644 index 000000000..2e56c21d6 --- /dev/null +++ b/docs/react.txt @@ -0,0 +1,4 @@ +https://www.youtube.com/watch?v=ElgfQdq-Htk + +https://www.youtube.com/watch?v=oN9W0Tkn8hg + diff --git a/move-to-blazor.txt b/move-to-blazor.txt new file mode 100644 index 000000000..e388316b6 --- /dev/null +++ b/move-to-blazor.txt @@ -0,0 +1,9 @@ +Recreate starter Blazor app with database authentication +- we will use JWT for client-side authentication + +Get chart of account to work + +CI/CD GiHul >> Azure + +Meet and decide on moving the current application into the Blazor template + diff --git a/src/AccountGoWeb/.vscode/launch.json b/src/AccountGoWeb/.vscode/launch.json index 5825c3616..ddd8758f9 100644 --- a/src/AccountGoWeb/.vscode/launch.json +++ b/src/AccountGoWeb/.vscode/launch.json @@ -4,6 +4,11 @@ // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 "version": "0.2.0", "configurations": [ + { + "name": ".NET Core Attach", + "type": "coreclr", + "request": "attach" + }, { "name": ".NET Core Launch (web)", "type": "coreclr", diff --git a/src/AccountGoWeb/AccountGoWeb.csproj b/src/AccountGoWeb/AccountGoWeb.csproj index 0175223c6..3d681ea82 100644 --- a/src/AccountGoWeb/AccountGoWeb.csproj +++ b/src/AccountGoWeb/AccountGoWeb.csproj @@ -1,46 +1,37 @@ - - + - net7.0 - true - AccountGoWeb - AccountGoWeb - latest - 0.0.1-alpha - Latest + net10.0 + GoodBooks + GoodBooks + 1.0.0 + enable + enable + true + aspnet-GoodBooks-21ac3a7f-d42e-4136-9340-b4f6254706df + + true + NU1701 - PreserveNewest - - - + + + + - - + + - - + + + + + - - - - - - - - - - - - - - - - - + + \ No newline at end of file diff --git a/src/AccountGoWeb/Components/App.razor b/src/AccountGoWeb/Components/App.razor new file mode 100644 index 000000000..abb7c3dd7 --- /dev/null +++ b/src/AccountGoWeb/Components/App.razor @@ -0,0 +1,23 @@ +@* + + + + + + *@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/AccountGoWeb/Components/Pages/Audit/AuditableEntities.razor b/src/AccountGoWeb/Components/Pages/Audit/AuditableEntities.razor new file mode 100644 index 000000000..3edc50471 --- /dev/null +++ b/src/AccountGoWeb/Components/Pages/Audit/AuditableEntities.razor @@ -0,0 +1,215 @@ +@page "/audit/auditable-entities-blazor" +@namespace AccountGoWeb.Components.Pages.Audit +@using Dto.Auditing +@using System.Text.Json +@inject IHttpClientFactory ClientFactory +@inject IConfiguration Configuration + +Auditable Entities + +@if (getError) +{ +
Unable to get data. Please try again later.
+} +else if (isLoading) +{ +
+
+ Loading... +
+

Loading auditable entities...

+
+} +else +{ +
+
+
+ + + + +
+ @if (entities == null || !entities.Any()) + { +
+

No auditable entities found.

+
+ } + else + { +
+ + + + + + + + + + + @foreach (var entity in entities) + { + + + + + + + } + +
IDEntity NameEnable AuditActions
@entity.Id@entity.EntityName + @if (entity.EnableAudit) + { + + } + else + { + + } + + + Edit + + + Attributes + + +
+
+ } +
+
+
+
+} + +@* Delete Confirmation Modal *@ +@if (isDeleteModalVisible && selectedEntity != null) +{ + + +} + +@code { + private List entities = new(); + private AuditableEntity? selectedEntity = null; + private bool isDeleteModalVisible = false; + private string errorMessage = string.Empty; + private bool isLoading = true; + private bool getError = false; + private string apiUrl = string.Empty; + + protected override async Task OnInitializedAsync() + { + // Get API URL from configuration (same as Program.cs sets it) + apiUrl = Configuration["ApiUrl"]!; + await LoadEntitiesFromApi(); + isLoading = false; + } + + private async Task LoadEntitiesFromApi() + { + try + { + var client = ClientFactory.CreateClient(); + + var response = await client.GetAsync($"{apiUrl}audit/entities"); + + if (response.IsSuccessStatusCode) + { + var jsonString = await response.Content.ReadAsStringAsync(); + entities = JsonSerializer.Deserialize>(jsonString, new JsonSerializerOptions + { + PropertyNameCaseInsensitive = true + }) ?? new List(); + } + else + { + getError = true; + } + } + catch + { + getError = true; + } + } + + private void OpenDeleteModal(AuditableEntity entity) + { + selectedEntity = entity; + errorMessage = string.Empty; + isDeleteModalVisible = true; + } + + private void CloseDeleteModal() + { + isDeleteModalVisible = false; + selectedEntity = null; + errorMessage = string.Empty; + } + + private async Task ConfirmDeleteEntity() + { + if (selectedEntity == null) + { + errorMessage = "No entity selected."; + return; + } + + try + { + var client = ClientFactory.CreateClient(); + + var response = await client.DeleteAsync($"{apiUrl}audit/entity/{selectedEntity.Id}"); + + if (response.IsSuccessStatusCode) + { + // Remove from local list + entities.Remove(selectedEntity); + CloseDeleteModal(); + + // Reload to ensure data is fresh + await LoadEntitiesFromApi(); + } + else + { + errorMessage = "Failed to delete entity. Please try again."; + } + } + catch (Exception ex) + { + errorMessage = $"Error deleting entity: {ex.Message}"; + } + } +} diff --git a/src/AccountGoWeb/Components/Pages/Audit/EntityForm.razor b/src/AccountGoWeb/Components/Pages/Audit/EntityForm.razor new file mode 100644 index 000000000..b87cb0159 --- /dev/null +++ b/src/AccountGoWeb/Components/Pages/Audit/EntityForm.razor @@ -0,0 +1,153 @@ +@page "/audit/entity-form" +@page "/audit/entity-form/{Id:int}" +@namespace AccountGoWeb.Components.Pages.Audit +@using Dto.Auditing +@using System.Text.Json +@using System.Text +@inject IHttpClientFactory ClientFactory +@inject IConfiguration Configuration +@inject NavigationManager Navigation + +@(Id == 0 ? "Add New" : "Edit") Auditable Entity + +@if (isLoading) +{ +
+
+ Loading... +
+
+} +else +{ +
+
+
+
+

@(Id == 0 ? "Add New" : "Edit") Auditable Entity

+
+ + + + +
+ +
+ + + +
+ + +
+
+ +
+
+ + @if (!string.IsNullOrEmpty(errorMessage)) + { +
@errorMessage
+ } +
+ + + +
+
+
+
+} + +@code { + [Parameter] + public int Id { get; set; } = 0; + + private AuditableEntity entity = new() { EnableAudit = true }; + private bool isSaving = false; + private bool isLoading = false; + private string errorMessage = string.Empty; + private string apiUrl = string.Empty; + + protected override async Task OnInitializedAsync() + { + apiUrl = Configuration["ApiUrl"] ?? "http://localhost:8001/api/"; + + if (Id > 0) + { + isLoading = true; + await LoadEntity(); + isLoading = false; + } + } + + private async Task LoadEntity() + { + try + { + var client = ClientFactory.CreateClient(); + var response = await client.GetAsync($"{apiUrl}audit/entity?id={Id}"); + + if (response.IsSuccessStatusCode) + { + var jsonString = await response.Content.ReadAsStringAsync(); + entity = JsonSerializer.Deserialize(jsonString, new JsonSerializerOptions + { + PropertyNameCaseInsensitive = true + }) ?? new AuditableEntity { EnableAudit = true }; + } + else + { + errorMessage = "Failed to load entity."; + } + } + catch (Exception ex) + { + errorMessage = $"Error loading entity: {ex.Message}"; + } + } + + private async Task HandleSubmit() + { + isSaving = true; + errorMessage = string.Empty; + + try + { + var client = ClientFactory.CreateClient(); + var json = JsonSerializer.Serialize(entity); + var content = new StringContent(json, Encoding.UTF8, "application/json"); + + var response = await client.PostAsync($"{apiUrl}audit/entity", content); + + if (response.IsSuccessStatusCode) + { + Navigation.NavigateTo("/Audit/GetAuditableEntities", forceLoad: true); + } + else + { + errorMessage = "Failed to save entity. Please try again."; + } + } + catch (Exception ex) + { + errorMessage = $"Error saving entity: {ex.Message}"; + } + finally + { + isSaving = false; + } + } +} diff --git a/src/AccountGoWeb/Components/Pages/Contact/Contact.razor b/src/AccountGoWeb/Components/Pages/Contact/Contact.razor new file mode 100644 index 000000000..a436d3d74 --- /dev/null +++ b/src/AccountGoWeb/Components/Pages/Contact/Contact.razor @@ -0,0 +1,230 @@ +@using ContactDto = Dto.Common.Contact +@inject HttpClient Http +@inject NavigationManager Navigation +@rendermode InteractiveServer + +@(Id == 0 ? "New" : "Edit") Contact + + + +
+
+
+ @if (Id != 0 && !isEditMode) + { + + } +
+
+ + @if (isLoading) + { +
+
+
+ Loading... +
+

Loading contact...

+
+
+ } + else if (errorMessage != null) + { +
+
+ +
+
+ } + else + { + + + +
+
+ @(Id == 0 ? "New Contact" : isEditMode ? "Edit Contact" : "Contact") +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ +
+
+
+
+ +
+
+ +
+
+
+
+ +
+
+ +
+
+
+
+ +
+
+ @if (isEditMode || Id == 0) + { + + } + +
+
+
+ } +
+ +@code { + [Parameter] + public int Id { get; set; } = 0; + + [Parameter] + [SupplyParameterFromQuery] + public int? PartyId { get; set; } + + [Parameter] + [SupplyParameterFromQuery] + public int? PartyType { get; set; } + + private ContactDto Model { get; set; } = new(); + + private bool isLoading = true; + private bool isEditMode = false; + private string? errorMessage; + + protected override async Task OnInitializedAsync() + { + isLoading = true; + + if (Id != 0) + { + await LoadContact(); + } + else + { + isEditMode = true; + Model.HoldingPartyId = PartyId.GetValueOrDefault(); + Model.HoldingPartyType = PartyType.GetValueOrDefault(); + } + + isLoading = false; + } + + private async Task LoadContact() + { + try + { + string baseApiUrl = Environment.GetEnvironmentVariable("APIURL") ?? "http://localhost:8001/api/"; + var apiUrl = $"{baseApiUrl}contact/contact?id={Id}"; + var response = await Http.GetAsync(apiUrl); + + if (response.IsSuccessStatusCode) + { + Model = await response.Content.ReadFromJsonAsync() ?? new(); + } + else + { + errorMessage = $"Failed to load contact. Status: {response.StatusCode}"; + } + } + catch (Exception ex) + { + errorMessage = $"Error loading contact: {ex.Message}"; + } + } + + private async Task HandleValidSubmit() + { + try + { + string baseApiUrl = Environment.GetEnvironmentVariable("APIURL") ?? "http://localhost:8001/api/"; + var apiUrl = baseApiUrl + "contact/savecontact"; + + var response = await Http.PostAsJsonAsync(apiUrl, Model); + + if (response.IsSuccessStatusCode) + { + if (PartyId.HasValue && PartyType.HasValue) + { + Navigation.NavigateTo($"/contact/contacts?partyId={PartyId}&partyType={PartyType}", forceLoad: true); + } + else + { + Navigation.NavigateTo("/sales/customers", forceLoad: true); + } + } + else + { + var errorContent = await response.Content.ReadAsStringAsync(); + errorMessage = $"Failed to save contact. Status: {response.StatusCode}. {errorContent}"; + } + } + catch (Exception ex) + { + errorMessage = $"Error saving contact: {ex.Message}"; + } + } + + private void EnableEditMode() + { + isEditMode = true; + } + + private void NavigateBack() + { + if (PartyId.HasValue && PartyType.HasValue) + { + Navigation.NavigateTo($"/contact/contacts?partyId={PartyId}&partyType={PartyType}", forceLoad: true); + } + else if (PartyId.HasValue) + { + Navigation.NavigateTo($"/sales/customer/{PartyId}", forceLoad: true); + } + else + { + Navigation.NavigateTo("/sales/customers", forceLoad: true); + } + } +} diff --git a/src/AccountGoWeb/Components/Pages/Contact/Contacts.razor b/src/AccountGoWeb/Components/Pages/Contact/Contacts.razor new file mode 100644 index 000000000..0cd019779 --- /dev/null +++ b/src/AccountGoWeb/Components/Pages/Contact/Contacts.razor @@ -0,0 +1,340 @@ +@using ContactDto = Dto.Common.Contact +@inject HttpClient Http +@inject NavigationManager Navigation +@rendermode InteractiveServer + +Contacts + +
+ + +
+
+ + @if (selectedContact != null) + { + + + } + +
+
+ + @if (successMessage != null) + { +
+
+ +
+
+ } + + @if (isLoading) + { +
+
+
+ Loading... +
+

Loading contacts...

+
+
+ } + else if (errorMessage != null) + { +
+
+ +
+
+ } + else if (contacts == null || !contacts.Any()) + { +
+
+ +
+
+ } + else + { +
+
+
+ + + + + + + + + + + @foreach (var contact in contacts) + { + + + + + + + } + +
+ Id @GetSortIcon(nameof(ContactDto.Id)) + + First Name @GetSortIcon(nameof(ContactDto.FirstName)) + + Last Name @GetSortIcon(nameof(ContactDto.LastName)) + Primary
+ + @contact.Id + + @contact.FirstName@contact.LastName + @if (IsPrimaryContact(contact)) + { + + Primary + + } +
+
+
+
+ +
+
+

Total: @contacts.Count() contact(s)

+
+
+ } +
+ +@code { + [Parameter] + [SupplyParameterFromQuery] + public int? PartyId { get; set; } + + [Parameter] + [SupplyParameterFromQuery] + public int? PartyType { get; set; } + + private List? contacts; + private ContactDto? selectedContact; + private Dto.Sales.Customer? currentCustomer; + private bool isLoading = true; + private string? errorMessage; + private string? successMessage; + private string? sortColumn; + private bool sortAscending = true; + + protected override async Task OnInitializedAsync() + { + await LoadCustomer(); + await LoadContacts(); + } + + private async Task LoadCustomer() + { + if (!PartyId.HasValue) return; + + try + { + string baseApiUrl = Environment.GetEnvironmentVariable("APIURL") ?? "http://localhost:8001/api/"; + var customerUrl = $"{baseApiUrl}sales/customer?id={PartyId}"; + var customerResponse = await Http.GetAsync(customerUrl); + + if (customerResponse.IsSuccessStatusCode) + { + currentCustomer = await customerResponse.Content.ReadFromJsonAsync(); + } + } + catch (Exception) + { + // Silently fail - customer may not exist yet + } + } + + private async Task LoadContacts() + { + isLoading = true; + errorMessage = null; + + try + { + string baseApiUrl = Environment.GetEnvironmentVariable("APIURL") ?? "http://localhost:8001/api/"; + var apiUrl = baseApiUrl + $"contact/contacts?partyId={PartyId}&partyType={PartyType}"; + var response = await Http.GetAsync(apiUrl); + + if (response.IsSuccessStatusCode) + { + contacts = await response.Content.ReadFromJsonAsync>(); + } + else + { + errorMessage = $"Failed to load contacts. Status: {response.StatusCode}"; + } + } + catch (HttpRequestException ex) + { + errorMessage = $"Network error: {ex.Message}. Please ensure the API is running on port 8001."; + } + catch (Exception ex) + { + errorMessage = $"Error loading contacts: {ex.Message}"; + } + finally + { + isLoading = false; + } + } + + private void SelectContact(ContactDto contact) + { + selectedContact = contact; + } + + private async Task SetAsPrimaryContact() + { + if (selectedContact == null || !PartyId.HasValue) return; + + try + { + string baseApiUrl = Environment.GetEnvironmentVariable("APIURL") ?? "http://localhost:8001/api/"; + + // Load the customer + var customerUrl = $"{baseApiUrl}sales/customer?id={PartyId}"; + var customerResponse = await Http.GetAsync(customerUrl); + + if (customerResponse.IsSuccessStatusCode) + { + var customer = await customerResponse.Content.ReadFromJsonAsync(); + if (customer != null) + { + // Update customer's primary contact data + if (customer.PrimaryContact == null) + { + customer.PrimaryContact = new ContactDto(); + } + customer.PrimaryContact.FirstName = selectedContact.FirstName; + customer.PrimaryContact.LastName = selectedContact.LastName; + customer.PrimaryContact.Party = selectedContact.Party; + + // Save the customer + var saveUrl = baseApiUrl + "sales/savecustomer"; + var saveResponse = await Http.PostAsJsonAsync(saveUrl, customer); + + if (saveResponse.IsSuccessStatusCode) + { + successMessage = $"Set {selectedContact.FirstName} {selectedContact.LastName} as primary contact."; + currentCustomer = customer; + StateHasChanged(); + } + else + { + errorMessage = $"Failed to update primary contact. Status: {saveResponse.StatusCode}"; + } + } + } + else + { + errorMessage = $"Failed to load customer. Status: {customerResponse.StatusCode}"; + } + } + catch (Exception ex) + { + errorMessage = $"Error setting primary contact: {ex.Message}"; + } + } + + private void NavigateToViewContact() + { + if (selectedContact != null) + { + Navigation.NavigateTo($"/contact/contact/{selectedContact.Id}?partyId={selectedContact.HoldingPartyId}&partyType={selectedContact.HoldingPartyType}", forceLoad: true); + } + } + + private void NavigateToNewContact() + { + if (PartyId.HasValue && PartyType.HasValue) + { + Navigation.NavigateTo($"/contact/contact?partyId={PartyId}&partyType={PartyType}", forceLoad: true); + } + else + { + Navigation.NavigateTo("/contact/contact", forceLoad: true); + } + } + + private void NavigateToCustomers() + { + if (PartyId.HasValue) + { + Navigation.NavigateTo($"/sales/customer/{PartyId}", forceLoad: true); + } + else + { + Navigation.NavigateTo("/sales/customers", forceLoad: true); + } + } + + private void SortBy(string column) + { + if (contacts == null) return; + + if (sortColumn == column) + { + sortAscending = !sortAscending; + } + else + { + sortColumn = column; + sortAscending = true; + } + + contacts = column switch + { + nameof(ContactDto.Id) => sortAscending + ? contacts.OrderBy(c => c.Id).ToList() + : contacts.OrderByDescending(c => c.Id).ToList(), + nameof(ContactDto.FirstName) => sortAscending + ? contacts.OrderBy(c => c.FirstName).ToList() + : contacts.OrderByDescending(c => c.FirstName).ToList(), + nameof(ContactDto.LastName) => sortAscending + ? contacts.OrderBy(c => c.LastName).ToList() + : contacts.OrderByDescending(c => c.LastName).ToList(), + _ => contacts + }; + } + + private string GetSortIcon(string column) + { + if (sortColumn != column) return ""; + return sortAscending ? "▲" : "▼"; + } + + private bool IsPrimaryContact(ContactDto contact) + { + if (currentCustomer?.PrimaryContact == null) return false; + return currentCustomer.PrimaryContact.FirstName == contact.FirstName && + currentCustomer.PrimaryContact.LastName == contact.LastName; + } +} diff --git a/src/AccountGoWeb/Components/Pages/Counter.razor b/src/AccountGoWeb/Components/Pages/Counter.razor new file mode 100644 index 000000000..0d9d43ad4 --- /dev/null +++ b/src/AccountGoWeb/Components/Pages/Counter.razor @@ -0,0 +1,19 @@ +@page "/counter" +@rendermode InteractiveServer + +Counter + +

Counter

+ +

Current count: @currentCount

+ + + +@code { + private int currentCount = 0; + + private void IncrementCount() + { + currentCount++; + } +} diff --git a/src/AccountGoWeb/Components/Pages/Donations/AddDonationInvoice.razor b/src/AccountGoWeb/Components/Pages/Donations/AddDonationInvoice.razor new file mode 100644 index 000000000..5290a4daa --- /dev/null +++ b/src/AccountGoWeb/Components/Pages/Donations/AddDonationInvoice.razor @@ -0,0 +1,476 @@ +@rendermode InteractiveServer +@using AspNetCoreGeneratedDocument +@using Dto.Donations +@using Dto.Sales +@using Dto.Inventory +@using System.Text.Json +@inject HttpClient Http +@inject IConfiguration Configuration +@inject NavigationManager NavigationManager + + + +Add Donation Invoice + + + +@if (!string.IsNullOrEmpty(errorMessage)) +{ + +} + +@if (!string.IsNullOrEmpty(successMessage)) +{ + +} + +@if (isLoading) +{ +
+
+ Loading... +
+
+} +else if (invoice != null) +{ + + + +
+
+ + + + + + + + + + + + + + + + + + + + + +
+ + + + + @foreach (var customer in customers) + { + + } + + + + + + +
+ + + + + + + +
+ + +
+ + +
+
+ + +
+ + +
+
+
+
+ +
+
+
Invoice Lines
+ +
+ +
+ + + + + + + + + + + + + @for (int i = 0; i < (invoice.DonationInvoiceLines?.Count ?? 0); i++) + { + var index = i; + var line = invoice.DonationInvoiceLines![index]; + + + + + + + + + } + +
ItemQuantityAmountMeasurementNotes
+ + + @foreach (var item in items) + { + + } + + + + + + + + + @foreach (var measurement in measurements) + { + + } + + + + + @if (invoice.DonationInvoiceLines.Count > 1) + { + + } +
+
+ +
+ Total: @invoice.Amount.ToString("C2") +
+
+ +
+ + +
+
+} + +@code { + private DonationInvoice? invoice; + private List customers = new(); + private List items = new(); + private List measurements = new(); + private bool isLoading = true; + private bool isSaving = false; + private string? errorMessage; + private string? successMessage; + + public class Measurement + { + public int Id { get; set; } + public string? Code { get; set; } + public string? Description { get; set; } + } + + protected override async Task OnInitializedAsync() + { + await LoadData(); + } + + private async Task LoadData() + { + isLoading = true; + try + { + var apiUrl = Configuration["ApiUrl"]; + + // Load customers + var customersResponse = await Http.GetAsync($"{apiUrl}sales/customers"); + if (customersResponse.IsSuccessStatusCode) + { + var customersJson = await customersResponse.Content.ReadAsStringAsync(); + customers = JsonSerializer.Deserialize>(customersJson, + new JsonSerializerOptions { PropertyNameCaseInsensitive = true }) ?? new(); + } + + // Load items + var itemsResponse = await Http.GetAsync($"{apiUrl}inventory/items"); + if (itemsResponse.IsSuccessStatusCode) + { + var itemsJson = await itemsResponse.Content.ReadAsStringAsync(); + items = JsonSerializer.Deserialize>(itemsJson, + new JsonSerializerOptions { PropertyNameCaseInsensitive = true }) ?? new(); + } + + // Load measurements + var measurementsResponse = await Http.GetAsync($"{apiUrl}common/measurements"); + if (measurementsResponse.IsSuccessStatusCode) + { + var measurementsJson = await measurementsResponse.Content.ReadAsStringAsync(); + measurements = JsonSerializer.Deserialize>(measurementsJson, + new JsonSerializerOptions { PropertyNameCaseInsensitive = true }) ?? new(); + } + + // Initialize invoice + invoice = new DonationInvoice + { + No = new Random().Next(1, 99999).ToString(), + DonationDate = DateTime.Now, + DonationInvoiceLines = new List +{ +new DonationInvoiceLine +{ +Amount = 0, +Quantity = 1, +ItemId = items.FirstOrDefault()?.Id ?? 0, +MeasurementId = measurements.FirstOrDefault()?.Id ?? 0 +} +} + }; + } + catch (Exception ex) + { + errorMessage = $"Error loading data: {ex.Message}"; + } + finally + { + isLoading = false; + } + } + + private void AddLine() + { + invoice?.DonationInvoiceLines?.Add(new DonationInvoiceLine + { + Amount = 0, + Quantity = 1, + ItemId = items.FirstOrDefault()?.Id ?? 0, + MeasurementId = measurements.FirstOrDefault()?.Id ?? 0 + }); + } + + private void RemoveLine(int index) + { + if (invoice?.DonationInvoiceLines != null && invoice.DonationInvoiceLines.Count > 1) + { + invoice.DonationInvoiceLines.RemoveAt(index); + } + } + + private async Task SaveInvoice() + { + if (invoice == null) return; + + isSaving = true; + errorMessage = null; + successMessage = null; + + try + { + var apiUrl = Configuration["ApiUrl"]; + var json = JsonSerializer.Serialize(invoice); + var content = new StringContent(json, System.Text.Encoding.UTF8, "application/json"); + + var response = await Http.PostAsync($"{apiUrl}Donations/CreateDonationInvoice", content); + + if (response.IsSuccessStatusCode) + { + successMessage = $"Donation invoice '{invoice.No}' saved successfully!"; + ResetForm(); + + // Auto-navigate after 2 seconds + await Task.Delay(2000); + NavigationManager.NavigateTo("/donations/donationinvoices"); + } + else + { + var error = await response.Content.ReadAsStringAsync(); + errorMessage = $"Failed to save: {response.StatusCode} - {error}"; + } + } + catch (Exception ex) + { + errorMessage = $"Error saving invoice: {ex.Message}"; + } + finally + { + isSaving = false; + } + } + private void ResetForm() + { + invoice = new DonationInvoice + { + No = new Random().Next(1, 99999).ToString(), + DonationDate = DateTime.Now, + DonationInvoiceLines = new List +{ +new DonationInvoiceLine +{ +Amount = 0, +Quantity = 1, +ItemId = items.FirstOrDefault()?.Id ?? 0, +MeasurementId = measurements.FirstOrDefault()?.Id ?? 0 +} +} + }; + } + + private void Cancel() + { + NavigationManager.NavigateTo("/donations/donationinvoices"); + } +} \ No newline at end of file diff --git a/src/AccountGoWeb/Components/Pages/Donations/DonationInvoices.razor b/src/AccountGoWeb/Components/Pages/Donations/DonationInvoices.razor new file mode 100644 index 000000000..626e0fbbf --- /dev/null +++ b/src/AccountGoWeb/Components/Pages/Donations/DonationInvoices.razor @@ -0,0 +1,370 @@ +@rendermode InteractiveServer +@using AspNetCoreGeneratedDocument +@using Dto.Donations +@inject HttpClient Http +@inject IConfiguration Configuration +@inject NavigationManager NavigationManager +@inject IJSRuntime JSRuntime + + + +Donation Invoices + + + + +
+
+ + New Donation Invoice + + + +
+
+ +@if (isLoading) +{ +
+
+ Loading... +
+
+} +else if (donationInvoices != null && donationInvoices.Any()) +{ +
+
+ + + + + + + + + + + + + + + + + @foreach (var invoice in donationInvoices) + { + + + + + + + + + + + + + } + +
Invoice NoDonor NameDateAmountPurposeReference NoTax Receipt NoTax ReceiptStatus
+ + @invoice.No@invoice.DonorName@invoice.DonationDate.ToString("MMM dd, yyyy")$@invoice.Amount.ToString("F2")@invoice.Purpose@invoice.ReferenceNo@invoice.TaxReceiptNo + @if (invoice.IsTaxReceiptIssued) + { + + Issued + + } + else + { + + Pending + + } + + @if (invoice.Posted) + { + + Posted + + } + else + { + + Draft + + } +
+
+
+} +else +{ +
+
+ + No donation invoices found. Click "New Donation Invoice" to create one. +
+
+} + +@* Delete Confirmation Modal *@ +@if (showDeleteModal) +{ + +} + + + +@code { + private List? donationInvoices; + private DonationInvoice? selectedInvoice; + private bool isLoading = true; + private bool showDeleteModal = false; + private bool HasSelection => selectedInvoice != null; + + protected override async Task OnInitializedAsync() + { + await LoadDonationInvoices(); + } + + private async Task LoadDonationInvoices() + { + isLoading = true; + try + { + var apiUrl = Configuration["ApiUrl"]; + var response = await Http.GetAsync($"{apiUrl}donations/donationinvoices"); + + if (response.IsSuccessStatusCode) + { + var responseJson = await response.Content.ReadAsStringAsync(); + donationInvoices = System.Text.Json.JsonSerializer.Deserialize>( + responseJson, + new System.Text.Json.JsonSerializerOptions + { + PropertyNameCaseInsensitive = true + }); + } + else + { + donationInvoices = new List(); + } + } + catch (Exception ex) + { + Console.WriteLine($"Error loading donation invoices: {ex.Message}"); + donationInvoices = new List(); + } + finally + { + isLoading = false; + } + } + + private void SelectInvoice(DonationInvoice invoice) + { + selectedInvoice = invoice; + StateHasChanged(); + } + + private void EditInvoice() + { + if (selectedInvoice != null) + { + NavigationManager.NavigateTo($"/donations/donationinvoice?id={selectedInvoice.Id}"); + } + } + + private void ShowDeleteModal() + { + if (selectedInvoice != null) + { + showDeleteModal = true; + } + } + + private void CloseDeleteModal() + { + showDeleteModal = false; + } + + private async Task DeleteInvoice() + { + if (selectedInvoice != null) + { + try + { + NavigationManager.NavigateTo($"/donations/deletedonationinvoice?id={selectedInvoice.Id}"); + showDeleteModal = false; + } + catch (Exception ex) + { + Console.WriteLine($"Error deleting invoice: {ex.Message}"); + } + } + } +} \ No newline at end of file diff --git a/src/AccountGoWeb/Components/Pages/Financial/AddJournalEntry.razor b/src/AccountGoWeb/Components/Pages/Financial/AddJournalEntry.razor new file mode 100644 index 000000000..d97f2d52e --- /dev/null +++ b/src/AccountGoWeb/Components/Pages/Financial/AddJournalEntry.razor @@ -0,0 +1,347 @@ +@using System.Net.Http.Json +@using Dto.Financial +@inject IHttpClientFactory HttpFactory +@inject IConfiguration Config +@using JournalEntryDto = Dto.Financial.JournalEntry +@using JournalEntryLineDto = Dto.Financial.JournalEntryLine + + +@*

Add Journal Entry

*@ + +@if (!string.IsNullOrEmpty(SuccessMessage)) +{ +
@SuccessMessage
+} + +@if (!string.IsNullOrEmpty(ErrorMessage)) +{ +
@ErrorMessage
+} + + + + + + + +
+
Header
+
+
+ + +
+ +
+ + +
+ +
+ + + + + + + + + +
+ +
+ + +
+
+
+ + +
+
+ Lines + +
+ +
+ @if (Accounts == null || Accounts.Count == 0) + { +
+ Accounts are not loaded yet. You won’t be able to save until accounts are available. +
+ } + + + + + + + + + + + + + @if (Entry.JournalEntryLines != null) + { + @foreach (var line in Entry.JournalEntryLines) + { + var currentLine = line; + + + + + + + + + + + + } + } + + + + + + + + + + + + + +
AccountDr/CrAmountMemo
+ @if (Accounts != null && Accounts.Count > 0) + { + + + @foreach (var acct in Accounts) + { + + } + + } + else + { + No accounts + } + + + + + + + + + + + + +
Total Debit@TotalDebit.ToString("0.00")
Total Credit@TotalCredit.ToString("0.00")
+
+
+ + +
+ +
+
+ + +@code { + private JournalEntryDto Entry = new(); + + private bool IsSaving; + private string? ErrorMessage; + private string? SuccessMessage; + + private List AccountTree = new(); + private List Accounts = new(); + + private decimal TotalDebit => + Entry.JournalEntryLines? + .Where(l => l.DrCr == 1) // 1 = Debit + .Sum(l => l.Amount ?? 0) ?? 0; + + private decimal TotalCredit => + Entry.JournalEntryLines? + .Where(l => l.DrCr == 2) // 2 = Credit + .Sum(l => l.Amount ?? 0) ?? 0; + + protected override async Task OnInitializedAsync() + { + // remove this if BaseDto doesn't have Id + // Entry.Id = 0; + + Entry.JournalDate = DateTime.Today; + Entry.Posted = false; + Entry.VoucherType ??= 1; + Entry.JournalEntryLines ??= new List(); + + AddLine(); + await LoadAccountsAsync(); + } + + private async Task LoadAccountsAsync() + { + try + { + var apiBaseUrl = Config["ApiUrl"]; + if (string.IsNullOrWhiteSpace(apiBaseUrl)) + { + ErrorMessage = "ApiUrl is not configured."; + return; + } + + var client = HttpFactory.CreateClient(); + var url = $"{apiBaseUrl}financials/accounts"; + + var tree = await client.GetFromJsonAsync>(url); + + if (tree != null) + { + AccountTree = tree; + Accounts = FlattenAccounts(AccountTree) + .OrderBy(a => a.AccountCode) + .ToList(); + } + else + { + ErrorMessage = "Could not load accounts (empty response)."; + } + } + catch (Exception ex) + { + ErrorMessage = $"Error loading accounts: {ex.Message}"; + } + } + + private List FlattenAccounts(IEnumerable nodes) + { + var list = new List(); + + void Walk(IEnumerable items) + { + foreach (var a in items) + { + list.Add(a); + if (a.ChildAccounts != null && a.ChildAccounts.Count > 0) + Walk(a.ChildAccounts); + } + } + + Walk(nodes); + return list; + } + + private void AddLine() + { + Entry.JournalEntryLines!.Add(new JournalEntryLineDto + { + + DrCr = 1, + Amount = 0 + }); + } + + private void RemoveLine(JournalEntryLineDto line) + { + Entry.JournalEntryLines!.Remove(line); + } + + private async Task HandleValidSubmit() + { + ErrorMessage = null; + SuccessMessage = null; + IsSaving = true; + + @* if (TotalDebit != TotalCredit) + { + ErrorMessage = "Debits and credits are not equal."; + IsSaving = false; + return; + } *@ + + if (Entry.JournalEntryLines == null || Entry.JournalEntryLines.Any(l => l.AccountId == null)) + { + ErrorMessage = "All lines must have an account selected."; + IsSaving = false; + return; + } + + try + { + var apiBaseUrl = Config["ApiUrl"]; + if (string.IsNullOrWhiteSpace(apiBaseUrl)) + { + ErrorMessage = "ApiUrl is not configured."; + return; + } + + var client = HttpFactory.CreateClient(); + var url = $"{apiBaseUrl}financials/SaveJournalEntry"; + + var response = await client.PostAsJsonAsync(url, Entry); + + if (response.IsSuccessStatusCode) + { + SuccessMessage = "Journal entry saved successfully."; + + // reset using DTO alias + Entry = new JournalEntryDto + { + JournalDate = DateTime.Today, + VoucherType = 1, + Posted = false, + JournalEntryLines = new List() + }; + AddLine(); + } + else + { + try + { + var errors = await response.Content.ReadFromJsonAsync(); + ErrorMessage = errors != null && errors.Length > 0 + ? string.Join("; ", errors) + : $"Error saving (HTTP {(int)response.StatusCode})"; + } + catch + { + ErrorMessage = $"Error saving (HTTP {(int)response.StatusCode})"; + } + } + } + catch (Exception ex) + { + ErrorMessage = $"Unexpected error: {ex.Message}"; + } + finally + { + IsSaving = false; + } + } +} \ No newline at end of file diff --git a/src/AccountGoWeb/Components/Pages/Financial/ChartOfAccounts.razor b/src/AccountGoWeb/Components/Pages/Financial/ChartOfAccounts.razor new file mode 100644 index 000000000..e34dbe7cd --- /dev/null +++ b/src/AccountGoWeb/Components/Pages/Financial/ChartOfAccounts.razor @@ -0,0 +1,638 @@ +@page "/financials/chart-of-accounts" +@using System.Text.Json +@using System.Text.Json.Serialization +@using System.Net.Http.Json +@using LibraryGDB.Models.Financial +@using Microsoft.JSInterop +@using Microsoft.Net.Http.Headers +@using Microsoft.AspNetCore.Components +@inject IHttpClientFactory ClientFactory +@inject Microsoft.JSInterop.IJSRuntime JSRuntime + +Chart of Accounts + +@if (getError || accounts is null) +{ +
Unable to get data. Please try again later.
+} +else if (isLoading) +{ +

Loading accounts...

+} +else +{ + @*
    + @foreach (var item in accounts) + { +
  • @item.AccountName
  • + } +
*@ + +
+ + + + + + + + + + + + + + + @for (int accountIdx = 0; accountIdx < accounts.Count(); ++accountIdx) + { + var account = accounts.ToList()[accountIdx]; + var accountTargetId = $"asset-{accountIdx}"; + + + + + + + + + + + + + + } + +
CodeNameBalanceDebitCreditActions
+ +
+
+ +} +@if (isAddModalVisible || isEditModalVisible) +{ + +} + +@if (isDeleteModalVisible) +{ + +} + +@code { + private List accounts = new(); + private AccountViewModel? selectedAccount = null; + private AccountViewModel? parentAccount = null; // Track parent account when adding sub-account + private bool isAddModalVisible = false; + private bool isEditModalVisible = false; + private bool isDeleteModalVisible = false; + private string errorMessage = string.Empty; + private bool isLoading = true; + private bool getError = false; + + // Recursive method to render nested accounts at any depth + private RenderFragment RenderNestedAccounts(List nestedAccounts, string parentPath) + { + return builder => + { + int sequence = 0; + builder.OpenElement(sequence++, "table"); + builder.AddAttribute(sequence++, "class", "table table-striped"); + + // Table header + builder.OpenElement(sequence++, "thead"); + builder.OpenElement(sequence++, "tr"); + builder.AddMarkupContent(sequence++, "CodeNameBalanceDebitCreditActions"); + builder.CloseElement(); // tr + builder.CloseElement(); // thead + + // Table body + builder.OpenElement(sequence++, "tbody"); + + for (int idx = 0; idx < nestedAccounts.Count; idx++) + { + var account = nestedAccounts[idx]; + var targetId = $"asset-{parentPath}-{idx}"; + + // Account row + builder.OpenElement(sequence++, "tr"); + builder.AddAttribute(sequence++, "data-bs-toggle", "collapse"); + builder.AddAttribute(sequence++, "data-bs-target", $"#{targetId}"); + builder.AddAttribute(sequence++, "aria-expanded", "false"); + builder.AddAttribute(sequence++, "aria-controls", targetId); + + builder.OpenElement(sequence++, "td"); + builder.AddContent(sequence++, account.AccountCode); + builder.CloseElement(); + + builder.OpenElement(sequence++, "td"); + builder.AddContent(sequence++, account.AccountName); + builder.CloseElement(); + + builder.OpenElement(sequence++, "td"); + builder.AddContent(sequence++, account.TotalBalance); + builder.CloseElement(); + + builder.OpenElement(sequence++, "td"); + builder.AddContent(sequence++, account.TotalDebitBalance); + builder.CloseElement(); + + builder.OpenElement(sequence++, "td"); + builder.AddContent(sequence++, account.TotalCreditBalance); + builder.CloseElement(); + + builder.OpenElement(sequence++, "td"); + builder.AddAttribute(sequence++, "onclick:stopPropagation", "true"); + builder.OpenElement(sequence++, "button"); + builder.AddAttribute(sequence++, "class", "btn btn-success btn-sm"); + builder.AddAttribute(sequence++, "onclick", EventCallback.Factory.Create(this, () => OpenAddModal(account))); + builder.AddAttribute(sequence++, "onclick:stopPropagation", "true"); + builder.AddContent(sequence++, "Add Account"); + builder.CloseElement(); // button + builder.OpenElement(sequence++, "button"); + builder.AddAttribute(sequence++, "class", "btn btn-primary btn-sm"); + builder.AddAttribute(sequence++, "onclick", EventCallback.Factory.Create(this, () => OpenEditModal(account))); + builder.AddAttribute(sequence++, "onclick:stopPropagation", "true"); + builder.AddContent(sequence++, "Edit"); + builder.CloseElement(); // button + builder.OpenElement(sequence++, "button"); + builder.AddAttribute(sequence++, "class", "btn btn-danger btn-sm"); + builder.AddAttribute(sequence++, "onclick", EventCallback.Factory.Create(this, () => OpenDeleteModal(account))); + builder.AddAttribute(sequence++, "onclick:stopPropagation", "true"); + builder.AddContent(sequence++, "Delete"); + builder.CloseElement(); // button + builder.CloseElement(); // td + + builder.CloseElement(); // tr + + // Collapsible row for children + if (account.ChildAccounts != null && account.ChildAccounts.Count > 0) + { + builder.OpenElement(sequence++, "tr"); + builder.OpenElement(sequence++, "td"); + builder.AddAttribute(sequence++, "colspan", "6"); + + builder.OpenElement(sequence++, "div"); + builder.AddAttribute(sequence++, "class", "collapse"); + builder.AddAttribute(sequence++, "id", targetId); + builder.AddAttribute(sequence++, "aria-expanded", "false"); + builder.AddAttribute(sequence++, "aria-controls", targetId); + + // Recursively render children + builder.AddContent(sequence++, RenderNestedAccounts(account.ChildAccounts, $"{parentPath}-{idx}")); + + builder.CloseElement(); // div + builder.CloseElement(); // td + builder.CloseElement(); // tr + } + } + + builder.CloseElement(); // tbody + builder.CloseElement(); // table + }; + } + + // Fetch accounts from API on initialization + protected override async Task OnInitializedAsync() + { + await LoadAccountsFromApi(); + isLoading = false; + } + + // Load accounts from API + private async Task LoadAccountsFromApi() + { + try + { + string apiUrl = Environment.GetEnvironmentVariable("APIURL") ?? "http://localhost:8001/api/"; + var client = ClientFactory.CreateClient(); + + var response = await client.GetAsync($"{apiUrl}financials/accounts"); + + if (response.IsSuccessStatusCode) + { + var jsonString = await response.Content.ReadAsStringAsync(); + accounts = JsonSerializer.Deserialize>(jsonString, new JsonSerializerOptions + { + PropertyNameCaseInsensitive = true + }) ?? new List(); + } + else + { + getError = true; + } + } + catch + { + getError = true; + } + + // Notify Blazor that the state has changed and UI needs to update + StateHasChanged(); + } + + + // Open Add Modal + private void OpenAddModal(AccountViewModel? parent = null) + { + parentAccount = parent; + selectedAccount = new AccountViewModel(); + errorMessage = string.Empty; + isAddModalVisible = true; + } + + // Open Edit Modal + private void OpenEditModal(AccountViewModel account) + { + selectedAccount = new AccountViewModel + { + AccountCode = account.AccountCode, + AccountName = account.AccountName, + TotalBalance = account.TotalBalance, + TotalDebitBalance = account.TotalDebitBalance, + TotalCreditBalance = account.TotalCreditBalance, + ChildAccounts = account.ChildAccounts + }; + errorMessage = string.Empty; + isEditModalVisible = true; + } + + // Close Add or Edit Modal + private void CloseModal() + { + isAddModalVisible = false; + isEditModalVisible = false; + selectedAccount = null; + parentAccount = null; + errorMessage = string.Empty; + } + + // Add or Update Account + private async Task SaveAccount() + { + if (selectedAccount == null) + { + errorMessage = "No account selected."; + return; + } + + if (string.IsNullOrWhiteSpace(selectedAccount.AccountCode) || string.IsNullOrWhiteSpace(selectedAccount.AccountName)) + { + errorMessage = "Both Account Code and Account Name are required."; + return; + } + + try + { + string apiUrl = Environment.GetEnvironmentVariable("APIURL") ?? "http://localhost:8001/api/"; + var client = ClientFactory.CreateClient(); + + var accountDto = new + { + AccountCode = selectedAccount.AccountCode, + AccountName = selectedAccount.AccountName, + ParentAccountId = parentAccount?.Id // Include parent account ID if adding a sub-account + }; + + HttpResponseMessage response; + + if (isEditModalVisible) + { + // Update existing account via API + response = await client.PutAsJsonAsync( + $"{apiUrl}financials/UpdateAccount/{selectedAccount.AccountCode}", + accountDto); + } + else + { + // Add new account via API + response = await client.PostAsJsonAsync( + $"{apiUrl}financials/AddAccount", + accountDto); + } + + if (response.IsSuccessStatusCode) + { + errorMessage = string.Empty; + // Close modal first to ensure UI state is updated + CloseModal(); + // Reload accounts from API to get updated data + await LoadAccountsFromApi(); + // StateHasChanged is called in LoadAccountsFromApi, but we also call it here + // to ensure the modal closes immediately + StateHasChanged(); + } + else + { + var errorContent = await response.Content.ReadAsStringAsync(); + + // Try to parse JSON error response for better error messages + string detailedError = errorContent; + try + { + // If the response is JSON, try to extract meaningful error messages + if (errorContent.Trim().StartsWith("{") || errorContent.Trim().StartsWith("[")) + { + var errorObj = JsonSerializer.Deserialize(errorContent); + if (errorObj.TryGetProperty("errors", out var errors)) + { + var errorList = new List(); + foreach (var error in errors.EnumerateObject()) + { + foreach (var msg in error.Value.EnumerateArray()) + { + errorList.Add($"{error.Name}: {msg.GetString()}"); + } + } + detailedError = string.Join("; ", errorList); + } + else if (errorObj.TryGetProperty("message", out var message)) + { + detailedError = message.GetString() ?? errorContent; + } + } + } + catch + { + // If parsing fails, use the raw error content + } + + errorMessage = $"Failed to save account ({response.StatusCode}): {detailedError}"; + StateHasChanged(); // Update UI to show error message + } + } + catch (Exception ex) + { + errorMessage = $"Error saving account: {ex.Message}"; + if (ex.InnerException != null) + { + errorMessage += $" ({ex.InnerException.Message})"; + } + StateHasChanged(); // Update UI to show error message + } + } + + // Open Delete Modal + private void OpenDeleteModal(AccountViewModel account) + { + selectedAccount = account; + isDeleteModalVisible = true; + errorMessage = string.Empty; + } + + // Close Delete Modal + private void CloseDeleteModal() + { + isDeleteModalVisible = false; + selectedAccount = null; + errorMessage = string.Empty; + } + + // Delete Account + private async Task ConfirmDeleteAccount() + { + if (selectedAccount == null) + { + errorMessage = "No account selected."; + StateHasChanged(); + return; + } + + try + { + string apiUrl = Environment.GetEnvironmentVariable("APIURL") ?? "http://localhost:8001/api/"; + var client = ClientFactory.CreateClient(); + + var response = await client.DeleteAsync( + $"{apiUrl}financials/DeleteAccount/{selectedAccount.AccountCode}"); + + if (response.IsSuccessStatusCode) + { + errorMessage = string.Empty; + // Close modal first to ensure UI state is updated + CloseDeleteModal(); + // Reload accounts from API to reflect deletion + await LoadAccountsFromApi(); + // StateHasChanged is called in LoadAccountsFromApi, but we also call it here + // to ensure the modal closes immediately + StateHasChanged(); + } + else + { + var errorContent = await response.Content.ReadAsStringAsync(); + errorMessage = $"Failed to delete account: {response.StatusCode}. {errorContent}"; + StateHasChanged(); // Update UI to show error message + } + } + catch (Exception ex) + { + errorMessage = $"Error deleting account: {ex.Message}"; + StateHasChanged(); // Update UI to show error message + } + } + + // ViewModel for Accounts + public class AccountViewModel + { + public int Id { get; set; } + public int? ParentAccountId { get; set; } + public string AccountCode { get; set; } = string.Empty; + public string AccountName { get; set; } = string.Empty; + public decimal TotalBalance { get; set; } + public decimal TotalDebitBalance { get; set; } + public decimal TotalCreditBalance { get; set; } + public List ChildAccounts { get; set; } = new(); + } +} diff --git a/src/AccountGoWeb/Components/Pages/Financial/JournalEntries.razor b/src/AccountGoWeb/Components/Pages/Financial/JournalEntries.razor new file mode 100644 index 000000000..7642a051f --- /dev/null +++ b/src/AccountGoWeb/Components/Pages/Financial/JournalEntries.razor @@ -0,0 +1,127 @@ +@namespace AccountGoWeb.Components.Pages.Financial +@using JournalEntryDto = Dto.Financial.JournalEntry +@using JournalEntryLineDto = Dto.Financial.JournalEntryLine +@using System.Net.Http.Json + +@inject HttpClient Http +@inject IConfiguration Config + + + +
+
+ @if (IsLoading && ErrorMessage is null) + { +

Loading journal entries...

+ } + else if (ErrorMessage is not null) + { +

Error: @ErrorMessage

+ } + else if (Entries is null || !Entries.Any()) + { +
+ No journal entries found. +
+ } + else + { + + + + + + + + + + + + + + + + @foreach (var e in Entries) + { + var isSelected = SelectedEntry?.Id == e.Id; + + + + + + + + + + + + + } + +
JournalVoucher TypeDebitCreditDateReference NoPostedMemo
@e.Id@e.VoucherType@e.debitAmount@e.creditAmount@e.JournalDate.ToShortDateString()@e.ReferenceNo +
+ +
+ +
@e.Memo
+ + } +
+ +
+ +@code { + // 🔴 CHANGE THESE TYPES TO THE DTO ALIAS + private List? Entries; + private string? ErrorMessage; + private bool IsLoading = true; + + private JournalEntryDto? SelectedEntry; + + private string ViewLinkHref => SelectedEntry is null + ? "/Financials/JournalEntry" + : $"/Financials/JournalEntry?id={SelectedEntry.Id}"; + + protected override async Task OnInitializedAsync() + { + try + { + var baseUrl = Config["ApiUrl"]; + if (string.IsNullOrWhiteSpace(baseUrl)) + { + ErrorMessage = "ApiUrl is not configured."; + return; + } + + var url = $"{baseUrl}financials/journalentries"; + + Entries = await Http.GetFromJsonAsync>(url); + } + catch (Exception ex) + { + ErrorMessage = ex.Message; + } + finally + { + IsLoading = false; + } + } + + private void OnRowClick(JournalEntryDto entry) + { + SelectedEntry = entry; + } +} \ No newline at end of file diff --git a/src/AccountGoWeb/Components/Pages/Financial/JournalEntry.razor b/src/AccountGoWeb/Components/Pages/Financial/JournalEntry.razor new file mode 100644 index 000000000..5514438b3 --- /dev/null +++ b/src/AccountGoWeb/Components/Pages/Financial/JournalEntry.razor @@ -0,0 +1,391 @@ +@using System.Net.Http.Json +@using Dto.Financial +@inject IHttpClientFactory HttpFactory +@inject IConfiguration Config +@using JournalEntryDto = Dto.Financial.JournalEntry +@using JournalEntryLineDto = Dto.Financial.JournalEntryLine + + + +@if (IsLoading) +{ +

Loading...

+} +else if (!string.IsNullOrEmpty(ErrorMessage)) +{ +
@ErrorMessage
+} +else if (Entry is null) +{ +
Journal entry not found.
+} +else +{ + @if (!string.IsNullOrEmpty(SuccessMessage)) + { +
@SuccessMessage
+ } + + + + +
+
Header
+
+
+ +

@Entry.JournalDate.ToString("yyyy-MM-dd")

+
+ +
+ +

@Entry.ReferenceNo

+
+ +
+ +

@Entry.VoucherType

+
+ +
+ +

@Entry.Memo

+
+ +
+ + @if (Entry.Posted.GetValueOrDefault()) + { + Posted + } + else + { + Not Posted + } +
+
+
+ +
+
Lines
+ +
+ @if (Entry.JournalEntryLines == null || Entry.JournalEntryLines.Count == 0) + { +
No lines found for this journal entry.
+ } + else + { +
+ + + + + + + + + + + + @foreach (var line in Entry.JournalEntryLines) + { + + + + + + + + + + + + + + } + + + + + + + + + + + + + + + +
Account IdDr/CrAmountMemo
+ + + + + + + +
Total Debit@TotalDebit.ToString("0.00")
Total Credit@TotalCredit.ToString("0.00")
+
+ + + } +
+
+ +
+ @if (!Entry.Posted.GetValueOrDefault()) + { + + + + + } +
+ +} + +@code { + [Parameter] public int Id { get; set; } + + private JournalEntryDto? Entry; + private bool IsLoading = true; + private bool IsPosting = false; + private bool IsSaving = false; + private string? ErrorMessage; + private string? SuccessMessage; + private List Accounts = new(); + private decimal TotalDebit => + Entry?.JournalEntryLines? + .Where(l => l.DrCr == 1) + .Sum(l => l.Amount ?? 0) ?? 0; + + private decimal TotalCredit => + Entry?.JournalEntryLines? + .Where(l => l.DrCr == 2) + .Sum(l => l.Amount ?? 0) ?? 0; + + protected override async Task OnInitializedAsync() + { + await LoadAccountsAsync(); + await LoadEntryAsync(); + } + + private async Task LoadAccountsAsync() + { + try + { + var apiBaseUrl = Config["ApiUrl"]; + if (string.IsNullOrWhiteSpace(apiBaseUrl)) + { + return; + } + + var client = HttpFactory.CreateClient(); + var url = $"{apiBaseUrl}financials/Accounts"; + + var result = await client.GetFromJsonAsync>(url); + + // result is a tree (each Account may have ChildAccounts) + var flatList = new List(); + if (result != null) + { + FlattenAccounts(result, flatList); + } + + Accounts = flatList; + } + catch (Exception ex) + { + Console.WriteLine($"Error loading accounts: {ex.Message}"); + } + } + + private void FlattenAccounts(IEnumerable source, List destination) + { + foreach (var acc in source) + { + destination.Add(acc); + + if (acc.ChildAccounts != null && acc.ChildAccounts.Count > 0) + { + FlattenAccounts(acc.ChildAccounts, destination); + } + } + } + + private async Task LoadEntryAsync() + { + try + { + ErrorMessage = null; + IsLoading = true; + + var apiBaseUrl = Config["ApiUrl"]; + if (string.IsNullOrWhiteSpace(apiBaseUrl)) + { + ErrorMessage = "ApiUrl is not configured."; + return; + } + + var client = HttpFactory.CreateClient(); + var url = $"{apiBaseUrl}financials/JournalEntry?id={Id}"; + + Entry = await client.GetFromJsonAsync(url); + + if (Entry == null) + ErrorMessage = "Journal entry not found."; + } + catch (Exception ex) + { + ErrorMessage = $"Error loading journal entry: {ex.Message}"; + } + finally + { + IsLoading = false; + } + } + + private async Task PostEntry() + { + if (Entry == null) + return; + + ErrorMessage = null; + SuccessMessage = null; + IsPosting = true; + + try + { + var apiBaseUrl = Config["ApiUrl"]; + if (string.IsNullOrWhiteSpace(apiBaseUrl)) + { + ErrorMessage = "ApiUrl is not configured."; + return; + } + + var client = HttpFactory.CreateClient(); + var url = $"{apiBaseUrl}financials/PostJournalEntry"; + + var response = await client.PostAsJsonAsync(url, Entry); + + if (response.IsSuccessStatusCode) + { + SuccessMessage = "Journal entry posted successfully."; + await LoadEntryAsync(); // refresh Posted/ReadyForPosting + } + else + { + var errors = await response.Content.ReadFromJsonAsync(); + ErrorMessage = errors != null && errors.Length > 0 + ? string.Join("; ", errors) + : $"Error posting (HTTP {(int)response.StatusCode})"; + } + } + catch (Exception ex) + { + ErrorMessage = $"Unexpected error while posting: {ex.Message}"; + } + finally + { + IsPosting = false; + } + } + + + private async Task SaveEntry() + { + if (Entry == null) + return; + + ErrorMessage = null; + SuccessMessage = null; + IsSaving = true; + + try + { + var apiBaseUrl = Config["ApiUrl"]; + if (string.IsNullOrWhiteSpace(apiBaseUrl)) + { + ErrorMessage = "ApiUrl is not configured."; + return; + } + + var client = HttpFactory.CreateClient(); + var url = $"{apiBaseUrl}financials/SaveJournalEntry"; + + var response = await client.PostAsJsonAsync(url, Entry); + + if (response.IsSuccessStatusCode) + { + SuccessMessage = "Journal entry saved successfully."; + await LoadEntryAsync(); // refresh from DB (lines, totals, ReadyForPosting) + } + else + { + var errors = await response.Content.ReadFromJsonAsync(); + ErrorMessage = errors != null && errors.Length > 0 + ? string.Join("; ", errors) + : $"Error saving (HTTP {(int)response.StatusCode})"; + } + } + catch (Exception ex) + { + ErrorMessage = $"Unexpected error while saving: {ex.Message}"; + } + finally + { + IsSaving = false; + } + } + +} \ No newline at end of file diff --git a/src/AccountGoWeb/Components/Pages/Inventory/ICJ.razor b/src/AccountGoWeb/Components/Pages/Inventory/ICJ.razor new file mode 100644 index 000000000..4c231bb97 --- /dev/null +++ b/src/AccountGoWeb/Components/Pages/Inventory/ICJ.razor @@ -0,0 +1,126 @@ +@* ICJ.razor - fetches its own data *@ +@using Dto.Inventory +@using Microsoft.Extensions.Configuration +@inject IHttpClientFactory HttpClientFactory +@inject IConfiguration Configuration + +@if (isLoading) +{ +
+
+ Loading... +
+
+} +else if (hasError) +{ +
Error loading inventory data.
+} +else +{ + + + + + + + + + + + + + @if (InventoryData == null || InventoryData.Count == 0) + { + + + + } + else + { + @foreach (var row in InventoryData) + { + + + + + + + + + } + } + +
IdItemMeasurementINOUTDate
No Rows to Show
@row.Id@row.Item@row.Measurement@row.In@row.Out@row.Date.ToShortDateString()
+} + + + +@code { + private List InventoryData { get; set; } = new(); + private bool isLoading = true; + private bool hasError = false; + + protected override async Task OnInitializedAsync() + { + try + { + var httpClient = HttpClientFactory.CreateClient(); + httpClient.BaseAddress = new Uri(Configuration["ApiUrl"] ?? "https://gdbapi.azurewebsites.net/api/"); + httpClient.DefaultRequestHeaders.Accept.Clear(); + httpClient.DefaultRequestHeaders.Accept.Add( + new System.Net.Http.Headers.MediaTypeWithQualityHeaderValue("application/json")); + + var response = await httpClient.GetAsync("Inventory/ICJ"); + if (response.IsSuccessStatusCode) + { + InventoryData = await response.Content.ReadFromJsonAsync>() ?? new List(); + } + else + { + hasError = true; + } + } + catch (Exception ex) + { + Console.WriteLine("Error loading ICJ data: " + ex.Message); + hasError = true; + } + finally + { + isLoading = false; + } + } + + private void OnRowClicked(InventoryControlJournal row) + { + Console.WriteLine($"Row clicked: {row.Id} - {row.Item}"); + } +} diff --git a/src/AccountGoWeb/Components/Pages/Inventory/ItemForm.razor b/src/AccountGoWeb/Components/Pages/Inventory/ItemForm.razor new file mode 100644 index 000000000..e0de4036e --- /dev/null +++ b/src/AccountGoWeb/Components/Pages/Inventory/ItemForm.razor @@ -0,0 +1,413 @@ +@using Dto.Inventory +@using Microsoft.AspNetCore.Components.Forms +@inject IHttpClientFactory HttpClientFactory +@inject NavigationManager Navigation + +
+ +
+ + + + +
+ @* General Section *@ +
+
+ General +
+
+
+
+
No
+
@item.No
+
+
+
Code
+
+ + +
+
+
+
Description
+
+ + +
+
+
+
+
+
Smallest UOM
+
+ + + @foreach (var measurement in measurements) + { + + } + +
+
+
+
Category
+
+ + + @foreach (var category in itemCategories) + { + + } + +
+
+
+
Item Tax Group
+
+ + + @foreach (var taxGroup in itemTaxGroups) + { + + } + +
+
+
+
+
+ + @* Pricing Section *@ +
+
+ Pricing +
+
+
+
+
Sell Description
+
+ +
+
+
+
Sell Price
+
+ +
+
+
+
Sell UOM
+
+ + + @foreach (var measurement in measurements) + { + + } + +
+
+
+
+
+
Purchase Description
+
+ +
+
+
+
Purchase Cost
+
+ +
+
+
+
Purchase UOM
+
+ + + @foreach (var measurement in measurements) + { + + } + +
+
+
+
+
+ + @* Invoicing Section *@ +
+
+ Invoicing +
+
+
+
+
Sales Account
+
+ + + @foreach (var account in accounts) + { + + } + +
+
+
+
+
+
Adjustment
+
+ + + @foreach (var account in accounts) + { + + } + +
+
+
+
Inventory Account
+
+ + + @foreach (var account in accounts) + { + + } + +
+
+
+
Cost of Good Sold
+
+ + + @foreach (var account in accounts) + { + + } + +
+
+
+
+
+
+ +
+ + Close + + @if (!string.IsNullOrEmpty(errorMessage)) + { +
@errorMessage
+ } + @if (!string.IsNullOrEmpty(successMessage)) + { +
@successMessage
+ } +
+
+ +@code { + [Parameter] + public int ItemId { get; set; } + + private Item item = new Item(); + private bool isEditMode = false; + private bool isSaving = false; + private string? errorMessage; + private string? successMessage; + + private List accounts = new(); + private List measurements = new(); + private List itemCategories = new(); + private List itemTaxGroups = new(); + + protected override async Task OnInitializedAsync() + { + await LoadDropdownData(); + + if (ItemId == 0) + { + // New item + isEditMode = true; + item.No = new Random().Next(1, 99999).ToString(); + } + else + { + // Edit existing item + await LoadItem(); + } + } + + private async Task LoadItem() + { + try + { + var http = HttpClientFactory.CreateClient(); + var apiUrl = Environment.GetEnvironmentVariable("APIURL") ?? "http://localhost:8001/api/"; + var response = await http.GetAsync($"{apiUrl}inventory/item?id={ItemId}"); + + if (response.IsSuccessStatusCode) + { + item = await response.Content.ReadFromJsonAsync() ?? new Item(); + } + else + { + errorMessage = "Failed to load item data."; + } + } + catch (Exception ex) + { + errorMessage = $"Error loading item: {ex.Message}"; + } + } + + private async Task LoadDropdownData() + { + try + { + var http = HttpClientFactory.CreateClient(); + var apiUrl = Environment.GetEnvironmentVariable("APIURL") ?? "http://localhost:8001/api/"; + + // Load accounts + var accountsResponse = await http.GetAsync($"{apiUrl}financials/accounts"); + if (accountsResponse.IsSuccessStatusCode) + { + var accountsList = await accountsResponse.Content.ReadFromJsonAsync>() ?? new(); + accounts = accountsList.Select(a => new SelectListItem { Value = a.Id.ToString(), Text = a.AccountName }).ToList(); + } + + // Load measurements + var measurementsResponse = await http.GetAsync($"{apiUrl}common/measurements"); + if (measurementsResponse.IsSuccessStatusCode) + { + var measurementsList = await measurementsResponse.Content.ReadFromJsonAsync>() ?? new(); + measurements = measurementsList.Select(m => new SelectListItem + { + Value = m.Id.ToString(), + Text = m.Description + }).ToList(); + } + + // Load item categories + var categoriesResponse = await http.GetAsync($"{apiUrl}common/itemcategories"); + if (categoriesResponse.IsSuccessStatusCode) + { + var categoriesList = await categoriesResponse.Content.ReadFromJsonAsync>() ?? new(); + itemCategories = categoriesList.Select(c => new SelectListItem { Value = c.Id.ToString(), Text = c.Name }).ToList(); + } + + // Load item tax groups + var taxGroupsResponse = await http.GetAsync($"{apiUrl}tax/itemtaxgroups"); + if (taxGroupsResponse.IsSuccessStatusCode) + { + var taxGroupsList = await taxGroupsResponse.Content.ReadFromJsonAsync>() ?? new(); + itemTaxGroups = taxGroupsList.Select(t => new SelectListItem { Value = t.Id.ToString(), Text = t.Name }).ToList(); + } + } + catch (Exception ex) + { + errorMessage = $"Error loading dropdown data: {ex.Message}"; + } + } + + private void ToggleEdit() + { + isEditMode = !isEditMode; + } + + private async Task HandleValidSubmit() + { + isSaving = true; + errorMessage = null; + successMessage = null; + + try + { + var http = HttpClientFactory.CreateClient(); + var apiUrl = Environment.GetEnvironmentVariable("APIURL") ?? "http://localhost:8001/api/"; + var response = await http.PostAsJsonAsync($"{apiUrl}inventory/saveitem", item); + + if (response.IsSuccessStatusCode) + { + successMessage = "Item saved successfully!"; + await Task.Delay(1000); + Navigation.NavigateTo("/Inventory"); + } + else + { + errorMessage = $"Failed to save item. Status: {response.StatusCode}"; + } + } + catch (Exception ex) + { + errorMessage = $"Error saving item: {ex.Message}"; + } + finally + { + isSaving = false; + } + } + + private class SelectListItem + { + public string Value { get; set; } = ""; + public string Text { get; set; } = ""; + } + + private class Account + { + public int Id { get; set; } + public string AccountName { get; set; } = ""; + } + + private class Measurement + { + public int Id { get; set; } + public string Description { get; set; } = ""; + } + + private class ItemCategory + { + public int Id { get; set; } + public string Name { get; set; } = ""; + } + + private class ItemTaxGroup + { + public int Id { get; set; } + public string Name { get; set; } = ""; + } +} \ No newline at end of file diff --git a/src/AccountGoWeb/Components/Pages/Inventory/Items.razor b/src/AccountGoWeb/Components/Pages/Inventory/Items.razor new file mode 100644 index 000000000..005420958 --- /dev/null +++ b/src/AccountGoWeb/Components/Pages/Inventory/Items.razor @@ -0,0 +1,261 @@ +@using Dto.Inventory +@inject HttpClient Http + + + +
+ +
+
+

Items

+
+
+ +
+
+ + New Item + + @if (selectedItem != null) + { + + View/Edit + + } +
+
+ + @if (isLoading) + { +
+
+
+ Loading... +
+

Loading items...

+
+
+ } + + else if (errorMessage != null) + + { +
+
+ +
+
+ } + + else if (items == null || !items.Any()) + + { +
+
+ +
+
+ } + else + { +
+
+
+ + + + + + + + + + + + + + + + + @foreach (var item in items) + { + + + + + + + + + + + + + + } + + +
+ Item @GetSortIcon(nameof(Item.Id)) + + Code @GetSortIcon(nameof(Item.Code)) + + Description @GetSortIcon(nameof(Item.Description)) + + Measurement @GetSortIcon(nameof(Item.Measurement)) + + Item Tax Group @GetSortIcon(nameof(Item.ItemTaxGroupName)) + + Cost @GetSortIcon(nameof(Item.Cost)) + + Price @GetSortIcon(nameof(Item.Price)) + + On Hand @GetSortIcon(nameof(Item.QuantityOnHand)) +
+ + @item.Id + + @item.Code@item.Description@item.Measurement@item.ItemTaxGroupName@item.Cost?.ToString("N2")@item.Price?.ToString("N2")@item.QuantityOnHand?.ToString("N2")
+
+
+
+ +
+
+

Total: @items.Count() item(s)

+
+
+ } +
+ +@code { + private List? items; + private List? allItems; + private Item? selectedItem; + private bool isLoading = true; + private string? errorMessage; + private string? sortColumn; + private bool sortAscending = true; + + protected override async Task OnInitializedAsync() + { + await LoadItems(); + } + + private async Task LoadItems() + { + isLoading = true; + errorMessage = null; + + try + { + string baseApiUrl = Environment.GetEnvironmentVariable("APIURL") ?? "http://localhost:8001/api/"; + var apiUrl = baseApiUrl + "inventory/items"; + var response = await Http.GetAsync(apiUrl); + + if (response.IsSuccessStatusCode) + { + allItems = await response.Content.ReadFromJsonAsync>(); + items = allItems; + } + else + { + errorMessage = $"Failed to load items. Status: {response.StatusCode}"; + } + } + + catch (HttpRequestException ex) + { + errorMessage = $"Network error: {ex.Message}. Please ensure the API is running on port 8001."; + } + + catch (Exception ex) + { + errorMessage = $"Error loading items: {ex.Message}"; + } + + finally + { + isLoading = false; + } + } + + private void SelectItem(Item item) + { + selectedItem = item; + } + + private void SortBy(string column) + { + if (items == null) return; + + if (sortColumn == column) + { + sortAscending = !sortAscending; + } + else + { + sortColumn = column; + sortAscending = true; + } + + items = column switch + { + nameof(Item.Id) => sortAscending + ? items.OrderBy(i => i.Id).ToList() + : items.OrderByDescending(i => i.Id).ToList(), + + nameof(Item.Code) => sortAscending + ? items.OrderBy(i => i.Code).ToList() + : items.OrderByDescending(i => i.Code).ToList(), + + nameof(Item.Description) => sortAscending + ? items.OrderBy(i => i.Description).ToList() + : items.OrderByDescending(i => i.Description).ToList(), + + nameof(Item.Measurement) => sortAscending + ? items.OrderBy(i => i.Measurement).ToList() + : items.OrderByDescending(i => i.Measurement).ToList(), + + nameof(Item.ItemTaxGroupName) => sortAscending + ? items.OrderBy(i => i.ItemTaxGroupName).ToList() + : items.OrderByDescending(i => i.ItemTaxGroupName).ToList(), + + nameof(Item.Cost) => sortAscending + ? items.OrderBy(i => i.Cost).ToList() + : items.OrderByDescending(i => i.Cost).ToList(), + + nameof(Item.Price) => sortAscending + ? items.OrderBy(i => i.Price).ToList() + : items.OrderByDescending(i => i.Price).ToList(), + + nameof(Item.QuantityOnHand) => sortAscending + ? items.OrderBy(i => i.QuantityOnHand).ToList() + : items.OrderByDescending(i => i.QuantityOnHand).ToList(), + _ => items + }; + } + + private string GetSortIcon(string column) + { + if (sortColumn != column) return ""; + return sortAscending ? "▲" : "▼"; + } +} \ No newline at end of file diff --git a/src/AccountGoWeb/Components/Pages/Payables/PurchaseInvoiceForm.razor b/src/AccountGoWeb/Components/Pages/Payables/PurchaseInvoiceForm.razor new file mode 100644 index 000000000..3ecf789e6 --- /dev/null +++ b/src/AccountGoWeb/Components/Pages/Payables/PurchaseInvoiceForm.razor @@ -0,0 +1,441 @@ +@rendermode InteractiveServer +@using System.Text.Json +@inject IHttpClientFactory ClientFactory +@inject IConfiguration Configuration +@inject NavigationManager NavigationManager +@inject IJSRuntime JSRuntime + + + +@if (loadError) +{ +
Unable to load purchase invoice. Please try again.
+} +else if (purchaseInvoice is null) +{ +

Loading...

+} +else +{ +
+ @if (InvoiceId > 0) + { + + } +
+ + +
+
+
+
+
+
Vendor Name
+
+ + + @if (vendors != null) + { + @foreach (var vendor in vendors) + { + + } + } + +
+
+ +
+
Date
+
+ +
+
+ +
+
Reference No
+
+ @if (isViewMode) + { + @(string.IsNullOrEmpty(purchaseInvoice.ReferenceNo) ? "-" : purchaseInvoice.ReferenceNo) + } + else + { + + } +
+
+
+ +
+ + + + + + + + + @if (!isViewMode) + { + + } + + + + @for (int i = 0; i < purchaseInvoice.PurchaseInvoiceLines.Count; i++) + { + var line = purchaseInvoice.PurchaseInvoiceLines[i]; + var index = i; + + + + + + + @if (!isViewMode) + { + + } + + } + @if (!isViewMode) + { + + + + } + +
ItemQuantityAmountDiscountMeasurementActions
+ + + @if (items != null) + { + @foreach (var item in items) + { + + } + } + + + + + + + + + + + @if (measurements != null) + { + @foreach (var measurement in measurements) + { + + } + } + + + +
+ +
+
+
+ +
+ @if (!isViewMode) + { + + } + Close +
+
+
+
+ + @if (saveError) + { +
+ Error saving purchase invoice. + @if (!string.IsNullOrEmpty(errorMessage)) + { +
Details: @errorMessage + } +
+ } + @if (saveSuccess) + { +
Purchase invoice saved successfully!
+ } +} + +@code { + [Parameter] + public int InvoiceId { get; set; } = 0; + + private PurchaseInvoiceDto? purchaseInvoice; + private List? vendors, items, measurements; + private bool loadError, saveError, saveSuccess, isViewMode = true, isSaving = false; + private string? errorMessage; + + public class SelectListItem { public string Value { get; set; } = ""; public string Text { get; set; } = ""; } + + protected override async Task OnInitializedAsync() + { + await LoadReferenceData(); + if (InvoiceId > 0) + { + await LoadPurchaseInvoice(InvoiceId); + isViewMode = true; + } + else + { + InitializeNewInvoice(); + isViewMode = false; + } + } + + protected override async Task OnAfterRenderAsync(bool firstRender) + { + if (firstRender) + { + try + { + await JSRuntime.InvokeVoidAsync("fixTableBackground"); + } + catch { } + } + } + + private void InitializeNewInvoice() + { + var random = new Random(); + purchaseInvoice = new PurchaseInvoiceDto + { + No = random.Next(1, 99999).ToString(), + InvoiceDate = DateTime.Now, + ReferenceNo = "", + AmountPaid = 0, + IsPaid = false, + Posted = false, + PurchaseInvoiceLines = new List + { + new PurchaseInvoiceLineDto { Quantity = 1, Amount = 0, Discount = 0 } + } + }; + } + + private async Task LoadReferenceData() + { + try + { + string apiurl = Configuration["ApiUrl"] ?? "http://localhost:8001/api/"; + var client = ClientFactory.CreateClient(); + + var vendorResponse = await client.GetAsync($"{apiurl}purchasing/vendors"); + if (vendorResponse.IsSuccessStatusCode) + { + var json = await vendorResponse.Content.ReadAsStringAsync(); + var list = JsonSerializer.Deserialize>(json, new JsonSerializerOptions { PropertyNameCaseInsensitive = true }); + vendors = list?.Select(v => new SelectListItem { Value = v.Id.ToString(), Text = v.Name ?? "" }).ToList(); + } + + var itemResponse = await client.GetAsync($"{apiurl}inventory/items"); + if (itemResponse.IsSuccessStatusCode) + { + var json = await itemResponse.Content.ReadAsStringAsync(); + var list = JsonSerializer.Deserialize>(json, new JsonSerializerOptions { PropertyNameCaseInsensitive = true }); + items = list?.Select(i => new SelectListItem { Value = i.Id.ToString(), Text = i.Description ?? "" }).ToList(); + } + + var measurementResponse = await client.GetAsync($"{apiurl}common/measurements"); + if (measurementResponse.IsSuccessStatusCode) + { + var json = await measurementResponse.Content.ReadAsStringAsync(); + var list = JsonSerializer.Deserialize>(json, new JsonSerializerOptions { PropertyNameCaseInsensitive = true }); + measurements = list?.Select(m => new SelectListItem { Value = m.Id.ToString(), Text = m.Description ?? "" }).ToList(); + } + } + catch (Exception) + { + vendors = new List(); + items = new List(); + measurements = new List(); + } + } + + private async Task LoadPurchaseInvoice(int id) + { + try + { + string apiurl = Configuration["ApiUrl"] ?? "http://localhost:8001/api/"; + var client = ClientFactory.CreateClient(); + var response = await client.GetAsync($"{apiurl}purchasing/purchaseinvoice?id={id}"); + if (response.IsSuccessStatusCode) + { + var json = await response.Content.ReadAsStringAsync(); + var tempInvoice = JsonSerializer.Deserialize(json, new JsonSerializerOptions { PropertyNameCaseInsensitive = true }); + + // Workaround: Try to get ReferenceNo from localStorage if API doesn't return it + if (tempInvoice != null) + { + purchaseInvoice = tempInvoice; + + // If API doesn't return ReferenceNo, try to get it from the list + if (string.IsNullOrEmpty(purchaseInvoice.ReferenceNo)) + { + try + { + var listResponse = await client.GetAsync($"{apiurl}purchasing/purchaseinvoices"); + if (listResponse.IsSuccessStatusCode) + { + var listJson = await listResponse.Content.ReadAsStringAsync(); + var invoicesList = JsonSerializer.Deserialize>(listJson, new JsonSerializerOptions { PropertyNameCaseInsensitive = true }); + var invoiceFromList = invoicesList?.FirstOrDefault(i => i.Id == id); + if (invoiceFromList != null && !string.IsNullOrEmpty(invoiceFromList.ReferenceNo)) + { + purchaseInvoice.ReferenceNo = invoiceFromList.ReferenceNo; + } + } + } + catch { } + } + } + } + else + { + loadError = true; + } + } + catch (Exception) + { + loadError = true; + } + } + + private async Task HandleSubmit() + { + try + { + isSaving = true; + saveError = false; + saveSuccess = false; + errorMessage = null; + + if (purchaseInvoice != null && purchaseInvoice.PurchaseInvoiceLines != null) + { + var validLines = purchaseInvoice.PurchaseInvoiceLines + .Where(line => line.ItemId.HasValue && line.ItemId.Value > 0) + .ToList(); + + if (validLines.Count == 0) + { + errorMessage = "Please add at least one item line with a valid item selected."; + saveError = true; + isSaving = false; + return; + } + + purchaseInvoice.PurchaseInvoiceLines = validLines; + } + + string apiurl = Configuration["ApiUrl"] ?? "http://localhost:8001/api/"; + var client = ClientFactory.CreateClient(); + var json = JsonSerializer.Serialize(purchaseInvoice); + var content = new StringContent(json, System.Text.Encoding.UTF8, "application/json"); + var response = await client.PostAsync($"{apiurl}purchasing/savepurchaseinvoice", content); + + if (response.IsSuccessStatusCode) + { + saveSuccess = true; + StateHasChanged(); + await Task.Delay(500); + NavigationManager.NavigateTo("/purchasing/purchaseinvoices", forceLoad: true); + } + else + { + var errorContent = await response.Content.ReadAsStringAsync(); + errorMessage = $"API returned {response.StatusCode}: {errorContent}"; + saveError = true; + } + } + catch (Exception ex) + { + errorMessage = ex.Message; + saveError = true; + } + finally + { + isSaving = false; + } + } + + private void ToggleEdit() { isViewMode = !isViewMode; } + private void AddLine() { purchaseInvoice?.PurchaseInvoiceLines?.Add(new PurchaseInvoiceLineDto { Quantity = 1, Amount = 0, Discount = 0 }); } + private void RemoveLine(int index) + { + if (purchaseInvoice?.PurchaseInvoiceLines != null && purchaseInvoice.PurchaseInvoiceLines.Count > 1) + purchaseInvoice.PurchaseInvoiceLines.RemoveAt(index); + } + + public class PurchaseInvoiceDto + { + public int Id { get; set; } + public string? No { get; set; } + public int VendorId { get; set; } + public DateTime InvoiceDate { get; set; } + public decimal AmountPaid { get; set; } + public bool IsPaid { get; set; } + public bool Posted { get; set; } + public string? VendorInvoiceNo { get; set; } + public string? ReferenceNo { get; set; } + public List PurchaseInvoiceLines { get; set; } = new(); + } + + public class PurchaseInvoiceLineDto + { + public int Id { get; set; } + public int? ItemId { get; set; } + public int? MeasurementId { get; set; } + public decimal? Quantity { get; set; } + public decimal? Amount { get; set; } + public decimal? Discount { get; set; } + } + + public class VendorDto { public int Id { get; set; } public string? Name { get; set; } } + public class ItemDto { public int Id { get; set; } public string? Description { get; set; } } + public class MeasurementDto { public int Id { get; set; } public string? Description { get; set; } } +} + diff --git a/src/AccountGoWeb/Components/Pages/Payables/PurchaseInvoiceForm.razor.css b/src/AccountGoWeb/Components/Pages/Payables/PurchaseInvoiceForm.razor.css new file mode 100644 index 000000000..9dd4ffb54 --- /dev/null +++ b/src/AccountGoWeb/Components/Pages/Payables/PurchaseInvoiceForm.razor.css @@ -0,0 +1,32 @@ +.card-body .row .col-sm-3 { + color: white; + font-weight: 500; + padding-top: 0.375rem; +} + +.table thead th { + color: white; + background-color: #343a40; +} + +.invoice-form-wrapper .table-container .table { + --bs-table-hover-bg: transparent; + --bs-table-active-bg: transparent; + --bs-table-striped-bg: transparent; +} + +.invoice-form-wrapper .table-container table tbody tr, +.invoice-form-wrapper .table-container table tbody tr td { + background-color: transparent !important; +} + +.invoice-form-wrapper .table-container table tbody tr td select:focus, +.invoice-form-wrapper .table-container table tbody tr td input:focus, +.invoice-form-wrapper .table-container table tbody tr td input[type="number"]:focus, +.invoice-form-wrapper .table-container table tbody tr td .form-control:focus { + border: 2px solid #0d6efd !important; + box-shadow: 0 0 0 0.2rem rgba(13, 110, 253, 0.25) !important; + background-color: #fff !important; + outline: none !important; +} + diff --git a/src/AccountGoWeb/Components/Pages/Payables/PurchaseInvoicesList.razor b/src/AccountGoWeb/Components/Pages/Payables/PurchaseInvoicesList.razor new file mode 100644 index 000000000..29bb84498 --- /dev/null +++ b/src/AccountGoWeb/Components/Pages/Payables/PurchaseInvoicesList.razor @@ -0,0 +1,153 @@ +@rendermode InteractiveServer +@using System.Text.Json +@using System.Text.Json.Serialization +@inject IHttpClientFactory ClientFactory + +@if (getError) +{ + +} +else if (purchaseInvoices is null) +{ +

Loading...

+} +else +{ +
+ + New Invoice + + @if (selectedInvoice != null) + { + + View + + @if (!selectedInvoice.IsPaid && selectedInvoice.Posted) + { + + Make Payment + + } + } +
+ +
+ + + + + + + + + + + + + + @foreach (var invoice in purchaseInvoices) + { + + + + + + + + + + } + +
NoVendor NameInvoice DateAmountAmount PaidRef NoActions
@invoice.No@invoice.VendorName@invoice.InvoiceDate.ToString("yyyy-MM-dd")@("$" + invoice.Amount.ToString("N2"))@("$" + invoice.AmountPaid.ToString("N2"))@invoice.ReferenceNo + + View + + @if (!invoice.IsPaid && invoice.Posted) + { + + Pay + + } +
+
+} + +@code { + private List? purchaseInvoices; + private PurchaseInvoiceDto? selectedInvoice; + private bool getError; + private bool shouldRender = true; + + protected override bool ShouldRender() => shouldRender; + + protected override async Task OnInitializedAsync() + { + await LoadPurchaseInvoices(); + } + + private async Task LoadPurchaseInvoices() + { + try + { + string apiurl = System.Environment.GetEnvironmentVariable("APIURL") ?? "http://localhost:8001/api/"; + + var request = new HttpRequestMessage(HttpMethod.Get, $"{apiurl}purchasing/purchaseinvoices"); + request.Headers.Add("Accept", "application/json"); + + var client = ClientFactory.CreateClient(); + client.DefaultRequestHeaders.Accept.Clear(); + client.DefaultRequestHeaders.Clear(); + client.DefaultRequestHeaders.CacheControl = new System.Net.Http.Headers.CacheControlHeaderValue() { NoCache = true }; + client.DefaultRequestHeaders.Accept.Add(new System.Net.Http.Headers.MediaTypeWithQualityHeaderValue("application/json")); + + var response = await client.SendAsync(request); + + if (response.IsSuccessStatusCode) + { + var responseString = await response.Content.ReadAsStringAsync(); + var options = new JsonSerializerOptions + { + PropertyNameCaseInsensitive = true, + DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, + }; + + purchaseInvoices = JsonSerializer.Deserialize>(responseString, options); + } + else + { + getError = true; + } + } + catch (Exception) + { + getError = true; + } + + shouldRender = true; + } + + private void SelectInvoice(PurchaseInvoiceDto invoice) + { + selectedInvoice = invoice; + StateHasChanged(); + } + + public class PurchaseInvoiceDto + { + public int Id { get; set; } + public string? No { get; set; } + public int VendorId { get; set; } + public string? VendorName { get; set; } + public DateTime InvoiceDate { get; set; } + public decimal Amount { get; set; } + public decimal AmountPaid { get; set; } + public string? ReferenceNo { get; set; } + public bool Posted { get; set; } + public bool IsPaid { get; set; } + } +} + diff --git a/src/AccountGoWeb/Components/Pages/Payables/PurchaseInvoicesList.razor.css b/src/AccountGoWeb/Components/Pages/Payables/PurchaseInvoicesList.razor.css new file mode 100644 index 000000000..9adbc4212 --- /dev/null +++ b/src/AccountGoWeb/Components/Pages/Payables/PurchaseInvoicesList.razor.css @@ -0,0 +1,20 @@ +.table { + --bs-table-hover-bg: rgba(255, 255, 255, 0.05); + --bs-table-active-bg: rgba(13, 110, 253, 0.2); + --bs-table-striped-bg: transparent; +} + +.table tbody tr.table-active, +.table tbody tr.table-active td { + background-color: rgba(13, 110, 253, 0.2) !important; + border-left: 3px solid #0d6efd; +} + +.table tbody tr:hover { + background-color: rgba(255, 255, 255, 0.05) !important; +} + +.table tbody tr.table-active:hover { + background-color: rgba(13, 110, 253, 0.25) !important; +} + diff --git a/src/AccountGoWeb/Components/Pages/Payables/PurchaseOrderForm.razor b/src/AccountGoWeb/Components/Pages/Payables/PurchaseOrderForm.razor new file mode 100644 index 000000000..2384b1ccf --- /dev/null +++ b/src/AccountGoWeb/Components/Pages/Payables/PurchaseOrderForm.razor @@ -0,0 +1,466 @@ +@rendermode InteractiveServer +@using System.Text.Json +@using System.Text.Json.Serialization +@inject IHttpClientFactory ClientFactory +@inject IConfiguration Configuration +@inject NavigationManager NavigationManager +@inject IJSRuntime JSRuntime + + + +@if (loadError) +{ + +} +else if (purchaseOrder is null) +{ +

Loading...

+} +else +{ +
+ @if (OrderId > 0) + { + + } +
+ + +
+
+
+
+
+
Vendor Name
+
+ + + @if (vendors != null) + { + @foreach (var vendor in vendors) + { + + } + } + +
+
+ +
+
Date
+
+ +
+
+ +
+
Reference No
+
+ @if (isViewMode) + { + @(purchaseOrder.ReferenceNo ?? "-") + } + else + { + + } +
+
+ + @if (OrderId > 0) + { +
+
Amount
+
+ +
+
+ } +
+ +
+ + + + + + + + + @if (!isViewMode) + { + + } + + + + @for (int i = 0; i < purchaseOrder.PurchaseOrderLines.Count; i++) + { + var line = purchaseOrder.PurchaseOrderLines[i]; + var index = i; + + + + + + + @if (!isViewMode) + { + + } + + } + @if (!isViewMode) + { + + + + } + +
ItemQuantityAmountDiscountMeasurementActions
+ + + @if (items != null) + { + @foreach (var item in items) + { + + } + } + + + + + + + + + + + @if (measurements != null) + { + @foreach (var measurement in measurements) + { + + } + } + + + +
+ +
+
+
+ +
+ @if (!isViewMode) + { + + } + Close +
+
+
+
+ + @if (saveError) + { +
+ Error saving purchase order. Please try again. + @if (!string.IsNullOrEmpty(errorMessage)) + { +
Details: @errorMessage + } +
+ } + @if (saveSuccess) + { +
Purchase order saved successfully!
+ } +} + +@code { + [Parameter] + public int OrderId { get; set; } = 0; + + private PurchaseOrderDto? purchaseOrder; + private List? vendors; + private List? items; + private List? measurements; + private bool loadError; + private bool saveError; + private bool saveSuccess; + private bool isViewMode = true; + private bool isSaving = false; + private string? errorMessage; + + public class SelectListItem + { + public string Value { get; set; } = ""; + public string Text { get; set; } = ""; + } + + protected override async Task OnInitializedAsync() + { + await LoadReferenceData(); + + if (OrderId > 0) + { + await LoadPurchaseOrder(OrderId); + isViewMode = true; + } + else + { + InitializeNewPurchaseOrder(); + isViewMode = false; + } + } + + protected override async Task OnAfterRenderAsync(bool firstRender) + { + if (firstRender) + { + try + { + await JSRuntime.InvokeVoidAsync("fixTableBackgroundOrder"); + } + catch { } + } + } + + private void InitializeNewPurchaseOrder() + { + var random = new Random(); + purchaseOrder = new PurchaseOrderDto + { + No = random.Next(1, 99999).ToString(), + OrderDate = DateTime.Now, + ReferenceNo = "", + Completed = false, + PurchaseOrderLines = new List + { + new PurchaseOrderLineDto { Quantity = 1, Amount = 0, Discount = 0, ItemId = 0, MeasurementId = 0 } + } + }; + } + + private async Task LoadReferenceData() + { + try + { + string apiurl = Configuration["ApiUrl"] ?? "http://localhost:8001/api/"; + var client = ClientFactory.CreateClient(); + + // Load vendors + var vendorResponse = await client.GetAsync($"{apiurl}purchasing/vendors"); + if (vendorResponse.IsSuccessStatusCode) + { + var vendorJson = await vendorResponse.Content.ReadAsStringAsync(); + var vendorList = JsonSerializer.Deserialize>(vendorJson, new JsonSerializerOptions { PropertyNameCaseInsensitive = true }); + vendors = vendorList?.Select(v => new SelectListItem { Value = v.Id.ToString(), Text = v.Name ?? "" }).ToList(); + } + + // Load items + var itemResponse = await client.GetAsync($"{apiurl}inventory/items"); + if (itemResponse.IsSuccessStatusCode) + { + var itemJson = await itemResponse.Content.ReadAsStringAsync(); + var itemList = JsonSerializer.Deserialize>(itemJson, new JsonSerializerOptions { PropertyNameCaseInsensitive = true }); + items = itemList?.Select(i => new SelectListItem { Value = i.Id.ToString(), Text = i.Description ?? "" }).ToList(); + } + + // Load measurements + var measurementResponse = await client.GetAsync($"{apiurl}common/measurements"); + if (measurementResponse.IsSuccessStatusCode) + { + var measurementJson = await measurementResponse.Content.ReadAsStringAsync(); + var measurementList = JsonSerializer.Deserialize>(measurementJson, new JsonSerializerOptions { PropertyNameCaseInsensitive = true }); + measurements = measurementList?.Select(m => new SelectListItem { Value = m.Id.ToString(), Text = m.Description ?? "" }).ToList(); + } + } + catch (Exception) + { + vendors = new List(); + items = new List(); + measurements = new List(); + } + } + + private async Task LoadPurchaseOrder(int id) + { + try + { + string apiurl = Configuration["ApiUrl"] ?? "http://localhost:8001/api/"; + var client = ClientFactory.CreateClient(); + var response = await client.GetAsync($"{apiurl}purchasing/purchaseorder?id={id}"); + + if (response.IsSuccessStatusCode) + { + var json = await response.Content.ReadAsStringAsync(); + purchaseOrder = JsonSerializer.Deserialize(json, new JsonSerializerOptions { PropertyNameCaseInsensitive = true }); + } + else + { + loadError = true; + } + } + catch (Exception) + { + loadError = true; + } + } + + private async Task HandleSubmit() + { + try + { + isSaving = true; + saveError = false; + saveSuccess = false; + errorMessage = null; + + if (purchaseOrder != null && purchaseOrder.PurchaseOrderLines != null) + { + var validLines = purchaseOrder.PurchaseOrderLines + .Where(line => line.ItemId.HasValue && line.ItemId.Value > 0) + .ToList(); + + if (validLines.Count == 0) + { + errorMessage = "Please add at least one item line with a valid item selected."; + saveError = true; + isSaving = false; + return; + } + + purchaseOrder.PurchaseOrderLines = validLines; + } + + string apiurl = Configuration["ApiUrl"] ?? "http://localhost:8001/api/"; + var client = ClientFactory.CreateClient(); + var json = JsonSerializer.Serialize(purchaseOrder); + var content = new StringContent(json, System.Text.Encoding.UTF8, "application/json"); + var response = await client.PostAsync($"{apiurl}purchasing/savepurchaseorder", content); + + if (response.IsSuccessStatusCode) + { + saveSuccess = true; + StateHasChanged(); + await Task.Delay(500); + NavigationManager.NavigateTo("/purchasing/purchaseorders", forceLoad: true); + } + else + { + var errorContent = await response.Content.ReadAsStringAsync(); + errorMessage = $"API returned {response.StatusCode}: {errorContent}"; + saveError = true; + } + } + catch (Exception ex) + { + errorMessage = ex.Message; + saveError = true; + } + finally + { + isSaving = false; + } + } + + private void ToggleEdit() + { + isViewMode = !isViewMode; + } + + private void AddLine() + { + purchaseOrder?.PurchaseOrderLines?.Add(new PurchaseOrderLineDto { Quantity = 1, Amount = 0, Discount = 0, ItemId = 0, MeasurementId = 0 }); + } + + private void RemoveLine(int index) + { + if (purchaseOrder?.PurchaseOrderLines != null && purchaseOrder.PurchaseOrderLines.Count > 1) + { + purchaseOrder.PurchaseOrderLines.RemoveAt(index); + } + } + + // DTOs + public class PurchaseOrderDto + { + public int Id { get; set; } + public string? No { get; set; } + public int VendorId { get; set; } + public DateTime OrderDate { get; set; } + public decimal Amount => PurchaseOrderLines?.Sum(l => ((l.Quantity ?? 0) * (l.Amount ?? 0)) * (1 - ((l.Discount ?? 0) / 100))) ?? 0; + public bool Completed { get; set; } + public string? ReferenceNo { get; set; } + public List PurchaseOrderLines { get; set; } = new(); + } + + public class PurchaseOrderLineDto + { + public int Id { get; set; } + public int? ItemId { get; set; } + public int? MeasurementId { get; set; } + public decimal? Quantity { get; set; } + public decimal? Amount { get; set; } + public decimal? Discount { get; set; } + } + + public class VendorDto + { + public int Id { get; set; } + public string? Name { get; set; } + } + + public class ItemDto + { + public int Id { get; set; } + public string? Description { get; set; } + } + + public class MeasurementDto + { + public int Id { get; set; } + public string? Description { get; set; } + } +} + diff --git a/src/AccountGoWeb/Components/Pages/Payables/PurchaseOrderForm.razor.css b/src/AccountGoWeb/Components/Pages/Payables/PurchaseOrderForm.razor.css new file mode 100644 index 000000000..bf127dfd3 --- /dev/null +++ b/src/AccountGoWeb/Components/Pages/Payables/PurchaseOrderForm.razor.css @@ -0,0 +1,32 @@ +.card-body .row .col-sm-3 { + color: white; + font-weight: 500; + padding-top: 0.375rem; +} + +.table thead th { + color: white; + background-color: #343a40; +} + +.order-form-wrapper .table-container .table { + --bs-table-hover-bg: transparent; + --bs-table-active-bg: transparent; + --bs-table-striped-bg: transparent; +} + +.order-form-wrapper .table-container table tbody tr, +.order-form-wrapper .table-container table tbody tr td { + background-color: transparent !important; +} + +.order-form-wrapper .table-container table tbody tr td select:focus, +.order-form-wrapper .table-container table tbody tr td input:focus, +.order-form-wrapper .table-container table tbody tr td input[type="number"]:focus, +.order-form-wrapper .table-container table tbody tr td .form-control:focus { + border: 2px solid #0d6efd !important; + box-shadow: 0 0 0 0.2rem rgba(13, 110, 253, 0.25) !important; + background-color: #fff !important; + outline: none !important; +} + diff --git a/src/AccountGoWeb/Components/Pages/Payables/PurchaseOrdersList.razor b/src/AccountGoWeb/Components/Pages/Payables/PurchaseOrdersList.razor new file mode 100644 index 000000000..1a4bd128d --- /dev/null +++ b/src/AccountGoWeb/Components/Pages/Payables/PurchaseOrdersList.razor @@ -0,0 +1,144 @@ +@rendermode InteractiveServer +@using System.Text.Json +@using System.Text.Json.Serialization +@inject IHttpClientFactory ClientFactory +@inject NavigationManager Navigation + +@if (getError) +{ + +} +else if (purchaseOrders is null) +{ +

Loading...

+} +else +{ +
+ + New Order + + @if (selectedPurchaseOrder != null) + { + + View + + @if (selectedPurchaseOrder.StatusId != 3) + { + + Create Invoice + + } + } +
+ +
+ + + + + + + + + + + + + @foreach (var order in purchaseOrders) + { + + + + + + + + + } + +
NoVendor NameOrder DateAmountRef NoActions
@order.No@order.VendorName@order.OrderDate.ToString("yyyy-MM-dd")@("$" + order.Amount.ToString("N2"))@order.ReferenceNo + + View + +
+
+} + +@code { + private List? purchaseOrders; + private PurchaseOrderDto? selectedPurchaseOrder; + private bool getError; + private bool shouldRender = true; + + protected override bool ShouldRender() => shouldRender; + + protected override async Task OnInitializedAsync() + { + await LoadPurchaseOrders(); + } + + private async Task LoadPurchaseOrders() + { + try + { + string apiurl = System.Environment.GetEnvironmentVariable("APIURL") ?? "http://localhost:8001/api/"; + + var request = new HttpRequestMessage(HttpMethod.Get, $"{apiurl}purchasing/purchaseorders"); + request.Headers.Add("Accept", "application/json"); + + var client = ClientFactory.CreateClient(); + client.DefaultRequestHeaders.Accept.Clear(); + client.DefaultRequestHeaders.Clear(); + client.DefaultRequestHeaders.CacheControl = new System.Net.Http.Headers.CacheControlHeaderValue() { NoCache = true }; + client.DefaultRequestHeaders.Accept.Add(new System.Net.Http.Headers.MediaTypeWithQualityHeaderValue("application/json")); + + var response = await client.SendAsync(request); + + if (response.IsSuccessStatusCode) + { + var responseString = await response.Content.ReadAsStringAsync(); + var options = new JsonSerializerOptions + { + PropertyNameCaseInsensitive = true, + DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, + }; + + purchaseOrders = JsonSerializer.Deserialize>(responseString, options); + } + else + { + getError = true; + } + } + catch (Exception) + { + getError = true; + } + + shouldRender = true; + } + + private void SelectOrder(PurchaseOrderDto order) + { + selectedPurchaseOrder = order; + StateHasChanged(); + } + + public class PurchaseOrderDto + { + public int Id { get; set; } + public string? No { get; set; } + public int VendorId { get; set; } + public string? VendorName { get; set; } + public DateTime OrderDate { get; set; } + public decimal Amount { get; set; } + public string? ReferenceNo { get; set; } + public int StatusId { get; set; } + } +} + diff --git a/src/AccountGoWeb/Components/Pages/Payables/PurchaseOrdersList.razor.css b/src/AccountGoWeb/Components/Pages/Payables/PurchaseOrdersList.razor.css new file mode 100644 index 000000000..9adbc4212 --- /dev/null +++ b/src/AccountGoWeb/Components/Pages/Payables/PurchaseOrdersList.razor.css @@ -0,0 +1,20 @@ +.table { + --bs-table-hover-bg: rgba(255, 255, 255, 0.05); + --bs-table-active-bg: rgba(13, 110, 253, 0.2); + --bs-table-striped-bg: transparent; +} + +.table tbody tr.table-active, +.table tbody tr.table-active td { + background-color: rgba(13, 110, 253, 0.2) !important; + border-left: 3px solid #0d6efd; +} + +.table tbody tr:hover { + background-color: rgba(255, 255, 255, 0.05) !important; +} + +.table tbody tr.table-active:hover { + background-color: rgba(13, 110, 253, 0.25) !important; +} + diff --git a/src/AccountGoWeb/Components/Pages/Purchasing/VendorForm.razor b/src/AccountGoWeb/Components/Pages/Purchasing/VendorForm.razor new file mode 100644 index 000000000..7471abb99 --- /dev/null +++ b/src/AccountGoWeb/Components/Pages/Purchasing/VendorForm.razor @@ -0,0 +1,406 @@ +@using Dto.Purchasing +@using Dto.Common +@using Microsoft.AspNetCore.Components.Forms +@inject IHttpClientFactory HttpClientFactory +@inject NavigationManager Navigation + +
+ + @if (vendor.Id > 0) + { + + Add Contact + + } +
+ + + + +
+ @* General Section *@ +
+
+ General +
+
+
+
+
No
+
@vendor.No
+
+
+
Name
+
+ + +
+
+
+
+
+
Phone
+
+ +
+
+
+
Fax
+
+ +
+
+
+
Website
+
+ +
+
+
+
Email
+
+ +
+
+
+
+
+ + @* Contact Section *@ +
+
+ Contact + @if (vendor.Id > 0) + { + ... + } +
+
+
+
+
First Name
+
+ +
+
+
+
Last Name
+
+ +
+
+
+
+
+
Phone
+
+ +
+
+
+
Fax
+
+ +
+
+
+
Website
+
+ +
+
+
+
Email
+
+ +
+
+
+
+
+ + @* Invoicing Section *@ +
+
+ Invoicing +
+
+
+
+
Accounts Payable
+
+ + + @foreach (var account in accounts) + { + + } + +
+
+
+
Purchase
+
+ + + @foreach (var account in accounts) + { + + } + +
+
+
+
Discount
+
+ + + @foreach (var account in accounts) + { + + } + +
+
+
+
+
+
Tax Group
+
+ + + @foreach (var taxGroup in taxGroups) + { + + } + +
+
+
+
+
+ + @* Payment Section *@ +
+
+ Payment +
+
+
+
+
Payment Term
+
+ + + @foreach (var term in paymentTerms) + { + + } + +
+
+
+
+
+
+ +
+ + Close + + @if (!string.IsNullOrEmpty(errorMessage)) + { +
@errorMessage
+ } + @if (!string.IsNullOrEmpty(successMessage)) + { +
@successMessage
+ } +
+
+ +@code { + [Parameter] + public int VendorId { get; set; } + + private Vendor vendor = new Vendor(); + private bool isEditMode = false; + private bool isSaving = false; + private string? errorMessage; + private string? successMessage; + + private List accounts = new(); + private List taxGroups = new(); + private List paymentTerms = new(); + + protected override async Task OnInitializedAsync() + { + await LoadDropdownData(); + + if (VendorId == -1 || VendorId == 0) + { + // New vendor + isEditMode = true; + vendor.No = new Random().Next(1, 99999).ToString(); + vendor.PrimaryContact = new Contact(); + } + else + { + // Edit existing vendor + await LoadVendor(); + } + } + + private async Task LoadVendor() + { + try + { + var http = HttpClientFactory.CreateClient(); + var apiUrl = Environment.GetEnvironmentVariable("APIURL") ?? "http://localhost:8001/api/"; + var response = await http.GetAsync($"{apiUrl}purchasing/vendor?id={VendorId}"); + + if (response.IsSuccessStatusCode) + { + vendor = await response.Content.ReadFromJsonAsync() ?? new Vendor(); + if (vendor.PrimaryContact == null) + { + vendor.PrimaryContact = new Contact(); + } + } + else + { + errorMessage = "Failed to load vendor data."; + } + } + catch (Exception ex) + { + errorMessage = $"Error loading vendor: {ex.Message}"; + } + } + + private async Task LoadDropdownData() + { + try + { + var http = HttpClientFactory.CreateClient(); + var apiUrl = Environment.GetEnvironmentVariable("APIURL") ?? "http://localhost:8001/api/"; + + // Load accounts + var accountsResponse = await http.GetAsync($"{apiUrl}financials/accounts"); + if (accountsResponse.IsSuccessStatusCode) + { + var accountsList = await accountsResponse.Content.ReadFromJsonAsync>() ?? new(); + accounts = accountsList.Select(a => new SelectListItem { Value = a.Id.ToString(), Text = a.AccountName }).ToList(); + } + + // Load tax groups + var taxGroupsResponse = await http.GetAsync($"{apiUrl}tax/taxgroups"); + if (taxGroupsResponse.IsSuccessStatusCode) + { + var taxGroupsList = await taxGroupsResponse.Content.ReadFromJsonAsync>() ?? new(); + taxGroups = taxGroupsList.Select(t => new SelectListItem { Value = t.Id.ToString(), Text = t.Description }).ToList(); + } + + // Load payment terms + var paymentTermsResponse = await http.GetAsync($"{apiUrl}financial/paymentterms"); + if (paymentTermsResponse.IsSuccessStatusCode) + { + var paymentTermsList = await paymentTermsResponse.Content.ReadFromJsonAsync>() ?? new(); + paymentTerms = paymentTermsList.Select(p => new SelectListItem + { + Value = p.Id.ToString(), + Text = p.Description + }).ToList(); + } + } + catch (Exception ex) + { + errorMessage = $"Error loading dropdown data: {ex.Message}"; + } + } + + private void ToggleEdit() + { + isEditMode = !isEditMode; + } + + private async Task HandleValidSubmit() + { + isSaving = true; + errorMessage = null; + successMessage = null; + + try + { + var http = HttpClientFactory.CreateClient(); + var apiUrl = Environment.GetEnvironmentVariable("APIURL") ?? "http://localhost:8001/api/"; + var response = await http.PostAsJsonAsync($"{apiUrl}purchasing/savevendor", vendor); + + if (response.IsSuccessStatusCode) + { + successMessage = "Vendor saved successfully!"; + await Task.Delay(1000); + Navigation.NavigateTo("/Purchasing/Vendors"); + } + else + { + errorMessage = $"Failed to save vendor. Status: {response.StatusCode}"; + } + } + catch (Exception ex) + { + errorMessage = $"Error saving vendor: {ex.Message}"; + } + finally + { + isSaving = false; + } + } + + private class SelectListItem + { + public string Value { get; set; } = ""; + public string Text { get; set; } = ""; + } + + private class Account + { + public int Id { get; set; } + public string AccountName { get; set; } = ""; + } + + private class TaxGroup + { + public int Id { get; set; } + public string Description { get; set; } = ""; + } + + private class PaymentTerm + { + public int Id { get; set; } + public string Description { get; set; } = ""; + } +} \ No newline at end of file diff --git a/src/AccountGoWeb/Components/Pages/Purchasing/Vendors.razor b/src/AccountGoWeb/Components/Pages/Purchasing/Vendors.razor new file mode 100644 index 000000000..83e7865d1 --- /dev/null +++ b/src/AccountGoWeb/Components/Pages/Purchasing/Vendors.razor @@ -0,0 +1,245 @@ +@using Dto.Purchasing +@inject HttpClient Http + + + +
+ +
+
+

Vendors

+
+
+ +
+
+ + New Vendor + + @if (selectedVendor != null) + { + + View/Edit + + } +
+
+ + @if (isLoading) + { +
+
+
+ Loading... +
+

Loading vendors...

+
+
+ } + + else if (errorMessage != null) + + { +
+
+ +
+
+ } + + else if (vendors == null || !vendors.Any()) + + { +
+
+ +
+
+ } + else + { +
+
+
+ + + + + + + + + + + + + + + @foreach (var vendor in vendors) + { + + + + + + + + + + + + } + + +
+ No @GetSortIcon(nameof(Vendor.No)) + + Name @GetSortIcon(nameof(Vendor.Name)) + + Phone @GetSortIcon(nameof(Vendor.Phone)) + + Contact @GetSortIcon(nameof(Vendor.Contact)) + + Tax Group @GetSortIcon(nameof(Vendor.TaxGroup)) + + Balance @GetSortIcon(nameof(Vendor.Balance)) +
+ + @vendor.No + + @vendor.Name@vendor.Phone@vendor.Contact@vendor.TaxGroup@vendor.Balance.ToString("N2")
+
+
+
+ +
+
+

Total: @vendors.Count() vendor(s)

+
+
+ } +
+ +@code { + private List? vendors; + private List? allVendors; + private Vendor? selectedVendor; + private bool isLoading = true; + private string? errorMessage; + private string? sortColumn; + private bool sortAscending = true; + + protected override async Task OnInitializedAsync() + { + await LoadVendors(); + } + + private async Task LoadVendors() + { + isLoading = true; + errorMessage = null; + + try + { + string baseApiUrl = Environment.GetEnvironmentVariable("APIURL") ?? "http://localhost:8001/api/"; + var apiUrl = baseApiUrl + "purchasing/vendors"; + var response = await Http.GetAsync(apiUrl); + + if (response.IsSuccessStatusCode) + { + allVendors = await response.Content.ReadFromJsonAsync>(); + vendors = allVendors; + } + else + { + errorMessage = $"Failed to load vendors. Status: {response.StatusCode}"; + } + } + + catch (HttpRequestException ex) + { + errorMessage = $"Network error: {ex.Message}. Please ensure the API is running on port 8001."; + } + + catch (Exception ex) + { + errorMessage = $"Error loading vendors: {ex.Message}"; + } + + finally + { + isLoading = false; + } + } + + private void SelectVendor(Vendor vendor) + { + selectedVendor = vendor; + } + + private void SortBy(string column) + { + if (vendors == null) return; + + if (sortColumn == column) + { + sortAscending = !sortAscending; + } + else + { + sortColumn = column; + sortAscending = true; + } + + vendors = column switch + { + nameof(Vendor.No) => sortAscending + ? vendors.OrderBy(v => v.No).ToList() + : vendors.OrderByDescending(v => v.No).ToList(), + + nameof(Vendor.Name) => sortAscending + ? vendors.OrderBy(v => v.Name).ToList() + : vendors.OrderByDescending(v => v.Name).ToList(), + + nameof(Vendor.Phone) => sortAscending + ? vendors.OrderBy(v => v.Phone).ToList() + : vendors.OrderByDescending(v => v.Phone).ToList(), + + nameof(Vendor.Contact) => sortAscending + ? vendors.OrderBy(v => v.Contact).ToList() + : vendors.OrderByDescending(v => v.Contact).ToList(), + + nameof(Vendor.TaxGroup) => sortAscending + ? vendors.OrderBy(v => v.TaxGroup).ToList() + : vendors.OrderByDescending(v => v.TaxGroup).ToList(), + + nameof(Vendor.Balance) => sortAscending + ? vendors.OrderBy(v => v.Balance).ToList() + : vendors.OrderByDescending(v => v.Balance).ToList(), + _ => vendors + }; + } + + private string GetSortIcon(string column) + { + if (sortColumn != column) return ""; + return sortAscending ? "▲" : "▼"; + } +} \ No newline at end of file diff --git a/src/AccountGoWeb/Components/Pages/Quotations/AddSalesQuotation.razor b/src/AccountGoWeb/Components/Pages/Quotations/AddSalesQuotation.razor new file mode 100644 index 000000000..4e9809975 --- /dev/null +++ b/src/AccountGoWeb/Components/Pages/Quotations/AddSalesQuotation.razor @@ -0,0 +1,255 @@ +@using Dto.Sales +@using Microsoft.AspNetCore.Mvc.Rendering +@using Microsoft.EntityFrameworkCore.Metadata.Internal +@inject HttpClient Http +@inject IConfiguration Configuration +@inject NavigationManager Navigation +@inject IJSRuntime JSRuntime + +Add Sales Quotation + +
+ + + + +
+
+ +
+
Customer
+
+ + + @foreach (var c in Customers) + { + + } + + +
+
+ +
+
Payment Term
+
+ + +
+
+
+ + + + + + + + + + + + + + @if (model?.SalesQuotationLines != null) + { + @for (int i = 0; i < model.SalesQuotationLines.Count; i++) + { + var line = model.SalesQuotationLines[i]; + + + + + + + + + } + } + else + { + + + + } + + + + +
ItemQuantityAmountDiscountMeasurement
+ + + + + + + + + +
Loading...
+ +
+
+ +
+ + +
+
+
+ +@code { +private SalesQuotation model = new SalesQuotation +{ + QuotationDate = DateTime.Now, + SalesQuotationLines = new List + { + new SalesQuotationLine + { + ItemId = 1, + MeasurementId = 1, + Quantity = 1.00m, + Amount = 0.00m, + Discount = 0.00m + } + } +}; + + private List Items = new(); + private List PaymentTerms = new(); + private List Measurements = new(); + private List Customers = new(); + + protected override async Task OnInitializedAsync() + { + var api = Configuration["ApiUrl"]; + if (!string.IsNullOrEmpty(api) && !api.EndsWith("/")) + api += "/"; + + Customers = await Load>(api + "sales/customers"); + PaymentTerms = await Load>(api + "common/paymentterms"); + Items = await Load>(api + "inventory/items"); + Measurements = await Load>(api + "common/measurements"); + } + + + private async Task Load(string url) + { + var response = await Http.GetAsync(url); + + if (!response.IsSuccessStatusCode) + return default!; + + var json = await response.Content.ReadAsStringAsync(); + return System.Text.Json.JsonSerializer.Deserialize(json, + new System.Text.Json.JsonSerializerOptions { PropertyNameCaseInsensitive = true })!; + } + + + + private void AddRow() + { + if (model?.SalesQuotationLines != null) + model.SalesQuotationLines.Add(new SalesQuotationLine + { + ItemId = 1, + MeasurementId = 1, + Quantity = 1.00m, + Amount = 0.00m, + Discount = 0.00m + }); + } + + + private void Close() + { + Navigation.NavigateTo("/quotations/salesquotations"); + } + +private async Task SaveQuotation() +{ + try + { + // Sanitize model before sending + if (model.CustomerId == null || model.CustomerId == 0) + { + Console.WriteLine("Error: Customer must be selected."); + return; // stop saving if customer not selected + } + + // Populate dependent fields + model.CustomerName = Customers.FirstOrDefault(c => c.Id == model.CustomerId)?.Name; + model.QuotationDate = DateTime.Now; + model.StatusId = 0; // Draft + model.SalesQuoteStatus = "Draft"; + + // Ensure all SalesQuotationLines have valid IDs + foreach (var line in model.SalesQuotationLines) + { + if (line.ItemId == 0) line.ItemId = 1; // default to first item + if (line.MeasurementId == 0) line.MeasurementId = 1; // default to first measurement + if (line.Quantity == 0) line.Quantity = 1.0m; + if (line.Amount == 0) line.Amount = 0.0m; + if (line.Discount == 0) line.Discount = 0.0m; + } + + // Serialize and log JSON + var json = System.Text.Json.JsonSerializer.Serialize(model, + new System.Text.Json.JsonSerializerOptions { WriteIndented = true }); + Console.WriteLine("Sending JSON to API:"); + Console.WriteLine(json); + + // Send to API + var content = new StringContent(json, System.Text.Encoding.UTF8, "application/json"); + var apiBase = Configuration["ApiUrl"] ?? "https://gdbapi.azurewebsites.net/api/"; + if (!apiBase.EndsWith("/")) apiBase += "/"; + var endpoint = apiBase + "sales/SaveQuotation"; + + var response = await Http.PostAsync(endpoint, content); + + Console.WriteLine($"API response status: {response.StatusCode}"); + var respContent = await response.Content.ReadAsStringAsync(); + Console.WriteLine($"API response content: {respContent}"); + + if (response.IsSuccessStatusCode) + { + Console.WriteLine("Quotation saved successfully!"); + Navigation.NavigateTo("/quotations/salesquotations"); + } + else + { + Console.WriteLine("Save failed."); + } + } + catch (Exception ex) + { + Console.WriteLine("Error saving quotation: " + ex.Message); + } +} + + + + + public class ItemDto { public int Id { get; set; } public string Name { get; set; } = string.Empty; } + public class PaymentTermDto { public int Id { get; set; } public string Name { get; set; } = string.Empty; } + public class MeasurementDto { public int Id { get; set; } public string Name { get; set; } = string.Empty; } + public class CustomerDto { public int Id { get; set; } public string Name { get; set; } = string.Empty; } +} \ No newline at end of file diff --git a/src/AccountGoWeb/Components/Pages/Quotations/SalesQuotations.razor b/src/AccountGoWeb/Components/Pages/Quotations/SalesQuotations.razor new file mode 100644 index 000000000..d2630799b --- /dev/null +++ b/src/AccountGoWeb/Components/Pages/Quotations/SalesQuotations.razor @@ -0,0 +1,162 @@ +@inject IHttpClientFactory HttpClientFactory +@inject IConfiguration Configuration +@inject NavigationManager NavigationManager +@inject IJSRuntime JSRuntime +@inject IJSRuntime JS + +@inject HttpClient Http + + +Sales Quotations + + + + +

Quotations

+ + +@if (isLoading) +{ +
+
+ Loading... +
+
+} +else if (hasError) +{ +
+ + Error! Unable to load quotations. Please try again. +
+} +else +{ +
+ + + + + + + + + + + + @foreach (var q in quotations) + { + + + + + + + + } + +
NoCustomerDateAmountStatus
@q.no@q.customerName@q.quotationDate.ToShortDateString()@q.amount@q.salesQuoteStatus
+
+} +@code { + private List quotations = new(); + + private string viewQuotationLink = "#"; + private string newOrderLink = "#"; + private bool isViewLinkActive = false; + private bool isNewOrderLinkActive = false; + private bool isLoading = true; + private bool hasError = false; +protected override async Task OnInitializedAsync() +{ + try + { + var apiBase = Configuration["ApiUrl"] ?? "https://gdbapi.azurewebsites.net/api/"; + if (!apiBase.EndsWith("/")) apiBase += "/"; + + var endpoint = apiBase + "Sales/Quotations"; + + var response = await Http.GetAsync(endpoint); + + if (response.IsSuccessStatusCode) + { + quotations = await response.Content.ReadFromJsonAsync>() ?? new List(); + isLoading = false; + } + else + { + hasError = true; + isLoading = false; + Console.WriteLine($"Error fetching quotations: {response.StatusCode}"); + } + } + catch (Exception ex) + { + hasError = true; + isLoading = false; + Console.WriteLine($"Exception fetching quotations: {ex.Message}"); + } +} + + + private void OnSelectionChanged(int id, int status) + { + viewQuotationLink = $"/quotations/quotation?id={id}"; + isViewLinkActive = true; + + if (status == 3) + { + isNewOrderLinkActive = false; + newOrderLink = "#"; + } + else if (status == 1) + { + newOrderLink = $"/sales/salesorder?quotationId={id}"; + isNewOrderLinkActive = true; + } + } + + // DTO class for deserialization + public class QuotationDto + { + public int id { get; set; } + public string no { get; set; } = ""; + public int statusId { get; set; } + public string customerName { get; set; } = ""; + public DateTime quotationDate { get; set; } + public decimal amount { get; set; } + public string salesQuoteStatus { get; set; } = ""; + } +} diff --git a/src/AccountGoWeb/Components/Pages/Sales/AddReceipt.razor b/src/AccountGoWeb/Components/Pages/Sales/AddReceipt.razor new file mode 100644 index 000000000..a222a637b --- /dev/null +++ b/src/AccountGoWeb/Components/Pages/Sales/AddReceipt.razor @@ -0,0 +1,372 @@ +@using System.Text.Json +@using System.Text.Json.Serialization +@using AccountGoWeb.Models +@using AccountGoWeb.Models.Sales +@using Microsoft.AspNetCore.Components + + + +
+ + + @if (loading) + { +

Loading...

+ } + else if (!string.IsNullOrEmpty(errorMessage)) + { + + } + else + { +
+
+ @if (!string.IsNullOrEmpty(errorMessage)) + { + + } +
+
+
+ +
+ + @if (string.IsNullOrEmpty(customerId?.ToString()) && submitAttempted) + { +
Customer is required
+ } +
+
+
+ +
+ +
+
+
+ +
+ + @if (string.IsNullOrEmpty(accountToDebitId?.ToString()) && submitAttempted) + { +
Debit account is required
+ } +
+
+
+ +
+ + @if (!IsValidCreditAccount() && submitAttempted) + { +
Credit account must be the customer's advance account +
+ } +
+
+
+ +
+ + @if (amount <= 0 && submitAttempted) + { +
Amount must be greater than 0
+ } +
+
+
+
+
+ +
+ + Close +
+
+ } +
+ +@code { + private bool loading = true; + private string errorMessage = ""; + private bool submitAttempted = false; + private int? customerId = null; + private DateTime receiptDate = DateTime.Now; + private int? accountToDebitId = null; + private int? accountToCreditId = null; + private decimal amount = 0; + + private List<(string Text, string Value)> customers = new(); + private List<(string Text, string Value)> debitAccounts = new(); + private List<(string Text, string Value)> creditAccounts = new(); + private Dictionary customerAdvanceAccounts = new(); + private Dictionary accountNames = new(); + + [Inject] + private IHttpClientFactory ClientFactory { get; set; } = null!; + + [Inject] + private NavigationManager Navigation { get; set; } = null!; + + protected override async Task OnInitializedAsync() + { + try + { + string apiurl = System.Environment.GetEnvironmentVariable("APIURL") ?? "http://localhost:8001/api/"; + var client = ClientFactory.CreateClient(); + client.Timeout = TimeSpan.FromSeconds(60); + + // Load customers from API + var customersResponse = await client.GetAsync($"{apiurl}sales/customers"); + if (customersResponse.IsSuccessStatusCode) + { + var json = await customersResponse.Content.ReadAsStringAsync(); + var options = new JsonSerializerOptions { PropertyNameCaseInsensitive = true }; + var customersData = JsonSerializer.Deserialize>(json, options); + if (customersData != null) + { + foreach (var customer in customersData) + { + var id = GetJsonPropertyString(customer, "id"); + var name = GetJsonPropertyString(customer, "name"); + var prepaymentAccountId = GetJsonPropertyString(customer, "prepaymentAccountId"); + customers.Add((name ?? "Unknown", id)); + + // Store the prepayment account ID for this customer + if (int.TryParse(id, out var customerId_int) && int.TryParse(prepaymentAccountId, out var accountId_int)) + { + customerAdvanceAccounts[customerId_int] = accountId_int; + } + } + } + } + + // Load debit accounts from API (Cash & Banks) + var debitResponse = await client.GetAsync($"{apiurl}financials/CashBanks"); + if (debitResponse.IsSuccessStatusCode) + { + var json = await debitResponse.Content.ReadAsStringAsync(); + var options = new JsonSerializerOptions { PropertyNameCaseInsensitive = true }; + var accountsData = JsonSerializer.Deserialize>(json, options); + if (accountsData != null) + { + foreach (var account in accountsData) + { + var id = GetJsonPropertyString(account, "id"); + var name = GetJsonPropertyString(account, "name"); + debitAccounts.Add((name ?? "Unknown", id)); + } + } + } + + // Load credit accounts from API (posting accounts only) + var creditResponse = await client.GetAsync($"{apiurl}common/postingaccounts"); + if (creditResponse.IsSuccessStatusCode) + { + var json = await creditResponse.Content.ReadAsStringAsync(); + var options = new JsonSerializerOptions { PropertyNameCaseInsensitive = true }; + var accountsData = JsonSerializer.Deserialize>(json, options); + if (accountsData != null) + { + foreach (var account in accountsData) + { + var id = GetJsonPropertyString(account, "id"); + var name = GetJsonPropertyString(account, "accountName"); + if (!string.IsNullOrEmpty(id) && !string.IsNullOrEmpty(name)) + { + creditAccounts.Add((name, id)); + // Store account name for display + if (int.TryParse(id, out var accountId)) + { + accountNames[accountId] = name; + } + } + } + } + } + + // Also load all accounts to get customer advance account names + var allAccountsResponse = await client.GetAsync($"{apiurl}financials/accounts"); + if (allAccountsResponse.IsSuccessStatusCode) + { + var json = await allAccountsResponse.Content.ReadAsStringAsync(); + var options = new JsonSerializerOptions { PropertyNameCaseInsensitive = true }; + var accountsData = JsonSerializer.Deserialize>(json, options); + if (accountsData != null) + { + foreach (var account in accountsData) + { + var id = GetJsonPropertyString(account, "id"); + var name = GetJsonPropertyString(account, "name"); + if (!string.IsNullOrEmpty(id) && !string.IsNullOrEmpty(name)) + { + // Store account name for display + if (int.TryParse(id, out var accountId)) + { + accountNames[accountId] = name; + } + } + } + } + } + + loading = false; + } + catch (Exception ex) + { + errorMessage = $"Failed to load form data: {ex.Message}"; + loading = false; + } + } + + private async Task OnCustomerChanged(ChangeEventArgs e) + { + if (e.Value == null || string.IsNullOrEmpty(e.Value.ToString())) + { + customerId = null; + accountToCreditId = null; + return; + } + + if (int.TryParse(e.Value.ToString(), out var selectedCustomerId)) + { + customerId = selectedCustomerId; + + // Auto-select the customer's prepayment account + if (customerAdvanceAccounts.TryGetValue(selectedCustomerId, out var advanceAccountId)) + { + accountToCreditId = advanceAccountId; + } + } + } + private async Task HandleSubmit() + { + submitAttempted = true; + + // Validate form + if (string.IsNullOrEmpty(customerId?.ToString()) || + string.IsNullOrEmpty(accountToDebitId?.ToString()) || + !IsValidCreditAccount() || + amount <= 0) + { + return; + } + + try + { + string apiurl = System.Environment.GetEnvironmentVariable("APIURL") ?? "http://localhost:8001/api/"; + var client = ClientFactory.CreateClient(); + + // Create an object with the form data + // Ensure all IDs are integers + var receiptData = new + { + CustomerId = customerId.HasValue ? customerId.Value : 0, + ReceiptDate = receiptDate, + AccountToDebitId = accountToDebitId.HasValue ? accountToDebitId.Value : 0, + AccountToCreditId = accountToCreditId.HasValue ? accountToCreditId.Value : 0, + Amount = amount + }; + + // Submit to API + var json = System.Text.Json.JsonSerializer.Serialize(receiptData); + var content = new StringContent(json, System.Text.Encoding.UTF8, "application/json"); + + var response = await client.PostAsync($"{apiurl}sales/savereceipt", content); + + if (response.IsSuccessStatusCode) + { + Navigation.NavigateTo("/Sales/SalesReceipts", forceLoad: true); + } + else + { + var responseContent = await response.Content.ReadAsStringAsync(); + errorMessage = $"Failed to save receipt. Status: {response.StatusCode}. Details: {responseContent}"; + } + } + catch (Exception ex) + { + errorMessage = $"Error saving receipt: {ex.Message}"; + } + } + + private bool IsValidCreditAccount() + { + // Credit account must be selected + if (!accountToCreditId.HasValue || accountToCreditId <= 0) + { + return false; + } + + // Credit account must match the customer's advance account + if (customerId.HasValue && customerAdvanceAccounts.TryGetValue(customerId.Value, out var advanceAccountId)) + { + return accountToCreditId == advanceAccountId; + } + + // If no customer selected, we can't validate + return false; + } + + private string GetJsonPropertyString(JsonElement element, string propertyName) + { + if (element.TryGetProperty(propertyName, out var property)) + { + if (property.ValueKind == JsonValueKind.String) + { + return property.GetString() ?? ""; + } + else if (property.ValueKind == JsonValueKind.Number) + { + return property.GetInt32().ToString(); + } + } + return ""; + } +} \ No newline at end of file diff --git a/src/AccountGoWeb/Components/Pages/Sales/Allocate.razor b/src/AccountGoWeb/Components/Pages/Sales/Allocate.razor new file mode 100644 index 000000000..a8b32e12f --- /dev/null +++ b/src/AccountGoWeb/Components/Pages/Sales/Allocate.razor @@ -0,0 +1,419 @@ +@using System.Text.Json +@using System.Text.Json.Serialization + + + +
+ + + @if (loading) + { +

Loading allocation data...

+ } + else if (errorMessage != null) + { + + } + else + { +
+ +
+
+
General
+
+
+
+
+ + +
+
+ + +
+
+ +
+
+ + +
+
+ + +
+
+ +
+
+ + +
+
+
+
+ + +
+
+
Invoices
+
+
+
+ + + + + + + + + + + @if (allocationLines.Count > 0) + { + @for (int i = 0; i < allocationLines.Count; i++) + { + int index = i; + var line = allocationLines[index]; + + + + + + + } + } + +
Invoice NoAmountAllocated AmountTo Allocate
+ @line.InvoiceId + + @line.Amount?.ToString("F2") + + @line.AllocatedAmount?.ToString("F2") + + +
+
+
+
+ + +
+
+ + + + Close + +
+
+ + @if (!string.IsNullOrEmpty(successMessage)) + { + + } +
+ } +
+ +@code { + [Parameter] + public string? ReceiptId { get; set; } + + [Inject] + private NavigationManager Navigation { get; set; } = null!; + + [Inject] + private IHttpClientFactory ClientFactory { get; set; } = null!; + + private bool loading = true; + private string? errorMessage = null; + private string? successMessage = null; + + private string receiptNo = ""; + private string customerName = ""; + private DateTime receiptDate = DateTime.Now; + private decimal amount = 0; + private decimal remainingAmount = 0; + private int customerId = 0; + + private List allocationLines = new(); + + private class AllocationLineModel + { + public int? InvoiceId { get; set; } + public decimal? Amount { get; set; } + public decimal? AllocatedAmount { get; set; } + public decimal? AmountToAllocate { get; set; } + } + + protected override async Task OnInitializedAsync() + { + // Extract ReceiptId from the current URL path + // URL format: /Sales/Allocate/123 + var uri = Navigation.ToAbsoluteUri(Navigation.Uri); + var segments = uri.AbsolutePath.Split('/'); + + // Find the ID from the URL segments (last segment should be the ID) + if (segments.Length > 0 && int.TryParse(segments[^1], out var receiptId)) + { + ReceiptId = receiptId.ToString(); + } + + // Fallback to query parameter if not found in route + if (string.IsNullOrEmpty(ReceiptId)) + { + var queryParams = System.Web.HttpUtility.ParseQueryString(uri.Query); + ReceiptId = queryParams["id"]; + } + + if (!string.IsNullOrEmpty(ReceiptId)) + { + await LoadAllocationData(); + } + else + { + errorMessage = "Receipt ID is required."; + loading = false; + } + } + + private async Task LoadAllocationData() + { + try + { + loading = true; + string apiurl = System.Environment.GetEnvironmentVariable("APIURL") ?? "http://localhost:8001/api/"; + + var client = ClientFactory.CreateClient(); + client.Timeout = TimeSpan.FromSeconds(60); + + // Fetch receipt details + var receiptResponse = await client.GetAsync($"{apiurl}sales/salesreceipt?id={ReceiptId}"); + if (receiptResponse.IsSuccessStatusCode) + { + var receiptString = await receiptResponse.Content.ReadAsStringAsync(); + var options = new JsonSerializerOptions { PropertyNameCaseInsensitive = true }; + var receiptJson = JsonSerializer.Deserialize(receiptString, options); + + receiptNo = GetJsonProperty(receiptJson, "receiptNo"); + customerName = GetJsonProperty(receiptJson, "customerName"); + amount = GetJsonPropertyDecimal(receiptJson, "amount"); + remainingAmount = GetJsonPropertyDecimal(receiptJson, "remainingAmountToAllocate"); + + if (receiptJson.TryGetProperty("customerId", out var custIdProp)) + { + if (custIdProp.TryGetInt32(out var id)) + customerId = id; + } + + System.Diagnostics.Debug.WriteLine($"DEBUG: Loaded Receipt - ReceiptNo: {receiptNo}, CustomerId: {customerId}"); + + if (receiptJson.TryGetProperty("receiptDate", out var dateProp)) + { + if (DateTime.TryParse(dateProp.GetString(), out var date)) + receiptDate = date; + } + + // Fetch customer invoices + System.Diagnostics.Debug.WriteLine($"DEBUG: Fetching invoices for customerId: {customerId}"); + await LoadInvoicesForCustomer(customerId, client, apiurl, options); + } + else + { + errorMessage = "Failed to load receipt details."; + } + } + catch (Exception ex) + { + errorMessage = $"Error loading allocation data: {ex.Message}"; + } + finally + { + loading = false; + } + } + + private async Task LoadInvoicesForCustomer(int custId, HttpClient? client = null, string? apiurl = null, + JsonSerializerOptions? options = null) + { + if (client == null) + { + client = ClientFactory.CreateClient(); + client.Timeout = TimeSpan.FromSeconds(60); + } + + if (string.IsNullOrEmpty(apiurl)) + { + apiurl = System.Environment.GetEnvironmentVariable("APIURL") ?? "http://localhost:8001/api/"; + } + + if (options == null) + { + options = new JsonSerializerOptions { PropertyNameCaseInsensitive = true }; + } + + try + { + System.Diagnostics.Debug.WriteLine($"DEBUG: Fetching invoices for customerId: {custId}"); + var invoiceResponse = await client.GetAsync($"{apiurl}sales/customerinvoices?id={custId}"); + System.Diagnostics.Debug.WriteLine($"DEBUG: Invoice API Response Status: {invoiceResponse.StatusCode}"); + + if (invoiceResponse.IsSuccessStatusCode) + { + var invoicesString = await invoiceResponse.Content.ReadAsStringAsync(); + System.Diagnostics.Debug.WriteLine($"DEBUG: Invoice Response: {invoicesString}"); + var invoicesJson = JsonSerializer.Deserialize>(invoicesString, options); + + System.Diagnostics.Debug.WriteLine($"DEBUG: Parsed {invoicesJson?.Count ?? 0} invoices"); + allocationLines.Clear(); + if (invoicesJson != null) + { + foreach (var invoice in invoicesJson) + { + var posted = false; + var totalAllocated = 0m; + var invoiceAmount = 0m; + + if (invoice.TryGetProperty("posted", out var postedProp)) + posted = postedProp.GetBoolean(); + + if (invoice.TryGetProperty("totalAllocatedAmount", out var allocProp)) + totalAllocated = allocProp.GetDecimal(); + + if (invoice.TryGetProperty("amount", out var amountProp)) + invoiceAmount = amountProp.GetDecimal(); + + // Include invoices with remaining balance + // TODO: Should filter for posted invoices only (posted == true), but currently allowing unposted for testing + // Invoices must be posted to the GL before they can be allocated. + // A "Post Invoice" feature should be added to SalesInvoice component. + if (totalAllocated < invoiceAmount) + { + var line = new AllocationLineModel + { + InvoiceId = GetJsonPropertyInt(invoice, "id"), + Amount = invoiceAmount, + AllocatedAmount = totalAllocated, + AmountToAllocate = null + }; + allocationLines.Add(line); + System.Diagnostics.Debug.WriteLine($"DEBUG: Added invoice {line.InvoiceId} - Posted: {posted}, Allocated: {totalAllocated}, Amount: {invoiceAmount}"); + } + else + { + System.Diagnostics.Debug.WriteLine($"DEBUG: Skipped invoice - Posted: {posted}, Allocated: {totalAllocated}, Amount: {invoiceAmount}"); + } + } + } + } + else + { + var errorContent = await invoiceResponse.Content.ReadAsStringAsync(); + errorMessage = "Failed to load customer invoices."; + } + } + catch (Exception ex) + { + errorMessage = $"Error loading customer invoices: {ex.Message}"; + } + } + + private async Task HandleSubmit() + { + try + { + string apiurl = System.Environment.GetEnvironmentVariable("APIURL") ?? "http://localhost:8001/api/"; + + var payload = new + { + CustomerId = customerId, + ReceiptId = int.Parse(ReceiptId ?? "0"), + Date = receiptDate, + Amount = amount, + RemainingAmountToAllocate = remainingAmount, + AllocationLines = allocationLines + .Where(l => l.AmountToAllocate.HasValue && l.AmountToAllocate.Value > 0) + .Select(l => new + { + InvoiceId = l.InvoiceId, + Amount = l.Amount, + AllocatedAmount = l.AllocatedAmount, + AmountToAllocate = l.AmountToAllocate + }) + .ToList() + }; + + var client = ClientFactory.CreateClient(); + var json = JsonSerializer.Serialize(payload); + var content = new StringContent(json, System.Text.Encoding.UTF8, "application/json"); + + var response = await client.PostAsync($"{apiurl}sales/saveallocation", content); + + if (response.IsSuccessStatusCode) + { + // Redirect to SalesReceipts page with forceLoad to refresh the MVC view + Navigation.NavigateTo("/Sales/SalesReceipts", forceLoad: true); + } + else + { + var errorContent = await response.Content.ReadAsStringAsync(); + errorMessage = $"Failed to save allocation. Status: {response.StatusCode}"; + } + } + catch (Exception ex) + { + errorMessage = $"Error saving allocation: {ex.Message}"; + } + } + + private string GetJsonProperty(System.Text.Json.JsonElement elem, string propName) + { + if (elem.TryGetProperty(propName, out var prop)) + return prop.GetString() ?? ""; + return ""; + } + + private decimal GetJsonPropertyDecimal(System.Text.Json.JsonElement elem, string propName) + { + if (elem.TryGetProperty(propName, out var prop)) + { + if (prop.TryGetDecimal(out var val)) + return val; + } + return 0; + } + + private int GetJsonPropertyInt(System.Text.Json.JsonElement elem, string propName) + { + if (elem.TryGetProperty(propName, out var prop)) + { + if (prop.TryGetInt32(out var val)) + return val; + } + return 0; + } +} \ No newline at end of file diff --git a/src/AccountGoWeb/Components/Pages/Sales/Customer.razor b/src/AccountGoWeb/Components/Pages/Sales/Customer.razor new file mode 100644 index 000000000..bfbc6b549 --- /dev/null +++ b/src/AccountGoWeb/Components/Pages/Sales/Customer.razor @@ -0,0 +1,483 @@ +@using CustomerDto = Dto.Sales.Customer +@using Dto.Common +@inject HttpClient Http +@inject NavigationManager Navigation +@rendermode InteractiveServer + +@(Id == 0 ? "New" : "Edit") Customer + + + +
+
+
+ @if (Id != 0 && !isEditMode) + { + + } +
+
+ + @if (isLoading) + { +
+
+
+ Loading... +
+

Loading customer...

+
+
+ } + else if (errorMessage != null) + { +
+
+ +
+
+ } + else + { + + + + @* General Section *@ +
+
+ General +
+
+
+
+
+
+ +
+
+ +
+
+
+
+ +
+
+ + +
+
+
+
+
+
+ +
+
+ +
+
+
+
+ +
+
+ +
+
+
+
+ +
+
+ +
+
+
+
+
+
+ + @* Contact Section *@ +
+
+ Contact + + @if (Id != 0) + { + + } +
+
+
+
+
+
+ +
+
+ +
+
+
+
+ +
+
+ +
+
+
+
+
+
+ +
+
+ +
+
+
+
+ +
+
+ +
+
+
+
+ +
+
+ +
+
+
+
+
+
+ + @* Invoicing Section *@ +
+
+ Invoicing +
+
+
+
+
+
+ +
+
+ + + @foreach (var account in Accounts) + { + + } + +
+
+
+
+ +
+
+ + + @foreach (var account in Accounts) + { + + } + +
+
+
+
+ +
+
+ + + @foreach (var account in Accounts) + { + + } + +
+
+
+
+ +
+
+ + + @foreach (var account in Accounts) + { + + } + +
+
+
+
+
+
+ +
+
+ + + @foreach (var taxGroup in TaxGroups) + { + + } + +
+
+
+
+
+
+ + @* Payment Section *@ +
+
+ Payment +
+
+
+
+
+
+ +
+
+ + + @foreach (var term in PaymentTerms) + { + + } + +
+
+
+
+
+
+ +
+
+ @if (isEditMode || Id == 0) + { + + } + +
+
+
+ } +
+ +@code { + [Parameter] + public int Id { get; set; } = 0; + + private CustomerDto Model { get; set; } = new(); + + private List Accounts { get; set; } = new(); + private List TaxGroups { get; set; } = new(); + private List PaymentTerms { get; set; } = new(); + + private bool isLoading = true; + private bool isEditMode = false; + private string? errorMessage; + + protected override async Task OnInitializedAsync() + { + isLoading = true; + + if (Id != 0) + { + await LoadCustomer(); + } + else + { + isEditMode = true; + await GenerateCustomerNumber(); + } + + await LoadDropdownData(); + + isLoading = false; + } + + private async Task GenerateCustomerNumber() + { + try + { + string baseApiUrl = Environment.GetEnvironmentVariable("APIURL") ?? "http://localhost:8001/api/"; + var apiUrl = baseApiUrl + "sales/customers"; + var response = await Http.GetAsync(apiUrl); + + if (response.IsSuccessStatusCode) + { + var customers = await response.Content.ReadFromJsonAsync>(); + if (customers != null && customers.Any()) + { + // Find the highest customer number + var maxNo = customers + .Select(c => int.TryParse(c.No, out int num) ? num : 0) + .DefaultIfEmpty(0) + .Max(); + + Model.No = (maxNo + 1).ToString(); + } + else + { + Model.No = "1"; + } + } + } + catch (Exception) + { + Model.No = "1"; + } + } + + private async Task LoadCustomer() + { + try + { + string baseApiUrl = Environment.GetEnvironmentVariable("APIURL") ?? "http://localhost:8001/api/"; + var apiUrl = $"{baseApiUrl}sales/customer?id={Id}"; + var response = await Http.GetAsync(apiUrl); + + if (response.IsSuccessStatusCode) + { + Model = await response.Content.ReadFromJsonAsync() ?? new(); + } + else + { + errorMessage = $"Failed to load customer. Status: {response.StatusCode}"; + } + } + catch (Exception ex) + { + errorMessage = $"Error loading customer: {ex.Message}"; + } + } + + private async Task LoadDropdownData() + { + try + { + string baseApiUrl = Environment.GetEnvironmentVariable("APIURL") ?? "http://localhost:8001/api/"; + + // Load accounts + var accountsResponse = await Http.GetAsync(baseApiUrl + "common/postingaccounts"); + if (accountsResponse.IsSuccessStatusCode) + { + var accounts = await accountsResponse.Content.ReadFromJsonAsync>(); + Accounts = accounts?.Select(a => new SelectListItem { Value = a.Id.ToString(), Text = a.AccountName! }).ToList() ?? new(); + } + + // Load tax groups + var taxGroupsResponse = await Http.GetAsync(baseApiUrl + "tax/taxgroups"); + if (taxGroupsResponse.IsSuccessStatusCode) + { + var taxGroups = await taxGroupsResponse.Content.ReadFromJsonAsync>(); + TaxGroups = taxGroups?.Select(tg => new SelectListItem { Value = tg.Id.ToString(), Text = tg.Description! }).ToList() ?? new(); + } + + // Load payment terms + var paymentTermsResponse = await Http.GetAsync(baseApiUrl + "common/paymentterms"); + if (paymentTermsResponse.IsSuccessStatusCode) + { + var paymentTerms = await paymentTermsResponse.Content.ReadFromJsonAsync>(); + PaymentTerms = paymentTerms?.Select(pt => new SelectListItem { Value = pt.Id.ToString(), Text = pt.Description }).ToList() ?? new(); + } + } + catch (Exception ex) + { + errorMessage = $"Error loading form data: {ex.Message}"; + } + } + + private async Task HandleValidSubmit() + { + try + { + string baseApiUrl = Environment.GetEnvironmentVariable("APIURL") ?? "http://localhost:8001/api/"; + var apiUrl = baseApiUrl + "sales/savecustomer"; + + var response = await Http.PostAsJsonAsync(apiUrl, Model); + + if (response.IsSuccessStatusCode) + { + Navigation.NavigateTo("/sales/customers", forceLoad: true); + } + else + { + var errorContent = await response.Content.ReadAsStringAsync(); + errorMessage = $"Failed to save customer. Status: {response.StatusCode}. {errorContent}"; + } + } + catch (Exception ex) + { + errorMessage = $"Error saving customer: {ex.Message}"; + } + } + + private void EnableEditMode() + { + isEditMode = true; + } + + private void NavigateToCustomerList() + { + Navigation.NavigateTo("/sales/customers", forceLoad: true); + } + + private void NavigateToAddContact() + { + Navigation.NavigateTo($"/contact/contact?partyId={Model.Id}&partyType=1", forceLoad: true); + } + + private void NavigateToContacts() + { + Navigation.NavigateTo($"/contact/contacts?partyId={Model.Id}&partyType=1", forceLoad: true); + } + + public class SelectListItem + { + public string Value { get; set; } = string.Empty; + public string Text { get; set; } = string.Empty; + } + + public class PaymentTermResponse + { + public int Id { get; set; } + public string Description { get; set; } = string.Empty; + } +} diff --git a/src/AccountGoWeb/Components/Pages/Sales/Customers.razor b/src/AccountGoWeb/Components/Pages/Sales/Customers.razor new file mode 100644 index 000000000..fbac6de14 --- /dev/null +++ b/src/AccountGoWeb/Components/Pages/Sales/Customers.razor @@ -0,0 +1,224 @@ +@using CustomerDto = Dto.Sales.Customer +@inject HttpClient Http +@inject NavigationManager Navigation +@rendermode InteractiveServer + +Customers + +
+ +
+
+ + @if (selectedCustomer != null) + { + + } +
+
+ + @if (isLoading) + { +
+
+
+ Loading... +
+

Loading customers...

+
+
+ } + else if (errorMessage != null) + { +
+
+ +
+
+ } + else if (customers == null || !customers.Any()) + { +
+
+ +
+
+ } + else + { +
+
+
+ + + + + + + + + + + + + @foreach (var customer in customers) + { + + + + + + + + + } + +
+ No @GetSortIcon(nameof(CustomerDto.No)) + + Name @GetSortIcon(nameof(CustomerDto.Name)) + + Phone @GetSortIcon(nameof(CustomerDto.Phone)) + + Contact @GetSortIcon(nameof(CustomerDto.Contact)) + + Tax Group @GetSortIcon(nameof(CustomerDto.TaxGroup)) + + Balance @GetSortIcon(nameof(CustomerDto.Balance)) +
+ + @customer.No + + @customer.Name@customer.Phone@customer.Contact@customer.TaxGroup@customer.Balance.ToString("N2")
+
+
+
+ +
+
+

Total: @customers.Count() customer(s)

+
+
+ } +
+ +@code { + private List? customers; + private List? allCustomers; + private CustomerDto? selectedCustomer; + private bool isLoading = true; + private string? errorMessage; + private string? sortColumn; + private bool sortAscending = true; + + protected override async Task OnInitializedAsync() + { + await LoadCustomers(); + } + + private async Task LoadCustomers() + { + isLoading = true; + errorMessage = null; + + try + { + string baseApiUrl = Environment.GetEnvironmentVariable("APIURL") ?? "http://localhost:8001/api/"; + var apiUrl = baseApiUrl + "sales/customers"; + var response = await Http.GetAsync(apiUrl); + + if (response.IsSuccessStatusCode) + { + allCustomers = await response.Content.ReadFromJsonAsync>(); + customers = allCustomers; + } + else + { + errorMessage = $"Failed to load customers. Status: {response.StatusCode}"; + } + } + catch (HttpRequestException ex) + { + errorMessage = $"Network error: {ex.Message}. Please ensure the API is running on port 8001."; + } + catch (Exception ex) + { + errorMessage = $"Error loading customers: {ex.Message}"; + } + finally + { + isLoading = false; + } + } + + private void SelectCustomer(CustomerDto customer) + { + selectedCustomer = customer; + } + + private void NavigateToNewCustomer() + { + Navigation.NavigateTo("/sales/customer", forceLoad: true); + } + + private void NavigateToViewCustomer() + { + if (selectedCustomer != null) + { + Navigation.NavigateTo($"/sales/customer/{selectedCustomer.Id}", forceLoad: true); + } + } + + private void SortBy(string column) + { + if (customers == null) return; + + if (sortColumn == column) + { + sortAscending = !sortAscending; + } + else + { + sortColumn = column; + sortAscending = true; + } + + customers = column switch + { + nameof(CustomerDto.No) => sortAscending + ? customers.OrderBy(c => c.No).ToList() + : customers.OrderByDescending(c => c.No).ToList(), + nameof(CustomerDto.Name) => sortAscending + ? customers.OrderBy(c => c.Name).ToList() + : customers.OrderByDescending(c => c.Name).ToList(), + nameof(CustomerDto.Phone) => sortAscending + ? customers.OrderBy(c => c.Phone).ToList() + : customers.OrderByDescending(c => c.Phone).ToList(), + nameof(CustomerDto.Contact) => sortAscending + ? customers.OrderBy(c => c.Contact).ToList() + : customers.OrderByDescending(c => c.Contact).ToList(), + nameof(CustomerDto.TaxGroup) => sortAscending + ? customers.OrderBy(c => c.TaxGroup).ToList() + : customers.OrderByDescending(c => c.TaxGroup).ToList(), + nameof(CustomerDto.Balance) => sortAscending + ? customers.OrderBy(c => c.Balance).ToList() + : customers.OrderByDescending(c => c.Balance).ToList(), + _ => customers + }; + } + + private string GetSortIcon(string column) + { + if (sortColumn != column) return ""; + return sortAscending ? "▲" : "▼"; + } +} diff --git a/src/AccountGoWeb/Components/Pages/Sales/NewSalesOrder.razor b/src/AccountGoWeb/Components/Pages/Sales/NewSalesOrder.razor new file mode 100644 index 000000000..23ca2b800 --- /dev/null +++ b/src/AccountGoWeb/Components/Pages/Sales/NewSalesOrder.razor @@ -0,0 +1,461 @@ +@using System.Text.Json +@using System.Text.Json.Serialization + + + +
+
+
+
+
+ @if (!isNew) + { + + } +
+
+ + @if (loading) + { +

Loading...

+ } + else if (errorMessage != null) + { + + } + else if (order == null) + { +

Sales order not found.

+ } + else + { +
+
+
+
+
+
+ + +
+
+ + +
+
+ +
+
+ + +
+
+ +
Line Items
+
+ + + + + + + + + + + + + @if (lineItems != null && lineItems.Count > 0) + { + @for (int i = 0; i < lineItems.Count; i++) + { + var line = lineItems[i]; + + + + + + + + + } + } + +
ItemQuantityAmountDiscount (%)MeasurementAction
+ + + + + + + + + + + @{ + int itemIndex = i; + } + +
+
+ + @if (editMode) + { + + } +
+
+ +
+ @if (editMode) + { + + Cancel + } +
+
+
+ } +
+ +@code { + private class LineItem + { + public int ItemId { get; set; } = 1; + public string ItemDescription { get; set; } = ""; + public int MeasurementId { get; set; } = 1; + public decimal Quantity { get; set; } = 1; + public decimal Amount { get; set; } = 0; + public decimal Discount { get; set; } = 0; + } + + private class Customer + { + public int id { get; set; } + public string name { get; set; } = ""; + } + + private class Item + { + public int id { get; set; } + public string description { get; set; } = ""; + } + + private class PaymentTerm + { + public int id { get; set; } + public string description { get; set; } = ""; + } + + private class Measurement + { + public int id { get; set; } + public string description { get; set; } = ""; + } + + [Parameter] + public string Id { get; set; } = null!; + + [Inject] + private IHttpClientFactory ClientFactory { get; set; } = null!; + + [Inject] + private NavigationManager Navigation { get; set; } = null!; + + private Dictionary order = new(); + private bool loading = true; + private bool editMode = false; + private bool isNew = false; + private string? errorMessage = null; + + private string orderNo = ""; + private int customerId = 1; + private string customerName = ""; + private DateTime orderDate = DateTime.Now; + private string referenceNo = ""; + private int paymentTermId = 1; + private decimal totalAmount = 0; + private List lineItems = new(); + + private List customers = new(); + private List items = new(); + private List paymentTerms = new(); + private List measurements = new(); + + protected override async Task OnInitializedAsync() + { + await LoadCustomers(); + await LoadItems(); + await LoadPaymentTerms(); + await LoadMeasurements(); + + isNew = Id == "new" || string.IsNullOrEmpty(Id); + + if (isNew) + { + order = new Dictionary(); + orderNo = new Random().Next(1, 99999).ToString(); + orderDate = DateTime.Now; + lineItems = new List { new LineItem() }; + editMode = true; + loading = false; + } + else + { + Navigation.NavigateTo("/Sales/AddSalesOrder"); + } + } + + private async Task LoadCustomers() + { + try + { + string apiurl = System.Environment.GetEnvironmentVariable("APIURL") ?? "http://localhost:8001/api/"; + var client = ClientFactory.CreateClient(); + var response = await client.GetAsync($"{apiurl}sales/customers"); + + if (response.IsSuccessStatusCode) + { + var responseString = await response.Content.ReadAsStringAsync(); + customers = JsonSerializer.Deserialize>(responseString) ?? new List(); + } + } + catch (Exception ex) + { + errorMessage = $"Error loading customers: {ex.Message}"; + } + } + + private async Task LoadItems() + { + try + { + string apiurl = System.Environment.GetEnvironmentVariable("APIURL") ?? "http://localhost:8001/api/"; + var client = ClientFactory.CreateClient(); + var response = await client.GetAsync($"{apiurl}inventory/items"); + + if (response.IsSuccessStatusCode) + { + var responseString = await response.Content.ReadAsStringAsync(); + items = JsonSerializer.Deserialize>(responseString) ?? new List(); + } + } + catch (Exception ex) + { + errorMessage = $"Error loading items: {ex.Message}"; + } + } + + private async Task LoadPaymentTerms() + { + try + { + string apiurl = System.Environment.GetEnvironmentVariable("APIURL") ?? "http://localhost:8001/api/"; + var client = ClientFactory.CreateClient(); + var response = await client.GetAsync($"{apiurl}common/paymentterms"); + + if (response.IsSuccessStatusCode) + { + var responseString = await response.Content.ReadAsStringAsync(); + paymentTerms = JsonSerializer.Deserialize>(responseString) ?? new List(); + } + } + catch (Exception ex) + { + errorMessage = $"Error loading payment terms: {ex.Message}"; + } + } + + private async Task LoadMeasurements() + { + try + { + string apiurl = System.Environment.GetEnvironmentVariable("APIURL") ?? "http://localhost:8001/api/"; + var client = ClientFactory.CreateClient(); + var response = await client.GetAsync($"{apiurl}common/measurements"); + + if (response.IsSuccessStatusCode) + { + var responseString = await response.Content.ReadAsStringAsync(); + measurements = JsonSerializer.Deserialize>(responseString) ?? new List(); + } + } + catch (Exception ex) + { + errorMessage = $"Error loading measurements: {ex.Message}"; + } + } + + private void MapJsonToProperties(System.Text.Json.JsonElement data) + { + orderNo = GetJsonProperty(data, "no"); + customerName = GetJsonProperty(data, "customerName"); + referenceNo = GetJsonProperty(data, "referenceNo"); + + if (data.TryGetProperty("orderDate", out var dateElem)) + { + if (DateTime.TryParse(dateElem.GetString(), out var date)) + orderDate = date; + } + + lineItems = new(); + if (data.TryGetProperty("salesOrderLines", out var linesElem) && linesElem.ValueKind == + System.Text.Json.JsonValueKind.Array) + { + foreach (var lineElem in linesElem.EnumerateArray()) + { + var line = new LineItem + { + ItemDescription = GetJsonProperty(lineElem, "itemDescription"), + Quantity = GetJsonPropertyDecimal(lineElem, "quantity"), + Amount = GetJsonPropertyDecimal(lineElem, "amount"), + Discount = GetJsonPropertyDecimal(lineElem, "discount") + }; + lineItems.Add(line); + } + } + + CalculateTotalAmount(); + } + + private string GetJsonProperty(System.Text.Json.JsonElement elem, string propName) + { + if (elem.TryGetProperty(propName, out var prop)) + { + return prop.GetString() ?? ""; + } + return ""; + } + + private decimal GetJsonPropertyDecimal(System.Text.Json.JsonElement elem, string propName) + { + if (elem.TryGetProperty(propName, out var prop)) + { + if (prop.TryGetDecimal(out var val)) + return val; + } + return 0; + } + + private void AddLineItem() + { + lineItems.Add(new LineItem()); + CalculateTotalAmount(); + StateHasChanged(); + } + + private void RemoveLineItem(int index) + { + if (index >= 0 && index < lineItems.Count) + { + lineItems.RemoveAt(index); + } + CalculateTotalAmount(); + StateHasChanged(); + } + + private void CalculateTotalAmount() + { + totalAmount = lineItems.Sum(line => + { + var qty = line.Quantity; + var amt = line.Amount; + var disc = line.Discount; + var total = qty * amt; + var discount = (disc / 100) * total; + return total - discount; + }); + } + + private async Task SaveSalesOrder() + { + try + { + CalculateTotalAmount(); + string apiurl = System.Environment.GetEnvironmentVariable("APIURL") ?? "http://localhost:8001/api/"; + + var payload = new + { + CustomerId = customerId, + OrderDate = orderDate, + PaymentTermId = paymentTermId, + ReferenceNo = referenceNo, + SalesOrderLines = lineItems.Select(line => new + { + ItemId = line.ItemId, + MeasurementId = line.MeasurementId, + Quantity = line.Quantity, + Amount = line.Amount, + Discount = line.Discount + }).ToList() + }; + + var client = ClientFactory.CreateClient(); + var json = JsonSerializer.Serialize(payload); + var content = new StringContent(json, System.Text.Encoding.UTF8, "application/json"); + + var response = await client.PostAsync($"{apiurl}sales/addsalesorder", content); + + if (response.IsSuccessStatusCode) + { + editMode = false; + errorMessage = null; + await Task.Delay(100); + Navigation.NavigateTo("/Sales/SalesOrders", forceLoad: true); + } + else + { + var errorContent = await response.Content.ReadAsStringAsync(); + errorMessage = $"Failed to save sales order. Status: {response.StatusCode}. Response: {errorContent}"; + StateHasChanged(); + } + } + catch (Exception ex) + { + errorMessage = $"Error saving sales order: {ex.Message}"; + StateHasChanged(); + } + } + + private void CancelSalesOrder() + { + Navigation.NavigateTo("/Sales/SalesOrders"); + } +} \ No newline at end of file diff --git a/src/AccountGoWeb/Components/Pages/Sales/SalesInvoice.razor b/src/AccountGoWeb/Components/Pages/Sales/SalesInvoice.razor new file mode 100644 index 000000000..2e805a788 --- /dev/null +++ b/src/AccountGoWeb/Components/Pages/Sales/SalesInvoice.razor @@ -0,0 +1,471 @@ +@using System.Text.Json +@using System.Text.Json.Serialization + + + +
+ + + @if (loading) + { +

Loading invoice...

+ } + else if (!string.IsNullOrEmpty(errorMessage)) + { + + } + else + { +
+
+
+
+
+
+ + +
+
+ + +
+
+ +
+
+ + +
+
+ + +
+
+ +
+
+ + +
+
+ +
Line Items
+
+ + + + + + + + + + + + @if (lineItems != null && lineItems.Count > 0) + { + @foreach (var line in lineItems) + { + + + + + + + + } + } + +
ItemQuantityAmountDiscount (%)Measurement
+ + + + + + + + + +
+
+ +
+ +
+
+
+ +
+ + Close +
+
+
+ } +
+ +@code { + private class LineItem + { + public int? ItemId { get; set; } = 0; + public decimal? Quantity { get; set; } = 0; + public decimal? Amount { get; set; } = 0; + public decimal? Discount { get; set; } = 0; + public int? MeasurementId { get; set; } = 0; + } + + private class Customer + { + public int id { get; set; } + public string name { get; set; } = ""; + } + + private class Item + { + public int id { get; set; } + public string description { get; set; } = ""; + } + + private class PaymentTerm + { + public int id { get; set; } + public string description { get; set; } = ""; + } + + private class Measurement + { + public int id { get; set; } + public string description { get; set; } = ""; + } + + [SupplyParameterFromQuery(Name = "id")] + public int Id { get; set; } + + [SupplyParameterFromQuery(Name = "orderId")] + public int? OrderId { get; set; } + + [Inject] + private IHttpClientFactory ClientFactory { get; set; } = null!; + + [Inject] + private NavigationManager Navigation { get; set; } = null!; + + private bool loading = true; + private string? errorMessage = null; + + private int invoiceId = 0; + private DateTime invoiceDate = DateTime.Now; + private int customerId = 0; + private int paymentTermId = 0; + private decimal totalAmount = 0; + private List lineItems = new(); + + private List customers = new(); + private List items = new(); + private List paymentTerms = new(); + private List measurements = new(); + + protected override async Task OnInitializedAsync() + { + // Use OrderId if provided, otherwise use Id + var finalId = OrderId ?? Id; + + await LoadCustomers(); + await LoadItems(); + await LoadPaymentTerms(); + await LoadMeasurements(); + await LoadInvoiceData(finalId); + } + + private async Task LoadCustomers() + { + try + { + string apiurl = System.Environment.GetEnvironmentVariable("APIURL") ?? "http://localhost:8001/api/"; + var client = ClientFactory.CreateClient(); + var response = await client.GetAsync($"{apiurl}sales/customers"); + + if (response.IsSuccessStatusCode) + { + var responseString = await response.Content.ReadAsStringAsync(); + customers = JsonSerializer.Deserialize>(responseString) ?? new List(); + } + } + catch (Exception ex) + { + errorMessage = $"Error loading customers: {ex.Message}"; + } + } + + private async Task LoadItems() + { + try + { + string apiurl = System.Environment.GetEnvironmentVariable("APIURL") ?? "http://localhost:8001/api/"; + var client = ClientFactory.CreateClient(); + var response = await client.GetAsync($"{apiurl}inventory/items"); + + if (response.IsSuccessStatusCode) + { + var responseString = await response.Content.ReadAsStringAsync(); + items = JsonSerializer.Deserialize>(responseString) ?? new List(); + } + } + catch (Exception ex) + { + errorMessage = $"Error loading items: {ex.Message}"; + } + } + + private async Task LoadPaymentTerms() + { + try + { + string apiurl = System.Environment.GetEnvironmentVariable("APIURL") ?? "http://localhost:8001/api/"; + var client = ClientFactory.CreateClient(); + var response = await client.GetAsync($"{apiurl}common/paymentterms"); + + if (response.IsSuccessStatusCode) + { + var responseString = await response.Content.ReadAsStringAsync(); + paymentTerms = JsonSerializer.Deserialize>(responseString) ?? new List(); + } + } + catch (Exception ex) + { + errorMessage = $"Error loading payment terms: {ex.Message}"; + } + } + + private async Task LoadMeasurements() + { + try + { + string apiurl = System.Environment.GetEnvironmentVariable("APIURL") ?? "http://localhost:8001/api/"; + var client = ClientFactory.CreateClient(); + var response = await client.GetAsync($"{apiurl}common/measurements"); + + if (response.IsSuccessStatusCode) + { + var responseString = await response.Content.ReadAsStringAsync(); + measurements = JsonSerializer.Deserialize>(responseString) ?? new List(); + } + } + catch (Exception ex) + { + errorMessage = $"Error loading measurements: {ex.Message}"; + } + } + + private async Task LoadInvoiceData(int saleOrderId) + { + try + { + string apiurl = System.Environment.GetEnvironmentVariable("APIURL") ?? "http://localhost:8001/api/"; + var client = ClientFactory.CreateClient(); + var response = await client.GetAsync($"{apiurl}sales/SalesInvoice?id={saleOrderId}"); + + if (response.IsSuccessStatusCode) + { + var responseString = await response.Content.ReadAsStringAsync(); + var invoiceData = JsonSerializer.Deserialize(responseString, new JsonSerializerOptions + { + PropertyNameCaseInsensitive = true + }); + + if (invoiceData.ValueKind == JsonValueKind.Object) + { + // Parse invoice data + if (invoiceData.TryGetProperty("id", out var idElem)) + invoiceId = idElem.GetInt32(); + + if (invoiceData.TryGetProperty("customerId", out var custElem)) + customerId = custElem.GetInt32(); + + if (invoiceData.TryGetProperty("invoiceDate", out var dateElem)) + { + if (DateTime.TryParse(dateElem.GetString(), out var date)) + invoiceDate = date; + } + + if (invoiceData.TryGetProperty("paymentTermId", out var termElem)) + paymentTermId = termElem.GetInt32(); + + // Parse line items + if (invoiceData.TryGetProperty("salesInvoiceLines", out var linesElem) && linesElem.ValueKind == JsonValueKind.Array) + { + foreach (var lineElem in linesElem.EnumerateArray()) + { + var line = new LineItem + { + ItemId = GetJsonInt(lineElem, "itemId"), + Quantity = GetJsonDecimal(lineElem, "quantity"), + Amount = GetJsonDecimal(lineElem, "amount"), + Discount = GetJsonDecimal(lineElem, "discount"), + MeasurementId = GetJsonInt(lineElem, "measurementId") + }; + lineItems.Add(line); + } + } + else if (saleOrderId == 0) + { + // For new invoices, add 1 empty row matching original implementation + lineItems.Add(new LineItem { ItemId = 1, Quantity = 1, Amount = 0, Discount = 0, MeasurementId = 1 }); + } + + CalculateTotalAmount(); + } + } + else + { + errorMessage = $"Failed to load invoice. Status: {response.StatusCode}"; + } + } + catch (Exception ex) + { + errorMessage = $"Error loading invoice: {ex.Message}"; + } + finally + { + loading = false; + StateHasChanged(); + } + } + + private int? GetJsonInt(JsonElement elem, string propName) + { + if (elem.TryGetProperty(propName, out var prop)) + { + if (prop.TryGetInt32(out var val)) + return val; + } + return null; + } + + private decimal? GetJsonDecimal(JsonElement elem, string propName) + { + if (elem.TryGetProperty(propName, out var prop)) + { + if (prop.TryGetDecimal(out var val)) + return val; + } + return null; + } + + private void CalculateTotalAmount() + { + totalAmount = lineItems.Sum(line => + { + var qty = line.Quantity ?? 0; + var amt = line.Amount ?? 0; + var disc = line.Discount ?? 0; + var total = qty * amt; + var discount = (disc / 100) * total; + return total - discount; + }); + } + + private void AddLineItem() + { + lineItems.Add(new LineItem { ItemId = 1, Quantity = 1, Amount = 0, Discount = 0, MeasurementId = 1 }); + StateHasChanged(); + } + + private async Task SaveSalesInvoice() + { + try + { + CalculateTotalAmount(); + string apiurl = System.Environment.GetEnvironmentVariable("APIURL") ?? "http://localhost:8001/api/"; + + // Use OrderId if provided, otherwise use Id + var finalId = OrderId ?? Id; + + var payload = new + { + Id = invoiceId, + CustomerId = customerId, + InvoiceDate = invoiceDate, + PaymentTermId = paymentTermId, + FromSalesOrderId = finalId, // The sales order this invoice is being created from + SalesInvoiceLines = lineItems.Select(line => new + { + ItemId = line.ItemId, + Quantity = line.Quantity, + Amount = line.Amount, + Discount = line.Discount, + MeasurementId = line.MeasurementId + }).ToList() + }; + + var client = ClientFactory.CreateClient(); + var json = JsonSerializer.Serialize(payload); + var content = new StringContent(json, System.Text.Encoding.UTF8, "application/json"); + + var response = await client.PostAsync($"{apiurl}sales/SaveSalesInvoice", content); + + if (response.IsSuccessStatusCode) + { + await Task.Delay(100); + Navigation.NavigateTo("/Sales/SalesInvoices", forceLoad: true); + } + else + { + var errorContent = await response.Content.ReadAsStringAsync(); + errorMessage = $"Failed to save invoice. Status: {response.StatusCode}. Response: {errorContent}"; + StateHasChanged(); + } + } + catch (Exception ex) + { + errorMessage = $"Error saving invoice: {ex.Message}"; + StateHasChanged(); + } + } +} \ No newline at end of file diff --git a/src/AccountGoWeb/Components/Pages/Sales/SalesOrder.razor b/src/AccountGoWeb/Components/Pages/Sales/SalesOrder.razor new file mode 100644 index 000000000..ab17467a0 --- /dev/null +++ b/src/AccountGoWeb/Components/Pages/Sales/SalesOrder.razor @@ -0,0 +1,313 @@ +@using System.Text.Json +@using System.Text.Json.Serialization +@using Dto.Sales + + + +
+ + + @if (loading) + { +

Loading...

+ } + else if (errorMessage != null) + { + + } + else if (!HasSalesOrder()) + { + + } + else + { +
+
+
+
Sales Order Details
+
+
+
+
+
+
Customer Name
+
+ @GetValue("customerName")
+
+
+
Order Date
+
+ @GetValue("orderDate")
+
+
+
Amount
+
+ @GetTotalAmount().ToString("F2")
+
+
+
+ +
Order Items
+
+ + + + + + + + + + + + @if (salesOrderLines != null && salesOrderLines.Count > 0) + { + @foreach (var line in salesOrderLines) + { + + + + + + + + } + } + +
ItemQuantityAmountDiscountMeasurement
@GetLineValue(line, "itemDescription")@GetLineValue(line, "quantity")@GetLineValue(line, "amount")@GetLineValue(line, "discount")@GetLineValue(line, "measurementDescription")
+
+
+
+
+ } +
+ +@code { + [SupplyParameterFromQuery] + public int Id { get; set; } + + [Inject] + private IHttpClientFactory ClientFactory { get; set; } = null!; + + [Inject] + private NavigationManager Navigation { get; set; } = null!; + + private System.Text.Json.JsonElement? salesOrder; + private List salesOrderLines = new(); + private bool loading = true; + private string? errorMessage = null; + private string apiResponse = "No response yet"; + + protected override async Task OnInitializedAsync() + { + await LoadSalesOrder(); + } + + private bool HasSalesOrder() + { + return salesOrder.HasValue && salesOrder.Value.ValueKind != System.Text.Json.JsonValueKind.Null; + } + + private async Task LoadSalesOrder() + { + try + { + loading = true; + string apiurl = System.Environment.GetEnvironmentVariable("APIURL") ?? "http://localhost:8001/api/"; + var client = ClientFactory.CreateClient(); + client.Timeout = TimeSpan.FromSeconds(60); + + var fullUrl = $"{apiurl}sales/SalesOrder?id={Id}"; + var response = await client.GetAsync(fullUrl); + + if (response.IsSuccessStatusCode) + { + var responseString = await response.Content.ReadAsStringAsync(); + apiResponse = responseString; + + var options = new JsonSerializerOptions + { + PropertyNameCaseInsensitive = true, + DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, + }; + + // Check if response is an exception (has "ClassName" property) + using (var doc = System.Text.Json.JsonDocument.Parse(responseString)) + { + if (doc.RootElement.TryGetProperty("ClassName", out var classNameProp)) + { + // This is an exception response + var className = classNameProp.GetString() ?? "Unknown Error"; + if (className.Contains("InvalidOperationException") && + doc.RootElement.TryGetProperty("Message", out var messageProp) && + messageProp.GetString()?.Contains("Nullable object") == true) + { + errorMessage = "This sales order is incomplete or missing required data (e.g., customer information). Please edit the order to complete it."; + } + else if (doc.RootElement.TryGetProperty("Message", out var msgProp)) + { + errorMessage = $"API Error: {msgProp.GetString()}"; + } + else + { + errorMessage = "API Error: Unknown error occurred"; + } + loading = false; + return; + } + } + + var doc2 = System.Text.Json.JsonDocument.Parse(responseString); + salesOrder = doc2.RootElement; + + // Extract sales order lines - try both cases + if (salesOrder.Value.TryGetProperty("salesOrderLines", out var linesProperty) || + salesOrder.Value.TryGetProperty("SalesOrderLines", out linesProperty)) + { + var linesList = JsonSerializer.Deserialize>(linesProperty.GetRawText(), options); + salesOrderLines = linesList?.Cast().ToList() ?? new(); + } + } + else + { + var content = await response.Content.ReadAsStringAsync(); + apiResponse = content; + errorMessage = $"Failed to load sales order. Status: {response.StatusCode}"; + } + } + catch (Exception ex) + { + apiResponse = ex.ToString(); + errorMessage = $"Error: {ex.Message}"; + } + finally + { + loading = false; + } + } + + private string GetValue(string propertyName) + { + try + { + if (salesOrder.HasValue && salesOrder.Value is System.Text.Json.JsonElement je) + { + if (je.TryGetProperty(propertyName, out var prop)) + { + return FormatValue(prop, propertyName); + } + + // Try case-insensitive + foreach (var objProp in je.EnumerateObject()) + { + if (objProp.Name.Equals(propertyName, StringComparison.OrdinalIgnoreCase)) + { + return FormatValue(objProp.Value, propertyName); + } + } + } + return "N/A"; + } + catch + { + return "N/A"; + } + } + + private string GetLineValue(dynamic obj, string propertyName) + { + try + { + if (obj is System.Text.Json.JsonElement je) + { + if (je.TryGetProperty(propertyName, out var prop)) + { + return FormatValue(prop, propertyName); + } + + // Try case-insensitive + foreach (var objProp in je.EnumerateObject()) + { + if (objProp.Name.Equals(propertyName, StringComparison.OrdinalIgnoreCase)) + { + return FormatValue(objProp.Value, propertyName); + } + } + } + return "N/A"; + } + catch + { + return "N/A"; + } + } + + private string FormatValue(System.Text.Json.JsonElement prop, string propertyName) + { + if ((propertyName == "amount" || propertyName == "discount") && prop.ValueKind == System.Text.Json.JsonValueKind.Number) + { + return prop.GetDecimal().ToString("F2"); + } + if (propertyName == "quantity" && prop.ValueKind == System.Text.Json.JsonValueKind.Number) + { + return prop.GetDecimal().ToString("F2"); + } + if (propertyName == "orderDate" && prop.ValueKind == System.Text.Json.JsonValueKind.String) + { + var dateStr = prop.GetString(); + return dateStr?.Substring(0, Math.Min(10, dateStr.Length)) ?? "N/A"; + } + if (prop.ValueKind == System.Text.Json.JsonValueKind.String) + { + return prop.GetString() ?? "N/A"; + } + if (prop.ValueKind == System.Text.Json.JsonValueKind.Number) + { + return prop.GetDecimal().ToString(); + } + return prop.ToString(); + } + + private decimal GetTotalAmount() + { + decimal total = 0; + if (salesOrderLines != null && salesOrderLines.Count > 0) + { + foreach (var line in salesOrderLines) + { + if (line is System.Text.Json.JsonElement je) + { + if (je.TryGetProperty("amount", out var amountProp) && amountProp.ValueKind == System.Text.Json.JsonValueKind.Number) + { + total += amountProp.GetDecimal(); + } + } + } + } + return total; + } + + private void OnClickEditButton() + { + Navigation.NavigateTo($"/Sales/AddSalesOrder?id={Id}"); + } +} \ No newline at end of file diff --git a/src/AccountGoWeb/Components/Pages/Sales/SalesOrders.razor b/src/AccountGoWeb/Components/Pages/Sales/SalesOrders.razor new file mode 100644 index 000000000..3986026a8 --- /dev/null +++ b/src/AccountGoWeb/Components/Pages/Sales/SalesOrders.razor @@ -0,0 +1,321 @@ +@using System.Text.Json +@using System.Text.Json.Serialization + + + +
+ + + @* SALES ORDER LIST *@ + + @if (loading) + { +

Loading...

+ } + else if (errorMessage != null) + { + + } + else if (salesOrders.Count == 0) + { + + } + else + { +
+ + + + + + + + + + + + + @if (salesOrders.Count > 0) + { + @for (int i = 0; i < salesOrders.Count; i++) + { + int index = i; + + + + + + + + + } + } + +
NoCustomer NameOrder DateRef noAmountStatus
@GetValue(salesOrders[index], "no")@GetValue(salesOrders[index], "customerName")@GetValue(salesOrders[index], "orderDate")@GetValue(salesOrders[index], "referenceNo")@GetValue(salesOrders[index], "amount")@GetValue(salesOrders[index], "status")
+
+ } +
+ +@code { + [Inject] + private IHttpClientFactory ClientFactory { get; set; } = null!; + + [Inject] + private NavigationManager Navigation { get; set; } = null!; + + private List salesOrders = new(); + private bool loading = true; + private string? errorMessage = null; + private bool shouldRefresh = false; + private string? selectedOrderId = null; + private int selectedRowIndex = -1; + private bool isInvoiceDisabled = false; + + protected override async Task OnInitializedAsync() + { + await LoadSalesOrders(); + Navigation.LocationChanged += OnLocationChanged; + } + + private void OnLocationChanged(object? sender, Microsoft.AspNetCore.Components.Routing.LocationChangedEventArgs e) + { + // Refresh data when navigating back to this page + if (e.Location.Contains("/sales/sales-orders")) + { + shouldRefresh = true; + InvokeAsync(StateHasChanged); + } + } + + protected override async Task OnAfterRenderAsync(bool firstRender) + { + if (shouldRefresh && !firstRender) + { + shouldRefresh = false; + await LoadSalesOrders(); + } + } + + public void Dispose() + { + Navigation.LocationChanged -= OnLocationChanged; + } + + private async Task LoadSalesOrders() + { + try + { + loading = true; + string apiurl = System.Environment.GetEnvironmentVariable("APIURL") ?? "http://localhost:8001/api/"; + + var client = ClientFactory.CreateClient(); + client.Timeout = TimeSpan.FromSeconds(60); // Increase timeout to 60 seconds + + var response = await client.GetAsync($"{apiurl}sales/salesorders"); + + if (response.IsSuccessStatusCode) + { + var responseString = await response.Content.ReadAsStringAsync(); + var options = new JsonSerializerOptions + { + PropertyNameCaseInsensitive = true, + DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, + }; + + var data = JsonSerializer.Deserialize>(responseString, options); + salesOrders = data?.Cast().ToList() ?? new(); + } + else + { + errorMessage = $"Failed to load sales orders. Status: {response.StatusCode}"; + } + } + catch (Exception ex) + { + errorMessage = $"Error loading sales orders: {ex.Message}"; + } + finally + { + loading = false; + } + } + + private string GetValue(dynamic obj, string propertyName) + { + try + { + if (obj is System.Text.Json.JsonElement je) + { + if (je.TryGetProperty(propertyName, out var prop)) + { + if (propertyName == "amount" && prop.ValueKind == System.Text.Json.JsonValueKind.Number) + { + return prop.GetDecimal().ToString("F2"); + } + return prop.GetString() ?? prop.ToString(); + } + } + return "N/A"; + } + catch + { + return "N/A"; + } + } + + private void OnRowSelected(int index) + { + // Toggle selection - if clicking the same row, deselect it + if (selectedRowIndex == index) + { + selectedRowIndex = -1; + selectedOrderId = null; + isInvoiceDisabled = false; + } + else + { + selectedRowIndex = index; + + try + { + if (index >= 0 && index < salesOrders.Count) + { + var order = salesOrders[index]; + if (order is System.Text.Json.JsonElement je) + { + // Try to get ID - it might be a number or string + string? orderId = null; + + if (je.TryGetProperty("id", out var idProp)) + { + // Handle both string and numeric IDs + if (idProp.ValueKind == System.Text.Json.JsonValueKind.String) + { + orderId = idProp.GetString(); + } + else if (idProp.ValueKind == System.Text.Json.JsonValueKind.Number) + { + orderId = idProp.GetInt32().ToString(); + } + } + else if (je.TryGetProperty("Id", out var idProp2)) + { + if (idProp2.ValueKind == System.Text.Json.JsonValueKind.String) + { + orderId = idProp2.GetString(); + } + else if (idProp2.ValueKind == System.Text.Json.JsonValueKind.Number) + { + orderId = idProp2.GetInt32().ToString(); + } + } + else if (je.TryGetProperty("salesOrderId", out var soIdProp)) + { + if (soIdProp.ValueKind == System.Text.Json.JsonValueKind.String) + { + orderId = soIdProp.GetString(); + } + else if (soIdProp.ValueKind == System.Text.Json.JsonValueKind.Number) + { + orderId = soIdProp.GetInt32().ToString(); + } + } + + if (!string.IsNullOrEmpty(orderId)) + { + selectedOrderId = orderId; + + // Check if status is 6 (Fully Invoiced) + isInvoiceDisabled = false; + if (je.TryGetProperty("statusId", out var statusProp)) + { + try + { + if (statusProp.ValueKind == System.Text.Json.JsonValueKind.Number && statusProp.GetInt32() == 6) + { + isInvoiceDisabled = true; + } + } + catch + { + // Status might not be an int, skip + } + } + } + } + } + } + catch + { + selectedOrderId = null; + } + } + + StateHasChanged(); + } + private void UpdateButtonLinks() + { + StateHasChanged(); + } + + private string GetViewOrderLink() + { + return selectedOrderId != null ? $"/Sales/SalesOrder?id={selectedOrderId}" : "javascript:void(0)"; + } + + private string GetInvoiceLink() + { + return selectedOrderId != null ? $"/Sales/SalesInvoice?orderId={selectedOrderId}" : "javascript:void(0)"; + } + + private string GetRawJson(int index) + { + if (index >= 0 && index < salesOrders.Count) + { + var order = salesOrders[index]; + if (order is System.Text.Json.JsonElement je) + { + return je.GetRawText(); + } + } + return "{}"; + } +} \ No newline at end of file diff --git a/src/AccountGoWeb/Components/Pages/Sales/SalesReceipt.razor b/src/AccountGoWeb/Components/Pages/Sales/SalesReceipt.razor new file mode 100644 index 000000000..56492b8a7 --- /dev/null +++ b/src/AccountGoWeb/Components/Pages/Sales/SalesReceipt.razor @@ -0,0 +1,456 @@ +@using System.Text.Json +@using System.Text.Json.Serialization +@using AccountGoWeb.Models +@using AccountGoWeb.Models.Sales +@using Microsoft.AspNetCore.Components + + + +
+
+
+

Sales Receipt

+
+
+ @if (!isEditing) + { + + } + else + { + + } + + + Back to Receipts + +
+
+ + @if (loading) + { +

Loading receipt...

+ } + else if (!string.IsNullOrEmpty(errorMessage)) + { + + } + else + { +
+
+ @if (!string.IsNullOrEmpty(errorMessage)) + { + + } +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+
+ @if (isEditing) + { +
+ + +
+ } +
+
+ } +
+ +@code { + private bool loading = true; + private bool isEditing = false; + private string errorMessage = ""; + private int receiptId = 0; + private string receiptNo = ""; + private int customerId = 0; + private int originalCustomerId = 0; + private DateTime receiptDate = DateTime.Now; + private DateTime originalReceiptDate = DateTime.Now; + private int accountToDebitId = 0; + private int originalAccountToDebitId = 0; + private int accountToCreditId = 0; + private int originalAccountToCreditId = 0; + private decimal amount = 0; + private decimal originalAmount = 0; + + private List<(string Text, string Value)> customers = new(); + private List<(string Text, string Value)> debitAccounts = new(); + private List<(string Text, string Value)> creditAccounts = new(); + private Dictionary customerAdvanceAccounts = new(); + private Dictionary accountNames = new(); + + [SupplyParameterFromQuery(Name = "id")] + public int Id { get; set; } + + [Inject] + private IHttpClientFactory ClientFactory { get; set; } = null!; + + [Inject] + private NavigationManager Navigation { get; set; } = null!; + + protected override async Task OnInitializedAsync() + { + try + { + string apiurl = System.Environment.GetEnvironmentVariable("APIURL") ?? "http://localhost:8001/api/"; + var client = ClientFactory.CreateClient(); + client.Timeout = TimeSpan.FromSeconds(60); + + // Load customers from API + var customersResponse = await client.GetAsync($"{apiurl}sales/customers"); + if (customersResponse.IsSuccessStatusCode) + { + var json = await customersResponse.Content.ReadAsStringAsync(); + var options = new JsonSerializerOptions { PropertyNameCaseInsensitive = true }; + var customersData = JsonSerializer.Deserialize>(json, options); + if (customersData != null) + { + foreach (var customer in customersData) + { + var id = GetJsonPropertyString(customer, "id"); + var name = GetJsonPropertyString(customer, "name"); + var prepaymentAccountId = GetJsonPropertyString(customer, "prepaymentAccountId"); + customers.Add((name ?? "Unknown", id)); + + // Store the prepayment account ID for this customer + if (int.TryParse(id, out var customerId_int) && int.TryParse(prepaymentAccountId, out var accountId_int)) + { + customerAdvanceAccounts[customerId_int] = accountId_int; + } + } + } + } + + // Load debit accounts from API (Cash & Banks) + var debitResponse = await client.GetAsync($"{apiurl}financials/CashBanks"); + if (debitResponse.IsSuccessStatusCode) + { + var json = await debitResponse.Content.ReadAsStringAsync(); + var options = new JsonSerializerOptions { PropertyNameCaseInsensitive = true }; + var accountsData = JsonSerializer.Deserialize>(json, options); + if (accountsData != null) + { + foreach (var account in accountsData) + { + var id = GetJsonPropertyString(account, "id"); + var name = GetJsonPropertyString(account, "name"); + debitAccounts.Add((name ?? "Unknown", id)); + } + } + } + + // Load credit accounts from API (posting accounts only) + var creditResponse = await client.GetAsync($"{apiurl}common/postingaccounts"); + if (creditResponse.IsSuccessStatusCode) + { + var json = await creditResponse.Content.ReadAsStringAsync(); + var options = new JsonSerializerOptions { PropertyNameCaseInsensitive = true }; + var accountsData = JsonSerializer.Deserialize>(json, options); + if (accountsData != null) + { + foreach (var account in accountsData) + { + var id = GetJsonPropertyString(account, "id"); + var name = GetJsonPropertyString(account, "accountName"); + if (!string.IsNullOrEmpty(id) && !string.IsNullOrEmpty(name)) + { + creditAccounts.Add((name, id)); + // Store account name for display + if (int.TryParse(id, out var accountId)) + { + accountNames[accountId] = name; + } + } + } + } + } + + // Load all accounts to get customer advance account names + var allAccountsResponse = await client.GetAsync($"{apiurl}financials/accounts"); + if (allAccountsResponse.IsSuccessStatusCode) + { + var json = await allAccountsResponse.Content.ReadAsStringAsync(); + var options = new JsonSerializerOptions { PropertyNameCaseInsensitive = true }; + var accountsData = JsonSerializer.Deserialize>(json, options); + if (accountsData != null) + { + foreach (var account in accountsData) + { + var id = GetJsonPropertyString(account, "id"); + var name = GetJsonPropertyString(account, "name"); + if (!string.IsNullOrEmpty(id) && !string.IsNullOrEmpty(name)) + { + // Store account name for display + if (int.TryParse(id, out var accountId)) + { + accountNames[accountId] = name; + } + } + } + } + } + + // Load receipt data if editing + if (Id > 0) + { + await LoadReceipt(); + } + + loading = false; + } + catch (Exception ex) + { + errorMessage = $"Failed to load form data: {ex.Message}"; + loading = false; + } + } + + private async Task LoadReceipt() + { + try + { + string apiurl = System.Environment.GetEnvironmentVariable("APIURL") ?? "http://localhost:8001/api/"; + var client = ClientFactory.CreateClient(); + var response = await client.GetAsync($"{apiurl}sales/salesreceipt?id={Id}"); + + if (response.IsSuccessStatusCode) + { + var json = await response.Content.ReadAsStringAsync(); + var options = new JsonSerializerOptions { PropertyNameCaseInsensitive = true }; + var receiptData = JsonSerializer.Deserialize(json, options); + + if (receiptData.ValueKind == JsonValueKind.Object) + { + receiptId = GetJsonInt(receiptData, "id") ?? 0; + receiptNo = GetJsonPropertyString(receiptData, "receiptNo"); + customerId = GetJsonInt(receiptData, "customerId") ?? 0; + accountToDebitId = GetJsonInt(receiptData, "accountToDebitId") ?? 0; + accountToCreditId = GetJsonInt(receiptData, "accountToCreditId") ?? 0; + amount = GetJsonDecimal(receiptData, "amount") ?? 0; + + // Store original values + originalCustomerId = customerId; + originalReceiptDate = receiptDate; + originalAccountToDebitId = accountToDebitId; + originalAccountToCreditId = accountToCreditId; + originalAmount = amount; + + // If credit account is empty but customer is set, auto-populate it + if (accountToCreditId == 0 && customerId > 0 && customerAdvanceAccounts.TryGetValue(customerId, out var + advanceAccountId)) + { + accountToCreditId = advanceAccountId; + } + + if (receiptData.TryGetProperty("receiptDate", out var dateElem)) + { + if (DateTime.TryParse(dateElem.GetString(), out var date)) + receiptDate = date; + } + } + } + else + { + errorMessage = $"Failed to load receipt. Status: {response.StatusCode}"; + } + } + catch (Exception ex) + { + errorMessage = $"Error loading receipt: {ex.Message}"; + } + } + + private int? GetJsonInt(JsonElement elem, string propName) + { + if (elem.TryGetProperty(propName, out var prop)) + { + if (prop.ValueKind == JsonValueKind.Number && prop.TryGetInt32(out var val)) + return val; + } + return null; + } + + private decimal? GetJsonDecimal(JsonElement elem, string propName) + { + if (elem.TryGetProperty(propName, out var prop)) + { + if (prop.ValueKind == JsonValueKind.Number && prop.TryGetDecimal(out var val)) + return val; + } + return null; + } + + private string GetJsonPropertyString(JsonElement element, string propertyName) + { + if (element.TryGetProperty(propertyName, out var property)) + { + if (property.ValueKind == JsonValueKind.String) + { + return property.GetString() ?? ""; + } + else if (property.ValueKind == JsonValueKind.Number) + { + return property.GetInt32().ToString(); + } + } + return ""; + } + + private void EnableEdit() + { + // Store original values + originalCustomerId = customerId; + originalReceiptDate = receiptDate; + originalAccountToDebitId = accountToDebitId; + originalAccountToCreditId = accountToCreditId; + originalAmount = amount; + isEditing = true; + } + + private void CancelEdit() + { + // Restore original values + customerId = originalCustomerId; + receiptDate = originalReceiptDate; + accountToDebitId = originalAccountToDebitId; + accountToCreditId = originalAccountToCreditId; + amount = originalAmount; + isEditing = false; + errorMessage = ""; + } + + private async Task HandleSubmit() + { + try + { + string apiurl = System.Environment.GetEnvironmentVariable("APIURL") ?? "http://localhost:8001/api/"; + + var payload = new + { + Id = receiptId, + CustomerId = customerId, + ReceiptDate = receiptDate, + Amount = amount, + AccountToDebitId = accountToDebitId, + AccountToCreditId = accountToCreditId + }; + + var client = ClientFactory.CreateClient(); + var json = JsonSerializer.Serialize(payload); + var content = new StringContent(json, System.Text.Encoding.UTF8, "application/json"); + + // Use UpdateReceipt endpoint when editing + var response = await client.PostAsync($"{apiurl}sales/updatereceipt", content); + + if (response.IsSuccessStatusCode) + { + isEditing = false; + // Reload the receipt data + await LoadReceipt(); + } + else + { + var errorContent = await response.Content.ReadAsStringAsync(); + errorMessage = $"Failed to save receipt. Status: {response.StatusCode}"; + } + } + catch (Exception ex) + { + errorMessage = $"Error saving receipt: {ex.Message}"; + } + } +} \ No newline at end of file diff --git a/src/AccountGoWeb/Components/Pages/Sales/SalesReceipts.razor b/src/AccountGoWeb/Components/Pages/Sales/SalesReceipts.razor new file mode 100644 index 000000000..f3623c4e3 --- /dev/null +++ b/src/AccountGoWeb/Components/Pages/Sales/SalesReceipts.razor @@ -0,0 +1,316 @@ +@using Dto.Sales +@using System.Text.Json +@using System.Text.Json.Serialization + + + +
+ + + @* SALES RECEIPTS LIST *@ + + @if (loading) + { +

Loading...

+ } + else if (errorMessage != null) + { + + } + else if (salesReceipts.Count == 0) + { + + } + else + { +
+ + + + + + + + + + + + + @if (salesReceipts.Count > 0) + { + @for (int i = 0; i < salesReceipts.Count; i++) + { + int index = i; + + + + + + + + + } + } + +
Receipt IDReceipt NoCustomer NameReceipt DateAmountLeft to Allocate
@GetValue(salesReceipts[index], "id")@GetValue(salesReceipts[index], "receiptNo")@GetValue(salesReceipts[index], "customerName")@FormatDate(GetValue(salesReceipts[index], "receiptDate"))@FormatAmount(GetValue(salesReceipts[index], "amount"))@FormatAmount(GetValue(salesReceipts[index], "remainingAmountToAllocate"))
+
+ } +
+ +@code { + [Inject] + private IHttpClientFactory ClientFactory { get; set; } = null!; + + [Inject] + private NavigationManager Navigation { get; set; } = null!; + + private List salesReceipts = new(); + private bool loading = true; + private string? errorMessage = null; + private bool shouldRefresh = false; + private string? selectedReceiptId = null; + private int selectedRowIndex = -1; + private bool isAllocateDisabled = false; + + protected override async Task OnInitializedAsync() + { + await LoadSalesReceipts(); + Navigation.LocationChanged += OnLocationChanged; + } + + private void OnLocationChanged(object? sender, Microsoft.AspNetCore.Components.Routing.LocationChangedEventArgs e) + { + // Refresh data when navigating back to this page + if (e.Location.Contains("/sales/salesreceipts") || e.Location.Contains("/sales/SalesReceipts")) + { + shouldRefresh = true; + InvokeAsync(StateHasChanged); + } + } + + protected override async Task OnAfterRenderAsync(bool firstRender) + { + if (shouldRefresh && !firstRender) + { + shouldRefresh = false; + await LoadSalesReceipts(); + } + } + + public void Dispose() + { + Navigation.LocationChanged -= OnLocationChanged; + } + + private async Task LoadSalesReceipts() + { + try + { + loading = true; + string apiurl = System.Environment.GetEnvironmentVariable("APIURL") ?? "http://localhost:8001/api/"; + + var client = ClientFactory.CreateClient(); + client.Timeout = TimeSpan.FromSeconds(60); + + var response = await client.GetAsync($"{apiurl}sales/salesreceipts"); + + if (response.IsSuccessStatusCode) + { + var responseString = await response.Content.ReadAsStringAsync(); + var options = new System.Text.Json.JsonSerializerOptions + { + PropertyNameCaseInsensitive = true, + DefaultIgnoreCondition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingNull, + }; + + var data = System.Text.Json.JsonSerializer.Deserialize>(responseString, options); + salesReceipts = data?.Cast().ToList() ?? new(); + } + else + { + errorMessage = $"Failed to load sales receipts. Status: {response.StatusCode}"; + } + } + catch (Exception ex) + { + errorMessage = $"Error loading sales receipts: {ex.Message}"; + } + finally + { + loading = false; + } + } + + private string GetValue(dynamic obj, string propertyName) + { + try + { + if (obj is System.Text.Json.JsonElement je) + { + if (je.TryGetProperty(propertyName, out var prop)) + { + if ((propertyName == "amount" || propertyName == "remainingAmountToAllocate") && prop.ValueKind == + System.Text.Json.JsonValueKind.Number) + { + return prop.GetDecimal().ToString("F2"); + } + // Handle numeric ID + if (propertyName == "id" && prop.ValueKind == System.Text.Json.JsonValueKind.Number) + { + return prop.GetInt32().ToString(); + } + return prop.GetString() ?? prop.ToString(); + } + } + return "N/A"; + } + catch + { + return "N/A"; + } + } + + private string FormatAmount(string value) + { + if (decimal.TryParse(value, out var amount)) + { + return amount.ToString("F2"); + } + return value; + } + + private string FormatDate(string value) + { + if (DateTime.TryParse(value, out var date)) + { + return date.ToString("yyyy-MM-dd"); + } + return value; + } + + private void OnRowSelected(int index) + { + // Toggle selection: deselect if clicking the same row + if (selectedRowIndex == index) + { + selectedRowIndex = -1; + selectedReceiptId = null; + isAllocateDisabled = false; + return; + } + + selectedRowIndex = index; + + try + { + if (index >= 0 && index < salesReceipts.Count) + { + var receipt = salesReceipts[index]; + if (receipt is System.Text.Json.JsonElement je) + { + string? receiptId = null; + + if (je.TryGetProperty("id", out var idProp)) + { + if (idProp.ValueKind == System.Text.Json.JsonValueKind.String) + { + receiptId = idProp.GetString(); + } + else if (idProp.ValueKind == System.Text.Json.JsonValueKind.Number) + { + receiptId = idProp.GetInt32().ToString(); + } + } + else if (je.TryGetProperty("Id", out var idProp2)) + { + if (idProp2.ValueKind == System.Text.Json.JsonValueKind.String) + { + receiptId = idProp2.GetString(); + } + else if (idProp2.ValueKind == System.Text.Json.JsonValueKind.Number) + { + receiptId = idProp2.GetInt32().ToString(); + } + } + + if (!string.IsNullOrEmpty(receiptId)) + { + selectedReceiptId = receiptId; + + // Check if there's remaining amount to allocate + isAllocateDisabled = true; + if (je.TryGetProperty("remainingAmountToAllocate", out var amountProp)) + { + try + { + if (amountProp.ValueKind == System.Text.Json.JsonValueKind.Number) + { + var remaining = amountProp.GetDecimal(); + isAllocateDisabled = remaining <= 0; + } + } + catch + { + // Amount might not be a number, skip + } + } + + StateHasChanged(); + } + } + } + } + catch + { + selectedReceiptId = null; + } + } + + private string GetViewReceiptLink() + { + return selectedReceiptId != null ? $"/Sales/SalesReceipt?id={selectedReceiptId}" : "javascript:void(0)"; + } + + private string GetAllocateLink() + { + return selectedReceiptId != null ? $"/Sales/Allocate/{selectedReceiptId}" : "javascript:void(0)"; + } +} \ No newline at end of file diff --git a/src/AccountGoWeb/Components/Pages/Students.razor b/src/AccountGoWeb/Components/Pages/Students.razor new file mode 100644 index 000000000..443d791b0 --- /dev/null +++ b/src/AccountGoWeb/Components/Pages/Students.razor @@ -0,0 +1,23 @@ +@page "/students" +@rendermode InteractiveServer +Students +

Students

+ + + +
+ + @context.FirstName @context.LastName + +
+
+ +
+ + +@code { + IQueryable students = Student.GetStudents(); + PaginationState pagination = new PaginationState { ItemsPerPage = 10 }; + GridSort sortByName = GridSort + .ByAscending(_ => _.FirstName).ThenAscending(_ => _.LastName); +} \ No newline at end of file diff --git a/src/AccountGoWeb/Components/Pages/Tax/TaxManagement.razor b/src/AccountGoWeb/Components/Pages/Tax/TaxManagement.razor new file mode 100644 index 000000000..dd14b0a1b --- /dev/null +++ b/src/AccountGoWeb/Components/Pages/Tax/TaxManagement.razor @@ -0,0 +1,428 @@ +@rendermode InteractiveServer +@inject HttpClient Http +@inject NavigationManager Navigation +@inject IConfiguration Configuration +@inject IJSRuntime JSRuntime + +Tax Management + + + +@* Delete Confirmation Modals would use JavaScript interop or a confirmation component *@ + +@code { + private string activeTab = "taxes"; + private bool isLoading = true; + + private List? taxes; + private List? taxGroups; + private List? itemTaxGroups; + + private Dto.TaxSystem.TaxGroup? selectedTaxGroup; + private Dto.TaxSystem.ItemTaxGroup? selectedItemTaxGroup; + + protected override async Task OnInitializedAsync() + { + await LoadTaxData(); + } + + private async Task LoadTaxData() + { + isLoading = true; + try + { + var baseUri = Configuration["ApiUrl"]; + var response = await Http.GetAsync($"{baseUri}tax/taxes"); + + if (response.IsSuccessStatusCode) + { + var content = await response.Content.ReadAsStringAsync(); + var taxSystemDto = System.Text.Json.JsonSerializer.Deserialize( + content, + new System.Text.Json.JsonSerializerOptions { PropertyNameCaseInsensitive = true } + ); + + taxes = taxSystemDto?.Taxes?.ToList(); + taxGroups = taxSystemDto?.TaxGroups?.ToList(); + itemTaxGroups = taxSystemDto?.ItemTaxGroups?.ToList(); + } + } + catch (Exception ex) + { + Console.WriteLine($"Error loading tax data: {ex.Message}"); + } + finally + { + isLoading = false; + } + } + + private void SetActiveTab(string tab) + { + activeTab = tab; + selectedTaxGroup = null; + selectedItemTaxGroup = null; + } + + private void EditTax(Dto.TaxSystem.Tax tax) + { + Navigation.NavigateTo($"/Tax/EditTax?id={tax.Id}", forceLoad: true); + } + + private async Task ConfirmDeleteTax(int taxId) + { + bool confirmed = await JSRuntime.InvokeAsync("confirmDialog.show", "Are you sure you want to delete this tax?"); + if (confirmed) + { + try + { + var baseUri = Configuration["ApiUrl"]; + var response = await Http.DeleteAsync($"{baseUri}tax/deletetax?id={taxId}"); + + if (response.IsSuccessStatusCode) + { + await LoadTaxData(); // Reload the data + } + else + { + Console.WriteLine($"Error deleting tax: {response.StatusCode}"); + } + } + catch (Exception ex) + { + Console.WriteLine($"Error deleting tax: {ex.Message}"); + } + } + } + + private void SelectTaxGroup(Dto.TaxSystem.TaxGroup group) + { + selectedTaxGroup = group; + } + + private async Task ConfirmDeleteTaxGroup(int groupId) + { + bool confirmed = await JSRuntime.InvokeAsync("confirmDialog.show", "Are you sure you want to delete this tax group?"); + if (confirmed) + { + try + { + var baseUri = Configuration["ApiUrl"]; + var response = await Http.DeleteAsync($"{baseUri}tax/deletetaxgroup?id={groupId}"); + + if (response.IsSuccessStatusCode) + { + await LoadTaxData(); // Reload the data + selectedTaxGroup = null; + } + else + { + Console.WriteLine($"Error deleting tax group: {response.StatusCode}"); + } + } + catch (Exception ex) + { + Console.WriteLine($"Error deleting tax group: {ex.Message}"); + } + } + } + + private void SelectItemTaxGroup(Dto.TaxSystem.ItemTaxGroup group) + { + selectedItemTaxGroup = group; + } + + private async Task ConfirmDeleteItemTaxGroup(int groupId) + { + bool confirmed = await JSRuntime.InvokeAsync("confirmDialog.show", "Are you sure you want to delete this item tax group?"); + if (confirmed) + { + try + { + var baseUri = Configuration["ApiUrl"]; + var response = await Http.DeleteAsync($"{baseUri}tax/deleteitemtaxgroup?id={groupId}"); + + if (response.IsSuccessStatusCode) + { + await LoadTaxData(); // Reload the data + selectedItemTaxGroup = null; + } + else + { + Console.WriteLine($"Error deleting item tax group: {response.StatusCode}"); + } + } + catch (Exception ex) + { + Console.WriteLine($"Error deleting item tax group: {ex.Message}"); + } + } + } + + private int GetTaxGroupTaxCount(Dto.TaxSystem.TaxGroup group) + { + return group.Taxes?.Count() ?? 0; + } + + private IEnumerable GetTaxGroupTaxes(Dto.TaxSystem.TaxGroup group) + { + if (group.Taxes == null || taxes == null) return Enumerable.Empty(); + + return group.Taxes + .Select(gt => taxes.FirstOrDefault(t => t.Id == gt.TaxId)) + .Where(t => t != null) + .Cast(); + } + + private int GetItemTaxGroupTaxCount(Dto.TaxSystem.ItemTaxGroup group) + { + return group.Taxes?.Count() ?? 0; + } + + private IEnumerable GetItemTaxGroupTaxes(Dto.TaxSystem.ItemTaxGroup group) + { + if (group.Taxes == null || taxes == null) return Enumerable.Empty(); + + return group.Taxes + .Select(gt => taxes.FirstOrDefault(t => t.Id == gt.TaxId)) + .Where(t => t != null) + .Cast(); + } +} + + \ No newline at end of file diff --git a/src/AccountGoWeb/Components/Routes.razor b/src/AccountGoWeb/Components/Routes.razor new file mode 100644 index 000000000..da8815572 --- /dev/null +++ b/src/AccountGoWeb/Components/Routes.razor @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/AccountGoWeb/Components/_Imports.razor b/src/AccountGoWeb/Components/_Imports.razor new file mode 100644 index 000000000..52e49f513 --- /dev/null +++ b/src/AccountGoWeb/Components/_Imports.razor @@ -0,0 +1,19 @@ +@using System.Net.Http +@using System.Net.Http.Json +@using Microsoft.AspNetCore.Components.Forms +@using Microsoft.AspNetCore.Components.Routing +@using Microsoft.AspNetCore.Components.Web +@using static Microsoft.AspNetCore.Components.Web.RenderMode +@using Microsoft.AspNetCore.Components.Web.Virtualization +@using Microsoft.JSInterop +@using Microsoft.AspNetCore.Components.QuickGrid +@using AccountGoWeb +@using AccountGoWeb.Components +@using AccountGoWeb.Models +@using AccountGoWeb.Models.Account +@using AccountGoWeb.Models.Bogus +@using AccountGoWeb.Models.Financial +@using AccountGoWeb.Models.Purchasing +@using AccountGoWeb.Models.Sales +@using AccountGoWeb.Models.TaxSystem +@using Dto.Donations diff --git a/src/AccountGoWeb/Controllers/AccountController.cs b/src/AccountGoWeb/Controllers/AccountController.cs index 0eba93c37..18fb676a6 100644 --- a/src/AccountGoWeb/Controllers/AccountController.cs +++ b/src/AccountGoWeb/Controllers/AccountController.cs @@ -3,25 +3,20 @@ using Microsoft.AspNetCore.Authentication.Cookies; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; -using Microsoft.Extensions.Configuration; -using System; -using System.Collections.Generic; -using System.Net.Http; using System.Security.Claims; -using System.Threading.Tasks; namespace AccountGoWeb.Controllers { - public class AccountController : BaseController + public class AccountController : GoodController { public AccountController(IConfiguration config) { - _baseConfig = config; + _configuration = config; } [HttpGet] [AllowAnonymous] - public IActionResult SignIn(string returnUrl = null) + public IActionResult SignIn(string? returnUrl = null) { ViewData["ReturnUrl"] = returnUrl; return View(new LoginViewModel() { Email = "admin@accountgo.ph", Password = "P@ssword1" }); @@ -29,7 +24,7 @@ public IActionResult SignIn(string returnUrl = null) [HttpPost] [AllowAnonymous] - public async Task SignIn(LoginViewModel model, string returnUrl = null) + public async Task SignIn(LoginViewModel model, string? returnUrl = null) { ViewData["ReturnUrl"] = returnUrl; @@ -47,8 +42,8 @@ public async Task SignIn(LoginViewModel model, string returnUrl = var claims = new List(); claims.Add(new Claim(ClaimTypes.IsPersistent, model.RememberMe.ToString())); - claims.Add(new Claim(ClaimTypes.NameIdentifier, user.Email)); - claims.Add(new Claim(ClaimTypes.Email, user.Email)); + claims.Add(new Claim(ClaimTypes.NameIdentifier, user.Email!)); + claims.Add(new Claim(ClaimTypes.Email, user.Email!)); string firstName = user.FirstName != null ? user.FirstName : ""; string lastName = user.LastName != null ? user.LastName : ""; @@ -58,7 +53,7 @@ public async Task SignIn(LoginViewModel model, string returnUrl = claims.Add(new Claim(ClaimTypes.Name, firstName + " " + lastName)); foreach(var role in user.Roles) - claims.Add(new Claim(ClaimTypes.Role, role.Name)); + claims.Add(new Claim(ClaimTypes.Role, role.Name!)); claims.Add(new Claim(ClaimTypes.UserData, Newtonsoft.Json.JsonConvert.SerializeObject(user))); @@ -70,7 +65,7 @@ public async Task SignIn(LoginViewModel model, string returnUrl = await HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, principal); - return RedirectToLocal(returnUrl); + return RedirectToLocal(returnUrl!); } else { @@ -83,7 +78,7 @@ public async Task SignIn(LoginViewModel model, string returnUrl = return View(model); } - public async Task SignOut() + public async Task Logout() { await HttpContext.SignOutAsync(); @@ -92,7 +87,7 @@ public async Task SignOut() public IActionResult SignedOut() { - if (HttpContext.User.Identity.IsAuthenticated) + if (HttpContext.User.Identity!.IsAuthenticated) { return RedirectToAction(nameof(HomeController.Index), "Home"); } @@ -106,7 +101,7 @@ public IActionResult Unauthorize() [HttpGet] [AllowAnonymous] - public IActionResult Register(string returnUrl = null) + public IActionResult Register(string? returnUrl = null) { ViewData["ReturnUrl"] = returnUrl; return View(); @@ -114,7 +109,7 @@ public IActionResult Register(string returnUrl = null) [HttpPost] [AllowAnonymous] - public IActionResult Register(RegisterViewModel model, string returnUrl = null) + public IActionResult Register(RegisterViewModel model, string? returnUrl = null) { ViewData["ReturnUrl"] = returnUrl; try @@ -127,9 +122,9 @@ public IActionResult Register(RegisterViewModel model, string returnUrl = null) HttpResponseMessage responseAddNewUser = Post("account/addnewuser", content); Newtonsoft.Json.Linq.JObject resultAddNewUser = Newtonsoft.Json.Linq.JObject.Parse(responseAddNewUser.Content.ReadAsStringAsync().Result); - HttpResponseMessage responseInitialized = null; - Newtonsoft.Json.Linq.JObject resultInitialized = null; - if ((bool)resultAddNewUser["succeeded"]) + HttpResponseMessage? responseInitialized = null; + Newtonsoft.Json.Linq.JObject? resultInitialized = null; + if ((bool)resultAddNewUser["succeeded"]!) { responseInitialized = Get("administration/initializedcompany"); resultInitialized = Newtonsoft.Json.Linq.JObject.Parse((responseInitialized.Content.ReadAsStringAsync().Result)); @@ -137,7 +132,7 @@ public IActionResult Register(RegisterViewModel model, string returnUrl = null) } else { - ModelState.AddModelError(string.Empty, resultAddNewUser["errors"][0]["description"].ToString()); + ModelState.AddModelError(string.Empty, resultAddNewUser["errors"]![0]!["description"]!.ToString()); return View(model); } } diff --git a/src/AccountGoWeb/Controllers/AdministrationController.cs b/src/AccountGoWeb/Controllers/AdministrationController.cs index a05f3f301..148b81334 100644 --- a/src/AccountGoWeb/Controllers/AdministrationController.cs +++ b/src/AccountGoWeb/Controllers/AdministrationController.cs @@ -1,14 +1,11 @@ using Dto.Administration; using Dto.Security; using Microsoft.AspNetCore.Mvc; -using Microsoft.Extensions.Configuration; -using System; -using System.Net.Http; namespace AccountGoWeb.Controllers { - [Microsoft.AspNetCore.Authorization.Authorize] - public class AdministrationController : BaseController + //[Microsoft.AspNetCore.Authorization.Authorize] + public class AdministrationController : BaseController { public AdministrationController(IConfiguration config) { @@ -125,13 +122,13 @@ public async System.Threading.Tasks.Task AuditLogs() HttpResponseMessage responseAddNewUser = Post("account/addnewuser", content); Newtonsoft.Json.Linq.JObject resultAddNewUser = Newtonsoft.Json.Linq.JObject.Parse(responseAddNewUser.Content.ReadAsStringAsync().Result); - if ((bool)resultAddNewUser["succeeded"]) + if ((bool)resultAddNewUser["succeeded"]!) { return RedirectToAction(nameof(AdministrationController.Users), "Administration"); } else { - ModelState.AddModelError(string.Empty, resultAddNewUser["errors"][0]["description"].ToString()); + ModelState.AddModelError(string.Empty, resultAddNewUser["errors"]![0]!["description"]!.ToString()); return View(model); } } diff --git a/src/AccountGoWeb/Controllers/AuditController.cs b/src/AccountGoWeb/Controllers/AuditController.cs new file mode 100644 index 000000000..5802766cd --- /dev/null +++ b/src/AccountGoWeb/Controllers/AuditController.cs @@ -0,0 +1,146 @@ +using System.Text; +using Dto.Auditing; +using Microsoft.AspNetCore.Mvc; +using Newtonsoft.Json; + + +namespace AccountGoWeb.Controllers +{ + /* + This controller provides web views for managing auditable entities and attributes. + NOTE: Manages both Auditable Entities and Auditable Attributes. + */ + public class AuditController : BaseController + { + private readonly ILogger _logger; + + public AuditController(IConfiguration config, ILogger logger) + { + _baseConfig = config; + _logger = logger; + } + + // #####Auditable Entities##### + + // Returns a view listing all auditable entities + public async Task GetAuditableEntities() + { + ViewBag.PageContentHeader = "Auditable Entities"; + + var entities = await GetAsync>("audit/entities"); + return View(entities); + } + + // Returns a view for a specific auditable entity by ID + public async Task GetEntity(int? id = null) + { + AuditableEntity model; + + if(id == null) + { + // If no ID is provided, create a new AuditableEntity model + model = new AuditableEntity() + { + EnableAudit = true + }; + } + else + { + model = await GetAsync($"audit/entity?id={id}"); + } + + ViewBag.PageContentHeader = "Auditable Entity"; + return View(model); + } + + // Saves an auditable entity (new or existing). This do updates via POST. + [HttpPost] + public async Task SaveEntity(AuditableEntity model) + { + if (!ModelState.IsValid) + { + return View("GetEntity", model); + } + + var json = JsonConvert.SerializeObject(model); + var content = new StringContent(json, Encoding.UTF8, "application/json"); + + await PostAsync("audit/entity", content); + + return RedirectToAction(nameof(GetAuditableEntities)); + } + + public async Task DeleteEntity(int id) + { + await DeleteAsync($"audit/entity/{id}"); + return RedirectToAction(nameof(GetAuditableEntities)); + } + + + + // #####Auditable Attributes##### + + // Returns a view listing all auditable attributes for a specific entity + public async Task GetAuditableAttributes(int entityId) + { + ViewBag.PageContentHeader = "Auditable Attributes"; + ViewBag.EntityId = entityId; + + var attributes = await GetAsync>($"audit/attributes?entityId={entityId}"); + return View(attributes); + } + + // Returns a view for a specific auditable attribute by ID or a new one if no ID is provided. + public async Task GetAttribute(int? id, int entityId) + { + AuditableAttribute model; + + if (id == null) + { + // If no ID is provided, create new AuditableAttribute + model = new AuditableAttribute() + { + AuditableEntityId = entityId, + EnableAudit = true + }; + } + else + { + model = await GetAsync($"audit/attribute?id={id}"); + } + + ViewBag.PageContentHeader = "Auditable Attribute"; + ViewBag.EntityId = entityId; + + return View(model); + } + + // Saves an auditable attribute (new or existing). This do updates via POST. + [HttpPost] + public async Task SaveAttribute(AuditableAttribute model, int entityId) + { + if (!ModelState.IsValid) + { + return View("GetAttribute", model); + } + + // This line make sure the attribute is linked to the correct entity + model.AuditableEntityId = entityId; + + var json = JsonConvert.SerializeObject(model); + var content = new StringContent(json, Encoding.UTF8, "application/json"); + + await PostAsync("audit/attribute", content); + + return RedirectToAction(nameof(GetAuditableAttributes), new { entityId }); + } + + // Deletes an auditable attribute by ID + public async Task DeleteAttribute(int id, int entityId) + { + await DeleteAsync($"audit/attribute/{id}"); + return RedirectToAction(nameof(GetAuditableAttributes), new { entityId }); + + } + } +} \ No newline at end of file diff --git a/src/AccountGoWeb/Controllers/BaseController.cs b/src/AccountGoWeb/Controllers/BaseController.cs index aafb66c5b..31340cc24 100644 --- a/src/AccountGoWeb/Controllers/BaseController.cs +++ b/src/AccountGoWeb/Controllers/BaseController.cs @@ -1,20 +1,18 @@ using Microsoft.AspNetCore.Mvc; -using Microsoft.Extensions.Configuration; -using System.Net.Http; namespace AccountGoWeb.Controllers { public class BaseController : Controller { - protected IConfiguration _baseConfig; + protected IConfiguration? _baseConfig; protected async System.Threading.Tasks.Task GetAsync(string uri) { string responseJson = string.Empty; using (var client = new HttpClient()) { - var baseUri = _baseConfig["ApiUrl"]; - client.BaseAddress = new System.Uri(baseUri); + var baseUri = _baseConfig!["ApiUrl"]; + client.BaseAddress = new System.Uri(baseUri!); client.DefaultRequestHeaders.Accept.Clear(); var response = await client.GetAsync(baseUri + uri); if (response.IsSuccessStatusCode) @@ -22,7 +20,7 @@ protected async System.Threading.Tasks.Task GetAsync(string uri) responseJson = await response.Content.ReadAsStringAsync(); } } - return Newtonsoft.Json.JsonConvert.DeserializeObject(responseJson); + return Newtonsoft.Json.JsonConvert.DeserializeObject(responseJson)!; } protected HttpResponseMessage Get(string uri) @@ -30,21 +28,44 @@ protected HttpResponseMessage Get(string uri) string responseJson = string.Empty; using (var client = new HttpClient()) { - var baseUri = _baseConfig["ApiUrl"]; - client.BaseAddress = new System.Uri(baseUri); + var baseUri = _baseConfig!["ApiUrl"]; + client.BaseAddress = new System.Uri(baseUri!); client.DefaultRequestHeaders.Accept.Clear(); var response = client.GetAsync(baseUri + uri); return response.Result; } } + /* + This method performs an HTTP DELETE request to the specified URI. + Used in AuditController to delete auditable entities and attributes. + */ + protected async System.Threading.Tasks.Task DeleteAsync(string uri) + { + using (var client = new HttpClient()) + { + var baseUri = _baseConfig!["ApiUrl"]; + client.BaseAddress = new System.Uri(baseUri!); + client.DefaultRequestHeaders.Accept.Clear(); + client.DefaultRequestHeaders.Add("UserName", GetCurrentUserName()); + + var response = await client.DeleteAsync(baseUri + uri); + + if (!response.IsSuccessStatusCode) + { + var message = await response.Content.ReadAsStringAsync(); + throw new System.Exception($"DELETE {uri} failed: {message}"); + } + } + } + protected async System.Threading.Tasks.Task PostAsync(string uri, StringContent data) { string responseJson = string.Empty; using (var client = new HttpClient()) { - var baseUri = _baseConfig["ApiUrl"]; - client.BaseAddress = new System.Uri(baseUri); + var baseUri = _baseConfig!["ApiUrl"]; + client.BaseAddress = new System.Uri(baseUri!); client.DefaultRequestHeaders.Accept.Clear(); client.DefaultRequestHeaders.Add("UserName", GetCurrentUserName()); @@ -55,7 +76,7 @@ protected async System.Threading.Tasks.Task PostAsync(string uri, String } } - return Newtonsoft.Json.JsonConvert.DeserializeObject(responseJson); + return Newtonsoft.Json.JsonConvert.DeserializeObject(responseJson)!; } protected HttpResponseMessage Post(string uri, StringContent data) @@ -63,8 +84,8 @@ protected HttpResponseMessage Post(string uri, StringContent data) string responseJson = string.Empty; using (var client = new HttpClient()) { - var baseUri = _baseConfig["ApiUrl"]; - client.BaseAddress = new System.Uri(baseUri); + var baseUri = _baseConfig!["ApiUrl"]; + client.BaseAddress = new System.Uri(baseUri!); client.DefaultRequestHeaders.Accept.Clear(); client.DefaultRequestHeaders.Accept.Add(new System.Net.Http.Headers.MediaTypeWithQualityHeaderValue("application/json")); client.DefaultRequestHeaders.Add("UserName", GetCurrentUserName()); @@ -76,7 +97,7 @@ protected HttpResponseMessage Post(string uri, StringContent data) protected bool HasPermission(string permission) { - if (HttpContext.User.Identity.IsAuthenticated) + if (HttpContext.User.Identity!.IsAuthenticated) { System.Collections.Generic.IList permissions = new System.Collections.Generic.List(); @@ -87,11 +108,11 @@ protected bool HasPermission(string permission) if (current.Type == System.Security.Claims.ClaimTypes.UserData) { Newtonsoft.Json.Linq.JObject userData = Newtonsoft.Json.Linq.JObject.Parse(current.Value); - foreach(var r in userData["Roles"]) + foreach(var r in userData["Roles"]!) { - foreach(var p in r["Permissions"]) + foreach(var p in r["Permissions"]!) { - permissions.Add(p["Name"].ToString()); + permissions.Add(p["Name"]!.ToString()); } } } @@ -105,7 +126,7 @@ protected bool HasPermission(string permission) protected string GetCurrentUserName() { - if (HttpContext.User.Identity.IsAuthenticated) + if (HttpContext.User.Identity!.IsAuthenticated) { var claimsEnumerator = HttpContext.User.Claims.GetEnumerator(); while (claimsEnumerator.MoveNext()) diff --git a/src/AccountGoWeb/Controllers/ContactController.cs b/src/AccountGoWeb/Controllers/ContactController.cs index fd3e1a60a..f22788253 100644 --- a/src/AccountGoWeb/Controllers/ContactController.cs +++ b/src/AccountGoWeb/Controllers/ContactController.cs @@ -1,11 +1,5 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; +using Dto.Common; using Microsoft.AspNetCore.Mvc; -using Microsoft.Extensions.Configuration; -using System.Net.Http; -using Dto.Common; // For more information on enabling MVC for empty projects, visit http://go.microsoft.com/fwlink/?LinkID=397860 namespace AccountGoWeb.Controllers @@ -35,8 +29,8 @@ public async System.Threading.Tasks.Task Contacts(int partyId = 0 //return View(model: contacts); using (var client = new HttpClient()) { - var baseUri = _baseConfig["ApiUrl"]; - client.BaseAddress = new System.Uri(baseUri); + var baseUri = _baseConfig!["ApiUrl"]; + client.BaseAddress = new System.Uri(baseUri!); client.DefaultRequestHeaders.Accept.Clear(); var response = await client.GetAsync(baseUri + "contact/contacts?partyId=" + partyId + "&partyType=" + partyType); if (response.IsSuccessStatusCode) @@ -57,7 +51,7 @@ public async System.Threading.Tasks.Task Contacts(int partyId = 0 /// public IActionResult Contact(int id = 0, int partyId = 0, int partyType = 0) { - Contact contact = null; + Contact? contact = null; if (id == 0) // creating new contact { diff --git a/src/AccountGoWeb/Controllers/DashboardController.cs b/src/AccountGoWeb/Controllers/DashboardController.cs index c8791445a..0753e5208 100644 --- a/src/AccountGoWeb/Controllers/DashboardController.cs +++ b/src/AccountGoWeb/Controllers/DashboardController.cs @@ -1,9 +1,8 @@ using Microsoft.AspNetCore.Mvc; -using Microsoft.Extensions.Configuration; namespace AccountGoWeb.Controllers { - [Microsoft.AspNetCore.Authorization.Authorize] + //[Microsoft.AspNetCore.Authorization.Authorize] public class DashboardController : BaseController { public DashboardController(IConfiguration config) @@ -19,7 +18,7 @@ public IActionResult Index() public IActionResult MonthlySales() { - ViewBag.ApiMontlySales = _baseConfig["ApiUrl"] + "sales/getmonthlysales"; + ViewBag.ApiMontlySales = _baseConfig!["ApiUrl"] + "sales/getmonthlysales"; return View(); } } diff --git a/src/AccountGoWeb/Controllers/DonationsController.cs b/src/AccountGoWeb/Controllers/DonationsController.cs new file mode 100644 index 000000000..fc4797ef6 --- /dev/null +++ b/src/AccountGoWeb/Controllers/DonationsController.cs @@ -0,0 +1,206 @@ +using AccountGoWeb.Models; +using Dto.Donations; +using Microsoft.AspNetCore.Mvc; +using Newtonsoft.Json; + +namespace AccountGoWeb.Controllers +{ + public class DonationsController : GoodController + { + private readonly ILogger _logger; + + public DonationsController(IConfiguration config, ILogger logger) + { + _configuration = config; + Models.SelectListItemHelper._config = config; + _logger = logger; + } + + public IActionResult Index() + { + return RedirectToAction("DonationInvoices"); + } + + public async System.Threading.Tasks.Task DonationInvoices() + { + ViewBag.PageContentHeader = "Donation Invoices"; + using (var client = new HttpClient()) + { + var baseUri = _configuration!["ApiUrl"]; + client.BaseAddress = new System.Uri(baseUri!); + client.DefaultRequestHeaders.Accept.Clear(); + var response = await client.GetAsync(baseUri + "donations/donationinvoices"); + if (response.IsSuccessStatusCode) + { + var responseJson = await response.Content.ReadAsStringAsync(); + return View(model: responseJson); + } + + @ViewBag.Customers = Models.SelectListItemHelper.Customers(); + @ViewBag.Items = Models.SelectListItemHelper.Items(); + @ViewBag.Measurements = Models.SelectListItemHelper.Measurements(); + } + return View(); + } + + [HttpGet] + public IActionResult AddDonationInvoice() + { + ViewBag.PageContentHeader = "Add Donation Invoice"; + + DonationInvoice donationInvoiceModel = new DonationInvoice(); + donationInvoiceModel.DonationInvoiceLines = new List { + new DonationInvoiceLine { + Amount = 0, + ItemId = 1, + Quantity = 1, + } + }; + donationInvoiceModel.No = new System.Random().Next(1, 99999).ToString(); + + @ViewBag.Customers = Models.SelectListItemHelper.Customers(); + @ViewBag.Items = Models.SelectListItemHelper.Items(); + @ViewBag.Measurements = Models.SelectListItemHelper.Measurements(); + + return View(donationInvoiceModel); + } + + [HttpPost] + public async System.Threading.Tasks.Task AddDonationInvoice(DonationInvoice Dto, string? addRowBtn) + { + if (!string.IsNullOrEmpty(addRowBtn)) + { + Dto.DonationInvoiceLines!.Add(new DonationInvoiceLine + { + Amount = 0, + Quantity = 1, + ItemId = 1, + MeasurementId = 1, + }); + + ViewBag.Customers = Models.SelectListItemHelper.Customers(); + ViewBag.Items = Models.SelectListItemHelper.Items(); + ViewBag.Measurements = Models.SelectListItemHelper.Measurements(); + + return View(Dto); + } + else if (ModelState.IsValid) + { + _logger.LogInformation("Posted value received: {Posted}", Dto.Posted); + var serialize = Newtonsoft.Json.JsonConvert.SerializeObject(Dto); + var content = new StringContent(serialize); + content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/json"); + + _logger.LogInformation("AddDonationInvoice: " + await content.ReadAsStringAsync()); + var response = Post("Donations/CreateDonationInvoice", content); + + _logger.LogInformation("AddDonationInvoice response: " + response.ToString()); + if (response.IsSuccessStatusCode) + return RedirectToAction("donationinvoices"); + else + { + var errorContent = await response.Content.ReadAsStringAsync(); + _logger.LogError("Failed to create donation invoice. Status: {Status}, Error: {Error}", response.StatusCode, errorContent); + ModelState.AddModelError("", $"Failed to save donation invoice: {response.StatusCode}"); + } + } + else + { + _logger.LogWarning("ModelState is invalid. Errors: {Errors}", + string.Join(", ", ModelState.Values.SelectMany(v => v.Errors).Select(e => e.ErrorMessage))); + } + + ViewBag.Customers = Models.SelectListItemHelper.Customers(); + ViewBag.Items = Models.SelectListItemHelper.Items(); + ViewBag.Measurements = Models.SelectListItemHelper.Measurements(); + + return View(Dto); + } + + public IActionResult DonationInvoice(int id) + { + ViewBag.PageContentHeader = "Donation Invoice"; + DonationInvoice? donationInvoiceModel = null; + + if (id == 0) + { + ViewBag.PageContentHeader = "Add Donation Invoice"; + return View("AddDonationInvoice"); + } + else + { + donationInvoiceModel = GetAsync("Donations/DonationInvoice?id=" + id).Result; + ViewBag.Id = donationInvoiceModel.Id; + ViewBag.DonorName = donationInvoiceModel.DonorName; + ViewBag.DonationDate = donationInvoiceModel.DonationDate; + ViewBag.DonationInvoiceLines = donationInvoiceModel.DonationInvoiceLines; + ViewBag.TotalAmount = donationInvoiceModel.Amount; + } + + @ViewBag.Customers = Models.SelectListItemHelper.Customers(); + @ViewBag.Items = Models.SelectListItemHelper.Items(); + @ViewBag.Measurements = Models.SelectListItemHelper.Measurements(); + + return View("DonationInvoice", donationInvoiceModel); + } + + [HttpPost] + public async System.Threading.Tasks.Task DonationInvoice(DonationInvoice donationInvoiceModel) + { + if (ModelState.IsValid) + { + var serialize = Newtonsoft.Json.JsonConvert.SerializeObject(donationInvoiceModel); + var content = new StringContent(serialize); + content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/json"); + string ReadAsStringAsync = await content.ReadAsStringAsync(); + _logger.LogInformation("SaveDonationInvoice: " + ReadAsStringAsync); + var response = Post("Donations/UpdateDonationInvoice", content); + + if (response.IsSuccessStatusCode) + { + return RedirectToAction("DonationInvoices"); + } + else + { + var errorContent = await response.Content.ReadAsStringAsync(); + _logger.LogError("Failed to update donation invoice. Status: {Status}, Error: {Error}", response.StatusCode, errorContent); + ModelState.AddModelError("", $"Failed to update donation invoice: {response.StatusCode}"); + } + } + else + { + _logger.LogWarning("ModelState is invalid. Errors: {Errors}", + string.Join(", ", ModelState.Values.SelectMany(v => v.Errors).Select(e => e.ErrorMessage))); + } + + ViewBag.Customers = SelectListItemHelper.Customers(); + ViewBag.Items = SelectListItemHelper.Items(); + ViewBag.Measurements = SelectListItemHelper.Measurements(); + ViewBag.TotalAmount = donationInvoiceModel.Amount; + + return View(donationInvoiceModel); + } + + public async Task DeleteDonationInvoice(int id) + { + using (var client = new HttpClient()) + { + var baseUri = _configuration!["ApiUrl"]; + client.BaseAddress = new System.Uri(baseUri!); + client.DefaultRequestHeaders.Accept.Clear(); + var response = await client.DeleteAsync(baseUri + "donations/deletedonationinvoice?id=" + id); + + if (response.IsSuccessStatusCode) + return RedirectToAction("DonationInvoices"); + } + + return RedirectToAction("DonationInvoices"); + } + + public IActionResult DonationInvoicePdf(int id) + { + var donationInvoice = GetAsync("Donations/DonationInvoice?id=" + id).Result; + return View(donationInvoice); + } + } +} diff --git a/src/AccountGoWeb/Controllers/FinancialsController.cs b/src/AccountGoWeb/Controllers/FinancialsController.cs index bbe60f680..b62c23c63 100644 --- a/src/AccountGoWeb/Controllers/FinancialsController.cs +++ b/src/AccountGoWeb/Controllers/FinancialsController.cs @@ -1,196 +1,215 @@ using Microsoft.AspNetCore.Mvc; -using System.Collections.Generic; namespace AccountGoWeb.Controllers { - [Microsoft.AspNetCore.Authorization.Authorize] - public class FinancialsController : BaseController + //[Microsoft.AspNetCore.Authorization.Authorize] + public class FinancialsController : BaseController + { + private readonly ILogger _logger; + + public FinancialsController(IConfiguration config, ILogger logger) { - public FinancialsController(Microsoft.Extensions.Configuration.IConfiguration config) - { - _baseConfig = config; - } + _baseConfig = config; + _logger = logger; + } - public IActionResult AddJournalEntry() - { - ViewBag.PageContentHeader = "Add Journal Entry"; - return View(); - } + public IActionResult AddJournalEntry() + { + ViewBag.PageContentHeader = "Add Journal Entry"; + return View(); + } - public IActionResult JournalEntry(int id) - { - ViewBag.PageContentHeader = "Journal Entry"; - return View(); - } + public IActionResult JournalEntry(int id) + { + ViewBag.PageContentHeader = "Journal Entry"; + return View(model: id); + } - public async System.Threading.Tasks.Task Accounts() + public async Task Accounts() + { + ViewBag.PageContentHeader = "Chart of Accounts"; + + using (var client = new System.Net.Http.HttpClient()) + { + var baseUri = _baseConfig!["ApiUrl"]; + _logger.LogInformation($"+++++++++++++++ baseUri={baseUri} +++++++++++++++"); + client.BaseAddress = new System.Uri(baseUri!); + client.DefaultRequestHeaders.Accept.Clear(); + var response = await client.GetAsync(baseUri + "financials/accounts"); + if (response.IsSuccessStatusCode) { - ViewBag.PageContentHeader = "Accounts"; - - using (var client = new System.Net.Http.HttpClient()) - { - var baseUri = _baseConfig["ApiUrl"]; - client.BaseAddress = new System.Uri(baseUri); - client.DefaultRequestHeaders.Accept.Clear(); - var response = await client.GetAsync(baseUri + "financials/accounts"); - if (response.IsSuccessStatusCode) - { - var responseJson = await response.Content.ReadAsStringAsync(); - return View(model: responseJson); - } - } - - return View(); + var responseJson = await response.Content.ReadAsStringAsync(); + var accountModels = Newtonsoft.Json.JsonConvert.DeserializeObject>(responseJson); + return View(accountModels); } + } + + return View(); + } + + public async Task Account(int? id = null) + { + Dto.Financial.Account? accountModel = null; + if (id == null) + { + accountModel = new Dto.Financial.Account(); + } + else + { + accountModel = await GetAsync("financials/account?id=" + id); + } + + ViewBag.PageContentHeader = "Account"; + return View(accountModel); + } - public async System.Threading.Tasks.Task Account(int? id = null) + public async System.Threading.Tasks.Task JournalEntries() + { + ViewBag.PageContentHeader = "Journal Entries"; + + using (var client = new System.Net.Http.HttpClient()) + { + var baseUri = _baseConfig!["ApiUrl"]; + client.BaseAddress = new System.Uri(baseUri!); + client.DefaultRequestHeaders.Accept.Clear(); + var response = await client.GetAsync(baseUri + "financials/journalentries"); + if (response.IsSuccessStatusCode) { - Dto.Financial.Account accountModel = null; - if (id == null) - { - accountModel = new Dto.Financial.Account(); - } - else - { - accountModel = await GetAsync("financials/account?id=" + id); - } - - ViewBag.PageContentHeader = "Account"; - return View(accountModel); + var responseJson = await response.Content.ReadAsStringAsync(); + return View(model: responseJson); } + } + + return View(); + } - public async System.Threading.Tasks.Task JournalEntries() + public async System.Threading.Tasks.Task GeneralLedger() + { + ViewBag.PageContentHeader = "General Ledger"; + + using (var client = new System.Net.Http.HttpClient()) + { + var baseUri = _baseConfig!["ApiUrl"]; + client.BaseAddress = new System.Uri(baseUri!); + client.DefaultRequestHeaders.Accept.Clear(); + var response = await client.GetAsync(baseUri + "financials/generalledger"); + if (response.IsSuccessStatusCode) { - ViewBag.PageContentHeader = "Journal Entries"; - - using (var client = new System.Net.Http.HttpClient()) - { - var baseUri = _baseConfig["ApiUrl"]; - client.BaseAddress = new System.Uri(baseUri); - client.DefaultRequestHeaders.Accept.Clear(); - var response = await client.GetAsync(baseUri + "financials/journalentries"); - if (response.IsSuccessStatusCode) - { - var responseJson = await response.Content.ReadAsStringAsync(); - return View(model: responseJson); - } - } - - return View(); + var responseJson = await response.Content.ReadAsStringAsync(); + return View(model: responseJson); } + } + + return View(); + } - public async System.Threading.Tasks.Task GeneralLedger() + public async System.Threading.Tasks.Task TrialBalance() + { + ViewBag.PageContentHeader = "Trial Balance"; + + using (var client = new System.Net.Http.HttpClient()) + { + var baseUri = _baseConfig!["ApiUrl"]; + client.BaseAddress = new System.Uri(baseUri!); + client.DefaultRequestHeaders.Accept.Clear(); + var response = await client.GetAsync(baseUri + "financials/trialbalance"); + if (response.IsSuccessStatusCode) { - ViewBag.PageContentHeader = "General Ledger"; - - using (var client = new System.Net.Http.HttpClient()) - { - var baseUri = _baseConfig["ApiUrl"]; - client.BaseAddress = new System.Uri(baseUri); - client.DefaultRequestHeaders.Accept.Clear(); - var response = await client.GetAsync(baseUri + "financials/generalledger"); - if (response.IsSuccessStatusCode) - { - var responseJson = await response.Content.ReadAsStringAsync(); - return View(model: responseJson); - } - } - - return View(); + var responseJson = await response.Content.ReadAsStringAsync(); + var trialBalanceModel = Newtonsoft.Json.JsonConvert.DeserializeObject>(responseJson); + return View(trialBalanceModel); } + } + + return View(); + } - public async System.Threading.Tasks.Task TrialBalance() + public async System.Threading.Tasks.Task BalanceSheet() + { + ViewBag.PageContentHeader = "Balance Sheet"; + + using (var client = new System.Net.Http.HttpClient()) + { + var baseUri = _baseConfig!["ApiUrl"]; + client.BaseAddress = new System.Uri(baseUri!); + client.DefaultRequestHeaders.Accept.Clear(); + var response = await client.GetAsync(baseUri + "financials/balancesheet"); + if (response.IsSuccessStatusCode) { - ViewBag.PageContentHeader = "Trial Balance"; - - using (var client = new System.Net.Http.HttpClient()) - { - var baseUri = _baseConfig["ApiUrl"]; - client.BaseAddress = new System.Uri(baseUri); - client.DefaultRequestHeaders.Accept.Clear(); - var response = await client.GetAsync(baseUri + "financials/trialbalance"); - if (response.IsSuccessStatusCode) - { - var responseJson = await response.Content.ReadAsStringAsync(); - var trialBalanceModel = Newtonsoft.Json.JsonConvert.DeserializeObject>(responseJson); - return View(trialBalanceModel); - } - } - - return View(); + var responseJson = await response.Content.ReadAsStringAsync(); + var balanceSheetModel = Newtonsoft.Json.JsonConvert.DeserializeObject>(responseJson); + return View(balanceSheetModel); } + } + return View(); + // return View(new List()); // Use this statement to test the view with an empty balance sheet + + //var Dto = _financialService.BalanceSheet().ToList(); + //var dt = Helpers.CollectionHelper.ConvertTo(Dto); + //var incomestatement = _financialService.IncomeStatement(); + //var netincome = incomestatement.Where(a => a.IsExpense == false).Sum(a => a.Amount) - incomestatement.Where(a => a.IsExpense == true).Sum(a => a.Amount); + + // TODO: Add logic to get the correct account for accumulated profit/loss. Currently, the account code is hard-coded here. + // Solution 1: Add two columns in general ledger setting for the account id of accumulated profit and loss. + // Solution 2: Add column to Account table to flag if account is net income (profit and loss) + //if (netincome < 0) + //{ + // var loss = Dto.Where(a => a.AccountCode == "30500").FirstOrDefault(); + // loss.Amount = netincome; + //} + //else + //{ + // var profit = Dto.Where(a => a.AccountCode == "30400").FirstOrDefault(); + // profit.Amount = netincome; + //} + + //return View(Dto); + } + + public async Task IncomeStatement() + { + ViewBag.PageContentHeader = "Income Statement"; + + using (var client = new System.Net.Http.HttpClient()) + { + var baseUri = _baseConfig!["ApiUrl"]; + client.BaseAddress = new System.Uri(baseUri!); + client.DefaultRequestHeaders.Accept.Clear(); - public async System.Threading.Tasks.Task BalanceSheet() + try { - ViewBag.PageContentHeader = "Balance Sheet"; - - using (var client = new System.Net.Http.HttpClient()) - { - var baseUri = _baseConfig["ApiUrl"]; - client.BaseAddress = new System.Uri(baseUri); - client.DefaultRequestHeaders.Accept.Clear(); - var response = await client.GetAsync(baseUri + "financials/balancesheet"); - if (response.IsSuccessStatusCode) - { - var responseJson = await response.Content.ReadAsStringAsync(); - var balanceSheetModel = Newtonsoft.Json.JsonConvert.DeserializeObject>(responseJson); - return View(balanceSheetModel); - } - } - - return View(); - //var Dto = _financialService.BalanceSheet().ToList(); - //var dt = Helpers.CollectionHelper.ConvertTo(Dto); - //var incomestatement = _financialService.IncomeStatement(); - //var netincome = incomestatement.Where(a => a.IsExpense == false).Sum(a => a.Amount) - incomestatement.Where(a => a.IsExpense == true).Sum(a => a.Amount); - - // TODO: Add logic to get the correct account for accumulated profit/loss. Currently, the account code is hard-coded here. - // Solution 1: Add two columns in general ledger setting for the account id of accumulated profit and loss. - // Solution 2: Add column to Account table to flag if account is net income (profit and loss) - //if (netincome < 0) - //{ - // var loss = Dto.Where(a => a.AccountCode == "30500").FirstOrDefault(); - // loss.Amount = netincome; - //} - //else - //{ - // var profit = Dto.Where(a => a.AccountCode == "30400").FirstOrDefault(); - // profit.Amount = netincome; - //} - - //return View(Dto); + var response = await client.GetAsync(baseUri + "financials/incomestatement"); + if (response.IsSuccessStatusCode) + { + var responseJson = await response.Content.ReadAsStringAsync(); + var incomeStatementModel = Newtonsoft.Json.JsonConvert.DeserializeObject>(responseJson); + return View(incomeStatementModel); + } + else + { + ViewBag.Error = "Failed to fetch income statement data."; + } } - - public async System.Threading.Tasks.Task IncomeStatement() + catch (Exception ex) { - ViewBag.PageContentHeader = "Income Statement"; - - using (var client = new System.Net.Http.HttpClient()) - { - var baseUri = _baseConfig["ApiUrl"]; - client.BaseAddress = new System.Uri(baseUri); - client.DefaultRequestHeaders.Accept.Clear(); - var response = await client.GetAsync(baseUri + "financials/incomestatement"); - if (response.IsSuccessStatusCode) - { - var responseJson = await response.Content.ReadAsStringAsync(); - var incomeStatementModel = Newtonsoft.Json.JsonConvert.DeserializeObject>(responseJson); - return View(incomeStatementModel); - } - } - - return View(); - //var Dto = _financialService.IncomeStatement(); - //return View(Dto); + ViewBag.Error = $"Error: {ex.Message}"; } + } - public IActionResult Banks() - { - ViewBag.PageContentHeader = "Cash/Banks"; + return View(new List()); + } - var banks = GetAsync>("financials/cashbanks").Result; - return View(banks); - } + public IActionResult Banks() + { + ViewBag.PageContentHeader = "Cash/Banks"; + + var banks = GetAsync>("financials/cashbanks").Result; + + return View(banks); } + + + + } } diff --git a/src/AccountGoWeb/Controllers/GoodController.cs b/src/AccountGoWeb/Controllers/GoodController.cs new file mode 100644 index 000000000..93b676218 --- /dev/null +++ b/src/AccountGoWeb/Controllers/GoodController.cs @@ -0,0 +1,74 @@ +using Microsoft.AspNetCore.Mvc; + +namespace AccountGoWeb.Controllers +{ + public class GoodController : Controller + { + protected IConfiguration? _configuration; + + protected HttpResponseMessage Get(string uri) + { + string responseJson = string.Empty; + using (var client = new HttpClient()) + { + string? baseUri = _configuration!["ApiUrl"]; + client.BaseAddress = new System.Uri(baseUri!); + client.DefaultRequestHeaders.Accept.Clear(); + var response = client.GetAsync(baseUri + uri); + return response.Result; + } + } + + protected HttpResponseMessage Post(string uri, StringContent data) + { + string responseJson = string.Empty; + using (var client = new HttpClient()) + { + string? baseUri = _configuration!["ApiUrl"]; + client.BaseAddress = new System.Uri(baseUri!); + client.DefaultRequestHeaders.Accept.Clear(); + client.DefaultRequestHeaders.Accept.Add(new System.Net.Http.Headers.MediaTypeWithQualityHeaderValue("application/json")); + //client.DefaultRequestHeaders.Add("UserName", GetCurrentUserName()); + + var response = client.PostAsync(baseUri + uri, data); + return response.Result; + } + } + + protected async System.Threading.Tasks.Task GetAsync(string uri) + { + string responseJson = string.Empty; + using (var client = new HttpClient()) + { + string? baseUri = _configuration!["ApiUrl"]; + client.BaseAddress = new System.Uri(baseUri!); + client.DefaultRequestHeaders.Accept.Clear(); + var response = await client.GetAsync(baseUri + uri); + if (response.IsSuccessStatusCode) + { + responseJson = await response.Content.ReadAsStringAsync(); + } + } + return Newtonsoft.Json.JsonConvert.DeserializeObject(responseJson)!; + } + + protected async System.Threading.Tasks.Task PostAsync(string uri, StringContent data) + { + string responseJson = string.Empty; + using (var client = new HttpClient()) + { + string? baseUri = _configuration!["ApiUrl"]; + client.BaseAddress = new System.Uri(baseUri!); + client.DefaultRequestHeaders.Accept.Clear(); + //client.DefaultRequestHeaders.Add("UserName", GetCurrentUserName()); + + var response = await client.PostAsync(baseUri + uri, data); + if (response.IsSuccessStatusCode) + { + responseJson = await response.Content.ReadAsStringAsync(); + } + } + return Newtonsoft.Json.JsonConvert.DeserializeObject(responseJson)!; + } + } +} diff --git a/src/AccountGoWeb/Controllers/HomeController.cs b/src/AccountGoWeb/Controllers/HomeController.cs index d7b5a2def..ee4b0b00a 100644 --- a/src/AccountGoWeb/Controllers/HomeController.cs +++ b/src/AccountGoWeb/Controllers/HomeController.cs @@ -1,9 +1,8 @@ using Microsoft.AspNetCore.Mvc; -using Microsoft.Extensions.Configuration; namespace AccountGoWeb.Controllers { - [Microsoft.AspNetCore.Authorization.Authorize] + //[Microsoft.AspNetCore.Authorization.Authorize] public class HomeController : BaseController { public HomeController(IConfiguration config) @@ -14,7 +13,7 @@ public HomeController(IConfiguration config) public IActionResult Index() { ViewBag.PageContentHeader = "Dashboard"; - ViewBag.ApiMontlySales = _baseConfig["ApiUrl"] + "sales/getmonthlysales"; + ViewBag.ApiMontlySales = _baseConfig!["ApiUrl"] + "sales/getmonthlysales"; return View(); } } diff --git a/src/AccountGoWeb/Controllers/InventoryController.cs b/src/AccountGoWeb/Controllers/InventoryController.cs index e15c5645b..01b18d8cc 100644 --- a/src/AccountGoWeb/Controllers/InventoryController.cs +++ b/src/AccountGoWeb/Controllers/InventoryController.cs @@ -1,26 +1,27 @@ using Dto.Inventory; using Microsoft.AspNetCore.Mvc; -using System.Net.Http; namespace AccountGoWeb.Controllers { - [Microsoft.AspNetCore.Authorization.Authorize] public class InventoryController : BaseController { - public InventoryController(Microsoft.Extensions.Configuration.IConfiguration config) + private readonly ILogger _logger; + + public InventoryController(Microsoft.Extensions.Configuration.IConfiguration config, + ILogger logger) { _baseConfig = config; - Models.SelectListItemHelper._config = config; + _logger = logger; } - public async System.Threading.Tasks.Task Items() + [HttpGet] + public async Task Index() { - ViewBag.PageContentHeader = "Items"; - + // Existing Index logic using (var client = new System.Net.Http.HttpClient()) { - var baseUri = _baseConfig["ApiUrl"]; - client.BaseAddress = new System.Uri(baseUri); + var baseUri = _baseConfig!["ApiUrl"]; + client.BaseAddress = new System.Uri(baseUri!); client.DefaultRequestHeaders.Accept.Clear(); var response = await client.GetAsync(baseUri + "inventory/items"); if (response.IsSuccessStatusCode) @@ -29,54 +30,47 @@ public async System.Threading.Tasks.Task Items() return View(model: responseJson); } } - return View(); } - public async System.Threading.Tasks.Task ICJ() + [HttpGet] // ← ADD THIS for initial page load + public IActionResult AddItem() { - ViewBag.PageContentHeader = "Inventory Control Journal"; - - using (var client = new System.Net.Http.HttpClient()) + var itemModel = new Item { - var baseUri = _baseConfig["ApiUrl"]; - client.BaseAddress = new System.Uri(baseUri); - client.DefaultRequestHeaders.Accept.Clear(); - var response = await client.GetAsync(baseUri + "inventory/icj"); - if (response.IsSuccessStatusCode) - { - var responseJson = await response.Content.ReadAsStringAsync(); - return View(model: responseJson); - } - } + Id = 0 + }; - return View(); + ViewBag.Accounts = Models.SelectListItemHelper.Accounts(); + ViewBag.ItemTaxGroups = Models.SelectListItemHelper.ItemTaxGroups(); + ViewBag.Measurements = Models.SelectListItemHelper.UnitOfMeasurements(); + ViewBag.ItemCategories = Models.SelectListItemHelper.ItemCategories(); + ViewBag.PreferredVendorId = Models.SelectListItemHelper.Vendors(); + ViewBag.PageContentHeader = "New Item"; + + return View("addItem", itemModel); } - public IActionResult Item(int id = -1) + [HttpPost] // ← Keep this for form submission + public async Task AddItem(Item itemModel, string? addRowBtn) { - Item itemModel = null; - if (id == -1) + if (!string.IsNullOrEmpty(addRowBtn)) { - ViewBag.PageContentHeader = "Item Customer"; - itemModel = new Item(); - itemModel.No = new System.Random().Next(1, 99999).ToString(); // TODO: Replace with system generated numbering. + itemModel.Id = 0; // Reset for new row + ViewBag.Accounts = Models.SelectListItemHelper.Accounts(); + ViewBag.ItemTaxGroups = Models.SelectListItemHelper.ItemTaxGroups(); + ViewBag.Measurements = Models.SelectListItemHelper.UnitOfMeasurements(); + ViewBag.ItemCategories = Models.SelectListItemHelper.ItemCategories(); + ViewBag.PreferredVendorId = Models.SelectListItemHelper.Vendors(); + ViewBag.PageContentHeader = "New Item"; + return View("addItem", itemModel); } - else - { - ViewBag.PageContentHeader = "Item Card"; - itemModel = GetAsync("inventory/item?id=" + id).Result; - } - - ViewBag.Accounts = Models.SelectListItemHelper.Accounts(); - ViewBag.ItemTaxGroups = Models.SelectListItemHelper.ItemTaxGroups(); - ViewBag.Measurements = Models.SelectListItemHelper.UnitOfMeasurements(); - ViewBag.ItemCategories = Models.SelectListItemHelper.ItemCategories(); - return View(itemModel); + return await SaveItem(itemModel); } - public IActionResult SaveItem(Item itemModel) + [HttpPost] + public async Task SaveItem(Item itemModel) { if (ModelState.IsValid) { @@ -84,23 +78,71 @@ public IActionResult SaveItem(Item itemModel) var content = new StringContent(serialize); content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/json"); - var response = PostAsync("inventory/saveitem", content); - - return RedirectToAction("Items"); + try + { + var response = Post("inventory/saveitem", content); + + _logger.LogInformation($"SaveItem Response Status: {response.StatusCode}"); + + if (response.IsSuccessStatusCode) + { + _logger.LogInformation($"Item saved successfully: {itemModel.Description}"); + return RedirectToAction("Index"); + } + else + { + var errorContent = await response.Content.ReadAsStringAsync(); + _logger.LogError($"API Error {response.StatusCode}: {errorContent}"); + + try + { + var errors = Newtonsoft.Json.JsonConvert.DeserializeObject(errorContent); + if (errors is Newtonsoft.Json.Linq.JArray errorArray) + { + foreach (var error in errorArray) + { + ModelState.AddModelError("", error.ToString()); + } + } + else if (errors is string errorString) + { + ModelState.AddModelError("", errorString); + } + else + { + ModelState.AddModelError("", $"API Error: {response.StatusCode}"); + } + } + catch + { + ModelState.AddModelError("", $"Failed to save item. Server error: {response.StatusCode}"); + } + } + } + catch (Exception ex) + { + _logger.LogError($"SaveItem Exception: {ex.Message}\n{ex.StackTrace}"); + ModelState.AddModelError("", $"Error: {ex.Message}"); + } + } + else + { + var errors = ModelState.Values.SelectMany(v => v.Errors); + foreach (var error in errors) + { + _logger.LogWarning($"Validation Error: {error.ErrorMessage}"); + } } + // Return to form with errors displayed ViewBag.Accounts = Models.SelectListItemHelper.Accounts(); ViewBag.ItemTaxGroups = Models.SelectListItemHelper.ItemTaxGroups(); ViewBag.Measurements = Models.SelectListItemHelper.UnitOfMeasurements(); ViewBag.ItemCategories = Models.SelectListItemHelper.ItemCategories(); + ViewBag.PreferredVendorId = Models.SelectListItemHelper.Vendors(); + ViewBag.PageContentHeader = itemModel.Id > 0 ? "Edit Item" : "New Item"; - - if (itemModel.Id > 0) - ViewBag.PageContentHeader = "Item Item"; - else - ViewBag.PageContentHeader = "New Card"; - - return View("Item", itemModel); + return View("addItem", itemModel); } } -} +} \ No newline at end of file diff --git a/src/AccountGoWeb/Controllers/PurchasingController.cs b/src/AccountGoWeb/Controllers/PurchasingController.cs index d8215259c..e51d8364e 100644 --- a/src/AccountGoWeb/Controllers/PurchasingController.cs +++ b/src/AccountGoWeb/Controllers/PurchasingController.cs @@ -1,16 +1,17 @@ -using Microsoft.AspNetCore.Mvc; -using Microsoft.Extensions.Configuration; -using System.Net.Http; +using Dto.Purchasing; +using Microsoft.AspNetCore.Mvc; namespace AccountGoWeb.Controllers { - [Microsoft.AspNetCore.Authorization.Authorize] + //[Microsoft.AspNetCore.Authorization.Authorize] public class PurchasingController : BaseController { - public PurchasingController(IConfiguration config) + private readonly ILogger _logger; + public PurchasingController(IConfiguration config, ILogger logger) { _baseConfig = config; Models.SelectListItemHelper._config = config; + _logger = logger; } public IActionResult Index() @@ -24,7 +25,7 @@ public IActionResult PurchaseOrders() string purchaseOrders = GetAsync("purchasing/purchaseorders") .Result - .ToString(); + .ToString()!; return View(model: purchaseOrders); } @@ -32,21 +33,106 @@ public IActionResult PurchaseOrders() public IActionResult AddPurchaseOrder() { ViewBag.PageContentHeader = "Add Purchase Order"; + PurchaseOrder purchaseOrderModel = new PurchaseOrder(); + purchaseOrderModel.PurchaseOrderLines = new List { new PurchaseOrderLine { + Amount = 0, + Discount = 0, + ItemId = 1, + Quantity = 1, + } }; + purchaseOrderModel.No = new System.Random().Next(1, 99999).ToString(); ViewBag.Vendors = Models.SelectListItemHelper.Vendors(); + ViewBag.PaymentTerms = Models.SelectListItemHelper.PaymentTerms(); + ViewBag.Items = Models.SelectListItemHelper.Items(); + ViewBag.Measurements = Models.SelectListItemHelper.Measurements(); - return View(); + return View(purchaseOrderModel); + } + + [HttpPost] + public IActionResult AddPurchaseOrder(PurchaseOrder purchaseOrder, string addRowBtn) + { + ViewBag.PageContentHeader = "Add Purchase Order"; + + if (!string.IsNullOrEmpty(addRowBtn)) + { + purchaseOrder.PurchaseOrderLines.Add(new PurchaseOrderLine + { + Amount = 0, + Discount = 0, + ItemId = 1, + Quantity = 1 + }); + + ViewBag.Vendors = Models.SelectListItemHelper.Vendors(); + ViewBag.PaymentTerms = Models.SelectListItemHelper.PaymentTerms(); + ViewBag.Items = Models.SelectListItemHelper.Items(); + ViewBag.Measurements = Models.SelectListItemHelper.Measurements(); + + return View(purchaseOrder); + } + else if (ModelState.IsValid) + { + var serialize = Newtonsoft.Json.JsonConvert.SerializeObject(purchaseOrder); + var content = new StringContent(serialize); + content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/json"); + + var response = PostAsync("purchasing/savepurchaseorder", content); + + return RedirectToAction("PurchaseOrders"); + } + + return View("PurchaseOrders"); + } + + public IActionResult PurchaseInvoice(int id) + { + ViewBag.PageContentHeader = "Purchase Invoice"; + + PurchaseInvoice? purchaseInvoiceModel = null; + + if (id == 0) + { + ViewBag.PageContentHeader = "New Purchase Invoice"; + return View("PurchaseInvoice"); + } + else + { + purchaseInvoiceModel = GetAsync("Purchasing/PurchaseInvoice?id=" + id).Result; + } + + ViewBag.Vendors = Models.SelectListItemHelper.Vendors(); + ViewBag.PaymentTerms = Models.SelectListItemHelper.PaymentTerms(); + ViewBag.Items = Models.SelectListItemHelper.Items(); + ViewBag.Measurements = Models.SelectListItemHelper.Measurements(); + + return View(purchaseInvoiceModel); } - public IActionResult PurchaseOrder(int purchId = 0) + + public IActionResult PurchaseOrder(int id) { ViewBag.PageContentHeader = "Purchase Order"; - var purchOrderDto = GetAsync("purchasing/purchaseorder?id=" + purchId).Result; + PurchaseOrder? purchaseOrderModel = null; + + if (id == 0) + { + ViewBag.PageContentHeader = "New Purchase Order"; + return View(); + } + else + { + purchaseOrderModel = GetAsync("Purchasing/PurchaseOrder?id=" + id).Result; + } ViewBag.Vendors = Models.SelectListItemHelper.Vendors(); + ViewBag.PaymentTerms = Models.SelectListItemHelper.PaymentTerms(); + ViewBag.Items = Models.SelectListItemHelper.Items(); + ViewBag.Measurements = Models.SelectListItemHelper.Measurements(); - return View(); + return View(purchaseOrderModel); } public async System.Threading.Tasks.Task PurchaseInvoices() @@ -54,8 +140,8 @@ public async System.Threading.Tasks.Task PurchaseInvoices() ViewBag.PageContentHeader = "Purchase Invoices"; using (var client = new HttpClient()) { - var baseUri = _baseConfig["ApiUrl"]; - client.BaseAddress = new System.Uri(baseUri); + var baseUri = _baseConfig!["ApiUrl"]; + client.BaseAddress = new System.Uri(baseUri!); client.DefaultRequestHeaders.Accept.Clear(); var response = await client.GetAsync(baseUri + "purchasing/purchaseinvoices"); if (response.IsSuccessStatusCode) @@ -67,18 +153,59 @@ public async System.Threading.Tasks.Task PurchaseInvoices() return View(); } - public IActionResult AddPurchaseInvoice(int purchId = 0) + public IActionResult AddPurchaseInvoice() { ViewBag.PageContentHeader = "New Invoice"; - return View(); + PurchaseInvoice purchaseInvoiceModel = new PurchaseInvoice(); + purchaseInvoiceModel.PurchaseInvoiceLines = new List { new PurchaseInvoiceLine { + Amount = 0, + Discount = 0, + ItemId = 1, + Quantity = 1, + } }; + purchaseInvoiceModel.No = new System.Random().Next(1, 99999).ToString(); + + ViewBag.Vendors = Models.SelectListItemHelper.Vendors(); + ViewBag.PaymentTerms = Models.SelectListItemHelper.PaymentTerms(); + ViewBag.Items = Models.SelectListItemHelper.Items(); + ViewBag.Measurements = Models.SelectListItemHelper.Measurements(); + + return View(purchaseInvoiceModel); } - public IActionResult PurchaseInvoice(int id) + [HttpPost] + public async System.Threading.Tasks.Task AddPurchaseInvoice(PurchaseInvoice purchaseInvoice, string addRowBtn) { - ViewBag.PageContentHeader = "Purchase Invoice"; + ViewBag.PageContentHeader = "New Invoice"; + if (!string.IsNullOrEmpty(addRowBtn)) + { + purchaseInvoice.PurchaseInvoiceLines.Add(new PurchaseInvoiceLine + { + Amount = 0, + Discount = 0, + ItemId = 1, + Quantity = 1 + }); - ViewBag.Vendors = Models.SelectListItemHelper.Vendors(); + ViewBag.Vendors = Models.SelectListItemHelper.Vendors(); + ViewBag.PaymentTerms = Models.SelectListItemHelper.PaymentTerms(); + ViewBag.Items = Models.SelectListItemHelper.Items(); + ViewBag.Measurements = Models.SelectListItemHelper.Measurements(); + + return View(purchaseInvoice); + } + else if (ModelState.IsValid) + { + var serialize = Newtonsoft.Json.JsonConvert.SerializeObject(purchaseInvoice); + var content = new StringContent(serialize); + content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/json"); + + var response = await PostAsync("Purchasing/SavePurchaseInvoice", content); + _logger.LogInformation("Purchase Invoice Saved" + purchaseInvoice.Id); + + return RedirectToAction("PurchaseInvoices"); + } return View(); } @@ -95,8 +222,8 @@ public async System.Threading.Tasks.Task Vendors() ViewBag.PageContentHeader = "Vendors"; using (var client = new HttpClient()) { - var baseUri = _baseConfig["ApiUrl"]; - client.BaseAddress = new System.Uri(baseUri); + var baseUri = _baseConfig!["ApiUrl"]; + client.BaseAddress = new System.Uri(baseUri!); client.DefaultRequestHeaders.Accept.Clear(); var response = await client.GetAsync(baseUri + "purchasing/vendors"); if (response.IsSuccessStatusCode) @@ -109,7 +236,7 @@ public async System.Threading.Tasks.Task Vendors() } public IActionResult Vendor(int id = -1) { - Dto.Purchasing.Vendor vendorModel = null; + Dto.Purchasing.Vendor? vendorModel = null; if (id == -1) { ViewBag.PageContentHeader = "New Vendor"; @@ -169,7 +296,7 @@ public IActionResult Payment(int id) VendorId = invoice.VendorId, VendorName = invoice.VendorName, InvoiceAmount = invoice.Amount, - AmountPaid = invoice.AmountPaid, + AmountPaid = invoice.AmountPaid, Date = invoice.InvoiceDate }; @@ -177,7 +304,7 @@ public IActionResult Payment(int id) return View(model); } - + [HttpPost] public IActionResult Payment(Models.Purchasing.Payment model) { diff --git a/src/AccountGoWeb/Controllers/QuotationsController.cs b/src/AccountGoWeb/Controllers/QuotationsController.cs index cbe402648..488b68b94 100644 --- a/src/AccountGoWeb/Controllers/QuotationsController.cs +++ b/src/AccountGoWeb/Controllers/QuotationsController.cs @@ -1,29 +1,42 @@ -using System.Net.Http; +using Dto.Sales; using Microsoft.AspNetCore.Mvc; -using Microsoft.Extensions.Configuration; namespace AccountGoWeb.Controllers { - [Microsoft.AspNetCore.Authorization.Authorize] - public class QuotationsController : BaseController + //[Microsoft.AspNetCore.Authorization.Authorize] + public class QuotationsController : GoodController { - public QuotationsController(IConfiguration config) { - _baseConfig = config; + //private readonly IConfiguration _configuration; + private readonly ILogger _logger; + public QuotationsController(IConfiguration config, ILogger logger) + { + _configuration = config; + _logger = logger; } public IActionResult Index() { - return RedirectToAction("quotations"); + return RedirectToAction("SalesQuotations"); } + + [HttpGet] + public IActionResult SalesQuotations() + { + return View(); + } + + + + public async System.Threading.Tasks.Task Quotations() { ViewBag.PageContentHeader = "Quotations"; using (var client = new HttpClient()) { - var baseUri = _baseConfig["ApiUrl"]; - client.BaseAddress = new System.Uri(baseUri); + var baseUri = _configuration!["ApiUrl"]; + client.BaseAddress = new System.Uri(baseUri!); client.DefaultRequestHeaders.Accept.Clear(); var response = await client.GetAsync(baseUri + "sales/quotations"); if (response.IsSuccessStatusCode) @@ -36,18 +49,147 @@ public async System.Threading.Tasks.Task Quotations() return View(); } + [HttpGet] public IActionResult AddSalesQuotation() { ViewBag.PageContentHeader = "Add Sales Quotation"; - return View(); + SalesQuotation model = new SalesQuotation(); + model.SalesQuotationLines = new List { new SalesQuotationLine { + Amount = 0, + Quantity = 1, + Discount = 0, + ItemId = 1, + MeasurementId = 1, + } }; + model.No = new System.Random().Next(1, 99999).ToString(); // TODO: Replace with system generated numbering. + + ViewBag.Customers = Models.SelectListItemHelper.Customers(); + ViewBag.Items = Models.SelectListItemHelper.Items(); + ViewBag.PaymentTerms = Models.SelectListItemHelper.PaymentTerms(); + ViewBag.Measurements = Models.SelectListItemHelper.Measurements(); + + return View(model); } - public IActionResult Quotation() + [HttpPost] + public async Task AddSalesQuotation(Dto.Sales.SalesQuotation model, string? addRowBtn) { - ViewBag.PageContentHeader = "Sales Quotation"; + if (!string.IsNullOrEmpty(addRowBtn)) + { + _logger.LogInformation("Add Row Button Clicked"); + model.SalesQuotationLines.Add(new SalesQuotationLine + { + Amount = 0, + Quantity = 1, + Discount = 0, + ItemId = 1, + MeasurementId = 1, + }); + ViewBag.Customers = Models.SelectListItemHelper.Customers(); + ViewBag.Items = Models.SelectListItemHelper.Items(); + ViewBag.PaymentTerms = Models.SelectListItemHelper.PaymentTerms(); + ViewBag.Measurements = Models.SelectListItemHelper.Measurements(); + + return View(model); + + } + else if (ModelState.IsValid) + { + var serialize = Newtonsoft.Json.JsonConvert.SerializeObject(model); + var content = new StringContent(serialize); + content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/json"); + + using (var client = new HttpClient()) + { + var baseUri = _configuration!["ApiUrl"]; + client.BaseAddress = new Uri(baseUri!); + var response = await client.PostAsync("sales/savequotation", content); + + if (response.IsSuccessStatusCode) + { + _logger.LogInformation("Quotation has been successfully saved."); + } + else + { + _logger.LogInformation("Quotation save failed."); + } + return RedirectToAction("quotations"); + } + } + else + { + _logger.LogInformation("Model State is not valid."); + return RedirectToAction("quotations"); + } - return View(); } + + [HttpGet] + public IActionResult Quotation(int id) + { + ViewBag.PageContentHeader = "Sales"; + + SalesQuotation? model = null; + + if (id == 0) + { + ViewBag.PageContentHeader = "Add Sales Quotation"; + return View("AddSalesQuotation"); + } + else + { + model = GetAsync("Sales/Quotation?id=" + id).Result; + @ViewBag.Id = model.Id; + @ViewBag.QuotationDate = model.QuotationDate; + @ViewBag.CustomerName = model.CustomerName; + @ViewBag.PaymentTermId = model.PaymentTermId; + @ViewBag.SalesQuotationLines = model.SalesQuotationLines; + @ViewBag.TotalAmount = model.Amount; + + var paymentTermsList = Models.SelectListItemHelper.PaymentTerms(); + var selectedPaymentTerm = paymentTermsList.FirstOrDefault(pt => pt.Value == model.PaymentTermId.ToString()); + @ViewBag.SelectedPaymentTerm = selectedPaymentTerm?.Text ?? "N/A"; + } + + @ViewBag.Customers = Models.SelectListItemHelper.Customers(); + ViewBag.Items = Models.SelectListItemHelper.Items(); + @ViewBag.PaymentTerms = Models.SelectListItemHelper.PaymentTerms(); + @ViewBag.Measurements = Models.SelectListItemHelper.Measurements(); + + return View(model); + } + + [HttpPost] + public IActionResult Quotation(SalesQuotation model) + { + ViewBag.Customers = Models.SelectListItemHelper.Customers(); + ViewBag.Items = Models.SelectListItemHelper.Items(); + ViewBag.PaymentTerms = Models.SelectListItemHelper.PaymentTerms(); + ViewBag.Measurements = Models.SelectListItemHelper.Measurements(); + + var serialize = Newtonsoft.Json.JsonConvert.SerializeObject(model); + var content = new StringContent(serialize); + content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/json"); + + using (var client = new HttpClient()) + { + var baseUri = _configuration!["ApiUrl"]; + client.BaseAddress = new Uri(baseUri!); + var response = client.PostAsync("sales/savequotation", content).Result; + + if (response.IsSuccessStatusCode) + { + _logger.LogInformation("Quotation has been successfully saved."); + } + else + { + _logger.LogInformation("Quotation save failed."); + } + } + + return RedirectToAction("Quotations"); + } + } } diff --git a/src/AccountGoWeb/Controllers/SPAProxyController.cs b/src/AccountGoWeb/Controllers/SPAProxyController.cs index 67313350c..b17b3bbd4 100644 --- a/src/AccountGoWeb/Controllers/SPAProxyController.cs +++ b/src/AccountGoWeb/Controllers/SPAProxyController.cs @@ -1,10 +1,7 @@ -using System.Net.Http; -using System.Text; -using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc; -using Microsoft.Extensions.Configuration; using Newtonsoft.Json; using Newtonsoft.Json.Linq; +using System.Text; namespace AccountGoWeb.Controllers { diff --git a/src/AccountGoWeb/Controllers/SalesController.cs b/src/AccountGoWeb/Controllers/SalesController.cs index ec582bf86..520edf5ad 100644 --- a/src/AccountGoWeb/Controllers/SalesController.cs +++ b/src/AccountGoWeb/Controllers/SalesController.cs @@ -1,19 +1,21 @@ using AccountGoWeb.Models; using Dto.Sales; using Microsoft.AspNetCore.Mvc; -using Microsoft.Extensions.Configuration; -using System.Collections.Generic; -using System.Net.Http; +using Newtonsoft.Json; namespace AccountGoWeb.Controllers { - [Microsoft.AspNetCore.Authorization.Authorize] - public class SalesController : BaseController + //[Microsoft.AspNetCore.Authorization.Authorize] + public class SalesController : GoodController { - public SalesController(IConfiguration config) + // private readonly IConfiguration _configuration; + private readonly ILogger _logger; + + public SalesController(IConfiguration config, ILogger logger) { - _baseConfig = config; + _configuration = config; Models.SelectListItemHelper._config = config; + _logger = logger; } public IActionResult Index() @@ -26,8 +28,8 @@ public async System.Threading.Tasks.Task SalesOrders() ViewBag.PageContentHeader = "Sales Orders"; using (var client = new HttpClient()) { - var baseUri = _baseConfig["ApiUrl"]; - client.BaseAddress = new System.Uri(baseUri); + var baseUri = _configuration!["ApiUrl"]; + client.BaseAddress = new System.Uri(baseUri!); client.DefaultRequestHeaders.Accept.Clear(); var response = await client.GetAsync(baseUri + "sales/salesorders"); if (response.IsSuccessStatusCode) @@ -42,26 +44,135 @@ public async System.Threading.Tasks.Task SalesOrders() public IActionResult AddSalesOrder() { ViewBag.PageContentHeader = "Add Sales Order"; - - return View(); + SalesOrder salesOrderModel = new SalesOrder(); + salesOrderModel.SalesOrderLines = new List { new SalesOrderLine { + Amount = 0, + Discount = 0, + ItemId = 1, + Quantity = 1, + } }; + salesOrderModel.No = new System.Random().Next(1, 99999).ToString(); + + @ViewBag.Customers = Models.SelectListItemHelper.Customers(); + @ViewBag.PaymentTerms = Models.SelectListItemHelper.PaymentTerms(); + @ViewBag.Items = Models.SelectListItemHelper.Items(); + @ViewBag.Measurements = Models.SelectListItemHelper.Measurements(); + + return View(salesOrderModel); } [HttpPost] - public IActionResult AddSalesOrder(object Dto) + public IActionResult AddSalesOrder(SalesOrder Dto, string addRowBtn) { - return Ok(); + if (!string.IsNullOrEmpty(addRowBtn)) + { + Dto.SalesOrderLines.Add(new SalesOrderLine + { + Amount = 0, + Quantity = 1, + Discount = 0, + ItemId = 1, + MeasurementId = 1, + }); + + ViewBag.Customers = Models.SelectListItemHelper.Customers(); + ViewBag.Items = Models.SelectListItemHelper.Items(); + ViewBag.PaymentTerms = Models.SelectListItemHelper.PaymentTerms(); + ViewBag.Measurements = Models.SelectListItemHelper.Measurements(); + + return View(Dto); + } + else if (ModelState.IsValid) + { + var serialize = Newtonsoft.Json.JsonConvert.SerializeObject(Dto); + var content = new StringContent(serialize); + content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/json"); + + var response = Post("Sales/addsalesorder", content); + if (response.IsSuccessStatusCode) + return RedirectToAction("salesorders"); + } + @ViewBag.Customers = Models.SelectListItemHelper.Customers(); + @ViewBag.PaymentTerms = Models.SelectListItemHelper.PaymentTerms(); + @ViewBag.Measurements = Models.SelectListItemHelper.Measurements(); + + return RedirectToAction("salesorders"); } - + public IActionResult SalesOrder(int id) { ViewBag.PageContentHeader = "Sales Order"; - return View(); + SalesOrder? salesOrderModel = null; + if (id == -1) + { + ViewBag.PageContentHeader = "Add Sales Order"; + return View("AddSalesOrder"); + + } + else + { + try + { + salesOrderModel = GetAsync("Sales/SalesOrder?id=" + id).Result; + if (salesOrderModel == null) + { + ViewBag.ErrorMessage = "Sales order not found. Please check the order ID and try again."; + _logger.LogWarning("Sales order with id {Id} returned null from API", id); + return View(salesOrderModel); + } + ViewBag.CustomerName = salesOrderModel.CustomerName ?? "Unknown"; + ViewBag.OrderDate = salesOrderModel.OrderDate; + ViewBag.SalesOrderLines = salesOrderModel.SalesOrderLines ?? new List(); + ViewBag.TotalAmount = salesOrderModel.Amount; + } + catch (HttpRequestException hre) + { + _logger.LogError(hre, "HTTP error loading sales order with id {Id}", id); + ViewBag.ErrorMessage = $"Failed to load sales order. Status: {hre.Message}"; + return View(salesOrderModel); + } + catch (Exception ex) + { + _logger.LogError(ex, "Unexpected error loading sales order with id {Id}", id); + ViewBag.ErrorMessage = "An unexpected error occurred while loading the sales order. Please try again later."; + return View(salesOrderModel); + } + } + + @ViewBag.Customers = Models.SelectListItemHelper.Customers(); + @ViewBag.PaymentTerms = Models.SelectListItemHelper.PaymentTerms(); + @ViewBag.Items = Models.SelectListItemHelper.Items(); + @ViewBag.Measurements = Models.SelectListItemHelper.Measurements(); + + return View(salesOrderModel); } public IActionResult SalesInvoice(int id) { ViewBag.PageContentHeader = "Sales Invoice"; - return View(); + SalesInvoice? salesInvoiceModel = null; + + if (id == 0) + { + ViewBag.PageContentHeader = "Add Sales Invoice"; + return View("AddSalesInvoice"); + } + else + { + salesInvoiceModel = GetAsync("Sales/SalesInvoice?id=" + id).Result; + ViewBag.Id = salesInvoiceModel.Id; + ViewBag.CustomerName = salesInvoiceModel.CustomerName; + ViewBag.InvoiceDate = salesInvoiceModel.InvoiceDate; + ViewBag.SalesInvoiceLines = salesInvoiceModel.SalesInvoiceLines; + ViewBag.TotalAmount = salesInvoiceModel.Amount; + } + + @ViewBag.Customers = Models.SelectListItemHelper.Customers(); + @ViewBag.PaymentTerms = Models.SelectListItemHelper.PaymentTerms(); + @ViewBag.Items = Models.SelectListItemHelper.Items(); + @ViewBag.Measurements = Models.SelectListItemHelper.Measurements(); + + return View("SalesInvoice", salesInvoiceModel); } public async System.Threading.Tasks.Task SalesInvoices() @@ -69,8 +180,8 @@ public async System.Threading.Tasks.Task SalesInvoices() ViewBag.PageContentHeader = "Sales Invoices"; using (var client = new HttpClient()) { - var baseUri = _baseConfig["ApiUrl"]; - client.BaseAddress = new System.Uri(baseUri); + var baseUri = _configuration!["ApiUrl"]; + client.BaseAddress = new System.Uri(baseUri!); client.DefaultRequestHeaders.Accept.Clear(); var response = await client.GetAsync(baseUri + "sales/salesinvoices"); if (response.IsSuccessStatusCode) @@ -78,47 +189,170 @@ public async System.Threading.Tasks.Task SalesInvoices() var responseJson = await response.Content.ReadAsStringAsync(); return View(model: responseJson); } + + @ViewBag.Customers = Models.SelectListItemHelper.Customers(); + @ViewBag.PaymentTerms = Models.SelectListItemHelper.PaymentTerms(); + @ViewBag.Items = Models.SelectListItemHelper.Items(); + @ViewBag.Measurements = Models.SelectListItemHelper.Measurements(); } return View(); } + [HttpPost] + public async System.Threading.Tasks.Task SalesInvoice(SalesInvoice salesInvoiceModel) + { + if (ModelState.IsValid) + { + var serialize = Newtonsoft.Json.JsonConvert.SerializeObject(salesInvoiceModel); + var content = new StringContent(serialize); + content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/json"); + string ReadAsStringAsync = await content.ReadAsStringAsync(); + _logger.LogInformation("SaveSalesInvoice: " + ReadAsStringAsync); + var response = Post("Sales/UpdateSalesInvoice", content); + + if (response.IsSuccessStatusCode) + { + return RedirectToAction("SalesInvoices"); + } + } + + ViewBag.Customers = SelectListItemHelper.Customers(); + ViewBag.PaymentTerms = SelectListItemHelper.PaymentTerms(); + ViewBag.Items = SelectListItemHelper.Items(); + ViewBag.Measurements = SelectListItemHelper.Measurements(); + ViewBag.TotalAmount = salesInvoiceModel.Amount; + + return View(salesInvoiceModel); + } + + [HttpGet] public IActionResult AddSalesInvoice() { ViewBag.PageContentHeader = "Add Sales Invoice"; - return View(); + SalesInvoice salesInvoiceModel = new SalesInvoice(); + salesInvoiceModel.SalesInvoiceLines = new List { new SalesInvoiceLine { + Amount = 0, + Discount = 0, + ItemId = 1, + Quantity = 1, + } }; + salesInvoiceModel.No = new System.Random().Next(1, 99999).ToString(); // TODO: Replace with system generated numbering. + + @ViewBag.Customers = Models.SelectListItemHelper.Customers(); + @ViewBag.PaymentTerms = Models.SelectListItemHelper.PaymentTerms(); + @ViewBag.Items = Models.SelectListItemHelper.Items(); + @ViewBag.Measurements = Models.SelectListItemHelper.Measurements(); + + return View(salesInvoiceModel); + } + + [HttpPost] + public async System.Threading.Tasks.Task AddSalesInvoice(SalesInvoice Dto, string? addRowBtn) + { + if (!string.IsNullOrEmpty(addRowBtn)) + { + Dto.SalesInvoiceLines!.Add(new SalesInvoiceLine + { + Amount = 0, + Quantity = 1, + Discount = 0, + ItemId = 1, + MeasurementId = 1, + }); + + ViewBag.Customers = Models.SelectListItemHelper.Customers(); + ViewBag.Items = Models.SelectListItemHelper.Items(); + ViewBag.PaymentTerms = Models.SelectListItemHelper.PaymentTerms(); + ViewBag.Measurements = Models.SelectListItemHelper.Measurements(); + + return View(Dto); + } + else if (ModelState.IsValid) + { + _logger.LogInformation("Posted value received: {Posted}", Dto.Posted); + var serialize = Newtonsoft.Json.JsonConvert.SerializeObject(Dto); + var content = new StringContent(serialize); + content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/json"); + + _logger.LogInformation("AddSalesInvoice: " + await content.ReadAsStringAsync()); + var response = Post("Sales/CreateSalesInvoice", content); + + _logger.LogInformation("AddSalesInvoice response: " + response.ToString()); + if (response.IsSuccessStatusCode) + return RedirectToAction("salesinvoices"); + } + + ViewBag.Customers = Models.SelectListItemHelper.Customers(); + ViewBag.Items = Models.SelectListItemHelper.Items(); + ViewBag.PaymentTerms = Models.SelectListItemHelper.PaymentTerms(); + ViewBag.Measurements = Models.SelectListItemHelper.Measurements(); + + return View(Dto); } public async System.Threading.Tasks.Task SalesReceipts() { ViewBag.PageContentHeader = "Sales Receipts"; - using (var client = new HttpClient()) + try { - var baseUri = _baseConfig["ApiUrl"]; - client.BaseAddress = new System.Uri(baseUri); - client.DefaultRequestHeaders.Accept.Clear(); - var response = await client.GetAsync(baseUri + "sales/salesreceipts"); - if (response.IsSuccessStatusCode) + using (var client = new HttpClient()) { - var responseJson = await response.Content.ReadAsStringAsync(); - return View(model: responseJson); + var baseUri = _configuration!["ApiUrl"]; + client.BaseAddress = new Uri(baseUri!); + client.DefaultRequestHeaders.Accept.Clear(); + + var response = await client.GetAsync("sales/salesreceipts"); + if (response.IsSuccessStatusCode) + { + var responseJson = await response.Content.ReadAsStringAsync(); + return View(model: responseJson); + } + else + { + _logger.LogError("Failed to fetch sales receipts. API returned status code: {StatusCode}", response.StatusCode); + ViewBag.ErrorMessage = "Failed to load sales receipts. Please try again later."; + } } } + catch (Exception ex) + { + _logger.LogError(ex, "An error occurred while fetching sales receipts."); + ViewBag.ErrorMessage = "An unexpected error occurred while loading sales receipts."; + } + + // Return the view with an error message if the API call fails + return View(model: "[]"); + } + + [HttpGet] + public IActionResult SalesReceipt(int id) + { return View(); } + [HttpGet] public IActionResult AddReceipt() { - ViewBag.PageContentHeader = "New Receipt"; - - var model = new Models.Sales.AddReceipt(); - - ViewBag.Customers = Models.SelectListItemHelper.Customers(); - ViewBag.DebitAccounts = Models.SelectListItemHelper.CashBanks(); - ViewBag.CreditAccounts = Models.SelectListItemHelper.Accounts(); - ViewBag.CustomersDetail = Newtonsoft.Json.JsonConvert.SerializeObject(GetAsync>("sales/customers").Result); - - return View(model); + try + { + ViewBag.PageContentHeader = "New Receipt"; + ViewBag.Customers = Models.SelectListItemHelper.Customers(); + ViewBag.DebitAccounts = Models.SelectListItemHelper.CashBanks(); + ViewBag.CreditAccounts = Models.SelectListItemHelper.Accounts(); + ViewBag.CustomersDetail = Newtonsoft.Json.JsonConvert.SerializeObject( + GetAsync>("sales/customers").Result + ); + + var model = new Models.Sales.AddReceipt(); + return View(model); + } + catch (Exception ex) + { + _logger.LogError(ex, "An error occurred while preparing the Add Receipt page."); + ViewBag.ErrorMessage = "Failed to load the page for adding a receipt. Please try again later."; + return View(new Models.Sales.AddReceipt()); + } } [HttpPost] @@ -126,32 +360,49 @@ public IActionResult AddReceipt(Models.Sales.AddReceipt model) { if (ModelState.IsValid) { - var serialize = Newtonsoft.Json.JsonConvert.SerializeObject(model); - var content = new StringContent(serialize); - content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/json"); - var response = Post("sales/savereceipt", content); - if(response.IsSuccessStatusCode) - return RedirectToAction("salesreceipts"); + try + { + var serialize = Newtonsoft.Json.JsonConvert.SerializeObject(model); + var content = new StringContent(serialize); + content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/json"); + + var response = Post("sales/savereceipt", content); + if (response.IsSuccessStatusCode) + { + return RedirectToAction("SalesReceipts"); + } + else + { + _logger.LogError("Failed to save receipt. API returned status code: {StatusCode}", response.StatusCode); + ViewBag.ErrorMessage = "Failed to save the receipt. Please try again."; + } + } + catch (Exception ex) + { + _logger.LogError(ex, "An error occurred while saving the receipt."); + ViewBag.ErrorMessage = "An unexpected error occurred. Please try again."; + } } + // Reload dropdowns and return the view if validation or API call fails ViewBag.PageContentHeader = "New Receipt"; - ViewBag.Customers = Models.SelectListItemHelper.Customers(); ViewBag.DebitAccounts = Models.SelectListItemHelper.CashBanks(); ViewBag.CreditAccounts = Models.SelectListItemHelper.Accounts(); - ViewBag.CustomersDetail = Newtonsoft.Json.JsonConvert.SerializeObject(GetAsync>("sales/customers").Result); + ViewBag.CustomersDetail = Newtonsoft.Json.JsonConvert.SerializeObject( + GetAsync>("sales/customers").Result + ); return View(model); } - public async System.Threading.Tasks.Task Customers() { ViewBag.PageContentHeader = "Customers"; using (var client = new HttpClient()) { - var baseUri = _baseConfig["ApiUrl"]; - client.BaseAddress = new System.Uri(baseUri); + var baseUri = _configuration!["ApiUrl"]; + client.BaseAddress = new System.Uri(baseUri!); client.DefaultRequestHeaders.Accept.Clear(); var response = await client.GetAsync(baseUri + "sales/customers"); if (response.IsSuccessStatusCode) @@ -162,10 +413,10 @@ public async System.Threading.Tasks.Task Customers() } return View(); } - + public IActionResult Customer(int id = -1) { - Customer customerModel = null; + Customer? customerModel = null; if (id == -1) { ViewBag.PageContentHeader = "New Customer"; @@ -185,24 +436,50 @@ public IActionResult Customer(int id = -1) return View(customerModel); } - public IActionResult SaveCustomer(Customer customerModel) + [HttpPost] + public async System.Threading.Tasks.Task SaveSalesInvoice(SalesInvoice salesInvoiceModel) { if (ModelState.IsValid) { - var serialize = Newtonsoft.Json.JsonConvert.SerializeObject(customerModel); + var serialize = Newtonsoft.Json.JsonConvert.SerializeObject(salesInvoiceModel); var content = new StringContent(serialize); content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/json"); - var response = PostAsync("sales/savecustomer", content); + string ReadAsStringAsync = await content.ReadAsStringAsync(); + _logger.LogInformation("SaveSalesInvoice: " + ReadAsStringAsync); + var response = Post("Sales/SaveSalesInvoice", content); + + if (response.IsSuccessStatusCode) + { + return RedirectToAction("SalesInvoices"); + } + } + ViewBag.Customers = SelectListItemHelper.Customers(); + ViewBag.PaymentTerms = SelectListItemHelper.PaymentTerms(); + ViewBag.Items = SelectListItemHelper.Items(); + ViewBag.Measurements = SelectListItemHelper.Measurements(); + + return View("SalesInvoice", salesInvoiceModel); + } + public async Task SaveCustomer(Customer customerModel) + { + if (ModelState.IsValid) + { + var serialize = Newtonsoft.Json.JsonConvert.SerializeObject(customerModel); + var content = new StringContent(serialize); + content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/json"); + string ReadAsStringAsync = await content.ReadAsStringAsync(); + var response = await PostAsync("Sales/SaveCustomer", content); return RedirectToAction("Customers"); } - else { + else + { ViewBag.Accounts = SelectListItemHelper.Accounts(); ViewBag.TaxGroups = SelectListItemHelper.TaxGroups(); ViewBag.PaymentTerms = SelectListItemHelper.PaymentTerms(); } - if(customerModel.Id == -1) + if (customerModel.Id == -1) ViewBag.PageContentHeader = "New Customer"; else ViewBag.PageContentHeader = "Customer Card"; @@ -210,6 +487,7 @@ public IActionResult SaveCustomer(Customer customerModel) return View("Customer", customerModel); } + public IActionResult CustomerAllocations(int id) { ViewBag.PageContentHeader = "Customer Allocations"; @@ -217,38 +495,71 @@ public IActionResult CustomerAllocations(int id) return View(); } + // [HttpGet] public IActionResult Allocate(int id) { - ViewBag.PageContentHeader = "Receipt Allocation"; + Console.WriteLine($"Allocate called with ID: {id}"); + + try + { + ViewBag.PageContentHeader = "Sales Receipt Allocation"; - var model = new Models.Sales.Allocate(); + var model = new Models.Sales.Allocate(); - var receipt = GetAsync("sales/salesreceipt?id=" + id).Result; + // Fetch receipt details + var receipt = GetAsync("sales/salesreceipt?id=" + id).Result; + if (receipt == null) + { + Console.WriteLine($"Receipt not found for ID: {id}"); + _logger.LogError("Failed to fetch receipt with id: {id}", id); + return NotFound($"Receipt with id {id} not found."); + } - ViewBag.CustomerName = receipt.CustomerName; - ViewBag.ReceiptNo = receipt.ReceiptNo; + ViewBag.CustomerName = receipt.CustomerName; + ViewBag.ReceiptNo = receipt.ReceiptNo; - model.CustomerId = receipt.CustomerId; - model.ReceiptId = receipt.Id; - model.Date = receipt.ReceiptDate; - model.Amount = receipt.Amount; - model.RemainingAmountToAllocate = receipt.RemainingAmountToAllocate; + model.CustomerId = receipt.CustomerId; + model.ReceiptId = receipt.Id; + model.Date = receipt.ReceiptDate; + model.Amount = receipt.Amount; + model.RemainingAmountToAllocate = receipt.RemainingAmountToAllocate; - var invoices = GetAsync>("sales/customerinvoices?id=" + receipt.CustomerId).Result; + // Fetch customer invoices + _logger.LogInformation("Calling API: sales/customerinvoices?id={id}", receipt.CustomerId); - foreach (var invoice in invoices) { - if (invoice.Posted && invoice.TotalAllocatedAmount < invoice.Amount) + var invoices = GetAsync>("sales/customerinvoices?id=" + receipt.CustomerId).Result; + if (invoices == null) { - model.AllocationLines.Add(new Models.Sales.AllocationLine() + _logger.LogError("Failed to fetch invoices for customer with id: {CustomerId}", receipt.CustomerId); + return NotFound($"Invoices for customer with id {receipt.CustomerId} not found."); + } + + foreach (var invoice in invoices) + { + _logger.LogInformation("Invoice: {Invoice}", JsonConvert.SerializeObject(invoice)); + if (invoice.Posted && invoice.TotalAllocatedAmount < invoice.Amount) { - InvoiceId = invoice.Id, - Amount = invoice.Amount, - AllocatedAmount = invoice.TotalAllocatedAmount - }); + model.AllocationLines.Add(new Models.Sales.AllocationLine() + { + InvoiceId = invoice.Id, + Amount = invoice.Amount, + AllocatedAmount = invoice.TotalAllocatedAmount + }); + } + else + { + _logger.LogInformation("Invoice excluded: Posted={Posted}, TotalAllocatedAmount={TotalAllocatedAmount}, Amount={Amount}", + invoice.Posted, invoice.TotalAllocatedAmount, invoice.Amount); + } } - } - return View(model); + return View(model); + } + catch (Exception ex) + { + _logger.LogError(ex, "An error occurred while processing the Allocate action for id: {id}", id); + return StatusCode(500, "An error occurred while processing your request."); + } } [HttpPost] @@ -256,7 +567,8 @@ public IActionResult Allocate(Models.Sales.Allocate model) { if (ModelState.IsValid) { - if (model.IsValid()) { + if (model.IsValid()) + { var serialize = Newtonsoft.Json.JsonConvert.SerializeObject(model); var content = new StringContent(serialize); content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/json"); @@ -288,5 +600,22 @@ public IActionResult SalesInvoicePdf(int id) salesInvoiceModel.SalesInvoiceLines = invoice.SalesInvoiceLines; return View(salesInvoiceModel); } + + public async Task DeleteSalesInvoice(int id) + { + using (var client = new HttpClient()) + { + var baseUri = _configuration!["ApiUrl"]; + client.BaseAddress = new System.Uri(baseUri!); + client.DefaultRequestHeaders.Accept.Clear(); + var response = await client.DeleteAsync(baseUri + "Sales/DeleteSalesInvoice?id=" + id); + + if (response.IsSuccessStatusCode) + return RedirectToAction("SalesInvoices"); + } + + return RedirectToAction("SalesInvoices"); + } + } } diff --git a/src/AccountGoWeb/Controllers/TaxController.cs b/src/AccountGoWeb/Controllers/TaxController.cs index bbc4e9132..72f80fc6e 100644 --- a/src/AccountGoWeb/Controllers/TaxController.cs +++ b/src/AccountGoWeb/Controllers/TaxController.cs @@ -1,43 +1,248 @@ -using Microsoft.AspNetCore.Mvc; +using AccountGoWeb.Models.TaxSystem; +using AutoMapper; +using Dto.TaxSystem; +using Microsoft.AspNetCore.Mvc; +using System; namespace AccountGoWeb.Controllers { - [Microsoft.AspNetCore.Authorization.Authorize] + //[Microsoft.AspNetCore.Authorization.Authorize] public class TaxController : BaseController { - public TaxController(Microsoft.Extensions.Configuration.IConfiguration config) + private readonly IMapper _mapper; + + public TaxController(Microsoft.Extensions.Configuration.IConfiguration config, IMapper mapper) { _baseConfig = config; + _mapper = mapper; + } + + public IActionResult Index() + { + return View("TaxesBlazor"); } - public IActionResult Index() { - return RedirectToAction("taxes"); + public IActionResult Taxes() + { + return View("TaxesBlazor"); } - public async System.Threading.Tasks.Task Taxes() + public IActionResult AddNewTax() { - ViewBag.PageContentHeader = "Tax"; + ViewBag.PageContentHeader = "Add New Tax"; + + @ViewBag.TaxGroups = Models.SelectListItemHelper.TaxGroups(); + @ViewBag.ItemTaxGroups = Models.SelectListItemHelper.ItemTaxGroups(); - using (var client = new System.Net.Http.HttpClient()) + return View(); + } + + [HttpPost] + public IActionResult AddNewTax(TaxForCreation taxForCreationDto) + { + if (ModelState.IsValid) { - var baseUri = _baseConfig["ApiUrl"]; - client.BaseAddress = new System.Uri(baseUri); - client.DefaultRequestHeaders.Accept.Clear(); - var response = await client.GetAsync(baseUri + "tax/taxes"); + var serialize = Newtonsoft.Json.JsonConvert.SerializeObject(taxForCreationDto); + var content = new StringContent(serialize); + content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/json"); + + var response = Post("Tax/addnewtax", content); if (response.IsSuccessStatusCode) + return RedirectToAction("Taxes"); + } + + @ViewBag.TaxGroups = Models.SelectListItemHelper.TaxGroups(); + @ViewBag.ItemTaxGroups = Models.SelectListItemHelper.ItemTaxGroups(); + + return View(); + } + + [HttpGet] + public async Task EditTax(int? id) + { + ViewBag.PageContentHeader = "Edit Tax"; + + if (!id.HasValue) + { + TempData["Error"] = "Tax ID is required."; + return RedirectToAction("Taxes"); + } + + try + { + // Fetch the full tax system data from API + using (var client = new HttpClient()) { - var responseJson = await response.Content.ReadAsStringAsync(); - var taxSystemDto = Newtonsoft.Json.JsonConvert.DeserializeObject(responseJson); - var taxSystemViewModel = new Models.TaxSystem.TaxSystemViewModel(); - taxSystemViewModel.Taxes = taxSystemDto.Taxes; - taxSystemViewModel.ItemTaxGroups = taxSystemDto.ItemTaxGroups; - taxSystemViewModel.TaxGroups = taxSystemDto.TaxGroups; - - return View(taxSystemViewModel); + var baseUri = _baseConfig!["ApiUrl"]; + Console.WriteLine($"Fetching tax data from: {baseUri}tax/taxes"); + client.BaseAddress = new System.Uri(baseUri!); + client.DefaultRequestHeaders.Accept.Clear(); + var response = await client.GetAsync(baseUri + "tax/taxes"); + + Console.WriteLine($"API Response Status: {response.StatusCode}"); + + if (response.IsSuccessStatusCode) + { + var responseJson = await response.Content.ReadAsStringAsync(); + Console.WriteLine($"API Response received, length: {responseJson.Length}"); + + var taxSystemDto = Newtonsoft.Json.JsonConvert.DeserializeObject(responseJson); + + if (taxSystemDto?.Taxes != null) + { + Console.WriteLine($"Found {taxSystemDto.Taxes.Count()} taxes in response"); + + var taxDto = taxSystemDto.Taxes.FirstOrDefault(t => t.Id == id.Value); + if (taxDto == null) + { + TempData["Error"] = $"Tax with ID {id.Value} not found."; + return RedirectToAction("Taxes"); + } + + Console.WriteLine($"Found tax: {taxDto.TaxName} (ID: {taxDto.Id})"); + + var editTaxViewModel = new Models.TaxSystem.EditTaxViewModel(); + editTaxViewModel.Tax = _mapper.Map(taxDto); + + // Initialize TaxGroup and ItemTaxGroup to avoid null reference + editTaxViewModel.TaxGroup = new Models.TaxSystem.TaxGroup(); + editTaxViewModel.ItemTaxGroup = new Models.TaxSystem.ItemTaxGroup(); + + // Find associated tax group + var taxGroupDto = taxSystemDto.TaxGroups?.FirstOrDefault(tg => + tg.Taxes?.Any(t => t.TaxId == id.Value) ?? false); + if (taxGroupDto != null) + { + editTaxViewModel.TaxGroup = _mapper.Map(taxGroupDto); + Console.WriteLine($"Found tax group: {taxGroupDto.Description}"); + } + + // Find associated item tax group + var itemTaxGroupDto = taxSystemDto.ItemTaxGroups?.FirstOrDefault(itg => + itg.Taxes?.Any(t => t.TaxId == id.Value) ?? false); + if (itemTaxGroupDto != null) + { + editTaxViewModel.ItemTaxGroup = _mapper.Map(itemTaxGroupDto); + Console.WriteLine($"Found item tax group: {itemTaxGroupDto.Name}"); + } + + // Set account IDs (defaulting to standard accounts if not set) + editTaxViewModel.SalesAccountId = 20300; // Sales Tax account + editTaxViewModel.PurchaseAccountId = 50700; // Purchase Tax account + + @ViewBag.TaxGroups = Models.SelectListItemHelper.TaxGroups(); + @ViewBag.ItemTaxGroups = Models.SelectListItemHelper.ItemTaxGroups(); + + Console.WriteLine("Returning EditTax view"); + return View("EditTax", editTaxViewModel); + } + else + { + Console.WriteLine("TaxSystemDto or Taxes is null"); + } + } + else + { + Console.WriteLine($"API call failed with status: {response.StatusCode}"); + } } } + catch (Exception ex) + { + // Log the error + Console.WriteLine($"Error loading tax for edit: {ex.Message}"); + Console.WriteLine($"Stack trace: {ex.StackTrace}"); + TempData["Error"] = $"Error loading tax: {ex.Message}"; + } - return View(); + return RedirectToAction("Taxes"); } + + [HttpPost] + [ValidateAntiForgeryToken] + public async Task EditTax(EditTaxViewModel editTaxViewModel) + { + if (ModelState.IsValid) + { + var taxForUpdateDto = _mapper.Map(editTaxViewModel); + + using (var client = new System.Net.Http.HttpClient()) + { + var baseUri = _baseConfig!["ApiUrl"]; + client.BaseAddress = new System.Uri(baseUri!); + client.DefaultRequestHeaders.Accept.Clear(); + + var serialize = Newtonsoft.Json.JsonConvert.SerializeObject(taxForUpdateDto); + var content = new StringContent(serialize); + content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/json"); + + var response = await client.PutAsync(baseUri + "Tax/edittax", content); + + if (response.IsSuccessStatusCode) + { + return RedirectToAction("Taxes"); + } + else + { + var errorContent = await response.Content.ReadAsStringAsync(); + ModelState.AddModelError(string.Empty, $"Error updating tax: {errorContent}"); + } + } + } + + @ViewBag.TaxGroups = Models.SelectListItemHelper.TaxGroups(); + @ViewBag.ItemTaxGroups = Models.SelectListItemHelper.ItemTaxGroups(); + + return View(editTaxViewModel); + } + + public async Task DeleteTax(int id) + { + using (var client = new HttpClient()) + { + var baseUri = _baseConfig!["ApiUrl"]; + client.BaseAddress = new System.Uri(baseUri!); + client.DefaultRequestHeaders.Accept.Clear(); + var response = await client.DeleteAsync(baseUri + "Tax/deletetax?id=" + id); + + if (response.IsSuccessStatusCode) + return RedirectToAction("Taxes"); + } + + return RedirectToAction("Taxes"); + } + + public async Task DeleteTaxGroup(int id) + { + using (var client = new HttpClient()) + { + var baseUri = _baseConfig!["ApiUrl"]; + client.BaseAddress = new System.Uri(baseUri!); + client.DefaultRequestHeaders.Accept.Clear(); + var response = await client.DeleteAsync(baseUri + "Tax/deletetaxgroup?id=" + id); + + if (response.IsSuccessStatusCode) + return RedirectToAction("Taxes"); + } + + return RedirectToAction("Taxes"); + } + + public async Task DeleteItemTaxGroup(int id) + { + using (var client = new HttpClient()) + { + var baseUri = _baseConfig!["ApiUrl"]; + client.BaseAddress = new System.Uri(baseUri!); + client.DefaultRequestHeaders.Accept.Clear(); + var response = await client.DeleteAsync(baseUri + "Tax/deleteitemtaxgroup?id=" + id); + + if (response.IsSuccessStatusCode) + return RedirectToAction("Taxes"); + } + + return RedirectToAction("Taxes"); + } + } } diff --git a/src/AccountGoWeb/MappingProfile.cs b/src/AccountGoWeb/MappingProfile.cs new file mode 100644 index 000000000..86e45f5c1 --- /dev/null +++ b/src/AccountGoWeb/MappingProfile.cs @@ -0,0 +1,58 @@ +using AutoMapper; + +namespace AccountGoWeb +{ + public class MappingProfile : Profile + { + public MappingProfile() + { + CreateMap(); + CreateMap(); + + #region TaxSystem + + // TaxView Model + CreateMap().ReverseMap(); + CreateMap().ReverseMap(); + CreateMap() + .ForMember(dest => dest.Taxes, opt => opt.MapFrom((src, dest, i, context) => + { + return context.Mapper.Map>(src.Taxes); + })) + .ReverseMap(); + CreateMap().ReverseMap(); + CreateMap() + .ForMember(dest => dest.Taxes, opt => opt.MapFrom((src, dest, i, context) => + { + return context.Mapper.Map>(src.Taxes); + })) + .ReverseMap(); + CreateMap() + .ForMember(dest => dest.Tax, opt => opt.MapFrom((src, dest, i, context) => + { + return context.Mapper.Map(src.Tax); + })) + .ReverseMap(); + + // TaxSystemViewModel + CreateMap() + .ForMember(dest => dest.Taxes, opt => opt.MapFrom((src, dest, i, context) => + { + return context.Mapper.Map>(src.Taxes); + })) + .ForMember(dest => dest.TaxGroups, opt => opt.MapFrom((src, dest, i, context) => + { + return context.Mapper.Map>(src.TaxGroups); + })) + .ForMember(dest => dest.ItemTaxGroups, opt => opt.MapFrom((src, dest, i, context) => + { + return context.Mapper.Map>(src.ItemTaxGroups); + }) + ) + .ReverseMap(); + + #endregion + + } + } +} diff --git a/src/AccountGoWeb/Models/Account/LoginViewModel.cs b/src/AccountGoWeb/Models/Account/LoginViewModel.cs index 2506b0433..418ef2eaa 100644 --- a/src/AccountGoWeb/Models/Account/LoginViewModel.cs +++ b/src/AccountGoWeb/Models/Account/LoginViewModel.cs @@ -1,18 +1,17 @@ using System.ComponentModel.DataAnnotations; -namespace AccountGoWeb.Models.Account +namespace AccountGoWeb.Models.Account; + +public class LoginViewModel { - public class LoginViewModel - { - [Required] - [EmailAddress] - public string Email { get; set; } + [Required] + [EmailAddress] + public string? Email { get; set; } - [Required] - [DataType(DataType.Password)] - public string Password { get; set; } + [Required] + [DataType(DataType.Password)] + public string? Password { get; set; } - [Display(Name = "Remember me?")] - public bool RememberMe { get; set; } - } + [Display(Name = "Remember me?")] + public bool RememberMe { get; set; } } diff --git a/src/AccountGoWeb/Models/Account/RegisterViewModel.cs b/src/AccountGoWeb/Models/Account/RegisterViewModel.cs index b072ae25e..5fbd58e5f 100644 --- a/src/AccountGoWeb/Models/Account/RegisterViewModel.cs +++ b/src/AccountGoWeb/Models/Account/RegisterViewModel.cs @@ -1,29 +1,28 @@ using System.ComponentModel.DataAnnotations; -namespace AccountGoWeb.Models.Account +namespace AccountGoWeb.Models.Account; + +public class RegisterViewModel { - public class RegisterViewModel - { - [Required] - [EmailAddress] - [Display(Name = "Email")] - public string Email { get; set; } + [Required] + [EmailAddress] + [Display(Name = "Email")] + public string? Email { get; set; } - [Required] - [StringLength(100, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 6)] - [DataType(DataType.Password)] - [Display(Name = "Password")] - public string Password { get; set; } + [Required] + [StringLength(100, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 6)] + [DataType(DataType.Password)] + [Display(Name = "Password")] + public string? Password { get; set; } - [DataType(DataType.Password)] - [Display(Name = "Confirm password")] - [Compare("Password", ErrorMessage = "The password and confirmation password do not match.")] - public string ConfirmPassword { get; set; } + [DataType(DataType.Password)] + [Display(Name = "Confirm password")] + [Compare("Password", ErrorMessage = "The password and confirmation password do not match.")] + public string? ConfirmPassword { get; set; } - [Display(Name = "First Name")] - public string FirstName { get; set; } + [Display(Name = "First Name")] + public string? FirstName { get; set; } - [Display(Name = "Last Name")] - public string LastName { get; set; } - } + [Display(Name = "Last Name")] + public string? LastName { get; set; } } diff --git a/src/AccountGoWeb/Models/Bogus/Student.cs b/src/AccountGoWeb/Models/Bogus/Student.cs new file mode 100644 index 000000000..cd50037d1 --- /dev/null +++ b/src/AccountGoWeb/Models/Bogus/Student.cs @@ -0,0 +1,62 @@ +namespace AccountGoWeb.Models.Bogus; + +public class Student +{ + required public int? Id { get; set; } + required public string FirstName { get; set; } + required public string LastName { get; set; } + required public string School { get; set; } + public static IQueryable GetStudents() + { + int ndx = 0; + List students = new List() { + new Student() { Id = ++ndx, FirstName="Max", LastName="Pao", School="Science" }, + new Student() { Id = ++ndx, FirstName="Tom", LastName="Fay", School="Mining" }, + new Student() { Id = ++ndx, FirstName="Ann", LastName="Sun", School="Nursing" }, + new Student() { Id = ++ndx, FirstName="Joe", LastName="Fox", School="Computing" }, + new Student() { Id = ++ndx, FirstName="Sue", LastName="Mai", School="Mining" }, + new Student() { Id = ++ndx, FirstName="Ben", LastName="Lau", School="Business" }, + new Student() { Id = ++ndx, FirstName="Zoe", LastName="Ray", School="Mining" }, + new Student() { Id = ++ndx, FirstName="Sam", LastName="Ash", School="Medicine" }, + new Student() { Id = ++ndx, FirstName="Dan", LastName="Lee", School="Computing" }, + new Student() { Id = ++ndx, FirstName="Pat", LastName="Day", School="Science" }, + new Student() { Id = ++ndx, FirstName="Kim", LastName="Rex", School="Computing" }, + new Student() { Id = ++ndx, FirstName="Tim", LastName="Ram", School="Business" }, + new Student() { Id = ++ndx, FirstName="Rob", LastName="Wei", School="Mining" }, + new Student() { Id = ++ndx, FirstName="Jan", LastName="Tex", School="Science" }, + new Student() { Id = ++ndx, FirstName="Jim", LastName="Kid", School="Business" }, + new Student() { Id = ++ndx, FirstName="Ben", LastName="Chu", School="Medicine" }, + new Student() { Id = ++ndx, FirstName="Mia", LastName="Tao", School="Computing" }, + new Student() { Id = ++ndx, FirstName="Ted", LastName="Day", School="Business" }, + new Student() { Id = ++ndx, FirstName="Amy", LastName="Roy", School="Science" }, + new Student() { Id = ++ndx, FirstName="Ian", LastName="Kit", School="Nursing" }, + new Student() { Id = ++ndx, FirstName="Liz", LastName="Tan", School="Medicine" }, + new Student() { Id = ++ndx, FirstName="Mat", LastName="Roy", School="Tourism" }, + new Student() { Id = ++ndx, FirstName="Deb", LastName="Luo", School="Medicine" }, + new Student() { Id = ++ndx, FirstName="Ana", LastName="Poe", School="Computing" }, + new Student() { Id = ++ndx, FirstName="Lyn", LastName="Raj", School="Science" }, + new Student() { Id = ++ndx, FirstName="Amy", LastName="Ash", School="Tourism" }, + new Student() { Id = ++ndx, FirstName="Kim", LastName="Kid", School="Mining" }, + new Student() { Id = ++ndx, FirstName="Bec", LastName="Fry", School="Nursing" }, + new Student() { Id = ++ndx, FirstName="Eva", LastName="Lap", School="Computing" }, + new Student() { Id = ++ndx, FirstName="Eli", LastName="Yim", School="Business" }, + new Student() { Id = ++ndx, FirstName="Sam", LastName="Hui", School="Science" }, + new Student() { Id = ++ndx, FirstName="Joe", LastName="Jin", School="Mining" }, + new Student() { Id = ++ndx, FirstName="Liz", LastName="Kuo", School="Agriculture" }, + new Student() { Id = ++ndx, FirstName="Ric", LastName="Mak", School="Tourism" }, + new Student() { Id = ++ndx, FirstName="Pam", LastName="Day", School="Computing" }, + new Student() { Id = ++ndx, FirstName="Stu", LastName="Gad", School="Business" }, + new Student() { Id = ++ndx, FirstName="Tom", LastName="Bee", School="Tourism" }, + new Student() { Id = ++ndx, FirstName="Bob", LastName="Lam", School="Agriculture" }, + new Student() { Id = ++ndx, FirstName="Jim", LastName="Ots", School="Medicine" }, + new Student() { Id = ++ndx, FirstName="Tom", LastName="Mag", School="Mining" }, + new Student() { Id = ++ndx, FirstName="Hal", LastName="Doe", School="Agriculture" }, + new Student() { Id = ++ndx, FirstName="Roy", LastName="Kim", School="Nursing" }, + new Student() { Id = ++ndx, FirstName="Vis", LastName="Cox", School="Science" }, + new Student() { Id = ++ndx, FirstName="Kay", LastName="Aga", School="Tourism" }, + new Student() { Id = ++ndx, FirstName="Reo", LastName="Hui", School="Business" }, + new Student() { Id = ++ndx, FirstName="Bob", LastName="Roe", School="Medicine" }, + }; + return students.AsQueryable(); + } +} diff --git a/src/AccountGoWeb/Models/Financial/AccountViewModel.cs b/src/AccountGoWeb/Models/Financial/AccountViewModel.cs new file mode 100644 index 000000000..8ff633a89 --- /dev/null +++ b/src/AccountGoWeb/Models/Financial/AccountViewModel.cs @@ -0,0 +1,15 @@ +namespace AccountGoWeb.Models.Financial +{ + public class AccountViewModel + { + public int Id { get; set; } + public int? ParentAccountId { get; set; } + public string? AccountCode { get; set; } + public string? AccountName { get; set; } + public decimal TotalBalance { get; set; } + public decimal TotalDebitBalance { get; set; } + public decimal TotalCreditBalance { get; set; } + public IList? ChildAccounts { get; set; } + + } +} diff --git a/src/AccountGoWeb/Models/Financial/GeneralLedgerSetting.cs b/src/AccountGoWeb/Models/Financial/GeneralLedgerSetting.cs index 866794e6d..a947408b8 100644 --- a/src/AccountGoWeb/Models/Financial/GeneralLedgerSetting.cs +++ b/src/AccountGoWeb/Models/Financial/GeneralLedgerSetting.cs @@ -1,14 +1,13 @@ -namespace AccountGoWeb.Models.Financial +namespace AccountGoWeb.Models.Financial; + +public class GeneralLedgerSetting { - public class GeneralLedgerSetting - { - public int Id { get; set; } - public int? CompanyId { get; set; } - public string CompanyCode { get; set; } - public int? PayableAccountId { get; set; } - public int? PurchaseDiscountAccountId { get; set; } - public int? GoodsReceiptNoteClearingAccountId { get; set; } - public int? SalesDiscountAccountId { get; set; } - public int? ShippingChargeAccountId { get; set; } - } + public int Id { get; set; } + public int? CompanyId { get; set; } + public string? CompanyCode { get; set; } + public int? PayableAccountId { get; set; } + public int? PurchaseDiscountAccountId { get; set; } + public int? GoodsReceiptNoteClearingAccountId { get; set; } + public int? SalesDiscountAccountId { get; set; } + public int? ShippingChargeAccountId { get; set; } } diff --git a/src/AccountGoWeb/Models/FinancialReports.cs b/src/AccountGoWeb/Models/FinancialReports.cs index 7e755e404..8c34a7d9e 100644 --- a/src/AccountGoWeb/Models/FinancialReports.cs +++ b/src/AccountGoWeb/Models/FinancialReports.cs @@ -1,53 +1,42 @@ -//----------------------------------------------------------------------- -// -// Copyright (c) AccountGo. All rights reserved. -// Marvin Perez -// 1/11/2015 9:48:38 AM -// -//----------------------------------------------------------------------- +namespace AccountGoWeb.Models; -using System; - -namespace AccountGoWeb.Models +public class TrialBalance { - public class TrialBalance - { - public int AccountId { get; set; } - public string AccountCode { get; set; } - public string AccountName { get; set; } - public decimal Debit { get; set; } - public decimal Credit { get; set; } - } + public int AccountId { get; set; } + public string? AccountCode { get; set; } + public string? AccountName { get; set; } + public decimal Debit { get; set; } + public decimal Credit { get; set; } +} - public class BalanceSheet - { - public int AccountId { get; set; } - public int AccountClassId { get; set; } - public string AccountCode { get; set; } - public string AccountName { get; set; } - public decimal Amount { get; set; } - } +public class BalanceSheet +{ + public int AccountId { get; set; } + public int AccountClassId { get; set; } + public string? AccountCode { get; set; } + public string? AccountName { get; set; } + public decimal Amount { get; set; } +} - public class IncomeStatement - { - public int AccountId { get; set; } - public bool IsExpense { get; set; } - public string AccountCode { get; set; } - public string AccountName { get; set; } - public decimal Amount { get; set; } - } +public class IncomeStatement +{ + public int AccountId { get; set; } + public bool IsExpense { get; set; } + public string? AccountCode { get; set; } + public string? AccountName { get; set; } + public decimal Amount { get; set; } +} - public partial class MasterGeneralLedger - { - public int Id { get; set; } - public int AccountId { get; set; } - public int CurrencyId { get; set; } - public string DocumentType { get; set; } - public int TransactionNo { get; set; } - public string AccountCode { get; set; } - public string AccountName { get; set; } - public DateTime Date { get; set; } - public decimal Debit { get; set; } - public decimal Credit { get; set; } - } +public partial class MasterGeneralLedger +{ + public int Id { get; set; } + public int AccountId { get; set; } + public int CurrencyId { get; set; } + public string? DocumentType { get; set; } + public int TransactionNo { get; set; } + public string? AccountCode { get; set; } + public string? AccountName { get; set; } + public DateTime Date { get; set; } + public decimal Debit { get; set; } + public decimal Credit { get; set; } } diff --git a/src/AccountGoWeb/Models/ObjectExtensions.cs b/src/AccountGoWeb/Models/ObjectExtensions.cs index 7e3d8c24b..780d99dfb 100644 --- a/src/AccountGoWeb/Models/ObjectExtensions.cs +++ b/src/AccountGoWeb/Models/ObjectExtensions.cs @@ -1,16 +1,14 @@ using Newtonsoft.Json; -using System.IO; -namespace AccountGoWeb.Models +namespace AccountGoWeb.Models; + +public static class ObjectExtensions { - public static class ObjectExtensions + public static string ToJson(this object obj) { - public static string ToJson(this object obj) - { - JsonSerializer js = JsonSerializer.Create(new JsonSerializerSettings()); - var jw = new StringWriter(); - js.Serialize(jw, obj); - return jw.ToString(); - } + JsonSerializer js = JsonSerializer.Create(new JsonSerializerSettings()); + var jw = new StringWriter(); + js.Serialize(jw, obj); + return jw.ToString(); } } diff --git a/src/AccountGoWeb/Models/Purchasing/Payment.cs b/src/AccountGoWeb/Models/Purchasing/Payment.cs index 58c9f230a..16aae98e5 100644 --- a/src/AccountGoWeb/Models/Purchasing/Payment.cs +++ b/src/AccountGoWeb/Models/Purchasing/Payment.cs @@ -1,19 +1,18 @@ -namespace AccountGoWeb.Models.Purchasing +namespace AccountGoWeb.Models.Purchasing; + +public class Payment { - public class Payment - { - public int InvoiceId { get; set; } - public string InvoiceNo { get; set; } - public int VendorId { get; set; } - public string VendorName { get; set; } - public decimal InvoiceAmount { get; set; } - public decimal AmountPaid { get; set; } - public decimal Balance { get { return InvoiceAmount - AmountPaid; } } - [ExpressiveAnnotations.Attributes.AssertThat("AmountToPay <= Balance", ErrorMessage = "Amount to pay cannot be greater than remaining amount to pay.")] - [ExpressiveAnnotations.Attributes.AssertThat("AmountToPay > 0", ErrorMessage = "Amount to pay cannot be zero.")] - public decimal AmountToPay { get; set; } - [System.ComponentModel.DataAnnotations.Required] - public int? AccountId { get; set; } - public System.DateTime Date { get; set; } - } + public int InvoiceId { get; set; } + public string? InvoiceNo { get; set; } + public int VendorId { get; set; } + public string? VendorName { get; set; } + public decimal InvoiceAmount { get; set; } + public decimal AmountPaid { get; set; } + public decimal Balance { get { return InvoiceAmount - AmountPaid; } } + [ExpressiveAnnotations.Attributes.AssertThat("AmountToPay <= Balance", ErrorMessage = "Amount to pay cannot be greater than remaining amount to pay.")] + [ExpressiveAnnotations.Attributes.AssertThat("AmountToPay > 0", ErrorMessage = "Amount to pay cannot be zero.")] + public decimal AmountToPay { get; set; } + [System.ComponentModel.DataAnnotations.Required] + public int? AccountId { get; set; } + public System.DateTime Date { get; set; } } diff --git a/src/AccountGoWeb/Models/Sales/AddSalesReceipt.cs b/src/AccountGoWeb/Models/Sales/AddSalesReceipt.cs index 67f10e309..377a9ecdb 100644 --- a/src/AccountGoWeb/Models/Sales/AddSalesReceipt.cs +++ b/src/AccountGoWeb/Models/Sales/AddSalesReceipt.cs @@ -1,20 +1,19 @@ -namespace AccountGoWeb.Models.Sales +namespace AccountGoWeb.Models.Sales; + +public class AddReceipt { - public class AddReceipt - { - [System.ComponentModel.DataAnnotations.Required] - public int? AccountToDebitId { get; set; } - [System.ComponentModel.DataAnnotations.Required] - public int? AccountToCreditId { get; set; } - [System.ComponentModel.DataAnnotations.Required] - public int? CustomerId { get; set; } - public System.DateTime ReceiptDate {get;set;} - [ExpressiveAnnotations.Attributes.AssertThat("Amount > 0", ErrorMessage = "Amount cannot be zero.")] - public decimal Amount { get; set; } + [System.ComponentModel.DataAnnotations.Required] + public int? AccountToDebitId { get; set; } + [System.ComponentModel.DataAnnotations.Required] + public int? AccountToCreditId { get; set; } + [System.ComponentModel.DataAnnotations.Required] + public int? CustomerId { get; set; } + public System.DateTime ReceiptDate {get;set;} + [ExpressiveAnnotations.Attributes.AssertThat("Amount > 0", ErrorMessage = "Amount cannot be zero.")] + public decimal Amount { get; set; } - public AddReceipt() - { - ReceiptDate = System.DateTime.Now; - } + public AddReceipt() + { + ReceiptDate = System.DateTime.Now; } } diff --git a/src/AccountGoWeb/Models/Sales/Allocate.cs b/src/AccountGoWeb/Models/Sales/Allocate.cs index 78da38d73..704ead295 100644 --- a/src/AccountGoWeb/Models/Sales/Allocate.cs +++ b/src/AccountGoWeb/Models/Sales/Allocate.cs @@ -1,51 +1,48 @@ -using System.Collections.Generic; +namespace AccountGoWeb.Models.Sales; -namespace AccountGoWeb.Models.Sales +public class Allocate { - public class Allocate - { - [System.ComponentModel.DataAnnotations.Required] - public int? CustomerId { get; set; } - [System.ComponentModel.DataAnnotations.Required] - public int? ReceiptId { get; set; } - [System.ComponentModel.DataAnnotations.Required] - public System.DateTime Date { get; set; } - public decimal Amount { get; set; } - public decimal RemainingAmountToAllocate { get; set; } - public decimal SumAllocatedAmount { get { return ComputeSumToAllocateAmount(); } } - public IList AllocationLines { get; set; } - - public Allocate() - { - AllocationLines = new List(); - } + [System.ComponentModel.DataAnnotations.Required] + public int? CustomerId { get; set; } + [System.ComponentModel.DataAnnotations.Required] + public int? ReceiptId { get; set; } + [System.ComponentModel.DataAnnotations.Required] + public System.DateTime Date { get; set; } + public decimal Amount { get; set; } + public decimal RemainingAmountToAllocate { get; set; } + public decimal SumAllocatedAmount { get { return ComputeSumToAllocateAmount(); } } + public IList AllocationLines { get; set; } - private decimal ComputeSumToAllocateAmount() - { - decimal sum = 0; + public Allocate() + { + AllocationLines = new List(); + } - foreach (var line in AllocationLines) { - sum += line.AmountToAllocate.GetValueOrDefault(); - } + private decimal ComputeSumToAllocateAmount() + { + decimal sum = 0; - return sum; + foreach (var line in AllocationLines) { + sum += line.AmountToAllocate.GetValueOrDefault(); } - public bool IsValid() - { - if (RemainingAmountToAllocate < SumAllocatedAmount) - return false; - else - return true; - } + return sum; } - public class AllocationLine + public bool IsValid() { - [System.ComponentModel.DataAnnotations.Required] - public int? InvoiceId { get; set; } - public decimal? Amount { get; set; } - public decimal? AllocatedAmount { get; set; } - public decimal? AmountToAllocate { get; set; } - } + if (RemainingAmountToAllocate < SumAllocatedAmount) + return false; + else + return true; + } } + +public class AllocationLine +{ + [System.ComponentModel.DataAnnotations.Required] + public int? InvoiceId { get; set; } + public decimal? Amount { get; set; } + public decimal? AllocatedAmount { get; set; } + public decimal? AmountToAllocate { get; set; } +} diff --git a/src/AccountGoWeb/Models/Sales/SalesQuotation.cs b/src/AccountGoWeb/Models/Sales/SalesQuotation.cs new file mode 100644 index 000000000..226716fca --- /dev/null +++ b/src/AccountGoWeb/Models/Sales/SalesQuotation.cs @@ -0,0 +1,21 @@ +namespace AccountGoWeb.Models.Sales; +public class SalesQuotations { + [System.ComponentModel.DataAnnotations.Required] + public int CustomerId { get; set; } + [System.ComponentModel.DataAnnotations.Required] + public int PaymentTermId { get; set; } + [System.ComponentModel.DataAnnotations.Required] + public int ItemId { get; set; } + [System.ComponentModel.DataAnnotations.Required] + public int Quantity { get; set; } + [ExpressiveAnnotations.Attributes.AssertThat("Amount > 0", ErrorMessage = "Amount cannot be zero.")] + public decimal Amount { get; set; } + public System.DateTime Date { get; set; } + public decimal Discount { get; set; } + + public SalesQuotations() + { + Date = System.DateTime.Now; + } + +} \ No newline at end of file diff --git a/src/AccountGoWeb/Models/SelectListItemHelper.cs b/src/AccountGoWeb/Models/SelectListItemHelper.cs index 39b6ccf30..2546a4da1 100644 --- a/src/AccountGoWeb/Models/SelectListItemHelper.cs +++ b/src/AccountGoWeb/Models/SelectListItemHelper.cs @@ -1,152 +1,147 @@ -using Microsoft.Extensions.Configuration; -using System.Collections.Generic; -using System.Net.Http; +namespace AccountGoWeb.Models; -namespace AccountGoWeb.Models +public static class SelectListItemHelper { - public static class SelectListItemHelper - { - public static IConfiguration _config; + public static IConfiguration? _config; - public static IEnumerable Accounts() - { - var accounts = GetAsync>("common/postingaccounts").Result; + public static IEnumerable Accounts() + { + var accounts = GetAsync>("common/postingaccounts").Result; - var selectAccounts = new HashSet(); - selectAccounts.Add(new Microsoft.AspNetCore.Mvc.Rendering.SelectListItem() { Value = "", Text = "" }); - foreach (var account in accounts) - selectAccounts.Add(new Microsoft.AspNetCore.Mvc.Rendering.SelectListItem() { Value = account.Id.ToString(), Text = account.AccountName }); + var selectAccounts = new HashSet(); + selectAccounts.Add(new Microsoft.AspNetCore.Mvc.Rendering.SelectListItem() { Value = "", Text = "" }); + foreach (var account in accounts) + selectAccounts.Add(new Microsoft.AspNetCore.Mvc.Rendering.SelectListItem() { Value = account.Id.ToString(), Text = account.AccountName }); - return selectAccounts; - } + return selectAccounts; + } - public static IEnumerable TaxGroups() - { - var taxGroups = GetAsync>("tax/taxgroups").Result; - var selectTaxGroups = new HashSet(); - selectTaxGroups.Add(new Microsoft.AspNetCore.Mvc.Rendering.SelectListItem() { Value = "", Text = "" }); - foreach (var taxGroup in taxGroups) - selectTaxGroups.Add(new Microsoft.AspNetCore.Mvc.Rendering.SelectListItem() { Value = taxGroup.Id.ToString(), Text = taxGroup.Description }); + public static IEnumerable TaxGroups() + { + var taxGroups = GetAsync>("tax/taxgroups").Result; + var selectTaxGroups = new HashSet(); + selectTaxGroups.Add(new Microsoft.AspNetCore.Mvc.Rendering.SelectListItem() { Value = "", Text = "" }); + foreach (var taxGroup in taxGroups) + selectTaxGroups.Add(new Microsoft.AspNetCore.Mvc.Rendering.SelectListItem() { Value = taxGroup.Id.ToString(), Text = taxGroup.Description }); - return selectTaxGroups; - } + return selectTaxGroups; + } - public static IEnumerable ItemTaxGroups() - { - var itemtaxgroups = GetAsync>("tax/itemtaxgroups").Result; - var selectitemtaxgroups = new HashSet(); - selectitemtaxgroups.Add(new Microsoft.AspNetCore.Mvc.Rendering.SelectListItem() { Value = "", Text = "" }); - foreach (var taxGroup in itemtaxgroups) - selectitemtaxgroups.Add(new Microsoft.AspNetCore.Mvc.Rendering.SelectListItem() { Value = taxGroup.Id.ToString(), Text = taxGroup.Name }); + public static IEnumerable ItemTaxGroups() + { + var itemtaxgroups = GetAsync>("tax/itemtaxgroups").Result; + var selectitemtaxgroups = new HashSet(); + selectitemtaxgroups.Add(new Microsoft.AspNetCore.Mvc.Rendering.SelectListItem() { Value = "", Text = "" }); + foreach (var taxGroup in itemtaxgroups) + selectitemtaxgroups.Add(new Microsoft.AspNetCore.Mvc.Rendering.SelectListItem() { Value = taxGroup.Id.ToString(), Text = taxGroup.Name }); - return selectitemtaxgroups; - } + return selectitemtaxgroups; + } - public static IEnumerable PaymentTerms() - { - var paymentTerms = GetAsync>("common/paymentterms").Result; - var selectPaymentTerms = new HashSet(); - selectPaymentTerms.Add(new Microsoft.AspNetCore.Mvc.Rendering.SelectListItem() { Value = "", Text = "" }); - foreach (var term in paymentTerms) - selectPaymentTerms.Add(new Microsoft.AspNetCore.Mvc.Rendering.SelectListItem() { Value = term.Id.ToString(), Text = term.Description }); + public static IEnumerable PaymentTerms() + { + var paymentTerms = GetAsync>("common/paymentterms").Result; + var selectPaymentTerms = new HashSet(); + selectPaymentTerms.Add(new Microsoft.AspNetCore.Mvc.Rendering.SelectListItem() { Value = "", Text = "" }); + foreach (var term in paymentTerms) + selectPaymentTerms.Add(new Microsoft.AspNetCore.Mvc.Rendering.SelectListItem() { Value = term.Id.ToString(), Text = term.Description }); - return selectPaymentTerms; - } + return selectPaymentTerms; + } - public static IEnumerable UnitOfMeasurements() - { - var uoms = GetAsync>("common/measurements").Result; - var selectUOMS = new HashSet(); - selectUOMS.Add(new Microsoft.AspNetCore.Mvc.Rendering.SelectListItem() { Value = "", Text = "" }); - foreach (var item in uoms) - selectUOMS.Add(new Microsoft.AspNetCore.Mvc.Rendering.SelectListItem() { Value = item.Id.ToString(), Text = item.Description }); + public static IEnumerable UnitOfMeasurements() + { + var uoms = GetAsync>("common/measurements").Result; + var selectUOMS = new HashSet(); + selectUOMS.Add(new Microsoft.AspNetCore.Mvc.Rendering.SelectListItem() { Value = "", Text = "" }); + foreach (var item in uoms) + selectUOMS.Add(new Microsoft.AspNetCore.Mvc.Rendering.SelectListItem() { Value = item.Id.ToString(), Text = item.Description }); - return selectUOMS; - } + return selectUOMS; + } - public static IEnumerable ItemCategories() - { - var categories = GetAsync>("common/itemcategories").Result; - var selectCategories = new HashSet(); - selectCategories.Add(new Microsoft.AspNetCore.Mvc.Rendering.SelectListItem() { Value = "", Text = "" }); - foreach (var item in categories) - selectCategories.Add(new Microsoft.AspNetCore.Mvc.Rendering.SelectListItem() { Value = item.Id.ToString(), Text = item.Name }); + public static IEnumerable ItemCategories() + { + var categories = GetAsync>("common/itemcategories").Result; + var selectCategories = new HashSet(); + selectCategories.Add(new Microsoft.AspNetCore.Mvc.Rendering.SelectListItem() { Value = "", Text = "" }); + foreach (var item in categories) + selectCategories.Add(new Microsoft.AspNetCore.Mvc.Rendering.SelectListItem() { Value = item.Id.ToString(), Text = item.Name }); - return selectCategories; - } + return selectCategories; + } - public static IEnumerable CashBanks() - { - var cashBanks = GetAsync>("common/cashbanks").Result; - var selectCashBanks = new HashSet(); - selectCashBanks.Add(new Microsoft.AspNetCore.Mvc.Rendering.SelectListItem() { Value = "", Text = "" }); - foreach (var item in cashBanks) - selectCashBanks.Add(new Microsoft.AspNetCore.Mvc.Rendering.SelectListItem() { Value = item.Id.ToString(), Text = item.Name }); + public static IEnumerable CashBanks() + { + var cashBanks = GetAsync>("common/cashbanks").Result; + var selectCashBanks = new HashSet(); + selectCashBanks.Add(new Microsoft.AspNetCore.Mvc.Rendering.SelectListItem() { Value = "", Text = "" }); + foreach (var item in cashBanks) + selectCashBanks.Add(new Microsoft.AspNetCore.Mvc.Rendering.SelectListItem() { Value = item.Id.ToString(), Text = item.Name }); - return selectCashBanks; - } + return selectCashBanks; + } - public static IEnumerable Customers() - { - var customers = GetAsync>("sales/customers").Result; - var selectCustomers = new HashSet(); - selectCustomers.Add(new Microsoft.AspNetCore.Mvc.Rendering.SelectListItem() { Value = "", Text = "" }); - foreach (var item in customers) - selectCustomers.Add(new Microsoft.AspNetCore.Mvc.Rendering.SelectListItem() { Value = item.Id.ToString(), Text = item.Name }); + public static IEnumerable Customers() + { + var customers = GetAsync>("sales/customers").Result; + var selectCustomers = new HashSet(); + selectCustomers.Add(new Microsoft.AspNetCore.Mvc.Rendering.SelectListItem() { Value = "", Text = "" }); + foreach (var item in customers) + selectCustomers.Add(new Microsoft.AspNetCore.Mvc.Rendering.SelectListItem() { Value = item.Id.ToString(), Text = item.Name }); - return selectCustomers; - } + return selectCustomers; + } - public static IEnumerable Vendors() - { - var vendors = GetAsync>("purchasing/vendors").Result; - var selectVendors = new HashSet(); - selectVendors.Add(new Microsoft.AspNetCore.Mvc.Rendering.SelectListItem() { Value = "", Text = "" }); - foreach (var item in vendors) - selectVendors.Add(new Microsoft.AspNetCore.Mvc.Rendering.SelectListItem() { Value = item.Id.ToString(), Text = item.Name }); + public static IEnumerable Vendors() + { + var vendors = GetAsync>("purchasing/vendors").Result; + var selectVendors = new HashSet(); + selectVendors.Add(new Microsoft.AspNetCore.Mvc.Rendering.SelectListItem() { Value = "", Text = "" }); + foreach (var item in vendors) + selectVendors.Add(new Microsoft.AspNetCore.Mvc.Rendering.SelectListItem() { Value = item.Id.ToString(), Text = item.Name }); - return selectVendors; - } + return selectVendors; + } - public static IEnumerable Items() - { - var items = GetAsync>("inventory/items").Result; - var selectItems = new HashSet(); - selectItems.Add(new Microsoft.AspNetCore.Mvc.Rendering.SelectListItem() { Value = "", Text = "" }); - foreach (var item in items) - selectItems.Add(new Microsoft.AspNetCore.Mvc.Rendering.SelectListItem() { Value = item.Id.ToString(), Text = item.Description }); + public static IEnumerable Items() + { + var items = GetAsync>("inventory/items").Result; + var selectItems = new HashSet(); + selectItems.Add(new Microsoft.AspNetCore.Mvc.Rendering.SelectListItem() { Value = "", Text = "" }); + foreach (var item in items) + selectItems.Add(new Microsoft.AspNetCore.Mvc.Rendering.SelectListItem() { Value = item.Id.ToString(), Text = item.Description }); - return selectItems; - } + return selectItems; + } - public static IEnumerable Measurements() - { - var measurements = GetAsync>("inventory/items").Result; - var selectMeasurements = new HashSet(); - selectMeasurements.Add(new Microsoft.AspNetCore.Mvc.Rendering.SelectListItem() { Value = "", Text = "" }); - foreach (var item in measurements) - selectMeasurements.Add(new Microsoft.AspNetCore.Mvc.Rendering.SelectListItem() { Value = item.Id.ToString(), Text = item.Description }); + public static IEnumerable Measurements() + { + var measurements = GetAsync>("common/measurements").Result; + var selectMeasurements = new HashSet(); + selectMeasurements.Add(new Microsoft.AspNetCore.Mvc.Rendering.SelectListItem() { Value = "", Text = "" }); + foreach (var item in measurements) + selectMeasurements.Add(new Microsoft.AspNetCore.Mvc.Rendering.SelectListItem() { Value = item.Id.ToString(), Text = item.Description }); - return selectMeasurements; - } + return selectMeasurements; + } - #region Private methods - public static async System.Threading.Tasks.Task GetAsync(string uri) + #region Private methods + public static async System.Threading.Tasks.Task GetAsync(string uri) + { + string responseJson = string.Empty; + using (var client = new HttpClient()) { - string responseJson = string.Empty; - using (var client = new HttpClient()) + var baseUri = _config!["ApiUrl"]; + client.BaseAddress = new System.Uri(baseUri!); + client.DefaultRequestHeaders.Accept.Clear(); + var response = await client.GetAsync(baseUri + uri); + if (response.IsSuccessStatusCode) { - var baseUri = _config["ApiUrl"]; - client.BaseAddress = new System.Uri(baseUri); - client.DefaultRequestHeaders.Accept.Clear(); - var response = await client.GetAsync(baseUri + uri); - if (response.IsSuccessStatusCode) - { - responseJson = await response.Content.ReadAsStringAsync(); - } + responseJson = await response.Content.ReadAsStringAsync(); } - return Newtonsoft.Json.JsonConvert.DeserializeObject(responseJson); } - #endregion + return Newtonsoft.Json.JsonConvert.DeserializeObject(responseJson)!; } + #endregion } diff --git a/src/AccountGoWeb/Models/TaxSystem/BaseViewModel.cs b/src/AccountGoWeb/Models/TaxSystem/BaseViewModel.cs new file mode 100644 index 000000000..3164ec6b7 --- /dev/null +++ b/src/AccountGoWeb/Models/TaxSystem/BaseViewModel.cs @@ -0,0 +1,9 @@ +namespace AccountGoWeb.Models.TaxSystem +{ + public abstract class BaseViewModel + { + public virtual int Id { get; set; } + // TODO: Get the user from the logged in user + public string? ModifiedBy { get; set; } + } +} diff --git a/src/AccountGoWeb/Models/TaxSystem/EditTaxViewModel.cs b/src/AccountGoWeb/Models/TaxSystem/EditTaxViewModel.cs new file mode 100644 index 000000000..68aded1b5 --- /dev/null +++ b/src/AccountGoWeb/Models/TaxSystem/EditTaxViewModel.cs @@ -0,0 +1,13 @@ +using System.ComponentModel.DataAnnotations; + +namespace AccountGoWeb.Models.TaxSystem +{ + public class EditTaxViewModel + { + public int SalesAccountId { get; set; } + public int PurchaseAccountId { get; set; } + public Tax? Tax { get; set; } + public TaxGroup? TaxGroup { get; set; } + public ItemTaxGroup? ItemTaxGroup { get; set; } + } +} diff --git a/src/AccountGoWeb/Models/TaxSystem/ItemTaxGroup.cs b/src/AccountGoWeb/Models/TaxSystem/ItemTaxGroup.cs new file mode 100644 index 000000000..a525eb093 --- /dev/null +++ b/src/AccountGoWeb/Models/TaxSystem/ItemTaxGroup.cs @@ -0,0 +1,20 @@ +using System.ComponentModel.DataAnnotations; + +namespace AccountGoWeb.Models.TaxSystem +{ + public class ItemTaxGroup : BaseViewModel + { + [Required(ErrorMessage = "The Tax Group Name field is Required.")] + [StringLength(50)] + public string? Name { get; set; } + + [Display(Name = "Fully Exempt")] + public bool IsFullyExempt { get; set; } + public IList Taxes { get; set; } + + public ItemTaxGroup() + { + Taxes = new List(); + } + } +} diff --git a/src/AccountGoWeb/Models/TaxSystem/ItemTaxGroupTax.cs b/src/AccountGoWeb/Models/TaxSystem/ItemTaxGroupTax.cs new file mode 100644 index 000000000..8ed90b741 --- /dev/null +++ b/src/AccountGoWeb/Models/TaxSystem/ItemTaxGroupTax.cs @@ -0,0 +1,9 @@ +namespace AccountGoWeb.Models.TaxSystem +{ + public class ItemTaxGroupTax + { + public int TaxId { get; set; } + public int ItemTaxGroupId { get; set; } + public bool IsExempt { get; set; } + } +} diff --git a/src/AccountGoWeb/Models/TaxSystem/Tax.cs b/src/AccountGoWeb/Models/TaxSystem/Tax.cs new file mode 100644 index 000000000..644a6af0c --- /dev/null +++ b/src/AccountGoWeb/Models/TaxSystem/Tax.cs @@ -0,0 +1,20 @@ +using System.ComponentModel.DataAnnotations; + +namespace AccountGoWeb.Models.TaxSystem +{ + public class Tax : BaseViewModel + { + [Required(ErrorMessage = "The Tax Name field is Required.")] + [StringLength(50)] + public string? TaxName { get; set; } + + [Required(ErrorMessage = "The Tax Code field is Required.")] + [StringLength(16)] + public string? TaxCode { get; set; } + + [Range(0, 100, ErrorMessage = "The Rate field must be between 0 and 100.")] + public decimal Rate { get; set; } + + public bool IsActive { get; set; } + } +} diff --git a/src/AccountGoWeb/Models/TaxSystem/TaxGroup.cs b/src/AccountGoWeb/Models/TaxSystem/TaxGroup.cs new file mode 100644 index 000000000..eae959112 --- /dev/null +++ b/src/AccountGoWeb/Models/TaxSystem/TaxGroup.cs @@ -0,0 +1,20 @@ +using System.ComponentModel.DataAnnotations; + +namespace AccountGoWeb.Models.TaxSystem +{ + public class TaxGroup : BaseViewModel + { + [Required(ErrorMessage = "The Tax Group Name field is Required.")] + [StringLength(50)] + public string? Description { get; set; } + public bool TaxAppliedToShipping { get; set; } + public bool IsActive { get; set; } + + public IList Taxes { get; set; } + + public TaxGroup() + { + Taxes = new List(); + } + } +} diff --git a/src/AccountGoWeb/Models/TaxSystem/TaxGroupTax.cs b/src/AccountGoWeb/Models/TaxSystem/TaxGroupTax.cs new file mode 100644 index 000000000..7edf4094c --- /dev/null +++ b/src/AccountGoWeb/Models/TaxSystem/TaxGroupTax.cs @@ -0,0 +1,8 @@ +namespace AccountGoWeb.Models.TaxSystem +{ + public class TaxGroupTax + { + public int TaxId { get; set; } + public int TaxGroupId { get; set; } + } +} diff --git a/src/AccountGoWeb/Models/TaxSystem/TaxSystemViewModel.cs b/src/AccountGoWeb/Models/TaxSystem/TaxSystemViewModel.cs index 23f09a05d..46a4fcbbf 100644 --- a/src/AccountGoWeb/Models/TaxSystem/TaxSystemViewModel.cs +++ b/src/AccountGoWeb/Models/TaxSystem/TaxSystemViewModel.cs @@ -1,11 +1,9 @@ using Dto.TaxSystem; -namespace AccountGoWeb.Models.TaxSystem +namespace AccountGoWeb.Models.TaxSystem; +public class TaxSystemViewModel { - public class TaxSystemViewModel - { - public System.Collections.Generic.IEnumerable Taxes { get; set; } - public System.Collections.Generic.IEnumerable TaxGroups { get; set; } - public System.Collections.Generic.IEnumerable ItemTaxGroups { get; set; } - } + public System.Collections.Generic.IEnumerable? Taxes { get; set; } + public System.Collections.Generic.IEnumerable? TaxGroups { get; set; } + public System.Collections.Generic.IEnumerable? ItemTaxGroups { get; set; } } diff --git a/src/AccountGoWeb/Program.cs b/src/AccountGoWeb/Program.cs index 79f367cc5..d917bdf31 100644 --- a/src/AccountGoWeb/Program.cs +++ b/src/AccountGoWeb/Program.cs @@ -1,26 +1,60 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Hosting; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.Hosting; -using Microsoft.Extensions.Logging; - -namespace AccountGoWeb +using AccountGoWeb.Components; +using Microsoft.AspNetCore.Authentication.Cookies; + +var builder = WebApplication.CreateBuilder(args); + +builder.AddServiceDefaults(); + +// Mapping +builder.Services.AddAutoMapper(typeof(Program)); + +builder.Services.AddControllersWithViews(); + +string apiurl = System.Environment.GetEnvironmentVariable("APIURL") ?? "http://localhost:8001/api/"; + +builder.Configuration["ApiUrl"] = apiurl; +Console.WriteLine($"[ASPNETCORE SERVER] API URL {builder.Configuration["ApiUrl"]}"); + +builder.Services.AddHttpClient(); + +// Configure HttpClient for Blazor components +builder.Services.AddScoped(sp => new HttpClient +{ + BaseAddress = new Uri(apiurl) +}); +builder.Services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme) + .AddCookie(o => o.LoginPath = new PathString("/account/signin")); + +builder.Services + .AddRazorComponents() + .AddInteractiveServerComponents() + .AddCircuitOptions(options => options.DetailedErrors = true); // for debugging razor components + +var app = builder.Build(); + +// Configure the HTTP request pipeline. +if (!app.Environment.IsDevelopment()) { - public class Program - { - public static void Main(string[] args) - { - CreateHostBuilder(args).Build().Run(); - } - - public static IHostBuilder CreateHostBuilder(string[] args) => - Host.CreateDefaultBuilder(args) - .ConfigureWebHostDefaults(webBuilder => - { - webBuilder.UseStartup(); - }); - } + app.UseExceptionHandler("/Home/Error"); + // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts. + app.UseHsts(); } + +app.UseHttpsRedirection(); +app.UseStaticFiles(); + +app.UseRouting(); + +app.UseAuthentication(); +app.UseAntiforgery(); +app.UseAuthorization(); + +// Map Blazor components first so they take precedence +app.MapRazorComponents() + .AddInteractiveServerRenderMode(); + +app.MapControllerRoute( + name: "default", + pattern: "{controller=Home}/{action=Index}/{id?}"); + +app.Run(); diff --git a/src/AccountGoWeb/Properties/launchSettings.json b/src/AccountGoWeb/Properties/launchSettings.json index 0c2a63276..684f35562 100644 --- a/src/AccountGoWeb/Properties/launchSettings.json +++ b/src/AccountGoWeb/Properties/launchSettings.json @@ -13,9 +13,9 @@ "launchBrowser": true, "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" - } - }, - "AccountGoWeb": { + } + }, + "AccountGoWeb": { "commandName": "Project", "launchBrowser": true, "environmentVariables": { diff --git a/src/AccountGoWeb/Resource.Designer.cs b/src/AccountGoWeb/Resource.Designer.cs index 2267688fc..4fa30fbab 100644 --- a/src/AccountGoWeb/Resource.Designer.cs +++ b/src/AccountGoWeb/Resource.Designer.cs @@ -8,55 +8,54 @@ // //------------------------------------------------------------------------------ -namespace AccountGoWeb { - using System; - using System.Reflection; +namespace AccountGoWeb; + +using System.Reflection; + + +/// +/// A strongly-typed resource class, for looking up localized strings, etc. +/// +// This class was auto-generated by the StronglyTypedResourceBuilder +// class via a tool like ResGen or Visual Studio. +// To add or remove a member, edit your .ResX file then rerun ResGen +// with the /str option, or rebuild your VS project. +[global::System.Diagnostics.DebuggerNonUserCodeAttribute()] +[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] +public class Resource { + + private static global::System.Resources.ResourceManager resourceMan; + private static global::System.Globalization.CultureInfo resourceCulture; + + internal Resource() { + } /// - /// A strongly-typed resource class, for looking up localized strings, etc. + /// Returns the cached ResourceManager instance used by this class. /// - // This class was auto-generated by the StronglyTypedResourceBuilder - // class via a tool like ResGen or Visual Studio. - // To add or remove a member, edit your .ResX file then rerun ResGen - // with the /str option, or rebuild your VS project. - [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] - [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - public class Resource { - - private static global::System.Resources.ResourceManager resourceMan; - - private static global::System.Globalization.CultureInfo resourceCulture; - - internal Resource() { - } - - /// - /// Returns the cached ResourceManager instance used by this class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - public static global::System.Resources.ResourceManager ResourceManager { - get { - if (object.ReferenceEquals(resourceMan, null)) { - global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("AccountGoWeb.Resource", typeof(Resource).GetTypeInfo().Assembly); - resourceMan = temp; - } - return resourceMan; + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + public static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("AccountGoWeb.Resource", typeof(Resource).GetTypeInfo().Assembly); + resourceMan = temp; } + return resourceMan; } - - /// - /// Overrides the current thread's CurrentUICulture property for all - /// resource lookups using this strongly typed resource class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - public static global::System.Globalization.CultureInfo Culture { - get { - return resourceCulture; - } - set { - resourceCulture = value; - } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + public static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; } } } diff --git a/src/AccountGoWeb/Scripts/Home/Index.tsx b/src/AccountGoWeb/Scripts/Home/Index.tsx deleted file mode 100644 index 997e4a6c0..000000000 --- a/src/AccountGoWeb/Scripts/Home/Index.tsx +++ /dev/null @@ -1,23 +0,0 @@ -import * as React from "react"; -import * as ReactDOM from "react-dom"; - -interface IHomeProps { - pageTitle: string; -} - -class Home extends React.Component { - render() { - return ( -
- Tiles or widgets here! -
- ); - } -} - -ReactDOM.render( - , - document.getElementById("home") -); - -export default Home; \ No newline at end of file diff --git a/src/AccountGoWeb/Scripts/Quotations/SalesQuotation.tsx b/src/AccountGoWeb/Scripts/Quotations/SalesQuotation.tsx deleted file mode 100644 index 5bd89650c..000000000 --- a/src/AccountGoWeb/Scripts/Quotations/SalesQuotation.tsx +++ /dev/null @@ -1,403 +0,0 @@ -import * as React from "react"; -import * as ReactDOM from "react-dom"; -import {observer} from "mobx-react"; -import * as d3 from "d3"; -import {autorun, observable} from 'mobx'; -import * as accounting from "accounting"; - -import SelectCustomer from "../Shared/Components/SelectCustomer"; -import SelectPaymentTerm from "../Shared/Components/SelectPaymentTerm"; -import SelectLineItem from "../Shared/Components/SelectLineItem"; -import SelectLineMeasurement from "../Shared/Components/SelectLineMeasurement"; - -import SalesQuotationLine from "../Shared/Stores/Quotations/SalesQuotationLine"; -import SalesQuotationStore from "../Shared/Stores/Quotations/SalesQuotationStore"; - - -let quotationId = window.location.search.split("?id=")[1]; - -let store = new SalesQuotationStore(quotationId); - -@observer -class ValidationErrors extends React.Component{ - render() { - - if (store.validationErrors !== undefined && store.validationErrors.length > 0) { - var errors = []; - store.validationErrors.map(function (item, index) { - errors.push(
  • {item}
  • ); - }); - return ( -
    -
      - {errors} -
    -
    - - ); - } - return null; - } -} - -@observer -class SaveQuotationButton extends React.Component{ - saveNewSalesQuotation(e) { - store.saveNewQuotation(); - } - //className = {!store.salesInvoice.posted && store.editMode - render() { - return ( - - ); - } -} - -class CancelQuotationButton extends React.Component{ - cancelOnClick() { - let baseUrl = location.protocol - + "//" + location.hostname - + (location.port && ":" + location.port) - + "/"; - - window.location.href = baseUrl + 'quotations'; - } - - render() { - return ( - - ); - } -} - -@observer -class SalesQuotationHeader extends React.Component{ - onChangeQuotationDate(e) { - store.changedQuotationDate(e.target.value); - } - - - - onChangeCustomer(e) { - alert(''); - } - - onChangeReferenceNo(e) { - store.changedReferenceNo(e.target.value); - } - - - render() { - return ( -
    -
    - Customer Information - {store.salesQuotation.customerId} -
    -
    -
    -
    -
    Customer
    -
    -
    -
    -
    Payment Term
    -
    -
    -
    -
    -
    -
    Date
    -
    - -
    -
    -
    Reference no.
    -
    - -
    -
    -
    Status
    -
    - -
    -
    -
    -
    - ); - } -} - -@observer -class SalesQuotationLines extends React.Component{ - - - - addLineItem() { - - if (store.validationLine()) { - - var itemId, measurementId, quantity, amount, discount, code; - itemId = (document.getElementById("optNewItemId") as HTMLInputElement).value; - - measurementId = (document.getElementById("optNewMeasurementId") as HTMLInputElement).value; - quantity = (document.getElementById("txtNewQuantity") as HTMLInputElement).value; - amount = (document.getElementById("txtNewAmount") as HTMLInputElement).value; - discount = (document.getElementById("txtNewDiscount") as HTMLInputElement).value; - code = (document.getElementById("txtNewCode") as HTMLInputElement).value; - //console.log(`itemId: ${itemId} | measurementId: ${measurementId} | quantity: ${quantity} | amount: ${amount} | discount: ${discount}`); - store.addLineItem(0, itemId, measurementId, quantity, amount, discount, code); - - - (document.getElementById("optNewItemId") as HTMLInputElement).value = ""; - (document.getElementById("txtNewCode") as HTMLInputElement).value = ""; - (document.getElementById("optNewMeasurementId") as HTMLInputElement).value = ""; - (document.getElementById("txtNewQuantity") as HTMLInputElement).value = "1"; - (document.getElementById("txtNewAmount") as HTMLInputElement).value = ""; - (document.getElementById("txtNewDiscount") as HTMLInputElement).value = ""; - - } - - } - - onClickRemoveLineItem(i, e) { - store.removeLineItem(i); - } - - onChangeQuantity(e) { - store.updateLineItem(e.target.name, "quantity", e.target.value); - } - - onChangeAmount(e) { - store.updateLineItem(e.target.name, "amount", e.target.value); - } - - onChangeDiscount(e) { - store.updateLineItem(e.target.name, "discount", e.target.value); - } - - onChangeCode(e) { - store.updateLineItem(e.target.name, "code", e.target.value); - } - - - onFocusOutItem(e, isNew, i) { - - var isExisting = false; - for (var x = 0; x < store.commonStore.items.length; x++) { - if (store.commonStore.items[x].code == i.target.value) { - isExisting = true; - if (isNew) { - (document.getElementById("optNewItemId") as HTMLInputElement).value = store.commonStore.items[x].id; - (document.getElementById("optNewMeasurementId") as HTMLInputElement).value = store.commonStore.items[x].sellMeasurementId; - (document.getElementById("txtNewAmount") as HTMLInputElement).value = store.commonStore.items[x].price; - (document.getElementById("txtNewQuantity") as HTMLInputElement).value = "1"; - document.getElementById("txtNewCode").style.borderColor = ""; - } - else { - store.updateLineItem(e, "itemId", store.commonStore.items[x].id); - store.updateLineItem(e, "measurementId", store.commonStore.items[x].sellMeasurementId); - store.updateLineItem(e, "amount", store.commonStore.items[x].price); - store.updateLineItem(e, "quantity", 1); - i.target.style.borderColor = ""; - } - } - } - - if (!isExisting) - - if (isNew) { - (document.getElementById("optNewItemId") as HTMLInputElement).value = ""; - (document.getElementById("optNewMeasurementId") as HTMLInputElement).value = ""; - (document.getElementById("txtNewAmount") as HTMLInputElement).value = ""; - (document.getElementById("txtNewQuantity") as HTMLInputElement).value = ""; - document.getElementById("txtNewCode").style.borderColor = '#FF0000'; - //document.getElementById("txtNewCode").appendChild(span); - // document.getElementById("txtNewCode").style.border = 'solid'; - } - else { - //store.updateLineItem(e, "itemId", ""); - //store.updateLineItem(e, "measurementId", ""); - //store.updateLineItem(e, "amount", ""); - //store.updateLineItem(e, "quantity", ""); - i.target.style.borderColor = "red"; - //i.target.appendChild(span); - // i.target.style.border = "solid"; - - } - - } - - @observable lineNo = 0; - - - render() { - var newLine = 0; - var lineItems = []; - - for (var i = 0; i < store.salesQuotation.salesQuotationLines.length; i++) { - newLine = newLine + 10; - //var initialCode = this.onloadCode(store.salesQuotation.salesQuotationLines[i].itemId); // this is for initial value of code - - - lineItems.push( - - - - - - - - - {store.getLineTotal(i) } - - - - - ); - //autorun(() => this.lineNo = newLine); - } - - return ( -
    -
    - Line Items -
    -
    - - - - - - - - - - - - - - - - {lineItems} - - - - - - - - - - - - -
    NoItemCodeMeasurementQuantityAmountDiscountLine Total
    - -
    -
    -
    - ); - } -} - -@observer -class SalesQuotationTotals extends React.Component{ - render() { - return ( -
    -
    -
    -
    -
    {accounting.formatMoney(store.RTotal, { symbol: "", format: "%s%v" }) }
    -
    -
    {accounting.formatMoney(store.TTotal, { symbol: "", format: "%s%v" }) }
    -
    -
    {accounting.formatMoney(store.GTotal, { symbol: "", format: "%s%v" }) }
    -
    -
    -
    - ); - } -} - -@observer -class BookButton extends React.Component{ - bookOnClick(e) { - store.bookQuotation(); - } - - render() { - return ( - - - ); - } -} - -@observer -class EditButton extends React.Component { - onClickEditButton() { - // Remove " disabledControl" from current className - var nodes = document.getElementById("divSalesQuotationForm").getElementsByTagName('*'); - for (var i = 0; i < nodes.length; i++) { - var subStringLength = nodes[i].className.length - " disabledControl".length; - nodes[i].className = nodes[i].className.substring(0, subStringLength); - } - - store.changedEditMode(true); - } - render() { - return ( - - - Edit - - ); - } - -} - -export default class SalesQuotation extends React.Component { - render() { - return ( -
    -
    - -
    -
    - - - - -
    -
    - - - -
    -
    - ); - } -} - - -ReactDOM.render(, document.getElementById("divSalesQuotation")); - - - \ No newline at end of file diff --git a/src/AccountGoWeb/Scripts/Shared/Stores/AppState.tsx b/src/AccountGoWeb/Scripts/Shared/Stores/AppState.tsx deleted file mode 100644 index 1e237b89f..000000000 --- a/src/AccountGoWeb/Scripts/Shared/Stores/AppState.tsx +++ /dev/null @@ -1,4 +0,0 @@ -import {observable} from 'mobx'; - -export default class AppState { -} \ No newline at end of file diff --git a/src/AccountGoWeb/Scripts/Shared/Stores/Common/CommonStore.tsx b/src/AccountGoWeb/Scripts/Shared/Stores/Common/CommonStore.tsx deleted file mode 100644 index 5b289ee11..000000000 --- a/src/AccountGoWeb/Scripts/Shared/Stores/Common/CommonStore.tsx +++ /dev/null @@ -1,130 +0,0 @@ -import {observable, extendObservable, action} from 'mobx'; -import * as axios from "axios"; - -import Config = require("Config"); - -export default class CommonStore { - @observable customers = []; - @observable paymentTerms = []; - @observable items = []; - @observable measurements = []; - @observable vendors = []; - @observable accounts = []; - @observable salesQuotationStatus = []; - - constructor() { - this.loadCustomersLookup(); - this.loadPaymentTermsLookup(); - this.loadItemsLookup(); - this.loadMeasurementsLookup(); - this.loadVendorsLookup(); - this.loadAccountsLookup(); - this.loadQuotationStatusLookup(); - } - - loadCustomersLookup() { - let customers = this.customers; - axios.get(Config.apiUrl + "common/customers") - .then(function (result) { - const data = result.data; - for (var i = 0; i < Object.keys(data).length; i++) { - customers.push(data[i]); - } - }); - } - - loadPaymentTermsLookup() { - let paymentTerms = this.paymentTerms; - axios.get(Config.apiUrl + "common/paymentterms") - .then(function (result) { - const data = result.data; - for (var i = 0; i < Object.keys(data).length; i++) { - paymentTerms.push(data[i]); - } - }); - } - - loadVendorsLookup() { - let vendors = this.vendors; - axios.get(Config.apiUrl + "common/vendors") - .then(function (result) { - const data = result.data; - for (var i = 0; i < Object.keys(data).length; i++) { - vendors.push(data[i]); - } - }.bind(this)); - } - - loadItemsLookup() { - let items = this.items; - axios.get(Config.apiUrl + "common/items") - .then(function (result) { - const data = result.data; - for (var i = 0; i < Object.keys(data).length; i++) { - items.push(data[i]); - } - }); - } - - loadMeasurementsLookup() { - let measurements = this.measurements; - axios.get(Config.apiUrl + "common/measurements") - .then(function (result) { - const data = result.data; - for (var i = 0; i < Object.keys(data).length; i++) { - measurements.push(data[i]); - } - }); - } - - loadVoucherTypesLookup() { - } - - loadQuotationStatusLookup() { - let quotationStatus = this.salesQuotationStatus; - axios.get(Config.apiUrl + "common/salesquotationstatus") - .then(function (result) { - const data = result.data; - for (var i = 0; i < Object.keys(data).length; i++) - { - quotationStatus.push(data[i]); - } - }) - } - - loadAccountsLookup() { - let accounts = this.accounts; - axios.get(Config.apiUrl + "common/postingaccounts") - .then(function (result) { - const data = result.data; - for (var i = 0; i < Object.keys(data).length; i++) { - accounts.push(data[i]); - } - }); - } - - getApplicableTaxes(itemId: number, partyId: number) { - var result = axios.get(Config.apiUrl + "tax/gettax?itemId=" + itemId + "&partyId=" + partyId); - result.then(function (result) { - return result.data; - }); - } - - getSalesLineTaxAmount(quantity: number, amount: number, discount: number, taxes: any) { - var lineTaxTotal = 0; - amount = (amount * quantity) - discount; - taxes.map(function (tax) { - lineTaxTotal = lineTaxTotal + (amount - (amount / (1 + (tax.rate / 100)))); - }); - return lineTaxTotal; - } - - getPurhcaseLineTaxAmount(quantity: number, amount: number, discount: number, taxes: any) { - var lineTaxTotal = 0; - amount = (amount * quantity) - discount; - taxes.map(function (tax) { - lineTaxTotal = lineTaxTotal + (amount - (amount / (1 + (tax.rate / 100)))); - }); - return lineTaxTotal; - } -} \ No newline at end of file diff --git a/src/AccountGoWeb/Scripts/Shared/Stores/Purchasing/PurchaseInvoice.tsx b/src/AccountGoWeb/Scripts/Shared/Stores/Purchasing/PurchaseInvoice.tsx deleted file mode 100644 index 025b004e8..000000000 --- a/src/AccountGoWeb/Scripts/Shared/Stores/Purchasing/PurchaseInvoice.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import PurchaseInvoiceLine from "./PurchaseInvoiceLine"; - -export default class PurchaseInvoice { - id: number; - fromPurchaseOrderId: number; - vendorId: number; - invoiceDate: Date; - paymentTermId: number; - referenceNo: string; - posted: boolean; - readyForPosting: boolean; - purchaseInvoiceLines: PurchaseInvoiceLine[] = []; - statusId: number; - - constructor() { - this.id = 0; - this.posted = false; - this.readyForPosting = false; - - } -} \ No newline at end of file diff --git a/src/AccountGoWeb/Scripts/Shared/Stores/Purchasing/PurchaseInvoiceLine.tsx b/src/AccountGoWeb/Scripts/Shared/Stores/Purchasing/PurchaseInvoiceLine.tsx deleted file mode 100644 index 52be17a37..000000000 --- a/src/AccountGoWeb/Scripts/Shared/Stores/Purchasing/PurchaseInvoiceLine.tsx +++ /dev/null @@ -1,19 +0,0 @@ -export default class PurchaseInvoiceLine { - id = 0; - itemId; - measurementId; - quantity; - amount; - discount; - remainingQtyToInvoice: number; - - constructor(id, itemId, measurementId, quantity, amount, discount) { - this.id = id; - this.itemId = itemId; - this.measurementId = measurementId; - this.quantity = quantity; - this.amount = amount; - this.discount = discount; - this.remainingQtyToInvoice = quantity; - } -} \ No newline at end of file diff --git a/src/AccountGoWeb/Scripts/Shared/Stores/Purchasing/PurchaseOrder.tsx b/src/AccountGoWeb/Scripts/Shared/Stores/Purchasing/PurchaseOrder.tsx deleted file mode 100644 index b82284b5b..000000000 --- a/src/AccountGoWeb/Scripts/Shared/Stores/Purchasing/PurchaseOrder.tsx +++ /dev/null @@ -1,11 +0,0 @@ -import PurchaseOrderLine from "./PurchaseOrderLine"; - -export default class PurchaseOrder { - id: number; - vendorId: number; - orderDate: Date; - paymentTermId: number; - referenceNo: string; - statusId: number; - purchaseOrderLines: PurchaseOrderLine[] = []; -} \ No newline at end of file diff --git a/src/AccountGoWeb/Scripts/Shared/Stores/Purchasing/PurchaseOrderLine.tsx b/src/AccountGoWeb/Scripts/Shared/Stores/Purchasing/PurchaseOrderLine.tsx deleted file mode 100644 index bd21b5b5f..000000000 --- a/src/AccountGoWeb/Scripts/Shared/Stores/Purchasing/PurchaseOrderLine.tsx +++ /dev/null @@ -1,19 +0,0 @@ -export default class PurchaseOrderLine { - id = 0; - itemId; - measurementId; - quantity; - amount; - discount; - code; - - constructor(id, itemId, measurementId, quantity, amount, discount, code) { - this.id = id; - this.itemId = itemId; - this.measurementId = measurementId; - this.quantity = quantity; - this.amount = amount; - this.discount = discount; - this.code = code; - } -} \ No newline at end of file diff --git a/src/AccountGoWeb/Scripts/Shared/Stores/Quotations/SalesQuotation.tsx b/src/AccountGoWeb/Scripts/Shared/Stores/Quotations/SalesQuotation.tsx deleted file mode 100644 index b692dffdf..000000000 --- a/src/AccountGoWeb/Scripts/Shared/Stores/Quotations/SalesQuotation.tsx +++ /dev/null @@ -1,11 +0,0 @@ -import SalesQuotationLine from "./SalesQuotationLine"; - -export default class SalesQuotation { - id: number; - customerId: number; - quotationDate: Date; - paymentTermId: number; - referenceNo: string; - statusId: number; - salesQuotationLines: SalesQuotationLine[] = []; -} \ No newline at end of file diff --git a/src/AccountGoWeb/Scripts/Shared/Stores/Sales/SalesOrder.tsx b/src/AccountGoWeb/Scripts/Shared/Stores/Sales/SalesOrder.tsx deleted file mode 100644 index 4381b3189..000000000 --- a/src/AccountGoWeb/Scripts/Shared/Stores/Sales/SalesOrder.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import SalesOrderLine from "./SalesOrderLine"; - -//interface ISalesOrder { -// id; -// customerId; -// orderDate; -// paymentTermId; -// referenceNo; -// salesOrderLines: SalesOrderLine[]; -//} - -export default class SalesOrder { - id: number; - customerId: number; - orderDate: Date; - paymentTermId: number; - referenceNo: string; - statusId: number; - quotationId: number; - salesOrderLines: SalesOrderLine[] = []; -} \ No newline at end of file diff --git a/src/AccountGoWeb/Scripts/Shared/Stores/Sales/SalesOrderLine.tsx b/src/AccountGoWeb/Scripts/Shared/Stores/Sales/SalesOrderLine.tsx deleted file mode 100644 index d491ebb40..000000000 --- a/src/AccountGoWeb/Scripts/Shared/Stores/Sales/SalesOrderLine.tsx +++ /dev/null @@ -1,18 +0,0 @@ -export default class SalesOrderLine { - id: number; - itemId: number; - measurementId: number; - quantity: number; - amount: number; - discount: number; - code: number; - constructor(id, itemId, measurementId, quantity, amount, discount, code) { - this.id = id; - this.itemId = itemId; - this.measurementId = measurementId; - this.quantity = quantity; - this.amount = amount; - this.discount = discount; - this.code = code; - } -} \ No newline at end of file diff --git a/src/AccountGoWeb/Scripts/Shared/Stores/TaxSystem/Tax.tsx b/src/AccountGoWeb/Scripts/Shared/Stores/TaxSystem/Tax.tsx deleted file mode 100644 index 08993815f..000000000 --- a/src/AccountGoWeb/Scripts/Shared/Stores/TaxSystem/Tax.tsx +++ /dev/null @@ -1,6 +0,0 @@ -export default class SalesOrder { - id: number; - code: string; - name: string; - rate: number; -} \ No newline at end of file diff --git a/src/AccountGoWeb/Startup.cs b/src/AccountGoWeb/Startup.cs deleted file mode 100644 index d6a8e1e4d..000000000 --- a/src/AccountGoWeb/Startup.cs +++ /dev/null @@ -1,81 +0,0 @@ -using System; -using System.IO; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Hosting; -using Microsoft.AspNetCore.HttpsPolicy; -using Microsoft.AspNetCore.Authentication.Cookies; -using Microsoft.AspNetCore.Http; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Hosting; -using Infrastructure.AssemblyLoader; - -namespace AccountGoWeb -{ - public class Startup - { - public Startup(IConfiguration configuration) - { - Configuration = configuration; - // var builder = new ConfigurationBuilder() - // .SetBasePath(env.ContentRootPath) - // .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) - // .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true) - // .AddEnvironmentVariables(); - // Configuration = builder.Build(); - - string urlhost = System.Environment.GetEnvironmentVariable("APIHOST") ?? "localhost"; - Configuration["ApiUrl"] = $"http://{urlhost}:8001/api/"; - System.Console.WriteLine($"[ASPNETCORE SERVER] API URL {Configuration["ApiUrl"]}"); - } - - public IConfiguration Configuration { get; } - - // This method gets called by the runtime. Use this method to add services to the container. - public void ConfigureServices(IServiceCollection services) - { - services.AddControllersWithViews(); - - services.AddSingleton(Configuration); - - services.AddHttpClient(); - - services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme) - .AddCookie(o => o.LoginPath = new PathString("/account/signin")); - - } - - // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. - public void Configure(IApplicationBuilder app, IWebHostEnvironment env) - { - if (env.IsDevelopment()) - { - app.UseDeveloperExceptionPage(); - } - else - { - app.UseExceptionHandler("/Home/Error"); - // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts. - app.UseHsts(); - } - // app.UseHttpsRedirection(); - app.UseStaticFiles(); - - app.UseRouting(); - - app.UseAuthentication(); - - app.UseAuthorization(); - - app.UseEndpoints(endpoints => - { - endpoints.MapControllerRoute( - name: "default", - pattern: "{controller=Home}/{action=Index}/{id?}"); - }); - } - } -} diff --git a/src/AccountGoWeb/Views/Account/SignIn.cshtml b/src/AccountGoWeb/Views/Account/SignIn.cshtml index f8493b30c..6afaf4883 100644 --- a/src/AccountGoWeb/Views/Account/SignIn.cshtml +++ b/src/AccountGoWeb/Views/Account/SignIn.cshtml @@ -16,12 +16,12 @@ - - - + + + - +
    @@ -74,11 +74,11 @@
    - - - - - - + + + + + + diff --git a/src/AccountGoWeb/Views/Administration/Company.cshtml b/src/AccountGoWeb/Views/Administration/Company.cshtml index acb4dff48..c3e7bdbb0 100644 --- a/src/AccountGoWeb/Views/Administration/Company.cshtml +++ b/src/AccountGoWeb/Views/Administration/Company.cshtml @@ -12,6 +12,7 @@ }

    + @* *@

    +
    + +
    + + +
    +
    diff --git a/src/AccountGoWeb/Views/Audit/GetAttribute.cshtml b/src/AccountGoWeb/Views/Audit/GetAttribute.cshtml new file mode 100644 index 000000000..4fc6eeaf3 --- /dev/null +++ b/src/AccountGoWeb/Views/Audit/GetAttribute.cshtml @@ -0,0 +1,53 @@ +@model Dto.Auditing.AuditableAttribute + +
    +
    +
    + + +
    +

    @(Model.Id == 0 ? "Add New" : "Edit") Auditable Attribute

    +
    + + +
    +
    + + + + + + + +
    + + + +
    + + +
    +
    + +
    + +
    + +
    + + + +
    +
    +
    +
    diff --git a/src/AccountGoWeb/Views/Audit/GetAuditableAttributes.cshtml b/src/AccountGoWeb/Views/Audit/GetAuditableAttributes.cshtml new file mode 100644 index 000000000..bb6effe6f --- /dev/null +++ b/src/AccountGoWeb/Views/Audit/GetAuditableAttributes.cshtml @@ -0,0 +1,71 @@ +@model List +@using AccountGoWeb.Models; + +
    +
    + +
    +
    + + diff --git a/src/AccountGoWeb/Views/Audit/GetAuditableEntities.cshtml b/src/AccountGoWeb/Views/Audit/GetAuditableEntities.cshtml new file mode 100644 index 000000000..a99055355 --- /dev/null +++ b/src/AccountGoWeb/Views/Audit/GetAuditableEntities.cshtml @@ -0,0 +1,78 @@ +@* +@model List +@using AccountGoWeb.Models; + +
    +
    +
    + + + + + +
    +
    +
    +
    +
    +
    + + + +*@ + +@using AccountGoWeb.Components.Pages.Audit +@{ + ViewData["Title"] = "Auditable Entities"; +} + + \ No newline at end of file diff --git a/src/AccountGoWeb/Views/Audit/GetEntity.cshtml b/src/AccountGoWeb/Views/Audit/GetEntity.cshtml new file mode 100644 index 000000000..e03f7b4b6 --- /dev/null +++ b/src/AccountGoWeb/Views/Audit/GetEntity.cshtml @@ -0,0 +1,57 @@ +@* +@model Dto.Auditing.AuditableEntity + +
    +
    +
    + +
    +

    @(Model.Id == 0 ? "Add New" : "Edit") Auditable Entity

    +
    + + +
    +
    + + + + +
    + + + +
    + + +
    +
    + +
    + +
    +
    + + + +
    +
    +
    +
    +*@ + +@using AccountGoWeb.Components.Pages.Audit +@{ + ViewData["Title"] = Model.Id == 0 ? "Add New Auditable Entity" : "Edit Auditable Entity"; +} + + \ No newline at end of file diff --git a/src/AccountGoWeb/Views/Contact/Contact.cshtml b/src/AccountGoWeb/Views/Contact/Contact.cshtml index 197a2dedb..aeb71ecac 100644 --- a/src/AccountGoWeb/Views/Contact/Contact.cshtml +++ b/src/AccountGoWeb/Views/Contact/Contact.cshtml @@ -1,90 +1,8 @@ -@model Dto.Common.Contact - - -
    -
    - @Html.HiddenFor(m => m.Id) - @Html.HiddenFor(m => m.HoldingPartyId) - @Html.HiddenFor(m => m.HoldingPartyType) - - -
    -

    Contact

    -
    - -
    -
    -
    -
    -
    -
    First Name
    -
    - - -
    -
    -
    -
    Last Name
    -
    - - -
    -
    -
    -
    -
    -
    Phone
    -
    - -
    -
    -
    -
    Fax
    -
    - -
    -
    -
    -
    Website
    -
    - -
    -
    -
    -
    Email
    -
    - -
    -
    -
    -
    -
    -
    - - Close -
    -
    - - - @section scripts{ - - - } +@using AccountGoWeb.Components.Pages.Contact +@{ + var contactId = Context.Request.RouteValues["id"]?.ToString() ?? "0"; + var partyId = Context.Request.Query["partyId"].ToString(); + var partyType = Context.Request.Query["partyType"].ToString(); +} + + diff --git a/src/AccountGoWeb/Views/Contact/Contacts.cshtml b/src/AccountGoWeb/Views/Contact/Contacts.cshtml index 1bf6d8d8a..bb7a5d79a 100644 --- a/src/AccountGoWeb/Views/Contact/Contacts.cshtml +++ b/src/AccountGoWeb/Views/Contact/Contacts.cshtml @@ -1,49 +1,7 @@ -@model string +@using AccountGoWeb.Components.Pages.Contact +@{ + var partyId = Context.Request.Query["partyId"].ToString(); + var partyType = Context.Request.Query["partyType"].ToString(); +} - - - -
    -
    -
    - \ No newline at end of file + \ No newline at end of file diff --git a/src/AccountGoWeb/Views/Dashboard/MonthlySales.cshtml b/src/AccountGoWeb/Views/Dashboard/MonthlySales.cshtml index c94ca011a..e3007bbbc 100644 --- a/src/AccountGoWeb/Views/Dashboard/MonthlySales.cshtml +++ b/src/AccountGoWeb/Views/Dashboard/MonthlySales.cshtml @@ -1,7 +1,7 @@  - + + + +} \ No newline at end of file diff --git a/src/AccountGoWeb/Views/Donations/DonationInvoices.cshtml b/src/AccountGoWeb/Views/Donations/DonationInvoices.cshtml new file mode 100644 index 000000000..202125a45 --- /dev/null +++ b/src/AccountGoWeb/Views/Donations/DonationInvoices.cshtml @@ -0,0 +1,6 @@ +@using AccountGoWeb.Components.Pages.Donations +@{ + ViewData["Title"] = "Donation Invoices"; +} + + \ No newline at end of file diff --git a/src/AccountGoWeb/Views/Financials/Accounts.cshtml b/src/AccountGoWeb/Views/Financials/Accounts.cshtml index 3bcf04104..ed13ff58d 100644 --- a/src/AccountGoWeb/Views/Financials/Accounts.cshtml +++ b/src/AccountGoWeb/Views/Financials/Accounts.cshtml @@ -1,67 +1,113 @@ -@model string +@model ICollection +@using AccountGoWeb.Components.Pages.Financial + +@{ + ViewBag.Title = "Chart of Accounts"; +} + +@*
    -
    -
    -
    -
    - \ No newline at end of file + + @childAccount.AccountCode + @childAccount.AccountName + @childAccount.TotalBalance + @childAccount.TotalDebitBalance + @childAccount.TotalCreditBalance + + + + + + + + } + + + + + + } + + + *@ + \ No newline at end of file diff --git a/src/AccountGoWeb/Views/Financials/AccountsPrev.cshtml b/src/AccountGoWeb/Views/Financials/AccountsPrev.cshtml new file mode 100644 index 000000000..6837eddf6 --- /dev/null +++ b/src/AccountGoWeb/Views/Financials/AccountsPrev.cshtml @@ -0,0 +1,91 @@ +@model string + +@{ + ViewBag.Title = "Chart of Accounts"; +} +
    +
    +
    +
    +
    + \ No newline at end of file diff --git a/src/AccountGoWeb/Views/Financials/AddJournalEntry.cshtml b/src/AccountGoWeb/Views/Financials/AddJournalEntry.cshtml index dd313b4e0..b8556af2e 100644 --- a/src/AccountGoWeb/Views/Financials/AddJournalEntry.cshtml +++ b/src/AccountGoWeb/Views/Financials/AddJournalEntry.cshtml @@ -1,3 +1,12 @@ -
    +@*
    \ No newline at end of file + *@ + + +@using AccountGoWeb.Components.Pages.Financial + +@{ + ViewBag.Title = "Add Journal Entry"; +} + + diff --git a/src/AccountGoWeb/Views/Financials/BalanceSheet.cshtml b/src/AccountGoWeb/Views/Financials/BalanceSheet.cshtml index 59ea65cd7..1378569db 100644 --- a/src/AccountGoWeb/Views/Financials/BalanceSheet.cshtml +++ b/src/AccountGoWeb/Views/Financials/BalanceSheet.cshtml @@ -1,18 +1,21 @@ @model ICollection @{ - ViewBag.Title = "BalanceSheet"; - Layout = "~/Views/Shared/_Layout.cshtml"; + ViewBag.Title = "BalanceSheet"; + Layout = "~/Views/Shared/_Layout_bootstrap.cshtml"; } -
    -

    Balance Sheet

    + +@if (Model != null) +{ +
    +

    Balance Sheet

    -
    - - - - - - +
    +
    Account CodeAccount NameAmount
    + + + + + @foreach (var asset in Model.Where(a => a.AccountClassId == 1)) { @@ -21,52 +24,62 @@ } - - - - -
    Account CodeAccount NameAmount
    @asset.Amount
    Total Assets@Model.Where(a => a.AccountClassId == 1).Sum(a => a.Amount)
    + + Total Assets + @Model.Where(a => a.AccountClassId == 1).Sum(a => a.Amount) + +
    -
    - - - - - - - @foreach (var liability in Model.Where(a => a.AccountClassId == 2)) - { - - - - - - } - - - +
    +
    Account CodeAccount NamceAmount
    @Html.ActionLink((string)string.Format("{0}", liability.AccountCode), "account", new { id = liability.AccountId })@liability.AccountName@liability.Amount
    Total Liabilities@Model.Where(a => a.AccountClassId == 2).Sum(a => a.Amount)
    + + + + + + @foreach (var liability in Model.Where(a => a.AccountClassId == 2)) + { + + + + + } + + + +
    Account CodeAccount NamceAmount
    @Html.ActionLink((string)string.Format("{0}", liability.AccountCode), "account", new + { + id = + liability.AccountId + })@liability.AccountName@liability.Amount
    Total Liabilities@Model.Where(a => a.AccountClassId == 2).Sum(a => a.Amount)
    -
    -
    - - - - - - - @foreach (var equity in Model.Where(a => a.AccountClassId == 3)) - { - - - - - - } - - - + +
    +
    Account CodeAccount NamceAmount
    @Html.ActionLink((string)string.Format("{0}", equity.AccountCode), "account", new { id = equity.AccountId })@equity.AccountName@equity.Amount
    Total Equities@Model.Where(a => a.AccountClassId == 3).Sum(a => a.Amount)
    + + + + + + @foreach (var equity in Model.Where(a => a.AccountClassId == 3)) + { + + + + + } + + + +
    Account CodeAccount NamceAmount
    @Html.ActionLink((string)string.Format("{0}", equity.AccountCode), "account", new { id = equity.AccountId }) + @equity.AccountName@equity.Amount
    Total Equities@Model.Where(a => a.AccountClassId == 3).Sum(a => a.Amount)
    -
    +
    +} +else +{ +

    Error fetching data.

    +} \ No newline at end of file diff --git a/src/AccountGoWeb/Views/Financials/Banks.cshtml b/src/AccountGoWeb/Views/Financials/Banks.cshtml index 28e3abb2c..c8075c656 100644 --- a/src/AccountGoWeb/Views/Financials/Banks.cshtml +++ b/src/AccountGoWeb/Views/Financials/Banks.cshtml @@ -1,5 +1,8 @@ @model IEnumerable - +
    diff --git a/src/AccountGoWeb/Views/Financials/IncomeStatement.cshtml b/src/AccountGoWeb/Views/Financials/IncomeStatement.cshtml index dbf6a541e..54cf2ac2e 100644 --- a/src/AccountGoWeb/Views/Financials/IncomeStatement.cshtml +++ b/src/AccountGoWeb/Views/Financials/IncomeStatement.cshtml @@ -1,57 +1,70 @@ @model ICollection + @{ - ViewBag.Title = "IncomeStatement"; - Layout = "~/Views/Shared/_Layout.cshtml"; - var netIncome = Model.Where(a => a.IsExpense == false).Sum(a => a.Amount) - Model.Where(a => a.IsExpense == true).Sum(a => a.Amount); + ViewBag.Title = "Income Statement"; + Layout = "~/Views/Shared/_Layout_bootstrap.cshtml"; + + var netIncome = Model?.Where(a => !a.IsExpense).Sum(a => a.Amount) ?? 0 - + Model?.Where(a => a.IsExpense).Sum(a => a.Amount) ?? 0; }

    Income Statement

    -
    -

    Income Statement

    -
    -
    - - - - - - - @foreach (var asset in Model.Where(a => a.IsExpense == false)) - { +@if (ViewBag.Error != null) +{ +
    @ViewBag.Error
    +} +else if (Model == null || !Model.Any()) +{ +

    No data available to display.

    +} +else +{ +
    +

    Revenues

    +
    Account CodeAccount NameAmount
    - - - + + + - } - - - - -
    @Html.ActionLink((string)string.Format("{0}", asset.AccountCode), "account", new { id = asset.AccountId })@asset.AccountName@asset.AmountAccount CodeAccount NameAmount
    Total Revenues@Model.Where(a => a.IsExpense == false).Sum(a => a.Amount)
    -
    -
    - - - - - - - @foreach (var asset in Model.Where(a => a.IsExpense == true)) - { + @foreach (var item in Model.Where(a => !a.IsExpense)) + { + + + + + + } - - - + + - } - - - - -
    Account CodeAccount NameAmount
    @Html.ActionLink(item.AccountCode, "Account", new { id = item.AccountId })@item.AccountName@item.Amount
    @Html.ActionLink((string)string.Format("{0}", asset.AccountCode), "account", new { id = asset.AccountId })@asset.AccountName@asset.AmountTotal Revenues@Model.Where(a => !a.IsExpense).Sum(a => a.Amount)
    Total Expenses@Model.Where(a => a.IsExpense == true).Sum(a => a.Amount)
    -
    -
    - Net Income: @netIncome -
    \ No newline at end of file + +
    +
    +

    Expenses

    + + + + + + + @foreach (var item in Model.Where(a => a.IsExpense)) + { + + + + + + } + + + + +
    Account CodeAccount NameAmount
    @Html.ActionLink(item.AccountCode, "Account", new { id = item.AccountId })@item.AccountName@item.Amount
    Total Expenses@Model.Where(a => a.IsExpense).Sum(a => a.Amount)
    +
    +
    + Net Income: @netIncome +
    +} diff --git a/src/AccountGoWeb/Views/Financials/JournalEntries.cshtml b/src/AccountGoWeb/Views/Financials/JournalEntries.cshtml index 6ad242692..af662691a 100644 --- a/src/AccountGoWeb/Views/Financials/JournalEntries.cshtml +++ b/src/AccountGoWeb/Views/Financials/JournalEntries.cshtml @@ -1,6 +1,7 @@ @model string +@using AccountGoWeb.Components.Pages.Financial -
    +@*
    New @@ -51,4 +52,7 @@ var eGridDiv = document.querySelector('#journalentries'); new agGrid.Grid(eGridDiv, gridOptions); }); - \ No newline at end of file + *@ + + + diff --git a/src/AccountGoWeb/Views/Financials/JournalEntry.cshtml b/src/AccountGoWeb/Views/Financials/JournalEntry.cshtml index dd313b4e0..837e30377 100644 --- a/src/AccountGoWeb/Views/Financials/JournalEntry.cshtml +++ b/src/AccountGoWeb/Views/Financials/JournalEntry.cshtml @@ -1,3 +1,14 @@ -
    +@*
    \ No newline at end of file + *@ + +@model int +@using AccountGoWeb.Components.Pages.Financial + +@{ + ViewBag.Title = "Journal Entry"; +} + +
    + +
    \ No newline at end of file diff --git a/src/AccountGoWeb/Views/Financials/TrialBalance.cshtml b/src/AccountGoWeb/Views/Financials/TrialBalance.cshtml index 4bd9d2f1c..ad258f287 100644 --- a/src/AccountGoWeb/Views/Financials/TrialBalance.cshtml +++ b/src/AccountGoWeb/Views/Financials/TrialBalance.cshtml @@ -1,7 +1,7 @@ @model ICollection @{ ViewBag.Title = "TrialBalance"; - Layout = "~/Views/Shared/_Layout.cshtml"; + Layout = "~/Views/Shared/_Layout_bootstrap.cshtml"; }

    diff --git a/src/AccountGoWeb/Views/Home/Index.cshtml b/src/AccountGoWeb/Views/Home/Index.cshtml index c1bfb55ba..a0a4470f7 100644 --- a/src/AccountGoWeb/Views/Home/Index.cshtml +++ b/src/AccountGoWeb/Views/Home/Index.cshtml @@ -1,10 +1,23 @@ -
    -
    - @Html.Partial("~/Views/Dashboard/_MonthlySales.cshtml") +@inject IConfiguration _configuration +
    +
    + +
    +
    +

    @_configuration["GoodDeeds:SuperOrganizationName"]

    -
    +
    + +
    -
    +
    +
    +
    + @* *@ + @* *@ +
    +
    +
    diff --git a/src/AccountGoWeb/Views/Inventory/ICJ.cshtml b/src/AccountGoWeb/Views/Inventory/ICJ.cshtml index 46a5e8210..1af17fd58 100644 --- a/src/AccountGoWeb/Views/Inventory/ICJ.cshtml +++ b/src/AccountGoWeb/Views/Inventory/ICJ.cshtml @@ -1,31 +1,7 @@ -@model string - -
    -
    -
    -
    -
    - \ No newline at end of file +@model List +@using AccountGoWeb.Components.Pages.Inventory +@using System.Text.Json +@{ + ViewBag.PageContentHeader = "Inventory Control Journal"; +} +@(await Html.RenderComponentAsync(RenderMode.Server)) diff --git a/src/AccountGoWeb/Views/Inventory/Index.cshtml b/src/AccountGoWeb/Views/Inventory/Index.cshtml new file mode 100644 index 000000000..148928576 --- /dev/null +++ b/src/AccountGoWeb/Views/Inventory/Index.cshtml @@ -0,0 +1,5 @@ +@model string + +@* New Blazor Component Implementation *@ +@using AccountGoWeb.Components.Pages.Inventory + \ No newline at end of file diff --git a/src/AccountGoWeb/Views/Inventory/Item.cshtml b/src/AccountGoWeb/Views/Inventory/Item.cshtml index 452d4f494..9f5b7c917 100644 --- a/src/AccountGoWeb/Views/Inventory/Item.cshtml +++ b/src/AccountGoWeb/Views/Inventory/Item.cshtml @@ -1,141 +1,5 @@ @model Dto.Inventory.Item -
    -
    -
    - @Html.HiddenFor(m => m.Id) -
    -
    - General -
    -
    -
    -
    -
    No
    -
    @Model.No
    -
    -
    -
    Code
    -
    - - -
    -
    -
    -
    Description
    -
    - - -
    -
    -
    -
    -
    -
    Smallest UOM
    -
    -
    -
    -
    Category
    -
    -
    -
    -
    Item Tax Group
    -
    -
    -
    -
    -
    -
    -
    - Pricing -
    -
    -
    -
    -
    Sell Description
    -
    -
    -
    -
    Sell Price
    -
    -
    -
    -
    Sell UOM
    -
    -
    -
    -
    -
    -
    Purchase Description
    -
    -
    -
    -
    Purchase Cost
    -
    -
    -
    -
    Purchase UOM
    -
    -
    -
    -
    -
    - -
    -
    - Invoicing -
    -
    -
    -
    -
    Sales Account
    -
    -
    - @*
    -
    Purchase Account
    -
    -
    *@ -
    -
    -
    -
    Adjustment
    -
    -
    -
    -
    Inventory Account
    -
    -
    -
    -
    Cost of Good Sold
    -
    -
    -
    -
    -
    -
    -
    -
    -
    - - Close -
    -
    -@section scripts{ - - -} \ No newline at end of file +@* New Blazor Component Implementation *@ +@using AccountGoWeb.Components.Pages.Inventory + \ No newline at end of file diff --git a/src/AccountGoWeb/Views/Inventory/Items.cshtml b/src/AccountGoWeb/Views/Inventory/Items.cshtml deleted file mode 100644 index c336efead..000000000 --- a/src/AccountGoWeb/Views/Inventory/Items.cshtml +++ /dev/null @@ -1,53 +0,0 @@ -@model string - - -
    -
    -
    - \ No newline at end of file diff --git a/src/AccountGoWeb/Views/Inventory/addItem.cshtml b/src/AccountGoWeb/Views/Inventory/addItem.cshtml new file mode 100644 index 000000000..d2dd936af --- /dev/null +++ b/src/AccountGoWeb/Views/Inventory/addItem.cshtml @@ -0,0 +1,170 @@ +@model Dto.Inventory.Item + +@* New Blazor Component Implementation *@ +@using AccountGoWeb.Components.Pages.Inventory + + + + +
    +
    + @Html.AntiForgeryToken() + + + @if (!ViewData.ModelState.IsValid) + { + + } + + @Html.ValidationSummary(true) +
    +
    + @* Description *@ +
    +
    Name
    +
    + + +
    +
    + @* Cost *@ +
    +
    Cost
    +
    + + +
    +
    + @* Item Category *@ +
    +
    Item Category
    +
    + + +
    +
    + @* Smallest Measurement *@ +
    +
    Smallest Measurement
    +
    + + +
    +
    + @* Purchase Measurement *@ +
    +
    Purchase Measurement
    +
    + + +
    +
    + @* Item Tax Group *@ +
    +
    Item Tax Group
    +
    + + +
    +
    + @* Preferred Vendor *@ +
    +
    Preferred Vendor
    +
    + + +
    +
    + @* Sales Account *@ +
    +
    Sales Account
    +
    + + +
    +
    + @* Inventory Account *@ +
    +
    Inventory Account
    +
    + + +
    +
    + @* Cost of Goods Account *@ +
    +
    Cost of Goods Account
    +
    + + +
    +
    + @* Inventory Adjustment Account *@ +
    +
    Inventory Adjustment Account
    +
    + + +
    +
    + @* Code *@ +
    +
    Code
    +
    + + +
    +
    + @* Price *@ +
    +
    Price
    +
    + + +
    +
    +
    +
    +
    +
    + + Close +
    +
    diff --git a/src/AccountGoWeb/Views/Purchasing/AddPurchaseInvoice.cshtml b/src/AccountGoWeb/Views/Purchasing/AddPurchaseInvoice.cshtml index b15e3d4f7..98bd1fd6f 100644 --- a/src/AccountGoWeb/Views/Purchasing/AddPurchaseInvoice.cshtml +++ b/src/AccountGoWeb/Views/Purchasing/AddPurchaseInvoice.cshtml @@ -1,3 +1,3 @@ -
    +@using AccountGoWeb.Components.Pages.Payables - \ No newline at end of file + \ No newline at end of file diff --git a/src/AccountGoWeb/Views/Purchasing/AddPurchaseOrder.cshtml b/src/AccountGoWeb/Views/Purchasing/AddPurchaseOrder.cshtml index 4b086b498..c120250aa 100644 --- a/src/AccountGoWeb/Views/Purchasing/AddPurchaseOrder.cshtml +++ b/src/AccountGoWeb/Views/Purchasing/AddPurchaseOrder.cshtml @@ -1,2 +1,3 @@ -
    - +@using AccountGoWeb.Components.Pages.Payables + + diff --git a/src/AccountGoWeb/Views/Purchasing/PurchaseInvoice.cshtml b/src/AccountGoWeb/Views/Purchasing/PurchaseInvoice.cshtml index b15e3d4f7..5ac656cb7 100644 --- a/src/AccountGoWeb/Views/Purchasing/PurchaseInvoice.cshtml +++ b/src/AccountGoWeb/Views/Purchasing/PurchaseInvoice.cshtml @@ -1,3 +1,4 @@ -
    +@model Dto.Purchasing.PurchaseInvoice +@using AccountGoWeb.Components.Pages.Payables - \ No newline at end of file + diff --git a/src/AccountGoWeb/Views/Purchasing/PurchaseInvoices.cshtml b/src/AccountGoWeb/Views/Purchasing/PurchaseInvoices.cshtml index 83e2d27c4..69df3d899 100644 --- a/src/AccountGoWeb/Views/Purchasing/PurchaseInvoices.cshtml +++ b/src/AccountGoWeb/Views/Purchasing/PurchaseInvoices.cshtml @@ -1,64 +1,3 @@ -@model string +@using AccountGoWeb.Components.Pages.Payables - -
    -
    -
    - \ No newline at end of file + \ No newline at end of file diff --git a/src/AccountGoWeb/Views/Purchasing/PurchaseOrder.cshtml b/src/AccountGoWeb/Views/Purchasing/PurchaseOrder.cshtml index a7f10ffe4..fd5164f83 100644 --- a/src/AccountGoWeb/Views/Purchasing/PurchaseOrder.cshtml +++ b/src/AccountGoWeb/Views/Purchasing/PurchaseOrder.cshtml @@ -1,2 +1,4 @@ -
    - \ No newline at end of file +@model Dto.Purchasing.PurchaseOrder +@using AccountGoWeb.Components.Pages.Payables + + \ No newline at end of file diff --git a/src/AccountGoWeb/Views/Purchasing/PurchaseOrders.cshtml b/src/AccountGoWeb/Views/Purchasing/PurchaseOrders.cshtml index 397531fd4..bfa126adc 100644 --- a/src/AccountGoWeb/Views/Purchasing/PurchaseOrders.cshtml +++ b/src/AccountGoWeb/Views/Purchasing/PurchaseOrders.cshtml @@ -1,81 +1,3 @@ -@model string +@using AccountGoWeb.Components.Pages.Payables - -
    -
    -
    - \ No newline at end of file + \ No newline at end of file diff --git a/src/AccountGoWeb/Views/Purchasing/Vendor.cshtml b/src/AccountGoWeb/Views/Purchasing/Vendor.cshtml index 188989895..51c9367ce 100644 --- a/src/AccountGoWeb/Views/Purchasing/Vendor.cshtml +++ b/src/AccountGoWeb/Views/Purchasing/Vendor.cshtml @@ -1,193 +1,11 @@ -@model Dto.Purchasing.Vendor +@model Dto.Purchasing.Vendor - -
    -
    - @Html.HiddenFor(m => m.Id) -
    -
    - General -
    -
    -
    -
    -
    No
    -
    @Model.No
    - @Html.HiddenFor(m => m.No) -
    -
    -
    Name
    -
    - - -
    -
    -
    -
    -
    -
    Phone
    -
    - -
    -
    -
    -
    Fax
    -
    - -
    -
    -
    -
    Website
    -
    - -
    -
    -
    -
    Email
    -
    - -
    -
    -
    -
    -
    +@* New Blazor Component Implementation *@ +@using AccountGoWeb.Components.Pages.Purchasing + -
    -
    - Contact - - ... - -
    -
    -
    -
    -
    First Name
    -
    - -
    -
    -
    -
    Last Name
    -
    - -
    -
    -
    -
    -
    -
    Phone
    -
    - -
    -
    -
    -
    Fax
    -
    - -
    -
    -
    -
    Website
    -
    - -
    -
    -
    -
    Email
    -
    - -
    -
    -
    -
    -
    - -
    -
    - Invoicing -
    -
    -
    -
    -
    Accounts Payable
    -
    - -
    -
    -
    -
    Purchase
    -
    - -
    -
    -
    -
    Discount
    -
    - -
    -
    -
    -
    -
    -
    Tax Group
    -
    - -
    -
    -
    -
    -
    - -
    -
    - Payment -
    -
    -
    -
    -
    Payment Term
    -
    - -
    -
    -
    -
    -
    -
    -
    -
    -
    - - Close -
    -
    -@section scripts{ - - -} \ No newline at end of file + diff --git a/src/AccountGoWeb/Views/Purchasing/Vendors.cshtml b/src/AccountGoWeb/Views/Purchasing/Vendors.cshtml index 55ac31afd..4439fed27 100644 --- a/src/AccountGoWeb/Views/Purchasing/Vendors.cshtml +++ b/src/AccountGoWeb/Views/Purchasing/Vendors.cshtml @@ -1,52 +1,5 @@ @model string - -
    -
    -
    - \ No newline at end of file +@* New Blazor Component Implementation *@ +@using AccountGoWeb.Components.Pages.Purchasing + \ No newline at end of file diff --git a/src/AccountGoWeb/Views/Quotations/AddSalesQuotation.cshtml b/src/AccountGoWeb/Views/Quotations/AddSalesQuotation.cshtml index 14d4e4d7e..647ed6e61 100644 --- a/src/AccountGoWeb/Views/Quotations/AddSalesQuotation.cshtml +++ b/src/AccountGoWeb/Views/Quotations/AddSalesQuotation.cshtml @@ -1,2 +1,3 @@ -
    - +@using AccountGoWeb.Components.Pages.Quotations + +@(await Html.RenderComponentAsync(RenderMode.Server)) diff --git a/src/AccountGoWeb/Views/Quotations/Quotation.cshtml b/src/AccountGoWeb/Views/Quotations/Quotation.cshtml index 11cae02cc..9ac88e406 100644 --- a/src/AccountGoWeb/Views/Quotations/Quotation.cshtml +++ b/src/AccountGoWeb/Views/Quotations/Quotation.cshtml @@ -1,2 +1,129 @@ -
    - \ No newline at end of file +@model Dto.Sales.SalesQuotation + + + +
    + +
    + +
    + @Html.HiddenFor(m => m.Id) + @Html.HiddenFor(m => m.PaymentTermId) +
    +
    +
    +
    + @* Customer Name *@ +
    +
    Customer Name
    +
    + + +
    +
    + @* Quotation Date *@ +
    +
    Quotation Date
    +
    + +
    +
    + @* Payment Term *@ +
    +
    Payment Term
    +
    @ViewBag.SelectedPaymentTerm
    +
    + @* Total Amount *@ +
    +
    Total Amount
    +
    $@ViewBag.TotalAmount.ToString("N2")
    +
    +
    + @* Table *@ + + + + + + + + + + @for (int i = 0; i < Model.SalesQuotationLines.Count; i++) + { + + + + + + + + + + + + + + } + +
    ItemQuantityAmountDiscountMeasurement
    + + + + + + + + + +
    +
    +
    +
    +
    + + Close +
    +
    + +@section scripts { + + +} \ No newline at end of file diff --git a/src/AccountGoWeb/Views/Quotations/Quotations.cshtml b/src/AccountGoWeb/Views/Quotations/Quotations.cshtml index 052bcac1d..bc047a15f 100644 --- a/src/AccountGoWeb/Views/Quotations/Quotations.cshtml +++ b/src/AccountGoWeb/Views/Quotations/Quotations.cshtml @@ -15,19 +15,19 @@
    -
    - +
    + + -
    + \ No newline at end of file diff --git a/src/AccountGoWeb/Views/Quotations/SalesQuotations.cshtml b/src/AccountGoWeb/Views/Quotations/SalesQuotations.cshtml new file mode 100644 index 000000000..0eb198eb9 --- /dev/null +++ b/src/AccountGoWeb/Views/Quotations/SalesQuotations.cshtml @@ -0,0 +1,3 @@ +@using AccountGoWeb.Components.Pages.Quotations + +@(await Html.RenderComponentAsync(RenderMode.Server)) diff --git a/src/AccountGoWeb/Views/Sales/AddReceipt.cshtml b/src/AccountGoWeb/Views/Sales/AddReceipt.cshtml index 36996ae05..587540137 100644 --- a/src/AccountGoWeb/Views/Sales/AddReceipt.cshtml +++ b/src/AccountGoWeb/Views/Sales/AddReceipt.cshtml @@ -1,77 +1,2 @@ -@model AccountGoWeb.Models.Sales.AddReceipt - -
    -
    - @Html.ValidationSummary(true) -
    -
    -
    -
    Customer
    -
    - - -
    -
    -
    -
    Date
    -
    - -
    -
    -
    -
    Debit
    -
    - - -
    -
    -
    -
    Credit
    -
    - - @Html.HiddenFor(m => m.AccountToCreditId) - -
    -
    -
    -
    Amount
    -
    - - -
    -
    -
    -
    -
    -
    - - Close -
    -
    -@section scripts{ - -} \ No newline at end of file +@using AccountGoWeb.Components.Pages.Sales + \ No newline at end of file diff --git a/src/AccountGoWeb/Views/Sales/AddSalesInvoice.cshtml b/src/AccountGoWeb/Views/Sales/AddSalesInvoice.cshtml index 356e804ee..6dd6a6900 100644 --- a/src/AccountGoWeb/Views/Sales/AddSalesInvoice.cshtml +++ b/src/AccountGoWeb/Views/Sales/AddSalesInvoice.cshtml @@ -1,3 +1,2 @@ -
    - - \ No newline at end of file +@using AccountGoWeb.Components.Pages.Sales + \ No newline at end of file diff --git a/src/AccountGoWeb/Views/Sales/AddSalesOrder.cshtml b/src/AccountGoWeb/Views/Sales/AddSalesOrder.cshtml index 9672f8008..71045a228 100644 --- a/src/AccountGoWeb/Views/Sales/AddSalesOrder.cshtml +++ b/src/AccountGoWeb/Views/Sales/AddSalesOrder.cshtml @@ -1,3 +1,4 @@ -
    +@model object - \ No newline at end of file +@using AccountGoWeb.Components.Pages.Sales + \ No newline at end of file diff --git a/src/AccountGoWeb/Views/Sales/Allocate.cshtml b/src/AccountGoWeb/Views/Sales/Allocate.cshtml index 20c1631d3..5ea30976d 100644 --- a/src/AccountGoWeb/Views/Sales/Allocate.cshtml +++ b/src/AccountGoWeb/Views/Sales/Allocate.cshtml @@ -1,103 +1,2 @@ -@model AccountGoWeb.Models.Sales.Allocate - -
    -
    -
    -
    -

    General

    -
    - -
    -
    -
    -
    -
    -
    Receipt No
    -
    - @ViewBag.ReceiptNo - @Html.HiddenFor(m => m.ReceiptId) -
    -
    -
    -
    Customer Name
    -
    - @ViewBag.CustomerName - @Html.HiddenFor(m => m.CustomerId) -
    -
    -
    -
    Receipt Date
    -
    - @Model.Date - @Html.HiddenFor(m => m.Date) -
    -
    -
    -
    Amount
    -
    - @Model.Amount - @Html.HiddenFor(m => m.Amount) -
    -
    -
    -
    Remaining
    -
    - @Model.RemainingAmountToAllocate - @Html.HiddenFor(m => m.RemainingAmountToAllocate) -
    -
    -
    -
    -
    -
    -
    -

    Invoice

    -
    - -
    -
    -
    -
    - - - - - - - - - - - @for (int i = 0; i < Model.AllocationLines.Count(); i++) - { - - - - - - - } - -
    Invoice NoAmountAllocated AmountTo Allocate
    - @Model.AllocationLines[i].InvoiceId - @Html.HiddenFor(m => Model.AllocationLines[i].InvoiceId) - - @Model.AllocationLines[i].Amount - - @Model.AllocationLines[i].AllocatedAmount - - -
    -
    -
    -
    -
    -
    - - Close -
    -
    \ No newline at end of file +@using AccountGoWeb.Components.Pages.Sales + \ No newline at end of file diff --git a/src/AccountGoWeb/Views/Sales/Customer.cshtml b/src/AccountGoWeb/Views/Sales/Customer.cshtml index c5bc35a1d..3b6beaeae 100644 --- a/src/AccountGoWeb/Views/Sales/Customer.cshtml +++ b/src/AccountGoWeb/Views/Sales/Customer.cshtml @@ -1,200 +1,6 @@ -@model Dto.Sales.Customer - -
    -
    - @Html.HiddenFor(m => m.Id) -
    -
    - General -
    -
    -
    -
    -
    No
    -
    @Model.No
    - @Html.HiddenFor(m => m.No) -
    -
    -
    Name
    -
    - - -
    -
    -
    -
    -
    -
    Phone
    -
    - -
    -
    -
    -
    Fax
    -
    - -
    -
    -
    -
    Website
    -
    - -
    -
    -
    -
    Email
    -
    - -
    -
    -
    -
    -
    +@using AccountGoWeb.Components.Pages.Sales +@{ + var customerId = Context.Request.RouteValues["id"]?.ToString() ?? "0"; +} -
    -
    - Contact - - ... - - @**@ -
    -
    -
    -
    -
    First Name
    -
    - -
    -
    -
    -
    Last Name
    -
    - -
    -
    -
    -
    -
    -
    Phone
    -
    - -
    -
    -
    -
    Fax
    -
    - -
    -
    -
    -
    Website
    -
    - -
    -
    -
    -
    Email
    -
    - -
    -
    -
    -
    -
    - -
    -
    - Invoicing -
    -
    -
    -
    -
    Accounts Receivable
    -
    - -
    -
    -
    -
    Sales
    -
    - -
    -
    -
    -
    Prepayment
    -
    - -
    -
    -
    -
    Discount
    -
    - -
    -
    -
    -
    -
    -
    Tax Group
    -
    - -
    -
    -
    -
    -
    - -
    -
    - Payment -
    -
    -
    -
    -
    Payment Term
    -
    - -
    -
    -
    -
    -
    -
    -
    -
    -
    - - Close -
    -
    - -@section scripts{ - - -} \ No newline at end of file + \ No newline at end of file diff --git a/src/AccountGoWeb/Views/Sales/Customers.cshtml b/src/AccountGoWeb/Views/Sales/Customers.cshtml index b94e0cecc..62e0bf9a3 100644 --- a/src/AccountGoWeb/Views/Sales/Customers.cshtml +++ b/src/AccountGoWeb/Views/Sales/Customers.cshtml @@ -1,50 +1,3 @@ -@model string +@using AccountGoWeb.Components.Pages.Sales - -
    -
    -
    - \ No newline at end of file + \ No newline at end of file diff --git a/src/AccountGoWeb/Views/Sales/DonationInvoice.cshtml b/src/AccountGoWeb/Views/Sales/DonationInvoice.cshtml new file mode 100644 index 000000000..089d40d95 --- /dev/null +++ b/src/AccountGoWeb/Views/Sales/DonationInvoice.cshtml @@ -0,0 +1,121 @@ +@model Dto.Sales.SalesInvoice + + + + + +
    +
    + @Html.HiddenFor(m => m.Id) +
    +
    +
    + @* ID *@ +
    +
    ID
    +
    @Model.Id
    +
    + @* Customer Name *@ +
    +
    Customer Name
    +
    + + + @Html.HiddenFor(m => m.CustomerId) +
    +
    + @* Invoice Date *@ +
    +
    Invoice Date
    +
    + +
    +
    + @* Total Amount *@ +
    +
    Total Amount
    +
    @ViewBag.TotalAmount
    +
    +
    +
    + + @* Table *@ + + + + + + + + + + @for (int i = 0; i < Model.SalesInvoiceLines!.Count; i++) + { + + + + + + + + } + +
    ItemQuantityAmountDiscountMeasurement
    + + + + + + + + + + + + + + +
    +
    +
    +
    + + Close +
    +
    + +@section scripts{ + + +} \ No newline at end of file diff --git a/src/AccountGoWeb/Views/Sales/DonationInvoices.cshtml b/src/AccountGoWeb/Views/Sales/DonationInvoices.cshtml new file mode 100644 index 000000000..d1d7592cc --- /dev/null +++ b/src/AccountGoWeb/Views/Sales/DonationInvoices.cshtml @@ -0,0 +1,54 @@ +@model string + + +
    +
    +
    + \ No newline at end of file diff --git a/src/AccountGoWeb/Views/Sales/SalesInvoice.cshtml b/src/AccountGoWeb/Views/Sales/SalesInvoice.cshtml index 797c1b185..6dd6a6900 100644 --- a/src/AccountGoWeb/Views/Sales/SalesInvoice.cshtml +++ b/src/AccountGoWeb/Views/Sales/SalesInvoice.cshtml @@ -1,2 +1,2 @@ -
    - \ No newline at end of file +@using AccountGoWeb.Components.Pages.Sales + \ No newline at end of file diff --git a/src/AccountGoWeb/Views/Sales/SalesInvoicePdf.cshtml b/src/AccountGoWeb/Views/Sales/SalesInvoicePdf.cshtml index 66b64f01e..6ce978657 100644 --- a/src/AccountGoWeb/Views/Sales/SalesInvoicePdf.cshtml +++ b/src/AccountGoWeb/Views/Sales/SalesInvoicePdf.cshtml @@ -114,7 +114,7 @@ apply the skin class to the body tag so the changes take effect. - @foreach (var item in Model.SalesInvoiceLines) + @foreach (var item in Model.SalesInvoiceLines!) { diff --git a/src/AccountGoWeb/Views/Sales/SalesInvoices.cshtml b/src/AccountGoWeb/Views/Sales/SalesInvoices.cshtml index 247e1c76f..7e35e6d5f 100644 --- a/src/AccountGoWeb/Views/Sales/SalesInvoices.cshtml +++ b/src/AccountGoWeb/Views/Sales/SalesInvoices.cshtml @@ -1,53 +1,114 @@ @model string + +
    - \ No newline at end of file +@* Delete Modal*@ + + +@section scripts { + +} \ No newline at end of file diff --git a/src/AccountGoWeb/Views/Sales/SalesOrder.cshtml b/src/AccountGoWeb/Views/Sales/SalesOrder.cshtml index 1a31098f3..71591a547 100644 --- a/src/AccountGoWeb/Views/Sales/SalesOrder.cshtml +++ b/src/AccountGoWeb/Views/Sales/SalesOrder.cshtml @@ -1,2 +1,3 @@ -
    - \ No newline at end of file +@model Dto.Sales.SalesOrder +@using AccountGoWeb.Components.Pages.Sales + \ No newline at end of file diff --git a/src/AccountGoWeb/Views/Sales/SalesReceipt.cshtml b/src/AccountGoWeb/Views/Sales/SalesReceipt.cshtml new file mode 100644 index 000000000..75f4edd90 --- /dev/null +++ b/src/AccountGoWeb/Views/Sales/SalesReceipt.cshtml @@ -0,0 +1,2 @@ +@using AccountGoWeb.Components.Pages.Sales + \ No newline at end of file diff --git a/src/AccountGoWeb/Views/Sales/SalesReceipts.cshtml b/src/AccountGoWeb/Views/Sales/SalesReceipts.cshtml index 9aa7ccb2a..e81ee3d94 100644 --- a/src/AccountGoWeb/Views/Sales/SalesReceipts.cshtml +++ b/src/AccountGoWeb/Views/Sales/SalesReceipts.cshtml @@ -1,56 +1,4 @@ @model string - - -
    -
    -
    - - \ No newline at end of file +@* New Blazor Component Implementation *@ +@using AccountGoWeb.Components.Pages.Sales + \ No newline at end of file diff --git a/src/AccountGoWeb/Views/Sales/salesorders.cshtml b/src/AccountGoWeb/Views/Sales/salesorders.cshtml index 81d7d9616..85eab6b15 100644 --- a/src/AccountGoWeb/Views/Sales/salesorders.cshtml +++ b/src/AccountGoWeb/Views/Sales/salesorders.cshtml @@ -1,67 +1,4 @@ @model string - -
    -
    -
    - \ No newline at end of file +@using AccountGoWeb.Components.Pages.Sales + \ No newline at end of file diff --git a/src/AccountGoWeb/Views/Shared/_Layout.cshtml b/src/AccountGoWeb/Views/Shared/_Layout.cshtml index 5e8ea9be4..1d9c075e8 100644 --- a/src/AccountGoWeb/Views/Shared/_Layout.cshtml +++ b/src/AccountGoWeb/Views/Shared/_Layout.cshtml @@ -1,133 +1,46 @@ -@{ Layout = ""; } - + + + + AccountGo + - - - - - + + + - + + + +
    - - - - - + @* *@ +
    - +

    + @Html.Raw(ViewBag.PageContentHeader) +

    @RenderBody()
    @@ -354,7 +276,9 @@
    -
    Today
    +
    + Today
    admin@accountgo.com @@ -385,7 +309,9 @@   On-line
    -
    Tomorrow
    +
    + Tomorrow
    New UI Project - @@ -399,19 +325,24 @@
    - admin@bootstrapmaster.com + admin@bootstrapmaster.com
    - admin@bootstrapmaster.com + admin@bootstrapmaster.com
    - admin@bootstrapmaster.com + admin@bootstrapmaster.com
    - admin@bootstrapmaster.com + admin@bootstrapmaster.com
    - admin@bootstrapmaster.com + admin@bootstrapmaster.com
    @@ -438,25 +369,32 @@
    - admin@bootstrapmaster.com + admin@bootstrapmaster.com
    - admin@bootstrapmaster.com + admin@bootstrapmaster.com
    - admin@bootstrapmaster.com + admin@bootstrapmaster.com
    - admin@bootstrapmaster.com + admin@bootstrapmaster.com
    - admin@bootstrapmaster.com + admin@bootstrapmaster.com
    - admin@bootstrapmaster.com + admin@bootstrapmaster.com
    - admin@bootstrapmaster.com + admin@bootstrapmaster.com
    @@ -475,7 +413,8 @@ 1:52 PM
    Lorem ipsum dolor sit amet
    - Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt... + Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do + eiusmod tempor incididunt...

    @@ -490,7 +429,8 @@ 1:52 PM
    Lorem ipsum dolor sit amet
    - Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt... + Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do + eiusmod tempor incididunt...

    @@ -505,7 +445,8 @@ 1:52 PM
    Lorem ipsum dolor sit amet
    - Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt... + Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do + eiusmod tempor incididunt...

    @@ -520,7 +461,8 @@ 1:52 PM
    Lorem ipsum dolor sit amet
    - Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt... + Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do + eiusmod tempor incididunt...

    @@ -535,7 +477,8 @@ 1:52 PM
    Lorem ipsum dolor sit amet
    - Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt... + Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do + eiusmod tempor incididunt...
    @@ -551,7 +494,8 @@
    - Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. + Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do + eiusmod tempor incididunt ut labore et dolore magna aliqua.

    @@ -565,7 +509,8 @@
    - Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. + Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do + eiusmod tempor incididunt ut labore et dolore magna aliqua.
    @@ -598,7 +543,8 @@
    -
    +
    348 Processes. 1/4 Cores.
    @@ -607,7 +553,8 @@
    -
    +
    11444GB/16384MB
    @@ -616,7 +563,8 @@
    -
    +
    243GB/256GB
    @@ -625,7 +573,8 @@
    -
    +
    25GB/256GB
    @@ -643,16 +592,21 @@
    - - - - - - - + + + + + + + + + + @* *@ + @RenderSection("Scripts", required: false) + \ No newline at end of file diff --git a/src/AccountGoWeb/Views/Shared/_LayoutPrev.cshtml b/src/AccountGoWeb/Views/Shared/_LayoutPrev.cshtml index ce08a0f74..6cdebb9af 100644 --- a/src/AccountGoWeb/Views/Shared/_LayoutPrev.cshtml +++ b/src/AccountGoWeb/Views/Shared/_LayoutPrev.cshtml @@ -57,9 +57,9 @@
    + +@* Delete Modal*@ + + + + + + @section scripts{ - - + + + +} diff --git a/src/AccountGoWeb/Views/_ViewImports.cshtml b/src/AccountGoWeb/Views/_ViewImports.cshtml index aa936be99..7e68b04d0 100644 --- a/src/AccountGoWeb/Views/_ViewImports.cshtml +++ b/src/AccountGoWeb/Views/_ViewImports.cshtml @@ -1,2 +1,6 @@ @using AccountGoWeb -@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers \ No newline at end of file +@using Microsoft.AspNetCore.Components.Web +@using AccountGoWeb.Components +@using AccountGoWeb.Components.Pages +@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers +@addTagHelper *, Microsoft.AspNetCore.Components.Web diff --git a/src/AccountGoWeb/Views/_ViewStart.cshtml b/src/AccountGoWeb/Views/_ViewStart.cshtml index a5f10045d..183ca479c 100644 --- a/src/AccountGoWeb/Views/_ViewStart.cshtml +++ b/src/AccountGoWeb/Views/_ViewStart.cshtml @@ -1,3 +1,3 @@ @{ - Layout = "_Layout"; + Layout = "_Layout_bootstrap"; } diff --git a/src/AccountGoWeb/appsettings.Development.json b/src/AccountGoWeb/appsettings.Development.json index 27e567cbf..493a57e80 100644 --- a/src/AccountGoWeb/appsettings.Development.json +++ b/src/AccountGoWeb/appsettings.Development.json @@ -1,4 +1,7 @@ { + "GoodDeeds": { + "SuperOrganizationName": "Good Deeds Books - Accounting System" + }, "Logging": { "LogLevel": { "Default": "Information", @@ -7,5 +10,5 @@ } }, "AllowedHosts": "*", - "ApiUrl": "http://{0}:8001/api/" + "ApiUrl": "http://{0}:{1}/api/" } \ No newline at end of file diff --git a/src/AccountGoWeb/appsettings.Production.json b/src/AccountGoWeb/appsettings.Production.json index a512442ba..9e9a0b7ae 100644 --- a/src/AccountGoWeb/appsettings.Production.json +++ b/src/AccountGoWeb/appsettings.Production.json @@ -1,3 +1,3 @@ { - "ApiUrl": "http://{0}:8001/api/" + "ApiUrl": "http://{0}:{1}/api/" } \ No newline at end of file diff --git a/src/AccountGoWeb/appsettings.json b/src/AccountGoWeb/appsettings.json index 27e567cbf..79d1e68ce 100644 --- a/src/AccountGoWeb/appsettings.json +++ b/src/AccountGoWeb/appsettings.json @@ -7,5 +7,5 @@ } }, "AllowedHosts": "*", - "ApiUrl": "http://{0}:8001/api/" + "ApiUrl": "http://{0}:{1}/api/" } \ No newline at end of file diff --git a/src/AccountGoWeb/gulpfile.js b/src/AccountGoWeb/gulpfile.js deleted file mode 100644 index e3f220378..000000000 --- a/src/AccountGoWeb/gulpfile.js +++ /dev/null @@ -1,37 +0,0 @@ -/// -var gulp = require('gulp'); - -var destPath = './wwwroot/lib/'; - -gulp.task("libs", - gulp.series(done => { - gulp.src([ - '@coreui/coreui/dist/**', - '@coreui/icons/css/**', - 'simple-line-icons/css/**', - 'simple-line-icons/fonts/**', - 'ionicons/dist/**', - 'bootstrap/dist/**', - 'perfect-scrollbar/dist/**', - 'jquery/dist/**', - 'ag-grid/dist/**', - 'font-awesome/css/**', - 'font-awesome/fonts/**', - 'knockout/build/output/**', - 'knockout-mapping/dist/**', - 'd3/dist/**', - 'jspdf/dist/**', - 'accounting/**', - 'html2canvas/dist/**', - 'popper.js/dist/**', - 'pace-progress/**' - ], - { - cwd: "node_modules/**" - }) - .pipe(gulp.dest(destPath)); - done(); - }) -); - -gulp.task('default', gulp.parallel(['libs'])); \ No newline at end of file diff --git a/src/AccountGoWeb/package-lock.json b/src/AccountGoWeb/package-lock.json deleted file mode 100644 index 3d3b90bcd..000000000 --- a/src/AccountGoWeb/package-lock.json +++ /dev/null @@ -1,9007 +0,0 @@ -{ - "name": "accountgoweb", - "version": "0.0.1", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "accountgoweb", - "version": "0.0.1", - "dependencies": { - "accounting": "0.4.1", - "ag-grid": "18.1.2", - "d3": "7.8.2", - "font-awesome": "^4.7.0", - "jspdf": "2.5.1", - "knockout": "^3.5.0", - "knockout-mapping": "2.6.0", - "pace-progress": "^1.0.2", - "react-dom-factories": "1.0.2" - }, - "devDependencies": { - "@coreui/coreui": "^2.1.16", - "@coreui/icons": "^0.3.0", - "ag-grid-community": "^19.1.4", - "ajv": "^6.12.6", - "axios": "^1.3.4", - "bootstrap": "^5.2.3", - "gulp": "^4.0.2", - "html2canvas": "^1.4.1", - "ionicons": "^4.6.3", - "jquery": "^3.6.4", - "mobx": "^5.15.7", - "mobx-react": "^5.4.4", - "mobx-react-devtools": "^6.1.1", - "node-sass": "^8.0.0", - "perfect-scrollbar": "^1.5.5", - "popper.js": "^1.16.1", - "react": "^16.14.0", - "react-dom": "^16.14.0", - "react-router": "^4.3.1", - "simple-line-icons": "^2.5.5", - "ts-loader": "^9.4.2", - "tsd": "^0.28.0", - "typescript": "^5.0.2", - "webpack": "^5.76.2", - "webpack-cli": "^5.0.1" - } - }, - "node_modules/@babel/code-frame": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz", - "integrity": "sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==", - "dev": true, - "dependencies": { - "@babel/highlight": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-validator-identifier": { - "version": "7.19.1", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz", - "integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==", - "dev": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/highlight": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.18.6.tgz", - "integrity": "sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==", - "dev": true, - "dependencies": { - "@babel/helper-validator-identifier": "^7.18.6", - "chalk": "^2.0.0", - "js-tokens": "^4.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/highlight/node_modules/ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "dependencies": { - "color-convert": "^1.9.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/highlight/node_modules/chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/highlight/node_modules/supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/runtime": { - "version": "7.10.5", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.10.5.tgz", - "integrity": "sha512-otddXKhdNn7d0ptoFRHtMLa8LqDxLYwTjB4nYgM1yy5N6gU/MUf8zqyyLltCH3yAVitBzmwK4us+DD0l/MauAg==", - "dev": true, - "dependencies": { - "regenerator-runtime": "^0.13.4" - } - }, - "node_modules/@coreui/coreui": { - "version": "2.1.16", - "resolved": "https://registry.npmjs.org/@coreui/coreui/-/coreui-2.1.16.tgz", - "integrity": "sha512-1YOnQAlcX2bIgnaX3k9GKaN4lD+wKam7tdDfFj7/ZQTN1XG3dwDELHp4aagWQs78ix2CCO1LyeLrzGpsMcLW3Q==", - "dev": true, - "hasInstallScript": true, - "dependencies": { - "@coreui/coreui-plugin-npm-postinstall": "^1.0.2", - "bootstrap": "^4.3.1", - "core-js": "^3.3.4", - "regenerator-runtime": "^0.13.3" - }, - "engines": { - "node": ">= 8.7", - "npm": ">= 5" - }, - "peerDependencies": { - "jquery": "1.9.1 - 3", - "perfect-scrollbar": "^1.3.0", - "popper.js": "^1.14.3" - } - }, - "node_modules/@coreui/coreui-plugin-npm-postinstall": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@coreui/coreui-plugin-npm-postinstall/-/coreui-plugin-npm-postinstall-1.0.2.tgz", - "integrity": "sha512-yeeoWp+bNS84nP1977Y8UCiQ9pssO+f4QuVj3i0/gYZFjjvOgxx0dnyWhtowD5sLYnCRMPlPpqyjwXze3SlkYg==", - "dev": true, - "bin": { - "coreui-plugin-npm-postinstall": "index.js" - } - }, - "node_modules/@coreui/coreui/node_modules/bootstrap": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-4.6.2.tgz", - "integrity": "sha512-51Bbp/Uxr9aTuy6ca/8FbFloBUJZLHwnhTcnjIeRn2suQWsWzcuJhGjKDB5eppVte/8oCdOL3VuwxvZDUggwGQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/twbs" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/bootstrap" - } - ], - "peerDependencies": { - "jquery": "1.9.1 - 3", - "popper.js": "^1.16.1" - } - }, - "node_modules/@coreui/icons": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/@coreui/icons/-/icons-0.3.0.tgz", - "integrity": "sha512-RbBi5K5hUA8LUI9mM/i1BTaLjlyoS6kHwKbxWsH62+/j9L9WF8gAiJUhrNjMt1br8TY9RLeolyQys0E9480fIg==", - "dev": true - }, - "node_modules/@discoveryjs/json-ext": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz", - "integrity": "sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==", - "dev": true, - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/@gar/promisify": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@gar/promisify/-/promisify-1.1.3.tgz", - "integrity": "sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw==", - "dev": true - }, - "node_modules/@jest/schemas": { - "version": "29.4.3", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.4.3.tgz", - "integrity": "sha512-VLYKXQmtmuEz6IxJsrZwzG9NvtkQsWNnWMsKxqWNu3+CnfzJQhp0WDDKWLVV9hLKr0l3SLLFRqcYHjhtyuDVxg==", - "dev": true, - "dependencies": { - "@sinclair/typebox": "^0.25.16" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz", - "integrity": "sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==", - "dev": true, - "dependencies": { - "@jridgewell/set-array": "^1.0.1", - "@jridgewell/sourcemap-codec": "^1.4.10", - "@jridgewell/trace-mapping": "^0.3.9" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", - "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==", - "dev": true, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/set-array": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", - "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", - "dev": true, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/source-map": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.2.tgz", - "integrity": "sha512-m7O9o2uR8k2ObDysZYzdfhb08VuEml5oWGiosa1VdaPZ/A6QyPkAJuwN0Q1lhULOf6B7MtQmHENS743hWtCrgw==", - "dev": true, - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.0", - "@jridgewell/trace-mapping": "^0.3.9" - } - }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.4.14", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", - "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==", - "dev": true - }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.17", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.17.tgz", - "integrity": "sha512-MCNzAp77qzKca9+W/+I0+sEpaUnZoeasnghNeVc41VZCEKaCH73Vq3BZZ/SzWIgrqE4H4ceI+p+b6C0mHf9T4g==", - "dev": true, - "dependencies": { - "@jridgewell/resolve-uri": "3.1.0", - "@jridgewell/sourcemap-codec": "1.4.14" - } - }, - "node_modules/@nodelib/fs.scandir": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", - "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", - "dev": true, - "dependencies": { - "@nodelib/fs.stat": "2.0.5", - "run-parallel": "^1.1.9" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.stat": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", - "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", - "dev": true, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.walk": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", - "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", - "dev": true, - "dependencies": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@npmcli/fs": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-2.1.2.tgz", - "integrity": "sha512-yOJKRvohFOaLqipNtwYB9WugyZKhC/DZC4VYPmpaCzDBrA8YpK3qHZ8/HGscMnE4GqbkLNuVcCnxkeQEdGt6LQ==", - "dev": true, - "dependencies": { - "@gar/promisify": "^1.1.3", - "semver": "^7.3.5" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" - } - }, - "node_modules/@npmcli/fs/node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@npmcli/fs/node_modules/semver": { - "version": "7.3.8", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", - "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", - "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@npmcli/move-file": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@npmcli/move-file/-/move-file-2.0.1.tgz", - "integrity": "sha512-mJd2Z5TjYWq/ttPLLGqArdtnC74J6bOzg4rMDnN+p1xTacZ2yPRCk2y0oSWQtygLR9YVQXgOcONrwtnk3JupxQ==", - "deprecated": "This functionality has been moved to @npmcli/fs", - "dev": true, - "dependencies": { - "mkdirp": "^1.0.4", - "rimraf": "^3.0.2" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" - } - }, - "node_modules/@npmcli/move-file/node_modules/mkdirp": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", - "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", - "dev": true, - "bin": { - "mkdirp": "bin/cmd.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@npmcli/move-file/node_modules/rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "dev": true, - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/@sinclair/typebox": { - "version": "0.25.24", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.25.24.tgz", - "integrity": "sha512-XJfwUVUKDHF5ugKwIcxEgc9k8b7HbznCp6eUfWgu710hMPNIO4aw4/zB5RogDQz8nd6gyCDpU9O/m6qYEWY6yQ==", - "dev": true - }, - "node_modules/@tootallnate/once": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", - "integrity": "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==", - "dev": true, - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tsd/typescript": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/@tsd/typescript/-/typescript-5.0.2.tgz", - "integrity": "sha512-UgFiSalbDaWrkMBQv8rHetnlwj3HVZtJo6i2aGLe50I6XdlPZFdGRfM2GOaP+i3Tm6p+YcyEql3yoi3ZPs/6Pw==", - "dev": true - }, - "node_modules/@types/eslint": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-7.29.0.tgz", - "integrity": "sha512-VNcvioYDH8/FxaeTKkM4/TiTwt6pBV9E3OfGmvaw8tPl0rrHCJ4Ll15HRT+pMiFAf/MLQvAzC+6RzUMEL9Ceng==", - "dev": true, - "dependencies": { - "@types/estree": "*", - "@types/json-schema": "*" - } - }, - "node_modules/@types/eslint-scope": { - "version": "3.7.4", - "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.4.tgz", - "integrity": "sha512-9K4zoImiZc3HlIp6AVUDE4CWYx22a+lhSZMYNpbjW04+YF0KWj4pJXnEMjdnFTiQibFFmElcsasJXDbdI/EPhA==", - "dev": true, - "dependencies": { - "@types/eslint": "*", - "@types/estree": "*" - } - }, - "node_modules/@types/estree": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.0.tgz", - "integrity": "sha512-WulqXMDUTYAXCjZnk6JtIHPigp55cVtDgDrO2gHRwhyJto21+1zbVCtOYB2L1F9w4qCQ0rOGWBnBe0FNTiEJIQ==", - "dev": true - }, - "node_modules/@types/json-schema": { - "version": "7.0.11", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz", - "integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==", - "dev": true - }, - "node_modules/@types/minimist": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/@types/minimist/-/minimist-1.2.2.tgz", - "integrity": "sha512-jhuKLIRrhvCPLqwPcx6INqmKeiA5EWrsCOPhrlFSrbrmU4ZMPjj5Ul/oLCMDO98XRUIwVm78xICz4EPCektzeQ==", - "dev": true - }, - "node_modules/@types/node": { - "version": "18.15.3", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.15.3.tgz", - "integrity": "sha512-p6ua9zBxz5otCmbpb5D3U4B5Nanw6Pk3PPyX05xnxbB/fRv71N7CPmORg7uAD5P70T0xmx1pzAx/FUfa5X+3cw==", - "dev": true - }, - "node_modules/@types/normalize-package-data": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.1.tgz", - "integrity": "sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw==", - "dev": true - }, - "node_modules/@types/raf": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/@types/raf/-/raf-3.4.0.tgz", - "integrity": "sha512-taW5/WYqo36N7V39oYyHP9Ipfd5pNFvGTIQsNGj86xV88YQ7GnI30/yMfKDF7Zgin0m3e+ikX88FvImnK4RjGw==", - "optional": true - }, - "node_modules/@webassemblyjs/ast": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.1.tgz", - "integrity": "sha512-ukBh14qFLjxTQNTXocdyksN5QdM28S1CxHt2rdskFyL+xFV7VremuBLVbmCePj+URalXBENx/9Lm7lnhihtCSw==", - "dev": true, - "dependencies": { - "@webassemblyjs/helper-numbers": "1.11.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.1" - } - }, - "node_modules/@webassemblyjs/floating-point-hex-parser": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.1.tgz", - "integrity": "sha512-iGRfyc5Bq+NnNuX8b5hwBrRjzf0ocrJPI6GWFodBFzmFnyvrQ83SHKhmilCU/8Jv67i4GJZBMhEzltxzcNagtQ==", - "dev": true - }, - "node_modules/@webassemblyjs/helper-api-error": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.1.tgz", - "integrity": "sha512-RlhS8CBCXfRUR/cwo2ho9bkheSXG0+NwooXcc3PAILALf2QLdFyj7KGsKRbVc95hZnhnERon4kW/D3SZpp6Tcg==", - "dev": true - }, - "node_modules/@webassemblyjs/helper-buffer": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.1.tgz", - "integrity": "sha512-gwikF65aDNeeXa8JxXa2BAk+REjSyhrNC9ZwdT0f8jc4dQQeDQ7G4m0f2QCLPJiMTTO6wfDmRmj/pW0PsUvIcA==", - "dev": true - }, - "node_modules/@webassemblyjs/helper-numbers": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.1.tgz", - "integrity": "sha512-vDkbxiB8zfnPdNK9Rajcey5C0w+QJugEglN0of+kmO8l7lDb77AnlKYQF7aarZuCrv+l0UvqL+68gSDr3k9LPQ==", - "dev": true, - "dependencies": { - "@webassemblyjs/floating-point-hex-parser": "1.11.1", - "@webassemblyjs/helper-api-error": "1.11.1", - "@xtuc/long": "4.2.2" - } - }, - "node_modules/@webassemblyjs/helper-wasm-bytecode": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.1.tgz", - "integrity": "sha512-PvpoOGiJwXeTrSf/qfudJhwlvDQxFgelbMqtq52WWiXC6Xgg1IREdngmPN3bs4RoO83PnL/nFrxucXj1+BX62Q==", - "dev": true - }, - "node_modules/@webassemblyjs/helper-wasm-section": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.1.tgz", - "integrity": "sha512-10P9No29rYX1j7F3EVPX3JvGPQPae+AomuSTPiF9eBQeChHI6iqjMIwR9JmOJXwpnn/oVGDk7I5IlskuMwU/pg==", - "dev": true, - "dependencies": { - "@webassemblyjs/ast": "1.11.1", - "@webassemblyjs/helper-buffer": "1.11.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.1", - "@webassemblyjs/wasm-gen": "1.11.1" - } - }, - "node_modules/@webassemblyjs/ieee754": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.11.1.tgz", - "integrity": "sha512-hJ87QIPtAMKbFq6CGTkZYJivEwZDbQUgYd3qKSadTNOhVY7p+gfP6Sr0lLRVTaG1JjFj+r3YchoqRYxNH3M0GQ==", - "dev": true, - "dependencies": { - "@xtuc/ieee754": "^1.2.0" - } - }, - "node_modules/@webassemblyjs/leb128": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.11.1.tgz", - "integrity": "sha512-BJ2P0hNZ0u+Th1YZXJpzW6miwqQUGcIHT1G/sf72gLVD9DZ5AdYTqPNbHZh6K1M5VmKvFXwGSWZADz+qBWxeRw==", - "dev": true, - "dependencies": { - "@xtuc/long": "4.2.2" - } - }, - "node_modules/@webassemblyjs/utf8": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.11.1.tgz", - "integrity": "sha512-9kqcxAEdMhiwQkHpkNiorZzqpGrodQQ2IGrHHxCy+Ozng0ofyMA0lTqiLkVs1uzTRejX+/O0EOT7KxqVPuXosQ==", - "dev": true - }, - "node_modules/@webassemblyjs/wasm-edit": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.1.tgz", - "integrity": "sha512-g+RsupUC1aTHfR8CDgnsVRVZFJqdkFHpsHMfJuWQzWU3tvnLC07UqHICfP+4XyL2tnr1amvl1Sdp06TnYCmVkA==", - "dev": true, - "dependencies": { - "@webassemblyjs/ast": "1.11.1", - "@webassemblyjs/helper-buffer": "1.11.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.1", - "@webassemblyjs/helper-wasm-section": "1.11.1", - "@webassemblyjs/wasm-gen": "1.11.1", - "@webassemblyjs/wasm-opt": "1.11.1", - "@webassemblyjs/wasm-parser": "1.11.1", - "@webassemblyjs/wast-printer": "1.11.1" - } - }, - "node_modules/@webassemblyjs/wasm-gen": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.1.tgz", - "integrity": "sha512-F7QqKXwwNlMmsulj6+O7r4mmtAlCWfO/0HdgOxSklZfQcDu0TpLiD1mRt/zF25Bk59FIjEuGAIyn5ei4yMfLhA==", - "dev": true, - "dependencies": { - "@webassemblyjs/ast": "1.11.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.1", - "@webassemblyjs/ieee754": "1.11.1", - "@webassemblyjs/leb128": "1.11.1", - "@webassemblyjs/utf8": "1.11.1" - } - }, - "node_modules/@webassemblyjs/wasm-opt": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.1.tgz", - "integrity": "sha512-VqnkNqnZlU5EB64pp1l7hdm3hmQw7Vgqa0KF/KCNO9sIpI6Fk6brDEiX+iCOYrvMuBWDws0NkTOxYEb85XQHHw==", - "dev": true, - "dependencies": { - "@webassemblyjs/ast": "1.11.1", - "@webassemblyjs/helper-buffer": "1.11.1", - "@webassemblyjs/wasm-gen": "1.11.1", - "@webassemblyjs/wasm-parser": "1.11.1" - } - }, - "node_modules/@webassemblyjs/wasm-parser": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.1.tgz", - "integrity": "sha512-rrBujw+dJu32gYB7/Lup6UhdkPx9S9SnobZzRVL7VcBH9Bt9bCBLEuX/YXOOtBsOZ4NQrRykKhffRWHvigQvOA==", - "dev": true, - "dependencies": { - "@webassemblyjs/ast": "1.11.1", - "@webassemblyjs/helper-api-error": "1.11.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.1", - "@webassemblyjs/ieee754": "1.11.1", - "@webassemblyjs/leb128": "1.11.1", - "@webassemblyjs/utf8": "1.11.1" - } - }, - "node_modules/@webassemblyjs/wast-printer": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.11.1.tgz", - "integrity": "sha512-IQboUWM4eKzWW+N/jij2sRatKMh99QEelo3Eb2q0qXkvPRISAj8Qxtmw5itwqK+TTkBuUIE45AxYPToqPtL5gg==", - "dev": true, - "dependencies": { - "@webassemblyjs/ast": "1.11.1", - "@xtuc/long": "4.2.2" - } - }, - "node_modules/@webpack-cli/configtest": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@webpack-cli/configtest/-/configtest-2.0.1.tgz", - "integrity": "sha512-njsdJXJSiS2iNbQVS0eT8A/KPnmyH4pv1APj2K0d1wrZcBLw+yppxOy4CGqa0OxDJkzfL/XELDhD8rocnIwB5A==", - "dev": true, - "engines": { - "node": ">=14.15.0" - }, - "peerDependencies": { - "webpack": "5.x.x", - "webpack-cli": "5.x.x" - } - }, - "node_modules/@webpack-cli/info": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@webpack-cli/info/-/info-2.0.1.tgz", - "integrity": "sha512-fE1UEWTwsAxRhrJNikE7v4EotYflkEhBL7EbajfkPlf6E37/2QshOy/D48Mw8G5XMFlQtS6YV42vtbG9zBpIQA==", - "dev": true, - "engines": { - "node": ">=14.15.0" - }, - "peerDependencies": { - "webpack": "5.x.x", - "webpack-cli": "5.x.x" - } - }, - "node_modules/@webpack-cli/serve": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@webpack-cli/serve/-/serve-2.0.1.tgz", - "integrity": "sha512-0G7tNyS+yW8TdgHwZKlDWYXFA6OJQnoLCQvYKkQP0Q2X205PSQ6RNUj0M+1OB/9gRQaUZ/ccYfaxd0nhaWKfjw==", - "dev": true, - "engines": { - "node": ">=14.15.0" - }, - "peerDependencies": { - "webpack": "5.x.x", - "webpack-cli": "5.x.x" - }, - "peerDependenciesMeta": { - "webpack-dev-server": { - "optional": true - } - } - }, - "node_modules/@xtuc/ieee754": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", - "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", - "dev": true - }, - "node_modules/@xtuc/long": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", - "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", - "dev": true - }, - "node_modules/abbrev": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", - "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", - "dev": true - }, - "node_modules/accounting": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/accounting/-/accounting-0.4.1.tgz", - "integrity": "sha1-h91BA+/39EYPHhhvXGd+1s9WaIM=" - }, - "node_modules/acorn": { - "version": "8.8.2", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz", - "integrity": "sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==", - "dev": true, - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/acorn-import-assertions": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.8.0.tgz", - "integrity": "sha512-m7VZ3jwz4eK6A4Vtt8Ew1/mNbP24u0FhdyfA7fSvnJR6LMdfOYnmuIrrJAgrYfYJ10F/otaHTtrtrtmHdMNzEw==", - "dev": true, - "peerDependencies": { - "acorn": "^8" - } - }, - "node_modules/ag-grid": { - "version": "18.1.2", - "resolved": "https://registry.npmjs.org/ag-grid/-/ag-grid-18.1.2.tgz", - "integrity": "sha512-HtJt8iFcRKCBj5UHBDmwSLLr72F3XDACeBNarH4nJWFHIqcnu7u0Ifrd2nftPmfEBj6YjFHawDqcZL2yo3YfmQ==", - "deprecated": "ag-grid is now deprecated - please use @ag-grid-community/all-modules. See www.ag-grid.com/javascript-grid-modules/ for more information." - }, - "node_modules/ag-grid-community": { - "version": "19.1.4", - "resolved": "https://registry.npmjs.org/ag-grid-community/-/ag-grid-community-19.1.4.tgz", - "integrity": "sha512-il498Bek+OTVyhMs6MgJlgUVQKBDuoUwVNFy3c93U5ulWaMHOVfmVxpKdv1UkqfDwnaPhQrx/hNDQ32BuBud8g==", - "dev": true - }, - "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, - "dependencies": { - "debug": "4" - }, - "engines": { - "node": ">= 6.0.0" - } - }, - "node_modules/agentkeepalive": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.2.1.tgz", - "integrity": "sha512-Zn4cw2NEqd+9fiSVWMscnjyQ1a8Yfoc5oBajLeo5w+YBHgDUcEBY2hS4YpTz6iN5f/2zQiktcuM6tS8x1p9dpA==", - "dev": true, - "dependencies": { - "debug": "^4.1.0", - "depd": "^1.1.2", - "humanize-ms": "^1.2.1" - }, - "engines": { - "node": ">= 8.0.0" - } - }, - "node_modules/aggregate-error": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", - "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", - "dev": true, - "dependencies": { - "clean-stack": "^2.0.0", - "indent-string": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, - "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/ajv-keywords": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", - "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", - "dev": true, - "peerDependencies": { - "ajv": "^6.9.1" - } - }, - "node_modules/ansi-colors": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-1.1.0.tgz", - "integrity": "sha512-SFKX67auSNoVR38N3L+nvsPjOE0bybKTYbkf5tRvushrAPQ9V75huw0ZxBkKVeRU9kqH3d6HA4xTckbwZ4ixmA==", - "dev": true, - "dependencies": { - "ansi-wrap": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/ansi-escapes": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", - "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", - "dev": true, - "dependencies": { - "type-fest": "^0.21.3" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/ansi-gray": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/ansi-gray/-/ansi-gray-0.1.1.tgz", - "integrity": "sha512-HrgGIZUl8h2EHuZaU9hTR/cU5nhKxpVE1V6kdGsQ8e4zirElJ5fvtfc8N7Q1oq1aatO275i8pUFUCpNWCAnVWw==", - "dev": true, - "dependencies": { - "ansi-wrap": "0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/ansi-styles/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/ansi-styles/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/ansi-wrap": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/ansi-wrap/-/ansi-wrap-0.1.0.tgz", - "integrity": "sha512-ZyznvL8k/FZeQHr2T6LzcJ/+vBApDnMNZvfVFy3At0knswWd6rJ3/0Hhmpu8oqa6C92npmozs890sX9Dl6q+Qw==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/anymatch": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz", - "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==", - "dev": true, - "dependencies": { - "micromatch": "^3.1.4", - "normalize-path": "^2.1.1" - } - }, - "node_modules/anymatch/node_modules/normalize-path": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", - "integrity": "sha512-3pKJwH184Xo/lnH6oyP1q2pMd7HcypqqmRs91/6/i2CGtWwIKGCkOOMTm/zXbgTEWHw1uNpNi/igc3ePOYHb6w==", - "dev": true, - "dependencies": { - "remove-trailing-separator": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/append-buffer": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/append-buffer/-/append-buffer-1.0.2.tgz", - "integrity": "sha512-WLbYiXzD3y/ATLZFufV/rZvWdZOs+Z/+5v1rBZ463Jn398pa6kcde27cvozYnBoxXblGZTFfoPpsaEw0orU5BA==", - "dev": true, - "dependencies": { - "buffer-equal": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/aproba": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", - "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==", - "dev": true - }, - "node_modules/archy": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/archy/-/archy-1.0.0.tgz", - "integrity": "sha512-Xg+9RwCg/0p32teKdGMPTPnVXKD0w3DfHnFTficozsAgsvq2XenPJq/MYpzzQ/v8zrOyJn6Ds39VA4JIDwFfqw==", - "dev": true - }, - "node_modules/are-we-there-yet": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-3.0.1.tgz", - "integrity": "sha512-QZW4EDmGwlYur0Yyf/b2uGucHQMa8aFUP7eu9ddR73vvhFyt4V0Vl3QHPcTNJ8l6qYOBdxgXdnBXQrHilfRQBg==", - "dev": true, - "dependencies": { - "delegates": "^1.0.0", - "readable-stream": "^3.6.0" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" - } - }, - "node_modules/are-we-there-yet/node_modules/readable-stream": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", - "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", - "dev": true, - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/arr-diff": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", - "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/arr-filter": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/arr-filter/-/arr-filter-1.1.2.tgz", - "integrity": "sha512-A2BETWCqhsecSvCkWAeVBFLH6sXEUGASuzkpjL3GR1SlL/PWL6M3J8EAAld2Uubmh39tvkJTqC9LeLHCUKmFXA==", - "dev": true, - "dependencies": { - "make-iterator": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/arr-flatten": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz", - "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/arr-map": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/arr-map/-/arr-map-2.0.2.tgz", - "integrity": "sha512-tVqVTHt+Q5Xb09qRkbu+DidW1yYzz5izWS2Xm2yFm7qJnmUfz4HPzNxbHkdRJbz2lrqI7S+z17xNYdFcBBO8Hw==", - "dev": true, - "dependencies": { - "make-iterator": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/arr-union": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", - "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/array-each": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/array-each/-/array-each-1.0.1.tgz", - "integrity": "sha512-zHjL5SZa68hkKHBFBK6DJCTtr9sfTCPCaph/L7tMSLcTFgy+zX7E+6q5UArbtOtMBCtxdICpfTCspRse+ywyXA==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/array-initial": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/array-initial/-/array-initial-1.1.0.tgz", - "integrity": "sha512-BC4Yl89vneCYfpLrs5JU2aAu9/a+xWbeKhvISg9PT7eWFB9UlRvI+rKEtk6mgxWr3dSkk9gQ8hCrdqt06NXPdw==", - "dev": true, - "dependencies": { - "array-slice": "^1.0.0", - "is-number": "^4.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/array-initial/node_modules/is-number": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-4.0.0.tgz", - "integrity": "sha512-rSklcAIlf1OmFdyAqbnWTLVelsQ58uvZ66S/ZyawjWqIviTWCjg2PzVGw8WUA+nNuPTqb4wgA+NszrJ+08LlgQ==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/array-last": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/array-last/-/array-last-1.3.0.tgz", - "integrity": "sha512-eOCut5rXlI6aCOS7Z7kCplKRKyiFQ6dHFBem4PwlwKeNFk2/XxTrhRh5T9PyaEWGy/NHTZWbY+nsZlNFJu9rYg==", - "dev": true, - "dependencies": { - "is-number": "^4.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/array-last/node_modules/is-number": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-4.0.0.tgz", - "integrity": "sha512-rSklcAIlf1OmFdyAqbnWTLVelsQ58uvZ66S/ZyawjWqIviTWCjg2PzVGw8WUA+nNuPTqb4wgA+NszrJ+08LlgQ==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/array-slice": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/array-slice/-/array-slice-1.1.0.tgz", - "integrity": "sha512-B1qMD3RBP7O8o0H2KbrXDyB0IccejMF15+87Lvlor12ONPRHP6gTjXMNkt/d3ZuOGbAe66hFmaCfECI24Ufp6w==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/array-sort": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/array-sort/-/array-sort-1.0.0.tgz", - "integrity": "sha512-ihLeJkonmdiAsD7vpgN3CRcx2J2S0TiYW+IS/5zHBI7mKUq3ySvBdzzBfD236ubDBQFiiyG3SWCPc+msQ9KoYg==", - "dev": true, - "dependencies": { - "default-compare": "^1.0.0", - "get-value": "^2.0.6", - "kind-of": "^5.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/array-sort/node_modules/kind-of": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", - "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/array-union": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", - "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/array-unique": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", - "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/arrify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", - "integrity": "sha512-3CYzex9M9FGQjCGMGyi6/31c8GJbgb0qGyrx5HWxPd0aCwh4cB2YjMb2Xf9UuoogrMrlO9cTqnB5rI5GHZTcUA==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/assign-symbols": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", - "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/async-done": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/async-done/-/async-done-1.3.2.tgz", - "integrity": "sha512-uYkTP8dw2og1tu1nmza1n1CMW0qb8gWWlwqMmLb7MhBVs4BXrFziT6HXUd+/RlRA/i4H9AkofYloUbs1fwMqlw==", - "dev": true, - "dependencies": { - "end-of-stream": "^1.1.0", - "once": "^1.3.2", - "process-nextick-args": "^2.0.0", - "stream-exhaust": "^1.0.1" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/async-each": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/async-each/-/async-each-1.0.6.tgz", - "integrity": "sha512-c646jH1avxr+aVpndVMeAfYw7wAa6idufrlN3LPA4PmKS0QEGp6PIC9nwz0WQkkvBGAMEki3pFdtxaF39J9vvg==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://paulmillr.com/funding/" - } - ] - }, - "node_modules/async-foreach": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/async-foreach/-/async-foreach-0.1.3.tgz", - "integrity": "sha512-VUeSMD8nEGBWaZK4lizI1sf3yEC7pnAQ/mrI7pC2fBz2s/tq5jWWEngTwaf0Gruu/OoXRGLGg1XFqpYBiGTYJA==", - "dev": true, - "engines": { - "node": "*" - } - }, - "node_modules/async-settle": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/async-settle/-/async-settle-1.0.0.tgz", - "integrity": "sha512-VPXfB4Vk49z1LHHodrEQ6Xf7W4gg1w0dAPROHngx7qgDjqmIQ+fXmwgGXTW/ITLai0YLSvWepJOP9EVpMnEAcw==", - "dev": true, - "dependencies": { - "async-done": "^1.2.2" - }, - "engines": { - "node": ">= 0.10" - } - }, - "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 - }, - "node_modules/atob": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", - "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==", - "bin": { - "atob": "bin/atob.js" - }, - "engines": { - "node": ">= 4.5.0" - } - }, - "node_modules/axios": { - "version": "1.3.4", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.3.4.tgz", - "integrity": "sha512-toYm+Bsyl6VC5wSkfkbbNB6ROv7KY93PEBBL6xyDczaIHasAiv4wPqQ/c4RjoQzipxRD2W5g21cOqQulZ7rHwQ==", - "dev": true, - "dependencies": { - "follow-redirects": "^1.15.0", - "form-data": "^4.0.0", - "proxy-from-env": "^1.1.0" - } - }, - "node_modules/bach": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/bach/-/bach-1.2.0.tgz", - "integrity": "sha512-bZOOfCb3gXBXbTFXq3OZtGR88LwGeJvzu6szttaIzymOTS4ZttBNOWSv7aLZja2EMycKtRYV0Oa8SNKH/zkxvg==", - "dev": true, - "dependencies": { - "arr-filter": "^1.1.1", - "arr-flatten": "^1.0.1", - "arr-map": "^2.0.0", - "array-each": "^1.0.0", - "array-initial": "^1.0.0", - "array-last": "^1.1.1", - "async-done": "^1.2.2", - "async-settle": "^1.0.0", - "now-and-later": "^2.0.0" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/balanced-match": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", - "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", - "dev": true - }, - "node_modules/base": { - "version": "0.11.2", - "resolved": "https://registry.npmjs.org/base/-/base-0.11.2.tgz", - "integrity": "sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==", - "dev": true, - "dependencies": { - "cache-base": "^1.0.1", - "class-utils": "^0.3.5", - "component-emitter": "^1.2.1", - "define-property": "^1.0.0", - "isobject": "^3.0.1", - "mixin-deep": "^1.2.0", - "pascalcase": "^0.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/base/node_modules/define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", - "dev": true, - "dependencies": { - "is-descriptor": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/base/node_modules/is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "dev": true, - "dependencies": { - "kind-of": "^6.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/base/node_modules/is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "dev": true, - "dependencies": { - "kind-of": "^6.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/base/node_modules/is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "dev": true, - "dependencies": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/binary-extensions": { - "version": "1.13.1", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.13.1.tgz", - "integrity": "sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/bindings": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", - "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", - "dev": true, - "optional": true, - "dependencies": { - "file-uri-to-path": "1.0.0" - } - }, - "node_modules/bootstrap": { - "version": "5.2.3", - "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.2.3.tgz", - "integrity": "sha512-cEKPM+fwb3cT8NzQZYEu4HilJ3anCrWqh3CHAok1p9jXqMPsPTBhU25fBckEJHJ/p+tTxTFTsFQGM+gaHpi3QQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/twbs" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/bootstrap" - } - ], - "peerDependencies": { - "@popperjs/core": "^2.11.6" - } - }, - "node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/braces": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", - "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", - "dev": true, - "dependencies": { - "arr-flatten": "^1.1.0", - "array-unique": "^0.3.2", - "extend-shallow": "^2.0.1", - "fill-range": "^4.0.0", - "isobject": "^3.0.1", - "repeat-element": "^1.1.2", - "snapdragon": "^0.8.1", - "snapdragon-node": "^2.0.1", - "split-string": "^3.0.2", - "to-regex": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/braces/node_modules/extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "dependencies": { - "is-extendable": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/browserslist": { - "version": "4.21.5", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.5.tgz", - "integrity": "sha512-tUkiguQGW7S3IhB7N+c2MV/HZPSCPAAiYBZXLsBhFB/PCy6ZKKsZrmBayHV9fdGV/ARIfJ14NkxKzRDjvp7L6w==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - } - ], - "dependencies": { - "caniuse-lite": "^1.0.30001449", - "electron-to-chromium": "^1.4.284", - "node-releases": "^2.0.8", - "update-browserslist-db": "^1.0.10" - }, - "bin": { - "browserslist": "cli.js" - }, - "engines": { - "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" - } - }, - "node_modules/btoa": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/btoa/-/btoa-1.2.1.tgz", - "integrity": "sha512-SB4/MIGlsiVkMcHmT+pSmIPoNDoHg+7cMzmt3Uxt628MTz2487DKSqK/fuhFBrkuqrYv5UCEnACpF4dTFNKc/g==", - "bin": { - "btoa": "bin/btoa.js" - }, - "engines": { - "node": ">= 0.4.0" - } - }, - "node_modules/buffer-equal": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/buffer-equal/-/buffer-equal-1.0.1.tgz", - "integrity": "sha512-QoV3ptgEaQpvVwbXdSO39iqPQTCxSF7A5U99AxbHYqUdCizL/lH2Z0A2y6nbZucxMEOtNyZfG2s6gsVugGpKkg==", - "dev": true, - "engines": { - "node": ">=0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/buffer-from": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", - "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", - "dev": true - }, - "node_modules/cache-base": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz", - "integrity": "sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==", - "dev": true, - "dependencies": { - "collection-visit": "^1.0.0", - "component-emitter": "^1.2.1", - "get-value": "^2.0.6", - "has-value": "^1.0.0", - "isobject": "^3.0.1", - "set-value": "^2.0.0", - "to-object-path": "^0.3.0", - "union-value": "^1.0.0", - "unset-value": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/call-bind": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", - "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", - "dev": true, - "dependencies": { - "function-bind": "^1.1.1", - "get-intrinsic": "^1.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/camelcase": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-3.0.0.tgz", - "integrity": "sha512-4nhGqUkc4BqbBBB4Q6zLuD7lzzrHYrjKGeYaEji/3tFR5VdJu9v+LilhGIVe8wxEJPPOeWo7eg8dwY13TZ1BNg==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/camelcase-keys": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-6.2.2.tgz", - "integrity": "sha512-YrwaA0vEKazPBkn0ipTiMpSajYDSe+KjQfrjhcBMxJt/znbvlHd8Pw/Vamaz5EB4Wfhs3SUR3Z9mwRu/P3s3Yg==", - "dev": true, - "dependencies": { - "camelcase": "^5.3.1", - "map-obj": "^4.0.0", - "quick-lru": "^4.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/camelcase-keys/node_modules/camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/camelcase-keys/node_modules/map-obj": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-4.3.0.tgz", - "integrity": "sha512-hdN1wVrZbb29eBGiGjJbeP8JbKjq1urkHJ/LIP/NY48MZ1QVXUsQBV1G1zvYFHn1XE06cwjBsOI2K3Ulnj1YXQ==", - "dev": true, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/caniuse-lite": { - "version": "1.0.30001468", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001468.tgz", - "integrity": "sha512-zgAo8D5kbOyUcRAgSmgyuvBkjrGk5CGYG5TYgFdpQv+ywcyEpo1LOWoG8YmoflGnh+V+UsNuKYedsoYs0hzV5A==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/caniuse-lite" - } - ] - }, - "node_modules/canvg": { - "version": "3.0.10", - "resolved": "https://registry.npmjs.org/canvg/-/canvg-3.0.10.tgz", - "integrity": "sha512-qwR2FRNO9NlzTeKIPIKpnTY6fqwuYSequ8Ru8c0YkYU7U0oW+hLUvWadLvAu1Rl72OMNiFhoLu4f8eUjQ7l/+Q==", - "optional": true, - "dependencies": { - "@babel/runtime": "^7.12.5", - "@types/raf": "^3.4.0", - "core-js": "^3.8.3", - "raf": "^3.4.1", - "regenerator-runtime": "^0.13.7", - "rgbcolor": "^1.0.1", - "stackblur-canvas": "^2.0.0", - "svg-pathdata": "^6.0.3" - }, - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/canvg/node_modules/@babel/runtime": { - "version": "7.20.13", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.20.13.tgz", - "integrity": "sha512-gt3PKXs0DBoL9xCvOIIZ2NEqAGZqHjAnmVbfQtB620V0uReIQutpel14KcneZuer7UioY8ALKZ7iocavvzTNFA==", - "optional": true, - "dependencies": { - "regenerator-runtime": "^0.13.11" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/canvg/node_modules/@babel/runtime/node_modules/regenerator-runtime": { - "version": "0.13.11", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", - "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==", - "optional": true - }, - "node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/chokidar": { - "version": "2.1.8", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.1.8.tgz", - "integrity": "sha512-ZmZUazfOzf0Nve7duiCKD23PFSCs4JPoYyccjUFF3aQkQadqBhfzhjkwBH2mNOG9cTBwhamM37EIsIkZw3nRgg==", - "deprecated": "Chokidar 2 does not receive security updates since 2019. Upgrade to chokidar 3 with 15x fewer dependencies", - "dev": true, - "dependencies": { - "anymatch": "^2.0.0", - "async-each": "^1.0.1", - "braces": "^2.3.2", - "glob-parent": "^3.1.0", - "inherits": "^2.0.3", - "is-binary-path": "^1.0.0", - "is-glob": "^4.0.0", - "normalize-path": "^3.0.0", - "path-is-absolute": "^1.0.0", - "readdirp": "^2.2.1", - "upath": "^1.1.1" - }, - "optionalDependencies": { - "fsevents": "^1.2.7" - } - }, - "node_modules/chrome-trace-event": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.2.tgz", - "integrity": "sha512-9e/zx1jw7B4CO+c/RXoCsfg/x1AfUBioy4owYH0bJprEYAx5hRFLRhWBqHAG57D0ZM4H7vxbP7bPe0VwhQRYDQ==", - "dev": true, - "dependencies": { - "tslib": "^1.9.0" - }, - "engines": { - "node": ">=6.0" - } - }, - "node_modules/class-utils": { - "version": "0.3.6", - "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz", - "integrity": "sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==", - "dev": true, - "dependencies": { - "arr-union": "^3.1.0", - "define-property": "^0.2.5", - "isobject": "^3.0.0", - "static-extend": "^0.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/class-utils/node_modules/define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, - "dependencies": { - "is-descriptor": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/clean-stack": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", - "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/cliui": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-3.2.0.tgz", - "integrity": "sha512-0yayqDxWQbqk3ojkYqUKqaAQ6AfNKeKWRNA8kR0WXzAsdHpP4BIaOmMAG87JGuO6qcobyW4GjxHd9PmhEd+T9w==", - "dev": true, - "dependencies": { - "string-width": "^1.0.1", - "strip-ansi": "^3.0.1", - "wrap-ansi": "^2.0.0" - } - }, - "node_modules/clone": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", - "integrity": "sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==", - "dev": true, - "engines": { - "node": ">=0.8" - } - }, - "node_modules/clone-buffer": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/clone-buffer/-/clone-buffer-1.0.0.tgz", - "integrity": "sha512-KLLTJWrvwIP+OPfMn0x2PheDEP20RPUcGXj/ERegTgdmPEZylALQldygiqrPPu8P45uNuPs7ckmReLY6v/iA5g==", - "dev": true, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/clone-deep": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz", - "integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==", - "dev": true, - "dependencies": { - "is-plain-object": "^2.0.4", - "kind-of": "^6.0.2", - "shallow-clone": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/clone-stats": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/clone-stats/-/clone-stats-1.0.0.tgz", - "integrity": "sha512-au6ydSpg6nsrigcZ4m8Bc9hxjeW+GJ8xh5G3BJCMt4WXe1H10UNaVOamqQTmrx1kjVuxAHIQSNU6hY4Nsn9/ag==", - "dev": true - }, - "node_modules/cloneable-readable": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/cloneable-readable/-/cloneable-readable-1.1.3.tgz", - "integrity": "sha512-2EF8zTQOxYq70Y4XKtorQupqF0m49MBz2/yf5Bj+MHjvpG3Hy7sImifnqD6UA+TKYxeSV+u6qqQPawN5UvnpKQ==", - "dev": true, - "dependencies": { - "inherits": "^2.0.1", - "process-nextick-args": "^2.0.0", - "readable-stream": "^2.3.5" - } - }, - "node_modules/code-point-at": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", - "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/collection-map": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/collection-map/-/collection-map-1.0.0.tgz", - "integrity": "sha512-5D2XXSpkOnleOI21TG7p3T0bGAsZ/XknZpKBmGYyluO8pw4zA3K8ZlrBIbC4FXg3m6z/RNFiUFfT2sQK01+UHA==", - "dev": true, - "dependencies": { - "arr-map": "^2.0.2", - "for-own": "^1.0.0", - "make-iterator": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/collection-visit": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz", - "integrity": "sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA=", - "dev": true, - "dependencies": { - "map-visit": "^1.0.0", - "object-visit": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, - "dependencies": { - "color-name": "1.1.3" - } - }, - "node_modules/color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", - "dev": true - }, - "node_modules/color-support": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", - "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==", - "dev": true, - "bin": { - "color-support": "bin.js" - } - }, - "node_modules/colorette": { - "version": "2.0.19", - "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.19.tgz", - "integrity": "sha512-3tlv/dIP7FWvj3BsbHrGLJ6l/oKh1O3TcgBqMn+yyCagOxc23fyzDS6HypQbgxWbkpDnf52p1LuR4eWDQ/K9WQ==", - "dev": true - }, - "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, - "dependencies": { - "delayed-stream": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/commander": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", - "dev": true - }, - "node_modules/component-emitter": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", - "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==", - "dev": true - }, - "node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", - "dev": true - }, - "node_modules/concat-stream": { - "version": "1.6.2", - "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", - "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", - "dev": true, - "engines": [ - "node >= 0.8" - ], - "dependencies": { - "buffer-from": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^2.2.2", - "typedarray": "^0.0.6" - } - }, - "node_modules/console-control-strings": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", - "integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==", - "dev": true - }, - "node_modules/convert-source-map": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", - "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", - "dev": true - }, - "node_modules/copy-anything": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/copy-anything/-/copy-anything-2.0.6.tgz", - "integrity": "sha512-1j20GZTsvKNkc4BY3NpMOM8tt///wY3FpIzozTOFO2ffuZcV61nojHXVKIy3WM+7ADCy5FVhdZYHYDdgTU0yJw==", - "dev": true, - "dependencies": { - "is-what": "^3.14.1" - }, - "funding": { - "url": "https://github.com/sponsors/mesqueeb" - } - }, - "node_modules/copy-descriptor": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz", - "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/copy-props": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/copy-props/-/copy-props-2.0.5.tgz", - "integrity": "sha512-XBlx8HSqrT0ObQwmSzM7WE5k8FxTV75h1DX1Z3n6NhQ/UYYAvInWYmG06vFt7hQZArE2fuO62aihiWIVQwh1sw==", - "dev": true, - "dependencies": { - "each-props": "^1.3.2", - "is-plain-object": "^5.0.0" - } - }, - "node_modules/copy-props/node_modules/is-plain-object": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz", - "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/core-js": { - "version": "3.27.2", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.27.2.tgz", - "integrity": "sha512-9ashVQskuh5AZEZ1JdQWp1GqSoC1e1G87MzRqg2gIfVAQ7Qn9K+uFj8EcniUFA4P2NLZfV+TOlX1SzoKfo+s7w==", - "devOptional": true, - "hasInstallScript": true, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/core-js" - } - }, - "node_modules/core-util-is": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", - "dev": true - }, - "node_modules/cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", - "dev": true, - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/cross-spawn/node_modules/path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/cross-spawn/node_modules/shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "dependencies": { - "shebang-regex": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/cross-spawn/node_modules/shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/cross-spawn/node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/css-line-break": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/css-line-break/-/css-line-break-2.1.0.tgz", - "integrity": "sha512-FHcKFCZcAha3LwfVBhCQbW2nCNbkZXn7KVUJcsT5/P8YmfsVja0FMPJr0B903j/E69HUphKiV9iQArX8SDYA4w==", - "devOptional": true, - "dependencies": { - "utrie": "^1.0.2" - } - }, - "node_modules/d": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/d/-/d-1.0.1.tgz", - "integrity": "sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA==", - "dev": true, - "dependencies": { - "es5-ext": "^0.10.50", - "type": "^1.0.1" - } - }, - "node_modules/d3": { - "version": "7.8.2", - "resolved": "https://registry.npmjs.org/d3/-/d3-7.8.2.tgz", - "integrity": "sha512-WXty7qOGSHb7HR7CfOzwN1Gw04MUOzN8qh9ZUsvwycIMb4DYMpY9xczZ6jUorGtO6bR9BPMPaueIKwiDxu9uiQ==", - "dependencies": { - "d3-array": "3", - "d3-axis": "3", - "d3-brush": "3", - "d3-chord": "3", - "d3-color": "3", - "d3-contour": "4", - "d3-delaunay": "6", - "d3-dispatch": "3", - "d3-drag": "3", - "d3-dsv": "3", - "d3-ease": "3", - "d3-fetch": "3", - "d3-force": "3", - "d3-format": "3", - "d3-geo": "3", - "d3-hierarchy": "3", - "d3-interpolate": "3", - "d3-path": "3", - "d3-polygon": "3", - "d3-quadtree": "3", - "d3-random": "3", - "d3-scale": "4", - "d3-scale-chromatic": "3", - "d3-selection": "3", - "d3-shape": "3", - "d3-time": "3", - "d3-time-format": "4", - "d3-timer": "3", - "d3-transition": "3", - "d3-zoom": "3" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-array": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.2.tgz", - "integrity": "sha512-yEEyEAbDrF8C6Ob2myOBLjwBLck1Z89jMGFee0oPsn95GqjerpaOA4ch+vc2l0FNFFwMD5N7OCSEN5eAlsUbgQ==", - "dependencies": { - "internmap": "1 - 2" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-axis": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/d3-axis/-/d3-axis-3.0.0.tgz", - "integrity": "sha512-IH5tgjV4jE/GhHkRV0HiVYPDtvfjHQlQfJHs0usq7M30XcSBvOotpmH1IgkcXsO/5gEQZD43B//fc7SRT5S+xw==", - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-brush": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/d3-brush/-/d3-brush-3.0.0.tgz", - "integrity": "sha512-ALnjWlVYkXsVIGlOsuWH1+3udkYFI48Ljihfnh8FZPF2QS9o+PzGLBslO0PjzVoHLZ2KCVgAM8NVkXPJB2aNnQ==", - "dependencies": { - "d3-dispatch": "1 - 3", - "d3-drag": "2 - 3", - "d3-interpolate": "1 - 3", - "d3-selection": "3", - "d3-transition": "3" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-chord": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/d3-chord/-/d3-chord-3.0.1.tgz", - "integrity": "sha512-VE5S6TNa+j8msksl7HwjxMHDM2yNK3XCkusIlpX5kwauBfXuyLAtNg9jCp/iHH61tgI4sb6R/EIMWCqEIdjT/g==", - "dependencies": { - "d3-path": "1 - 3" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-color": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz", - "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==", - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-contour": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/d3-contour/-/d3-contour-4.0.2.tgz", - "integrity": "sha512-4EzFTRIikzs47RGmdxbeUvLWtGedDUNkTcmzoeyg4sP/dvCexO47AaQL7VKy/gul85TOxw+IBgA8US2xwbToNA==", - "dependencies": { - "d3-array": "^3.2.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-delaunay": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/d3-delaunay/-/d3-delaunay-6.0.2.tgz", - "integrity": "sha512-IMLNldruDQScrcfT+MWnazhHbDJhcRJyOEBAJfwQnHle1RPh6WDuLvxNArUju2VSMSUuKlY5BGHRJ2cYyoFLQQ==", - "dependencies": { - "delaunator": "5" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-dispatch": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-3.0.1.tgz", - "integrity": "sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==", - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-drag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/d3-drag/-/d3-drag-3.0.0.tgz", - "integrity": "sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==", - "dependencies": { - "d3-dispatch": "1 - 3", - "d3-selection": "3" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-dsv": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/d3-dsv/-/d3-dsv-3.0.1.tgz", - "integrity": "sha512-UG6OvdI5afDIFP9w4G0mNq50dSOsXHJaRE8arAS5o9ApWnIElp8GZw1Dun8vP8OyHOZ/QJUKUJwxiiCCnUwm+Q==", - "dependencies": { - "commander": "7", - "iconv-lite": "0.6", - "rw": "1" - }, - "bin": { - "csv2json": "bin/dsv2json.js", - "csv2tsv": "bin/dsv2dsv.js", - "dsv2dsv": "bin/dsv2dsv.js", - "dsv2json": "bin/dsv2json.js", - "json2csv": "bin/json2dsv.js", - "json2dsv": "bin/json2dsv.js", - "json2tsv": "bin/json2dsv.js", - "tsv2csv": "bin/dsv2dsv.js", - "tsv2json": "bin/dsv2json.js" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-dsv/node_modules/commander": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", - "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", - "engines": { - "node": ">= 10" - } - }, - "node_modules/d3-ease": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz", - "integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==", - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-fetch": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/d3-fetch/-/d3-fetch-3.0.1.tgz", - "integrity": "sha512-kpkQIM20n3oLVBKGg6oHrUchHM3xODkTzjMoj7aWQFq5QEM+R6E4WkzT5+tojDY7yjez8KgCBRoj4aEr99Fdqw==", - "dependencies": { - "d3-dsv": "1 - 3" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-force": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/d3-force/-/d3-force-3.0.0.tgz", - "integrity": "sha512-zxV/SsA+U4yte8051P4ECydjD/S+qeYtnaIyAs9tgHCqfguma/aAQDjo85A9Z6EKhBirHRJHXIgJUlffT4wdLg==", - "dependencies": { - "d3-dispatch": "1 - 3", - "d3-quadtree": "1 - 3", - "d3-timer": "1 - 3" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-format": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.0.tgz", - "integrity": "sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==", - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-geo": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/d3-geo/-/d3-geo-3.1.0.tgz", - "integrity": "sha512-JEo5HxXDdDYXCaWdwLRt79y7giK8SbhZJbFWXqbRTolCHFI5jRqteLzCsq51NKbUoX0PjBVSohxrx+NoOUujYA==", - "dependencies": { - "d3-array": "2.5.0 - 3" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-hierarchy": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/d3-hierarchy/-/d3-hierarchy-3.1.2.tgz", - "integrity": "sha512-FX/9frcub54beBdugHjDCdikxThEqjnR93Qt7PvQTOHxyiNCAlvMrHhclk3cD5VeAaq9fxmfRp+CnWw9rEMBuA==", - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-interpolate": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz", - "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==", - "dependencies": { - "d3-color": "1 - 3" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-path": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz", - "integrity": "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==", - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-polygon": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/d3-polygon/-/d3-polygon-3.0.1.tgz", - "integrity": "sha512-3vbA7vXYwfe1SYhED++fPUQlWSYTTGmFmQiany/gdbiWgU/iEyQzyymwL9SkJjFFuCS4902BSzewVGsHHmHtXg==", - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-quadtree": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/d3-quadtree/-/d3-quadtree-3.0.1.tgz", - "integrity": "sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw==", - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-random": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/d3-random/-/d3-random-3.0.1.tgz", - "integrity": "sha512-FXMe9GfxTxqd5D6jFsQ+DJ8BJS4E/fT5mqqdjovykEB2oFbTMDVdg1MGFxfQW+FBOGoB++k8swBrgwSHT1cUXQ==", - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-scale": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz", - "integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==", - "dependencies": { - "d3-array": "2.10.0 - 3", - "d3-format": "1 - 3", - "d3-interpolate": "1.2.0 - 3", - "d3-time": "2.1.1 - 3", - "d3-time-format": "2 - 4" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-scale-chromatic": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/d3-scale-chromatic/-/d3-scale-chromatic-3.0.0.tgz", - "integrity": "sha512-Lx9thtxAKrO2Pq6OO2Ua474opeziKr279P/TKZsMAhYyNDD3EnCffdbgeSYN5O7m2ByQsxtuP2CSDczNUIZ22g==", - "dependencies": { - "d3-color": "1 - 3", - "d3-interpolate": "1 - 3" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-selection": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz", - "integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==", - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-shape": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.0.tgz", - "integrity": "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==", - "dependencies": { - "d3-path": "^3.1.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-time": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz", - "integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==", - "dependencies": { - "d3-array": "2 - 3" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-time-format": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz", - "integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==", - "dependencies": { - "d3-time": "1 - 3" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-timer": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz", - "integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==", - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-transition": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/d3-transition/-/d3-transition-3.0.1.tgz", - "integrity": "sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==", - "dependencies": { - "d3-color": "1 - 3", - "d3-dispatch": "1 - 3", - "d3-ease": "1 - 3", - "d3-interpolate": "1 - 3", - "d3-timer": "1 - 3" - }, - "engines": { - "node": ">=12" - }, - "peerDependencies": { - "d3-selection": "2 - 3" - } - }, - "node_modules/d3-zoom": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/d3-zoom/-/d3-zoom-3.0.0.tgz", - "integrity": "sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==", - "dependencies": { - "d3-dispatch": "1 - 3", - "d3-drag": "2 - 3", - "d3-interpolate": "1 - 3", - "d3-selection": "2 - 3", - "d3-transition": "2 - 3" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dev": true, - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/debug/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - }, - "node_modules/decamelize": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", - "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/decamelize-keys": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/decamelize-keys/-/decamelize-keys-1.1.1.tgz", - "integrity": "sha512-WiPxgEirIV0/eIOMcnFBA3/IJZAZqKnwAwWyvvdi4lsr1WCN22nhdf/3db3DoZcUjTV2SqfzIwNyp6y2xs3nmg==", - "dev": true, - "dependencies": { - "decamelize": "^1.1.0", - "map-obj": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/decode-uri-component": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.2.tgz", - "integrity": "sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ==", - "dev": true, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/default-compare": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/default-compare/-/default-compare-1.0.0.tgz", - "integrity": "sha512-QWfXlM0EkAbqOCbD/6HjdwT19j7WCkMyiRhWilc4H9/5h/RzTF9gv5LYh1+CmDV5d1rki6KAWLtQale0xt20eQ==", - "dev": true, - "dependencies": { - "kind-of": "^5.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/default-compare/node_modules/kind-of": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", - "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/default-resolution": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/default-resolution/-/default-resolution-2.0.0.tgz", - "integrity": "sha512-2xaP6GiwVwOEbXCGoJ4ufgC76m8cj805jrghScewJC2ZDsb9U0b4BIrba+xt/Uytyd0HvQ6+WymSRTfnYj59GQ==", - "dev": true, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/define-properties": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.0.tgz", - "integrity": "sha512-xvqAVKGfT1+UAvPwKTVw/njhdQ8ZhXK4lI0bCIuCMrp2up9nPnaDftrLtmpTazqd1o+UY4zgzU+avtMbDP+ldA==", - "dev": true, - "dependencies": { - "has-property-descriptors": "^1.0.0", - "object-keys": "^1.1.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/define-property": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz", - "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==", - "dev": true, - "dependencies": { - "is-descriptor": "^1.0.2", - "isobject": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/define-property/node_modules/is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "dev": true, - "dependencies": { - "kind-of": "^6.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/define-property/node_modules/is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "dev": true, - "dependencies": { - "kind-of": "^6.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/define-property/node_modules/is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "dev": true, - "dependencies": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/delaunator": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/delaunator/-/delaunator-5.0.0.tgz", - "integrity": "sha512-AyLvtyJdbv/U1GkiS6gUUzclRoAY4Gs75qkMygJJhU75LW4DNuSF2RMzpxs9jw9Oz1BobHjTdkG3zdP55VxAqw==", - "dependencies": { - "robust-predicates": "^3.0.0" - } - }, - "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, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/delegates": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", - "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==", - "dev": true - }, - "node_modules/depd": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", - "integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==", - "dev": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/detect-file": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/detect-file/-/detect-file-1.0.0.tgz", - "integrity": "sha1-8NZtA2cqglyxtzvbP+YjEMjlUrc=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/diff-sequences": { - "version": "29.4.3", - "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.4.3.tgz", - "integrity": "sha512-ofrBgwpPhCD85kMKtE9RYFFq6OC1A89oW2vvgWZNCwxrUpRUILopY7lsYyMDSjc8g6U6aiO0Qubg6r4Wgt5ZnA==", - "dev": true, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/dir-glob": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", - "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", - "dev": true, - "dependencies": { - "path-type": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/dir-glob/node_modules/path-type": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", - "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/dompurify": { - "version": "2.4.3", - "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-2.4.3.tgz", - "integrity": "sha512-q6QaLcakcRjebxjg8/+NP+h0rPfatOgOzc46Fst9VAA3jF2ApfKBNKMzdP4DYTqtUMXSCd5pRS/8Po/OmoCHZQ==", - "optional": true - }, - "node_modules/duplexify": { - "version": "3.7.1", - "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.7.1.tgz", - "integrity": "sha512-07z8uv2wMyS51kKhD1KsdXJg5WQ6t93RneqRxUHnskXVtlYYkLqM0gqStQZ3pj073g687jPCHrqNfCzawLYh5g==", - "dev": true, - "dependencies": { - "end-of-stream": "^1.0.0", - "inherits": "^2.0.1", - "readable-stream": "^2.0.0", - "stream-shift": "^1.0.0" - } - }, - "node_modules/each-props": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/each-props/-/each-props-1.3.2.tgz", - "integrity": "sha512-vV0Hem3zAGkJAyU7JSjixeU66rwdynTAa1vofCrSA5fEln+m67Az9CcnkVD776/fsN/UjIWmBDoNRS6t6G9RfA==", - "dev": true, - "dependencies": { - "is-plain-object": "^2.0.1", - "object.defaults": "^1.1.0" - } - }, - "node_modules/electron-to-chromium": { - "version": "1.4.333", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.333.tgz", - "integrity": "sha512-YyE8+GKyGtPEP1/kpvqsdhD6rA/TP1DUFDN4uiU/YI52NzDxmwHkEb3qjId8hLBa5siJvG0sfC3O66501jMruQ==", - "dev": true - }, - "node_modules/encoding": { - "version": "0.1.13", - "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz", - "integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==", - "dev": true, - "optional": true, - "dependencies": { - "iconv-lite": "^0.6.2" - } - }, - "node_modules/end-of-stream": { - "version": "1.4.4", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", - "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", - "dev": true, - "dependencies": { - "once": "^1.4.0" - } - }, - "node_modules/enhanced-resolve": { - "version": "5.12.0", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.12.0.tgz", - "integrity": "sha512-QHTXI/sZQmko1cbDoNAa3mJ5qhWUUNAq3vR0/YiD379fWQrcfuoX1+HW2S0MTt7XmoPLapdaDKUtelUSPic7hQ==", - "dev": true, - "dependencies": { - "graceful-fs": "^4.2.4", - "tapable": "^2.2.0" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/env-paths": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", - "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/envinfo": { - "version": "7.8.1", - "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.8.1.tgz", - "integrity": "sha512-/o+BXHmB7ocbHEAs6F2EnG0ogybVVUdkRunTT2glZU9XAaGmhqskrvKwqXuDfNjEO0LZKWdejEEpnq8aM0tOaw==", - "dev": true, - "bin": { - "envinfo": "dist/cli.js" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/err-code": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/err-code/-/err-code-2.0.3.tgz", - "integrity": "sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==", - "dev": true - }, - "node_modules/errno": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.7.tgz", - "integrity": "sha512-MfrRBDWzIWifgq6tJj60gkAwtLNb6sQPlcFrSOflcP1aFmmruKQ2wRnze/8V6kgyz7H3FF8Npzv78mZ7XLLflg==", - "dev": true, - "optional": true, - "dependencies": { - "prr": "~1.0.1" - }, - "bin": { - "errno": "cli.js" - } - }, - "node_modules/error-ex": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", - "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", - "dev": true, - "dependencies": { - "is-arrayish": "^0.2.1" - } - }, - "node_modules/es-module-lexer": { - "version": "0.9.3", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-0.9.3.tgz", - "integrity": "sha512-1HQ2M2sPtxwnvOvT1ZClHyQDiggdNjURWpY2we6aMKCQiUVxTmVs2UYPLIrD84sS+kMdUwfBSylbJPwNnBrnHQ==", - "dev": true - }, - "node_modules/es5-ext": { - "version": "0.10.62", - "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.62.tgz", - "integrity": "sha512-BHLqn0klhEpnOKSrzn/Xsz2UIW8j+cGmo9JLzr8BiUapV8hPL9+FliFqjwr9ngW7jWdnxv6eO+/LqyhJVqgrjA==", - "dev": true, - "hasInstallScript": true, - "dependencies": { - "es6-iterator": "^2.0.3", - "es6-symbol": "^3.1.3", - "next-tick": "^1.1.0" - }, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/es6-iterator": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz", - "integrity": "sha512-zw4SRzoUkd+cl+ZoE15A9o1oQd920Bb0iOJMQkQhl3jNc03YqVjAhG7scf9C5KWRU/R13Orf588uCC6525o02g==", - "dev": true, - "dependencies": { - "d": "1", - "es5-ext": "^0.10.35", - "es6-symbol": "^3.1.1" - } - }, - "node_modules/es6-symbol": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.3.tgz", - "integrity": "sha512-NJ6Yn3FuDinBaBRWl/q5X/s4koRHBrgKAu+yGI6JCBeiu3qrcbJhwT2GeR/EXVfylRk8dpQVJoLEFhK+Mu31NA==", - "dev": true, - "dependencies": { - "d": "^1.0.1", - "ext": "^1.1.2" - } - }, - "node_modules/es6-weak-map": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/es6-weak-map/-/es6-weak-map-2.0.3.tgz", - "integrity": "sha512-p5um32HOTO1kP+w7PRnB+5lQ43Z6muuMuIMffvDN8ZB4GcnjLBV6zGStpbASIMk4DCAvEaamhe2zhyCb/QXXsA==", - "dev": true, - "dependencies": { - "d": "1", - "es5-ext": "^0.10.46", - "es6-iterator": "^2.0.3", - "es6-symbol": "^3.1.1" - } - }, - "node_modules/escalade": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", - "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", - "dev": true, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/eslint-formatter-pretty": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/eslint-formatter-pretty/-/eslint-formatter-pretty-4.1.0.tgz", - "integrity": "sha512-IsUTtGxF1hrH6lMWiSl1WbGaiP01eT6kzywdY1U+zLc0MP+nwEnUiS9UI8IaOTUhTeQJLlCEWIbXINBH4YJbBQ==", - "dev": true, - "dependencies": { - "@types/eslint": "^7.2.13", - "ansi-escapes": "^4.2.1", - "chalk": "^4.1.0", - "eslint-rule-docs": "^1.1.5", - "log-symbols": "^4.0.0", - "plur": "^4.0.0", - "string-width": "^4.2.0", - "supports-hyperlinks": "^2.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/eslint-formatter-pretty/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/eslint-formatter-pretty/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true - }, - "node_modules/eslint-formatter-pretty/node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/eslint-formatter-pretty/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/eslint-formatter-pretty/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/eslint-rule-docs": { - "version": "1.1.235", - "resolved": "https://registry.npmjs.org/eslint-rule-docs/-/eslint-rule-docs-1.1.235.tgz", - "integrity": "sha512-+TQ+x4JdTnDoFEXXb3fDvfGOwnyNV7duH8fXWTPD1ieaBmB8omj7Gw/pMBBu4uI2uJCCU8APDaQJzWuXnTsH4A==", - "dev": true - }, - "node_modules/eslint-scope": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", - "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", - "dev": true, - "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^4.1.1" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/esrecurse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", - "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", - "dev": true, - "dependencies": { - "estraverse": "^5.2.0" - }, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/esrecurse/node_modules/estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/estraverse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", - "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", - "dev": true, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/events": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", - "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", - "dev": true, - "engines": { - "node": ">=0.8.x" - } - }, - "node_modules/expand-brackets": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", - "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=", - "dev": true, - "dependencies": { - "debug": "^2.3.3", - "define-property": "^0.2.5", - "extend-shallow": "^2.0.1", - "posix-character-classes": "^0.1.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/expand-brackets/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/expand-brackets/node_modules/define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, - "dependencies": { - "is-descriptor": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/expand-brackets/node_modules/extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "dependencies": { - "is-extendable": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/expand-tilde": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/expand-tilde/-/expand-tilde-2.0.2.tgz", - "integrity": "sha1-l+gBqgUt8CRU3kawK/YhZCzchQI=", - "dev": true, - "dependencies": { - "homedir-polyfill": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/ext": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/ext/-/ext-1.7.0.tgz", - "integrity": "sha512-6hxeJYaL110a9b5TEJSj0gojyHQAmA2ch5Os+ySCiA1QGdS697XWY1pzsrSjqA9LDEEgdB/KypIlR59RcLuHYw==", - "dev": true, - "dependencies": { - "type": "^2.7.2" - } - }, - "node_modules/ext/node_modules/type": { - "version": "2.7.2", - "resolved": "https://registry.npmjs.org/type/-/type-2.7.2.tgz", - "integrity": "sha512-dzlvlNlt6AXU7EBSfpAscydQ7gXB+pPGsPnfJnZpiNJBDj7IaJzQlBZYGdEi4R9HmPdBv2XmWJ6YUtoTa7lmCw==", - "dev": true - }, - "node_modules/extend": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", - "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", - "dev": true - }, - "node_modules/extend-shallow": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", - "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", - "dev": true, - "dependencies": { - "assign-symbols": "^1.0.0", - "is-extendable": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/extend-shallow/node_modules/is-extendable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", - "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", - "dev": true, - "dependencies": { - "is-plain-object": "^2.0.4" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/extglob": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", - "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", - "dev": true, - "dependencies": { - "array-unique": "^0.3.2", - "define-property": "^1.0.0", - "expand-brackets": "^2.1.4", - "extend-shallow": "^2.0.1", - "fragment-cache": "^0.2.1", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/extglob/node_modules/define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", - "dev": true, - "dependencies": { - "is-descriptor": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/extglob/node_modules/extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "dependencies": { - "is-extendable": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/extglob/node_modules/is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "dev": true, - "dependencies": { - "kind-of": "^6.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/extglob/node_modules/is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "dev": true, - "dependencies": { - "kind-of": "^6.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/extglob/node_modules/is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "dev": true, - "dependencies": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/fancy-log": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/fancy-log/-/fancy-log-1.3.3.tgz", - "integrity": "sha512-k9oEhlyc0FrVh25qYuSELjr8oxsCoc4/LEZfg2iJJrfEk/tZL9bCoJE47gqAvI2m/AUjluCS4+3I0eTx8n3AEw==", - "dev": true, - "dependencies": { - "ansi-gray": "^0.1.1", - "color-support": "^1.1.3", - "parse-node-version": "^1.0.0", - "time-stamp": "^1.0.0" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true - }, - "node_modules/fast-glob": { - "version": "3.2.12", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.12.tgz", - "integrity": "sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==", - "dev": true, - "dependencies": { - "@nodelib/fs.stat": "^2.0.2", - "@nodelib/fs.walk": "^1.2.3", - "glob-parent": "^5.1.2", - "merge2": "^1.3.0", - "micromatch": "^4.0.4" - }, - "engines": { - "node": ">=8.6.0" - } - }, - "node_modules/fast-glob/node_modules/braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "dev": true, - "dependencies": { - "fill-range": "^7.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/fast-glob/node_modules/fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "dev": true, - "dependencies": { - "to-regex-range": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/fast-glob/node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/fast-glob/node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true, - "engines": { - "node": ">=0.12.0" - } - }, - "node_modules/fast-glob/node_modules/micromatch": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", - "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", - "dev": true, - "dependencies": { - "braces": "^3.0.2", - "picomatch": "^2.3.1" - }, - "engines": { - "node": ">=8.6" - } - }, - "node_modules/fast-glob/node_modules/to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "dependencies": { - "is-number": "^7.0.0" - }, - "engines": { - "node": ">=8.0" - } - }, - "node_modules/fast-json-stable-stringify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true - }, - "node_modules/fast-levenshtein": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-1.1.4.tgz", - "integrity": "sha512-Ia0sQNrMPXXkqVFt6w6M1n1oKo3NfKs+mvaV811Jwir7vAk9a6PVV9VPYf6X3BU97QiLEmuW3uXH9u87zDFfdw==", - "dev": true - }, - "node_modules/fastest-levenshtein": { - "version": "1.0.16", - "resolved": "https://registry.npmjs.org/fastest-levenshtein/-/fastest-levenshtein-1.0.16.tgz", - "integrity": "sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg==", - "dev": true, - "engines": { - "node": ">= 4.9.1" - } - }, - "node_modules/fastq": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", - "integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==", - "dev": true, - "dependencies": { - "reusify": "^1.0.4" - } - }, - "node_modules/fflate": { - "version": "0.4.8", - "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.4.8.tgz", - "integrity": "sha512-FJqqoDBR00Mdj9ppamLa/Y7vxm+PRmNWA67N846RvsoYVMKB4q3y/de5PA7gUmRMYK/8CMz2GDZQmCRN1wBcWA==" - }, - "node_modules/file-uri-to-path": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", - "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==", - "dev": true, - "optional": true - }, - "node_modules/fill-range": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", - "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", - "dev": true, - "dependencies": { - "extend-shallow": "^2.0.1", - "is-number": "^3.0.0", - "repeat-string": "^1.6.1", - "to-regex-range": "^2.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/fill-range/node_modules/extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "dependencies": { - "is-extendable": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/find-up": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz", - "integrity": "sha512-jvElSjyuo4EMQGoTwo1uJU5pQMwTW5lS1x05zzfJuTIyLR3zwO27LYrxNg+dlvKpGOuGy/MzBdXh80g0ve5+HA==", - "dev": true, - "dependencies": { - "path-exists": "^2.0.0", - "pinkie-promise": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/findup-sync": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/findup-sync/-/findup-sync-3.0.0.tgz", - "integrity": "sha512-YbffarhcicEhOrm4CtrwdKBdCuz576RLdhJDsIfvNtxUuhdRet1qZcsMjqbePtAseKdAnDyM/IyXbu7PRPRLYg==", - "dev": true, - "dependencies": { - "detect-file": "^1.0.0", - "is-glob": "^4.0.0", - "micromatch": "^3.0.4", - "resolve-dir": "^1.0.1" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/fined": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/fined/-/fined-1.2.0.tgz", - "integrity": "sha512-ZYDqPLGxDkDhDZBjZBb+oD1+j0rA4E0pXY50eplAAOPg2N/gUBSSk5IM1/QhPfyVo19lJ+CvXpqfvk+b2p/8Ng==", - "dev": true, - "dependencies": { - "expand-tilde": "^2.0.2", - "is-plain-object": "^2.0.3", - "object.defaults": "^1.1.0", - "object.pick": "^1.2.0", - "parse-filepath": "^1.0.1" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/flagged-respawn": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/flagged-respawn/-/flagged-respawn-1.0.1.tgz", - "integrity": "sha512-lNaHNVymajmk0OJMBn8fVUAU1BtDeKIqKoVhk4xAALB57aALg6b4W0MfJ/cUE0g9YBXy5XhSlPIpYIJ7HaY/3Q==", - "dev": true, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/flush-write-stream": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/flush-write-stream/-/flush-write-stream-1.1.1.tgz", - "integrity": "sha512-3Z4XhFZ3992uIq0XOqb9AreonueSYphE6oYbpt5+3u06JWklbsPkNv3ZKkP9Bz/r+1MWCaMoSQ28P85+1Yc77w==", - "dev": true, - "dependencies": { - "inherits": "^2.0.3", - "readable-stream": "^2.3.6" - } - }, - "node_modules/follow-redirects": { - "version": "1.15.2", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz", - "integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://github.com/sponsors/RubenVerborgh" - } - ], - "engines": { - "node": ">=4.0" - }, - "peerDependenciesMeta": { - "debug": { - "optional": true - } - } - }, - "node_modules/font-awesome": { - "version": "4.7.0", - "resolved": "https://registry.npmjs.org/font-awesome/-/font-awesome-4.7.0.tgz", - "integrity": "sha1-j6jPBBGhoxr9B7BtKQK7n8gVoTM=", - "engines": { - "node": ">=0.10.3" - } - }, - "node_modules/for-in": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", - "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/for-own": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/for-own/-/for-own-1.0.0.tgz", - "integrity": "sha512-0OABksIGrxKK8K4kynWkQ7y1zounQxP+CWnyclVwj81KW3vlLlGUx57DKGcP/LH216GzqnstnPocF16Nxs0Ycg==", - "dev": true, - "dependencies": { - "for-in": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/form-data": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", - "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", - "dev": true, - "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "mime-types": "^2.1.12" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/fragment-cache": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz", - "integrity": "sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk=", - "dev": true, - "dependencies": { - "map-cache": "^0.2.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/fs-minipass": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", - "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", - "dev": true, - "dependencies": { - "minipass": "^3.0.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/fs-mkdirp-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs-mkdirp-stream/-/fs-mkdirp-stream-1.0.0.tgz", - "integrity": "sha512-+vSd9frUnapVC2RZYfL3FCB2p3g4TBhaUmrsWlSudsGdnxIuUvBB2QM1VZeBtc49QFwrp+wQLrDs3+xxDgI5gQ==", - "dev": true, - "dependencies": { - "graceful-fs": "^4.1.11", - "through2": "^2.0.3" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", - "dev": true - }, - "node_modules/fsevents": { - "version": "1.2.13", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.13.tgz", - "integrity": "sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==", - "deprecated": "fsevents 1 will break on node v14+ and could be using insecure binaries. Upgrade to fsevents 2.", - "dev": true, - "hasInstallScript": true, - "optional": true, - "os": [ - "darwin" - ], - "dependencies": { - "bindings": "^1.5.0", - "nan": "^2.12.1" - }, - "engines": { - "node": ">= 4.0" - } - }, - "node_modules/function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", - "dev": true - }, - "node_modules/gauge": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/gauge/-/gauge-4.0.4.tgz", - "integrity": "sha512-f9m+BEN5jkg6a0fZjleidjN51VE1X+mPFQ2DJ0uv1V39oCLCbsGe6yjbBnp7eK7z/+GAon99a3nHuqbuuthyPg==", - "dev": true, - "dependencies": { - "aproba": "^1.0.3 || ^2.0.0", - "color-support": "^1.1.3", - "console-control-strings": "^1.1.0", - "has-unicode": "^2.0.1", - "signal-exit": "^3.0.7", - "string-width": "^4.2.3", - "strip-ansi": "^6.0.1", - "wide-align": "^1.1.5" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" - } - }, - "node_modules/gauge/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/gauge/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true - }, - "node_modules/gauge/node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/gauge/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/gauge/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/gaze": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/gaze/-/gaze-1.1.3.tgz", - "integrity": "sha512-BRdNm8hbWzFzWHERTrejLqwHDfS4GibPoq5wjTPIoJHoBtKGPg3xAFfxmM+9ztbXelxcf2hwQcaz1PtmFeue8g==", - "dev": true, - "dependencies": { - "globule": "^1.0.0" - }, - "engines": { - "node": ">= 4.0.0" - } - }, - "node_modules/get-caller-file": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.3.tgz", - "integrity": "sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w==", - "dev": true - }, - "node_modules/get-intrinsic": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.0.tgz", - "integrity": "sha512-L049y6nFOuom5wGyRc3/gdTLO94dySVKRACj1RmJZBQXlbTMhtNIgkWkUHq+jYmZvKf14EW1EoJnnjbmoHij0Q==", - "dev": true, - "dependencies": { - "function-bind": "^1.1.1", - "has": "^1.0.3", - "has-symbols": "^1.0.3" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/get-stdin": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-4.0.1.tgz", - "integrity": "sha512-F5aQMywwJ2n85s4hJPTT9RPxGmubonuB10MNYo17/xph174n2MIR33HRguhzVag10O/npM7SPk73LMZNP+FaWw==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/get-value": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz", - "integrity": "sha1-3BXKHGcjh8p2vTesCjlbogQqLCg=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/glob": { - "version": "7.1.6", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", - "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", - "dev": true, - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/glob-parent": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", - "integrity": "sha512-E8Ak/2+dZY6fnzlR7+ueWvhsH1SjHr4jjss4YS/h4py44jY9MhK/VFdaZJAWDz6BbL21KeteKxFSFpq8OS5gVA==", - "dev": true, - "dependencies": { - "is-glob": "^3.1.0", - "path-dirname": "^1.0.0" - } - }, - "node_modules/glob-parent/node_modules/is-glob": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", - "integrity": "sha512-UFpDDrPgM6qpnFNI+rh/p3bUaq9hKLZN8bMUWzxmcnZVS3omf4IPK+BrewlnWjO1WmUsMYuSjKh4UJuV4+Lqmw==", - "dev": true, - "dependencies": { - "is-extglob": "^2.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/glob-stream": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/glob-stream/-/glob-stream-6.1.0.tgz", - "integrity": "sha512-uMbLGAP3S2aDOHUDfdoYcdIePUCfysbAd0IAoWVZbeGU/oNQ8asHVSshLDJUPWxfzj8zsCG7/XeHPHTtow0nsw==", - "dev": true, - "dependencies": { - "extend": "^3.0.0", - "glob": "^7.1.1", - "glob-parent": "^3.1.0", - "is-negated-glob": "^1.0.0", - "ordered-read-streams": "^1.0.0", - "pumpify": "^1.3.5", - "readable-stream": "^2.1.5", - "remove-trailing-separator": "^1.0.1", - "to-absolute-glob": "^2.0.0", - "unique-stream": "^2.0.2" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/glob-to-regexp": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", - "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", - "dev": true - }, - "node_modules/glob-watcher": { - "version": "5.0.5", - "resolved": "https://registry.npmjs.org/glob-watcher/-/glob-watcher-5.0.5.tgz", - "integrity": "sha512-zOZgGGEHPklZNjZQaZ9f41i7F2YwE+tS5ZHrDhbBCk3stwahn5vQxnFmBJZHoYdusR6R1bLSXeGUy/BhctwKzw==", - "dev": true, - "dependencies": { - "anymatch": "^2.0.0", - "async-done": "^1.2.0", - "chokidar": "^2.0.0", - "is-negated-glob": "^1.0.0", - "just-debounce": "^1.0.0", - "normalize-path": "^3.0.0", - "object.defaults": "^1.1.0" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/global-modules": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-1.0.0.tgz", - "integrity": "sha512-sKzpEkf11GpOFuw0Zzjzmt4B4UZwjOcG757PPvrfhxcLFbq0wpsgpOqxpxtxFiCG4DtG93M6XRVbF2oGdev7bg==", - "dev": true, - "dependencies": { - "global-prefix": "^1.0.1", - "is-windows": "^1.0.1", - "resolve-dir": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/global-prefix": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-1.0.2.tgz", - "integrity": "sha1-2/dDxsFJklk8ZVVoy2btMsASLr4=", - "dev": true, - "dependencies": { - "expand-tilde": "^2.0.2", - "homedir-polyfill": "^1.0.1", - "ini": "^1.3.4", - "is-windows": "^1.0.1", - "which": "^1.2.14" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/globby": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", - "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", - "dev": true, - "dependencies": { - "array-union": "^2.1.0", - "dir-glob": "^3.0.1", - "fast-glob": "^3.2.9", - "ignore": "^5.2.0", - "merge2": "^1.4.1", - "slash": "^3.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/globule": { - "version": "1.3.4", - "resolved": "https://registry.npmjs.org/globule/-/globule-1.3.4.tgz", - "integrity": "sha512-OPTIfhMBh7JbBYDpa5b+Q5ptmMWKwcNcFSR/0c6t8V4f3ZAVBEsKNY37QdVqmLRYSMhOUGYrY0QhSoEpzGr/Eg==", - "dev": true, - "dependencies": { - "glob": "~7.1.1", - "lodash": "^4.17.21", - "minimatch": "~3.0.2" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/globule/node_modules/minimatch": { - "version": "3.0.8", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.8.tgz", - "integrity": "sha512-6FsRAQsxQ61mw+qP1ZzbL9Bc78x2p5OqNgNpnoAFLTrX8n5Kxph0CsnhmKKNXTWjXqU5L0pGPR7hYk+XWZr60Q==", - "dev": true, - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/glogg": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/glogg/-/glogg-1.0.2.tgz", - "integrity": "sha512-5mwUoSuBk44Y4EshyiqcH95ZntbDdTQqA3QYSrxmzj28Ai0vXBGMH1ApSANH14j2sIRtqCEyg6PfsuP7ElOEDA==", - "dev": true, - "dependencies": { - "sparkles": "^1.0.0" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/graceful-fs": { - "version": "4.2.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", - "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", - "dev": true - }, - "node_modules/gulp": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/gulp/-/gulp-4.0.2.tgz", - "integrity": "sha512-dvEs27SCZt2ibF29xYgmnwwCYZxdxhQ/+LFWlbAW8y7jt68L/65402Lz3+CKy0Ov4rOs+NERmDq7YlZaDqUIfA==", - "dev": true, - "dependencies": { - "glob-watcher": "^5.0.3", - "gulp-cli": "^2.2.0", - "undertaker": "^1.2.1", - "vinyl-fs": "^3.0.0" - }, - "bin": { - "gulp": "bin/gulp.js" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/gulp-cli": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/gulp-cli/-/gulp-cli-2.3.0.tgz", - "integrity": "sha512-zzGBl5fHo0EKSXsHzjspp3y5CONegCm8ErO5Qh0UzFzk2y4tMvzLWhoDokADbarfZRL2pGpRp7yt6gfJX4ph7A==", - "dev": true, - "dependencies": { - "ansi-colors": "^1.0.1", - "archy": "^1.0.0", - "array-sort": "^1.0.0", - "color-support": "^1.1.3", - "concat-stream": "^1.6.0", - "copy-props": "^2.0.1", - "fancy-log": "^1.3.2", - "gulplog": "^1.0.0", - "interpret": "^1.4.0", - "isobject": "^3.0.1", - "liftoff": "^3.1.0", - "matchdep": "^2.0.0", - "mute-stdout": "^1.0.0", - "pretty-hrtime": "^1.0.0", - "replace-homedir": "^1.0.0", - "semver-greatest-satisfied-range": "^1.1.0", - "v8flags": "^3.2.0", - "yargs": "^7.1.0" - }, - "bin": { - "gulp": "bin/gulp.js" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/gulplog": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/gulplog/-/gulplog-1.0.0.tgz", - "integrity": "sha512-hm6N8nrm3Y08jXie48jsC55eCZz9mnb4OirAStEk2deqeyhXU3C1otDVh+ccttMuc1sBi6RX6ZJ720hs9RCvgw==", - "dev": true, - "dependencies": { - "glogg": "^1.0.0" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/hard-rejection": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/hard-rejection/-/hard-rejection-2.1.0.tgz", - "integrity": "sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/has": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", - "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", - "dev": true, - "dependencies": { - "function-bind": "^1.1.1" - }, - "engines": { - "node": ">= 0.4.0" - } - }, - "node_modules/has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/has-property-descriptors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz", - "integrity": "sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==", - "dev": true, - "dependencies": { - "get-intrinsic": "^1.1.1" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-symbols": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", - "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", - "dev": true, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-unicode": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", - "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==", - "dev": true - }, - "node_modules/has-value": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz", - "integrity": "sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc=", - "dev": true, - "dependencies": { - "get-value": "^2.0.6", - "has-values": "^1.0.0", - "isobject": "^3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/has-values": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-values/-/has-values-1.0.0.tgz", - "integrity": "sha1-lbC2P+whRmGab+V/51Yo1aOe/k8=", - "dev": true, - "dependencies": { - "is-number": "^3.0.0", - "kind-of": "^4.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/has-values/node_modules/kind-of": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz", - "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=", - "dev": true, - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/history": { - "version": "4.10.1", - "resolved": "https://registry.npmjs.org/history/-/history-4.10.1.tgz", - "integrity": "sha512-36nwAD620w12kuzPAsyINPWJqlNbij+hpK1k9XRloDtym8mxzGYl2c17LnV6IAGB2Dmg4tEa7G7DlawS0+qjew==", - "dev": true, - "dependencies": { - "@babel/runtime": "^7.1.2", - "loose-envify": "^1.2.0", - "resolve-pathname": "^3.0.0", - "tiny-invariant": "^1.0.2", - "tiny-warning": "^1.0.0", - "value-equal": "^1.0.1" - } - }, - "node_modules/hoist-non-react-statics": { - "version": "2.5.5", - "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-2.5.5.tgz", - "integrity": "sha512-rqcy4pJo55FTTLWt+bU8ukscqHeE/e9KWvsOW2b/a3afxQZhwkQdT1rPPCJ0rYXdj4vNcasY8zHTH+jF/qStxw==", - "dev": true - }, - "node_modules/homedir-polyfill": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz", - "integrity": "sha512-eSmmWE5bZTK2Nou4g0AI3zZ9rswp7GRKoKXS1BLUkvPviOqs4YTN1djQIqrXy9k5gEtdLPy86JjRwsNM9tnDcA==", - "dev": true, - "dependencies": { - "parse-passwd": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/hosted-git-info": { - "version": "2.8.9", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", - "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==", - "dev": true - }, - "node_modules/html2canvas": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/html2canvas/-/html2canvas-1.4.1.tgz", - "integrity": "sha512-fPU6BHNpsyIhr8yyMpTLLxAbkaK8ArIBcmZIRiBLiDhjeqvXolaEmDGmELFuX9I4xDcaKKcJl+TKZLqruBbmWA==", - "devOptional": true, - "dependencies": { - "css-line-break": "^2.1.0", - "text-segmentation": "^1.0.3" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/http-cache-semantics": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz", - "integrity": "sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==", - "dev": true - }, - "node_modules/http-proxy-agent": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", - "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", - "dev": true, - "dependencies": { - "@tootallnate/once": "2", - "agent-base": "6", - "debug": "4" - }, - "engines": { - "node": ">= 6" - } - }, - "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, - "dependencies": { - "agent-base": "6", - "debug": "4" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/humanize-ms": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz", - "integrity": "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==", - "dev": true, - "dependencies": { - "ms": "^2.0.0" - } - }, - "node_modules/iconv-lite": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", - "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/ignore": { - "version": "5.2.4", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", - "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==", - "dev": true, - "engines": { - "node": ">= 4" - } - }, - "node_modules/image-size": { - "version": "0.5.5", - "resolved": "https://registry.npmjs.org/image-size/-/image-size-0.5.5.tgz", - "integrity": "sha512-6TDAlDPZxUFCv+fuOkIoXT/V/f3Qbq8e37p+YOiYrUv3v9cc3/6x78VdfPgFVaB9dZYeLUfKgHRebpkm/oP2VQ==", - "dev": true, - "optional": true, - "bin": { - "image-size": "bin/image-size.js" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/import-local": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.1.0.tgz", - "integrity": "sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg==", - "dev": true, - "dependencies": { - "pkg-dir": "^4.2.0", - "resolve-cwd": "^3.0.0" - }, - "bin": { - "import-local-fixture": "fixtures/cli.js" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", - "dev": true, - "engines": { - "node": ">=0.8.19" - } - }, - "node_modules/indent-string": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", - "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/infer-owner": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/infer-owner/-/infer-owner-1.0.4.tgz", - "integrity": "sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A==", - "dev": true - }, - "node_modules/inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", - "dev": true, - "dependencies": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true - }, - "node_modules/ini": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", - "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", - "dev": true - }, - "node_modules/internmap": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz", - "integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==", - "engines": { - "node": ">=12" - } - }, - "node_modules/interpret": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.4.0.tgz", - "integrity": "sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==", - "dev": true, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/invariant": { - "version": "2.2.4", - "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", - "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", - "dev": true, - "dependencies": { - "loose-envify": "^1.0.0" - } - }, - "node_modules/invert-kv": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-1.0.0.tgz", - "integrity": "sha512-xgs2NH9AE66ucSq4cNG1nhSFghr5l6tdL15Pk+jl46bmmBapgoaY/AacXyaDznAqmGL99TiLSQgO/XazFSKYeQ==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/ionicons": { - "version": "4.6.3", - "resolved": "https://registry.npmjs.org/ionicons/-/ionicons-4.6.3.tgz", - "integrity": "sha512-cgP+VIr2cTJpMfFyVHTerq6n2jeoiGboVoe3GlaAo5zoSBDAEXORwUZhv6m+lCyxlsHCS3nqPUE+MKyZU71t8Q==", - "dev": true - }, - "node_modules/ip": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ip/-/ip-2.0.0.tgz", - "integrity": "sha512-WKa+XuLG1A1R0UWhl2+1XQSi+fZWMsYKffMZTTYsiZaUD8k2yDAj5atimTUD2TZkyCkNEeYE5NhFZmupOGtjYQ==", - "dev": true - }, - "node_modules/irregular-plurals": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/irregular-plurals/-/irregular-plurals-3.4.0.tgz", - "integrity": "sha512-YXxECO/W6N9aMBVKMKKZ8TXESgq7EFrp3emCGGUcrYY1cgJIeZjoB75MTu8qi+NAKntS9NwPU8VdcQ3r6E6aWQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-absolute": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-absolute/-/is-absolute-1.0.0.tgz", - "integrity": "sha512-dOWoqflvcydARa360Gvv18DZ/gRuHKi2NU/wU5X1ZFzdYfH29nkiNZsF3mp4OJ3H4yo9Mx8A/uAGNzpzPN3yBA==", - "dev": true, - "dependencies": { - "is-relative": "^1.0.0", - "is-windows": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-accessor-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", - "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", - "dev": true, - "dependencies": { - "kind-of": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-accessor-descriptor/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-arrayish": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", - "dev": true - }, - "node_modules/is-binary-path": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-1.0.1.tgz", - "integrity": "sha512-9fRVlXc0uCxEDj1nQzaWONSpbTfx0FmJfzHF7pwlI8DkWGoHBBea4Pg5Ky0ojwwxQmnSifgbKkI06Qv0Ljgj+Q==", - "dev": true, - "dependencies": { - "binary-extensions": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-buffer": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", - "dev": true - }, - "node_modules/is-core-module": { - "version": "2.11.0", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.11.0.tgz", - "integrity": "sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw==", - "dev": true, - "dependencies": { - "has": "^1.0.3" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-data-descriptor": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", - "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", - "dev": true, - "dependencies": { - "kind-of": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-data-descriptor/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", - "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", - "dev": true, - "dependencies": { - "is-accessor-descriptor": "^0.1.6", - "is-data-descriptor": "^0.1.4", - "kind-of": "^5.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-descriptor/node_modules/kind-of": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", - "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-extendable": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-fullwidth-code-point": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", - "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", - "dev": true, - "dependencies": { - "number-is-nan": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-glob": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", - "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", - "dev": true, - "dependencies": { - "is-extglob": "^2.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-lambda": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-lambda/-/is-lambda-1.0.1.tgz", - "integrity": "sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ==", - "dev": true - }, - "node_modules/is-negated-glob": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-negated-glob/-/is-negated-glob-1.0.0.tgz", - "integrity": "sha512-czXVVn/QEmgvej1f50BZ648vUI+em0xqMq2Sn+QncCLN4zj1UAxlT+kw/6ggQTOaZPd1HqKQGEqbpQVtJucWug==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-number": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", - "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", - "dev": true, - "dependencies": { - "kind-of": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-number/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-plain-obj": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", - "integrity": "sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-plain-object": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", - "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", - "dev": true, - "dependencies": { - "isobject": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-relative": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-relative/-/is-relative-1.0.0.tgz", - "integrity": "sha512-Kw/ReK0iqwKeu0MITLFuj0jbPAmEiOsIwyIXvvbfa6QfmN9pkD1M+8pdk7Rl/dTKbH34/XBFMbgD4iMJhLQbGA==", - "dev": true, - "dependencies": { - "is-unc-path": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-unc-path": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-unc-path/-/is-unc-path-1.0.0.tgz", - "integrity": "sha512-mrGpVd0fs7WWLfVsStvgF6iEJnbjDFZh9/emhRDcGWTduTfNHd9CHeUwH3gYIjdbwo4On6hunkztwOaAw0yllQ==", - "dev": true, - "dependencies": { - "unc-path-regex": "^0.1.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-unicode-supported": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", - "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-utf8": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz", - "integrity": "sha512-rMYPYvCzsXywIsldgLaSoPlw5PfoB/ssr7hY4pLfcodrA5M/eArza1a9VmTiNIBNMjOGr1Ow9mTyU2o69U6U9Q==", - "dev": true - }, - "node_modules/is-valid-glob": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-valid-glob/-/is-valid-glob-1.0.0.tgz", - "integrity": "sha512-AhiROmoEFDSsjx8hW+5sGwgKVIORcXnrlAx/R0ZSeaPw70Vw0CqkGBBhHGL58Uox2eXnU1AnvXJl1XlyedO5bA==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-what": { - "version": "3.14.1", - "resolved": "https://registry.npmjs.org/is-what/-/is-what-3.14.1.tgz", - "integrity": "sha512-sNxgpk9793nzSs7bA6JQJGeIuRBQhAaNGG77kzYQgMkrID+lS6SlK07K5LaptscDlSaIgH+GPFzf+d75FVxozA==", - "dev": true - }, - "node_modules/is-windows": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", - "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/isarray": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", - "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", - "dev": true - }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", - "dev": true - }, - "node_modules/isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/jest-diff": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.5.0.tgz", - "integrity": "sha512-LtxijLLZBduXnHSniy0WMdaHjmQnt3g5sa16W4p0HqukYTTsyTW3GD1q41TyGl5YFXj/5B2U6dlh5FM1LIMgxw==", - "dev": true, - "dependencies": { - "chalk": "^4.0.0", - "diff-sequences": "^29.4.3", - "jest-get-type": "^29.4.3", - "pretty-format": "^29.5.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-get-type": { - "version": "29.4.3", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.4.3.tgz", - "integrity": "sha512-J5Xez4nRRMjk8emnTpWrlkyb9pfRQQanDrvWHhsR1+VUfbwxi30eVcZFlcdGInRibU4G5LwHXpI7IRHU0CY+gg==", - "dev": true, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-worker": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", - "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", - "dev": true, - "dependencies": { - "@types/node": "*", - "merge-stream": "^2.0.0", - "supports-color": "^8.0.0" - }, - "engines": { - "node": ">= 10.13.0" - } - }, - "node_modules/jest-worker/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-worker/node_modules/supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" - } - }, - "node_modules/jquery": { - "version": "3.6.4", - "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.6.4.tgz", - "integrity": "sha512-v28EW9DWDFpzcD9O5iyJXg3R3+q+mET5JhnjJzQUZMHOv67bpSIHq81GEYpPNZHG+XXHsfSme3nxp/hndKEcsQ==", - "dev": true - }, - "node_modules/js-base64": { - "version": "2.6.4", - "resolved": "https://registry.npmjs.org/js-base64/-/js-base64-2.6.4.tgz", - "integrity": "sha512-pZe//GGmwJndub7ZghVHz7vjb2LgC1m8B07Au3eYqeqv9emhESByMXxaEgkUkEqJe87oBbSniGYoQNIBklc7IQ==", - "dev": true - }, - "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 - }, - "node_modules/json-parse-even-better-errors": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", - "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", - "dev": true - }, - "node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true - }, - "node_modules/json-stable-stringify-without-jsonify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", - "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", - "dev": true - }, - "node_modules/jspdf": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/jspdf/-/jspdf-2.5.1.tgz", - "integrity": "sha512-hXObxz7ZqoyhxET78+XR34Xu2qFGrJJ2I2bE5w4SM8eFaFEkW2xcGRVUss360fYelwRSid/jT078kbNvmoW0QA==", - "dependencies": { - "@babel/runtime": "^7.14.0", - "atob": "^2.1.2", - "btoa": "^1.2.1", - "fflate": "^0.4.8" - }, - "optionalDependencies": { - "canvg": "^3.0.6", - "core-js": "^3.6.0", - "dompurify": "^2.2.0", - "html2canvas": "^1.0.0-rc.5" - } - }, - "node_modules/jspdf/node_modules/@babel/runtime": { - "version": "7.20.13", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.20.13.tgz", - "integrity": "sha512-gt3PKXs0DBoL9xCvOIIZ2NEqAGZqHjAnmVbfQtB620V0uReIQutpel14KcneZuer7UioY8ALKZ7iocavvzTNFA==", - "dependencies": { - "regenerator-runtime": "^0.13.11" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/jspdf/node_modules/regenerator-runtime": { - "version": "0.13.11", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", - "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==" - }, - "node_modules/just-debounce": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/just-debounce/-/just-debounce-1.1.0.tgz", - "integrity": "sha512-qpcRocdkUmf+UTNBYx5w6dexX5J31AKK1OmPwH630a83DdVVUIngk55RSAiIGpQyoH0dlr872VHfPjnQnK1qDQ==", - "dev": true - }, - "node_modules/kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/knockout": { - "version": "3.5.1", - "resolved": "https://registry.npmjs.org/knockout/-/knockout-3.5.1.tgz", - "integrity": "sha512-wRJ9I4az0QcsH7A4v4l0enUpkS++MBx0BnL/68KaLzJg7x1qmbjSlwEoCNol7KTYZ+pmtI7Eh2J0Nu6/2Z5J/Q==" - }, - "node_modules/knockout-mapping": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/knockout-mapping/-/knockout-mapping-2.6.0.tgz", - "integrity": "sha1-BSdZ0B7PMNSUfo6+ZK0sgpM/Bv8=", - "peerDependencies": { - "knockout": ">=2.2.0" - } - }, - "node_modules/last-run": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/last-run/-/last-run-1.1.1.tgz", - "integrity": "sha512-U/VxvpX4N/rFvPzr3qG5EtLKEnNI0emvIQB3/ecEwv+8GHaUKbIB8vxv1Oai5FAF0d0r7LXHhLLe5K/yChm5GQ==", - "dev": true, - "dependencies": { - "default-resolution": "^2.0.0", - "es6-weak-map": "^2.0.1" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/lazystream": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/lazystream/-/lazystream-1.0.1.tgz", - "integrity": "sha512-b94GiNHQNy6JNTrt5w6zNyffMrNkXZb3KTkCZJb2V1xaEGCk093vkZ2jk3tpaeP33/OiXC+WvK9AxUebnf5nbw==", - "dev": true, - "dependencies": { - "readable-stream": "^2.0.5" - }, - "engines": { - "node": ">= 0.6.3" - } - }, - "node_modules/lcid": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/lcid/-/lcid-1.0.0.tgz", - "integrity": "sha512-YiGkH6EnGrDGqLMITnGjXtGmNtjoXw9SVUzcaos8RBi7Ps0VBylkq+vOcY9QE5poLasPCR849ucFUkl0UzUyOw==", - "dev": true, - "dependencies": { - "invert-kv": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/lead": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/lead/-/lead-1.0.0.tgz", - "integrity": "sha512-IpSVCk9AYvLHo5ctcIXxOBpMWUe+4TKN3VPWAKUbJikkmsGp0VrSM8IttVc32D6J4WUsiPE6aEFRNmIoF/gdow==", - "dev": true, - "dependencies": { - "flush-write-stream": "^1.0.2" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/less": { - "version": "3.13.1", - "resolved": "https://registry.npmjs.org/less/-/less-3.13.1.tgz", - "integrity": "sha512-SwA1aQXGUvp+P5XdZslUOhhLnClSLIjWvJhmd+Vgib5BFIr9lMNlQwmwUNOjXThF/A0x+MCYYPeWEfeWiLRnTw==", - "dev": true, - "dependencies": { - "copy-anything": "^2.0.1", - "tslib": "^1.10.0" - }, - "bin": { - "lessc": "bin/lessc" - }, - "engines": { - "node": ">=6" - }, - "optionalDependencies": { - "errno": "^0.1.1", - "graceful-fs": "^4.1.2", - "image-size": "~0.5.0", - "make-dir": "^2.1.0", - "mime": "^1.4.1", - "native-request": "^1.0.5", - "source-map": "~0.6.0" - } - }, - "node_modules/liftoff": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/liftoff/-/liftoff-3.1.0.tgz", - "integrity": "sha512-DlIPlJUkCV0Ips2zf2pJP0unEoT1kwYhiiPUGF3s/jtxTCjziNLoiVVh+jqWOWeFi6mmwQ5fNxvAUyPad4Dfog==", - "dev": true, - "dependencies": { - "extend": "^3.0.0", - "findup-sync": "^3.0.0", - "fined": "^1.0.1", - "flagged-respawn": "^1.0.0", - "is-plain-object": "^2.0.4", - "object.map": "^1.0.0", - "rechoir": "^0.6.2", - "resolve": "^1.1.7" - }, - "engines": { - "node": ">= 0.8" - } - }, - "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 - }, - "node_modules/load-json-file": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz", - "integrity": "sha512-cy7ZdNRXdablkXYNI049pthVeXFurRyb9+hA/dZzerZ0pGTx42z+y+ssxBaVV2l70t1muq5IdKhn4UtcoGUY9A==", - "dev": true, - "dependencies": { - "graceful-fs": "^4.1.2", - "parse-json": "^2.2.0", - "pify": "^2.0.0", - "pinkie-promise": "^2.0.0", - "strip-bom": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/loader-runner": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.0.tgz", - "integrity": "sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==", - "dev": true, - "engines": { - "node": ">=6.11.5" - } - }, - "node_modules/locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dev": true, - "dependencies": { - "p-locate": "^4.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "dev": true - }, - "node_modules/log-symbols": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", - "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", - "dev": true, - "dependencies": { - "chalk": "^4.1.0", - "is-unicode-supported": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "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, - "dependencies": { - "js-tokens": "^3.0.0 || ^4.0.0" - }, - "bin": { - "loose-envify": "cli.js" - } - }, - "node_modules/lru-cache": { - "version": "7.14.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.14.1.tgz", - "integrity": "sha512-ysxwsnTKdAx96aTRdhDOCQfDgbHnt8SK0KY8SEjO0wHinhWOFTESbjVCMPbU1uGXg/ch4lifqx0wfjOawU2+WA==", - "dev": true, - "engines": { - "node": ">=12" - } - }, - "node_modules/make-dir": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", - "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", - "dev": true, - "optional": true, - "dependencies": { - "pify": "^4.0.1", - "semver": "^5.6.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/make-dir/node_modules/pify": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", - "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", - "dev": true, - "optional": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/make-fetch-happen": { - "version": "10.2.1", - "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-10.2.1.tgz", - "integrity": "sha512-NgOPbRiaQM10DYXvN3/hhGVI2M5MtITFryzBGxHM5p4wnFxsVCbxkrBrDsk+EZ5OB4jEOT7AjDxtdF+KVEFT7w==", - "dev": true, - "dependencies": { - "agentkeepalive": "^4.2.1", - "cacache": "^16.1.0", - "http-cache-semantics": "^4.1.0", - "http-proxy-agent": "^5.0.0", - "https-proxy-agent": "^5.0.0", - "is-lambda": "^1.0.1", - "lru-cache": "^7.7.1", - "minipass": "^3.1.6", - "minipass-collect": "^1.0.2", - "minipass-fetch": "^2.0.3", - "minipass-flush": "^1.0.5", - "minipass-pipeline": "^1.2.4", - "negotiator": "^0.6.3", - "promise-retry": "^2.0.1", - "socks-proxy-agent": "^7.0.0", - "ssri": "^9.0.0" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" - } - }, - "node_modules/make-fetch-happen/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/make-fetch-happen/node_modules/cacache": { - "version": "16.1.3", - "resolved": "https://registry.npmjs.org/cacache/-/cacache-16.1.3.tgz", - "integrity": "sha512-/+Emcj9DAXxX4cwlLmRI9c166RuL3w30zp4R7Joiv2cQTtTtA+jeuCAjH3ZlGnYS3tKENSrKhAzVVP9GVyzeYQ==", - "dev": true, - "dependencies": { - "@npmcli/fs": "^2.1.0", - "@npmcli/move-file": "^2.0.0", - "chownr": "^2.0.0", - "fs-minipass": "^2.1.0", - "glob": "^8.0.1", - "infer-owner": "^1.0.4", - "lru-cache": "^7.7.1", - "minipass": "^3.1.6", - "minipass-collect": "^1.0.2", - "minipass-flush": "^1.0.5", - "minipass-pipeline": "^1.2.4", - "mkdirp": "^1.0.4", - "p-map": "^4.0.0", - "promise-inflight": "^1.0.1", - "rimraf": "^3.0.2", - "ssri": "^9.0.0", - "tar": "^6.1.11", - "unique-filename": "^2.0.0" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" - } - }, - "node_modules/make-fetch-happen/node_modules/chownr": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", - "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", - "dev": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/make-fetch-happen/node_modules/glob": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", - "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", - "dev": true, - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^5.0.1", - "once": "^1.3.0" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/make-fetch-happen/node_modules/minimatch": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", - "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", - "dev": true, - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/make-fetch-happen/node_modules/mkdirp": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", - "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", - "dev": true, - "bin": { - "mkdirp": "bin/cmd.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/make-fetch-happen/node_modules/rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "dev": true, - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/make-fetch-happen/node_modules/rimraf/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/make-fetch-happen/node_modules/rimraf/node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "dev": true, - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/make-fetch-happen/node_modules/rimraf/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/make-fetch-happen/node_modules/ssri": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/ssri/-/ssri-9.0.1.tgz", - "integrity": "sha512-o57Wcn66jMQvfHG1FlYbWeZWW/dHZhJXjpIcTfXldXEk5nz5lStPo3mK0OJQfGR3RbZUlbISexbljkJzuEj/8Q==", - "dev": true, - "dependencies": { - "minipass": "^3.1.1" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" - } - }, - "node_modules/make-fetch-happen/node_modules/unique-filename": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-2.0.1.tgz", - "integrity": "sha512-ODWHtkkdx3IAR+veKxFV+VBkUMcN+FaqzUUd7IZzt+0zhDZFPFxhlqwPF3YQvMHx1TD0tdgYl+kuPnJ8E6ql7A==", - "dev": true, - "dependencies": { - "unique-slug": "^3.0.0" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" - } - }, - "node_modules/make-fetch-happen/node_modules/unique-slug": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-3.0.0.tgz", - "integrity": "sha512-8EyMynh679x/0gqE9fT9oilG+qEt+ibFyqjuVTsZn1+CMxH+XLlpvr2UZx4nVcCwTpx81nICr2JQFkM+HPLq4w==", - "dev": true, - "dependencies": { - "imurmurhash": "^0.1.4" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" - } - }, - "node_modules/make-iterator": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/make-iterator/-/make-iterator-1.0.1.tgz", - "integrity": "sha512-pxiuXh0iVEq7VM7KMIhs5gxsfxCux2URptUQaXo4iZZJxBAzTPOLE2BumO5dbfVYq/hBJFBR/a1mFDmOx5AGmw==", - "dev": true, - "dependencies": { - "kind-of": "^6.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/map-cache": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz", - "integrity": "sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/map-obj": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz", - "integrity": "sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/map-visit": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/map-visit/-/map-visit-1.0.0.tgz", - "integrity": "sha1-7Nyo8TFE5mDxtb1B8S80edmN+48=", - "dev": true, - "dependencies": { - "object-visit": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/matchdep": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/matchdep/-/matchdep-2.0.0.tgz", - "integrity": "sha512-LFgVbaHIHMqCRuCZyfCtUOq9/Lnzhi7Z0KFUE2fhD54+JN2jLh3hC02RLkqauJ3U4soU6H1J3tfj/Byk7GoEjA==", - "dev": true, - "dependencies": { - "findup-sync": "^2.0.0", - "micromatch": "^3.0.4", - "resolve": "^1.4.0", - "stack-trace": "0.0.10" - }, - "engines": { - "node": ">= 0.10.0" - } - }, - "node_modules/matchdep/node_modules/findup-sync": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/findup-sync/-/findup-sync-2.0.0.tgz", - "integrity": "sha512-vs+3unmJT45eczmcAZ6zMJtxN3l/QXeccaXQx5cu/MeJMhewVfoWZqibRkOxPnmoR59+Zy5hjabfQc6JLSah4g==", - "dev": true, - "dependencies": { - "detect-file": "^1.0.0", - "is-glob": "^3.1.0", - "micromatch": "^3.0.4", - "resolve-dir": "^1.0.1" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/matchdep/node_modules/is-glob": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", - "integrity": "sha512-UFpDDrPgM6qpnFNI+rh/p3bUaq9hKLZN8bMUWzxmcnZVS3omf4IPK+BrewlnWjO1WmUsMYuSjKh4UJuV4+Lqmw==", - "dev": true, - "dependencies": { - "is-extglob": "^2.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/meow": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/meow/-/meow-9.0.0.tgz", - "integrity": "sha512-+obSblOQmRhcyBt62furQqRAQpNyWXo8BuQ5bN7dG8wmwQ+vwHKp/rCFD4CrTP8CsDQD1sjoZ94K417XEUk8IQ==", - "dev": true, - "dependencies": { - "@types/minimist": "^1.2.0", - "camelcase-keys": "^6.2.2", - "decamelize": "^1.2.0", - "decamelize-keys": "^1.1.0", - "hard-rejection": "^2.1.0", - "minimist-options": "4.1.0", - "normalize-package-data": "^3.0.0", - "read-pkg-up": "^7.0.1", - "redent": "^3.0.0", - "trim-newlines": "^3.0.0", - "type-fest": "^0.18.0", - "yargs-parser": "^20.2.3" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/meow/node_modules/find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dev": true, - "dependencies": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/meow/node_modules/hosted-git-info": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-4.1.0.tgz", - "integrity": "sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA==", - "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/meow/node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/meow/node_modules/normalize-package-data": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-3.0.3.tgz", - "integrity": "sha512-p2W1sgqij3zMMyRC067Dg16bfzVH+w7hyegmpIvZ4JNjqtGOVAIvLmjBx3yP7YTe9vKJgkoNOPjwQGogDoMXFA==", - "dev": true, - "dependencies": { - "hosted-git-info": "^4.0.1", - "is-core-module": "^2.5.0", - "semver": "^7.3.4", - "validate-npm-package-license": "^3.0.1" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/meow/node_modules/parse-json": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", - "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.0.0", - "error-ex": "^1.3.1", - "json-parse-even-better-errors": "^2.3.0", - "lines-and-columns": "^1.1.6" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/meow/node_modules/path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/meow/node_modules/read-pkg": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz", - "integrity": "sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==", - "dev": true, - "dependencies": { - "@types/normalize-package-data": "^2.4.0", - "normalize-package-data": "^2.5.0", - "parse-json": "^5.0.0", - "type-fest": "^0.6.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/meow/node_modules/read-pkg-up": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-7.0.1.tgz", - "integrity": "sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==", - "dev": true, - "dependencies": { - "find-up": "^4.1.0", - "read-pkg": "^5.2.0", - "type-fest": "^0.8.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/meow/node_modules/read-pkg-up/node_modules/type-fest": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", - "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/meow/node_modules/read-pkg/node_modules/hosted-git-info": { - "version": "2.8.9", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", - "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==", - "dev": true - }, - "node_modules/meow/node_modules/read-pkg/node_modules/normalize-package-data": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", - "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", - "dev": true, - "dependencies": { - "hosted-git-info": "^2.1.4", - "resolve": "^1.10.0", - "semver": "2 || 3 || 4 || 5", - "validate-npm-package-license": "^3.0.1" - } - }, - "node_modules/meow/node_modules/read-pkg/node_modules/semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "dev": true, - "bin": { - "semver": "bin/semver" - } - }, - "node_modules/meow/node_modules/read-pkg/node_modules/type-fest": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.6.0.tgz", - "integrity": "sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/meow/node_modules/semver": { - "version": "7.3.8", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", - "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", - "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/meow/node_modules/type-fest": { - "version": "0.18.1", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.18.1.tgz", - "integrity": "sha512-OIAYXk8+ISY+qTOwkHtKqzAuxchoMiD9Udx+FSGQDuiRR+PJKJHc2NJAXlbhkGwTt/4/nKZxELY1w3ReWOL8mw==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/merge-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", - "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", - "dev": true - }, - "node_modules/merge2": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", - "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", - "dev": true, - "engines": { - "node": ">= 8" - } - }, - "node_modules/micromatch": { - "version": "3.1.10", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", - "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", - "dev": true, - "dependencies": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "braces": "^2.3.1", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "extglob": "^2.0.4", - "fragment-cache": "^0.2.1", - "kind-of": "^6.0.2", - "nanomatch": "^1.2.9", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/mime": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", - "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", - "dev": true, - "optional": true, - "bin": { - "mime": "cli.js" - }, - "engines": { - "node": ">=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, - "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, - "dependencies": { - "mime-db": "1.52.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/min-indent": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", - "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/minimist-options": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/minimist-options/-/minimist-options-4.1.0.tgz", - "integrity": "sha512-Q4r8ghd80yhO/0j1O3B2BjweX3fiHg9cdOwjJd2J76Q135c+NDxGCqdYKQ1SKBuFfgWbAUzBfvYjPUEeNgqN1A==", - "dev": true, - "dependencies": { - "arrify": "^1.0.1", - "is-plain-obj": "^1.1.0", - "kind-of": "^6.0.3" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/minipass": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", - "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", - "dev": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/minipass-collect": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/minipass-collect/-/minipass-collect-1.0.2.tgz", - "integrity": "sha512-6T6lH0H8OG9kITm/Jm6tdooIbogG9e0tLgpY6mphXSm/A9u8Nq1ryBG+Qspiub9LjWlBPsPS3tWQ/Botq4FdxA==", - "dev": true, - "dependencies": { - "minipass": "^3.0.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/minipass-fetch": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-2.1.2.tgz", - "integrity": "sha512-LT49Zi2/WMROHYoqGgdlQIZh8mLPZmOrN2NdJjMXxYe4nkN6FUyuPuOAOedNJDrx0IRGg9+4guZewtp8hE6TxA==", - "dev": true, - "dependencies": { - "minipass": "^3.1.6", - "minipass-sized": "^1.0.3", - "minizlib": "^2.1.2" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" - }, - "optionalDependencies": { - "encoding": "^0.1.13" - } - }, - "node_modules/minipass-flush": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/minipass-flush/-/minipass-flush-1.0.5.tgz", - "integrity": "sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw==", - "dev": true, - "dependencies": { - "minipass": "^3.0.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/minipass-pipeline": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/minipass-pipeline/-/minipass-pipeline-1.2.4.tgz", - "integrity": "sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A==", - "dev": true, - "dependencies": { - "minipass": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/minipass-sized": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/minipass-sized/-/minipass-sized-1.0.3.tgz", - "integrity": "sha512-MbkQQ2CTiBMlA2Dm/5cY+9SWFEN8pzzOXi6rlM5Xxq0Yqbda5ZQy9sU75a673FE9ZK0Zsbr6Y5iP6u9nktfg2g==", - "dev": true, - "dependencies": { - "minipass": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/minizlib": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", - "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", - "dev": true, - "dependencies": { - "minipass": "^3.0.0", - "yallist": "^4.0.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/mixin-deep": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.2.tgz", - "integrity": "sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==", - "dev": true, - "dependencies": { - "for-in": "^1.0.2", - "is-extendable": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/mixin-deep/node_modules/is-extendable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", - "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", - "dev": true, - "dependencies": { - "is-plain-object": "^2.0.4" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/mobx": { - "version": "5.15.7", - "resolved": "https://registry.npmjs.org/mobx/-/mobx-5.15.7.tgz", - "integrity": "sha512-wyM3FghTkhmC+hQjyPGGFdpehrcX1KOXsDuERhfK2YbJemkUhEB+6wzEN639T21onxlfYBmriA1PFnvxTUhcKw==", - "dev": true, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/mobx" - } - }, - "node_modules/mobx-react": { - "version": "5.4.4", - "resolved": "https://registry.npmjs.org/mobx-react/-/mobx-react-5.4.4.tgz", - "integrity": "sha512-2mTzpyEjVB/RGk2i6KbcmP4HWcAUFox5ZRCrGvSyz49w20I4C4qql63grPpYrS9E9GKwgydBHQlA4y665LuRCQ==", - "dev": true, - "dependencies": { - "hoist-non-react-statics": "^3.0.0", - "react-lifecycles-compat": "^3.0.2" - }, - "peerDependencies": { - "mobx": "^4.0.0 || ^5.0.0", - "react": "^0.13.0 || ^0.14.0 || ^15.0.0 || ^16.0.0" - } - }, - "node_modules/mobx-react-devtools": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/mobx-react-devtools/-/mobx-react-devtools-6.1.1.tgz", - "integrity": "sha512-nc5IXLdEUFLn3wZal65KF3/JFEFd+mbH4KTz/IG5BOPyw7jo8z29w/8qm7+wiCyqVfUIgJ1gL4+HVKmcXIOgqA==", - "dev": true, - "peerDependencies": { - "mobx": "^4.3.1 || ^5.0.0", - "mobx-react": "^4.0.0 || ^5.0.0" - } - }, - "node_modules/mobx-react/node_modules/hoist-non-react-statics": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", - "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", - "dev": true, - "dependencies": { - "react-is": "^16.7.0" - } - }, - "node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true - }, - "node_modules/mute-stdout": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/mute-stdout/-/mute-stdout-1.0.1.tgz", - "integrity": "sha512-kDcwXR4PS7caBpuRYYBUz9iVixUk3anO3f5OYFiIPwK/20vCzKCHyKoulbiDY1S53zD2bxUpxN/IJ+TnXjfvxg==", - "dev": true, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/nan": { - "version": "2.17.0", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.17.0.tgz", - "integrity": "sha512-2ZTgtl0nJsO0KQCjEpxcIr5D+Yv90plTitZt9JBfQvVJDS5seMl3FOvsh3+9CoYWXf/1l5OaZzzF6nDm4cagaQ==", - "dev": true - }, - "node_modules/nanomatch": { - "version": "1.2.13", - "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz", - "integrity": "sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==", - "dev": true, - "dependencies": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "fragment-cache": "^0.2.1", - "is-windows": "^1.0.2", - "kind-of": "^6.0.2", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/native-request": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/native-request/-/native-request-1.1.0.tgz", - "integrity": "sha512-uZ5rQaeRn15XmpgE0xoPL8YWqcX90VtCFglYwAgkvKM5e8fog+vePLAhHxuuv/gRkrQxIeh5U3q9sMNUrENqWw==", - "dev": true, - "optional": true - }, - "node_modules/negotiator": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", - "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", - "dev": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/neo-async": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", - "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", - "dev": true - }, - "node_modules/next-tick": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.1.0.tgz", - "integrity": "sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==", - "dev": true - }, - "node_modules/node-gyp": { - "version": "8.4.1", - "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-8.4.1.tgz", - "integrity": "sha512-olTJRgUtAb/hOXG0E93wZDs5YiJlgbXxTwQAFHyNlRsXQnYzUaF2aGgujZbw+hR8aF4ZG/rST57bWMWD16jr9w==", - "dev": true, - "dependencies": { - "env-paths": "^2.2.0", - "glob": "^7.1.4", - "graceful-fs": "^4.2.6", - "make-fetch-happen": "^9.1.0", - "nopt": "^5.0.0", - "npmlog": "^6.0.0", - "rimraf": "^3.0.2", - "semver": "^7.3.5", - "tar": "^6.1.2", - "which": "^2.0.2" - }, - "bin": { - "node-gyp": "bin/node-gyp.js" - }, - "engines": { - "node": ">= 10.12.0" - } - }, - "node_modules/node-gyp/node_modules/@npmcli/fs": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-1.1.1.tgz", - "integrity": "sha512-8KG5RD0GVP4ydEzRn/I4BNDuxDtqVbOdm8675T49OIG/NGhaK0pjPX7ZcDlvKYbA+ulvVK3ztfcF4uBdOxuJbQ==", - "dev": true, - "dependencies": { - "@gar/promisify": "^1.0.1", - "semver": "^7.3.5" - } - }, - "node_modules/node-gyp/node_modules/@npmcli/move-file": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@npmcli/move-file/-/move-file-1.1.2.tgz", - "integrity": "sha512-1SUf/Cg2GzGDyaf15aR9St9TWlb+XvbZXWpDx8YKs7MLzMH/BCeopv+y9vzrzgkfykCGuWOlSu3mZhj2+FQcrg==", - "deprecated": "This functionality has been moved to @npmcli/fs", - "dev": true, - "dependencies": { - "mkdirp": "^1.0.4", - "rimraf": "^3.0.2" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/node-gyp/node_modules/@tootallnate/once": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz", - "integrity": "sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==", - "dev": true, - "engines": { - "node": ">= 6" - } - }, - "node_modules/node-gyp/node_modules/cacache": { - "version": "15.3.0", - "resolved": "https://registry.npmjs.org/cacache/-/cacache-15.3.0.tgz", - "integrity": "sha512-VVdYzXEn+cnbXpFgWs5hTT7OScegHVmLhJIR8Ufqk3iFD6A6j5iSX1KuBTfNEv4tdJWE2PzA6IVFtcLC7fN9wQ==", - "dev": true, - "dependencies": { - "@npmcli/fs": "^1.0.0", - "@npmcli/move-file": "^1.0.1", - "chownr": "^2.0.0", - "fs-minipass": "^2.0.0", - "glob": "^7.1.4", - "infer-owner": "^1.0.4", - "lru-cache": "^6.0.0", - "minipass": "^3.1.1", - "minipass-collect": "^1.0.2", - "minipass-flush": "^1.0.5", - "minipass-pipeline": "^1.2.2", - "mkdirp": "^1.0.3", - "p-map": "^4.0.0", - "promise-inflight": "^1.0.1", - "rimraf": "^3.0.2", - "ssri": "^8.0.1", - "tar": "^6.0.2", - "unique-filename": "^1.1.1" - }, - "engines": { - "node": ">= 10" - } - }, - "node_modules/node-gyp/node_modules/chownr": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", - "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", - "dev": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/node-gyp/node_modules/http-proxy-agent": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz", - "integrity": "sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg==", - "dev": true, - "dependencies": { - "@tootallnate/once": "1", - "agent-base": "6", - "debug": "4" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/node-gyp/node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/node-gyp/node_modules/make-fetch-happen": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-9.1.0.tgz", - "integrity": "sha512-+zopwDy7DNknmwPQplem5lAZX/eCOzSvSNNcSKm5eVwTkOBzoktEfXsa9L23J/GIRhxRsaxzkPEhrJEpE2F4Gg==", - "dev": true, - "dependencies": { - "agentkeepalive": "^4.1.3", - "cacache": "^15.2.0", - "http-cache-semantics": "^4.1.0", - "http-proxy-agent": "^4.0.1", - "https-proxy-agent": "^5.0.0", - "is-lambda": "^1.0.1", - "lru-cache": "^6.0.0", - "minipass": "^3.1.3", - "minipass-collect": "^1.0.2", - "minipass-fetch": "^1.3.2", - "minipass-flush": "^1.0.5", - "minipass-pipeline": "^1.2.4", - "negotiator": "^0.6.2", - "promise-retry": "^2.0.1", - "socks-proxy-agent": "^6.0.0", - "ssri": "^8.0.0" - }, - "engines": { - "node": ">= 10" - } - }, - "node_modules/node-gyp/node_modules/minipass-fetch": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-1.4.1.tgz", - "integrity": "sha512-CGH1eblLq26Y15+Azk7ey4xh0J/XfJfrCox5LDJiKqI2Q2iwOLOKrlmIaODiSQS8d18jalF6y2K2ePUm0CmShw==", - "dev": true, - "dependencies": { - "minipass": "^3.1.0", - "minipass-sized": "^1.0.3", - "minizlib": "^2.0.0" - }, - "engines": { - "node": ">=8" - }, - "optionalDependencies": { - "encoding": "^0.1.12" - } - }, - "node_modules/node-gyp/node_modules/mkdirp": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", - "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", - "dev": true, - "bin": { - "mkdirp": "bin/cmd.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/node-gyp/node_modules/rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "dev": true, - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/node-gyp/node_modules/semver": { - "version": "7.3.8", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", - "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", - "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/node-gyp/node_modules/socks-proxy-agent": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-6.2.1.tgz", - "integrity": "sha512-a6KW9G+6B3nWZ1yB8G7pJwL3ggLy1uTzKAgCb7ttblwqdz9fMGJUuTy3uFzEP48FAs9FLILlmzDlE2JJhVQaXQ==", - "dev": true, - "dependencies": { - "agent-base": "^6.0.2", - "debug": "^4.3.3", - "socks": "^2.6.2" - }, - "engines": { - "node": ">= 10" - } - }, - "node_modules/node-gyp/node_modules/ssri": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/ssri/-/ssri-8.0.1.tgz", - "integrity": "sha512-97qShzy1AiyxvPNIkLWoGua7xoQzzPjQ0HAH4B0rWKo7SZ6USuPcrUiAFrws0UH8RrbWmgq3LMTObhPIHbbBeQ==", - "dev": true, - "dependencies": { - "minipass": "^3.1.1" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/node-gyp/node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/node-releases": { - "version": "2.0.10", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.10.tgz", - "integrity": "sha512-5GFldHPXVG/YZmFzJvKK2zDSzPKhEp0+ZR5SVaoSag9fsL5YgHbUHDfnG5494ISANDcK4KwPXAx2xqVEydmd7w==", - "dev": true - }, - "node_modules/node-sass": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/node-sass/-/node-sass-8.0.0.tgz", - "integrity": "sha512-jPzqCF2/e6JXw6r3VxfIqYc8tKQdkj5Z/BDATYyG6FL6b/LuYBNFGFVhus0mthcWifHm/JzBpKAd+3eXsWeK/A==", - "dev": true, - "hasInstallScript": true, - "dependencies": { - "async-foreach": "^0.1.3", - "chalk": "^4.1.2", - "cross-spawn": "^7.0.3", - "gaze": "^1.0.0", - "get-stdin": "^4.0.1", - "glob": "^7.0.3", - "lodash": "^4.17.15", - "make-fetch-happen": "^10.0.4", - "meow": "^9.0.0", - "nan": "^2.17.0", - "node-gyp": "^8.4.1", - "sass-graph": "^4.0.1", - "stdout-stream": "^1.4.0", - "true-case-path": "^2.2.1" - }, - "bin": { - "node-sass": "bin/node-sass" - }, - "engines": { - "node": ">=14" - } - }, - "node_modules/nopt": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz", - "integrity": "sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==", - "dev": true, - "dependencies": { - "abbrev": "1" - }, - "bin": { - "nopt": "bin/nopt.js" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/normalize-package-data": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", - "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", - "dev": true, - "dependencies": { - "hosted-git-info": "^2.1.4", - "resolve": "^1.10.0", - "semver": "2 || 3 || 4 || 5", - "validate-npm-package-license": "^3.0.1" - } - }, - "node_modules/normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/now-and-later": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/now-and-later/-/now-and-later-2.0.1.tgz", - "integrity": "sha512-KGvQ0cB70AQfg107Xvs/Fbu+dGmZoTRJp2TaPwcwQm3/7PteUyN2BCgk8KBMPGBUXZdVwyWS8fDCGFygBm19UQ==", - "dev": true, - "dependencies": { - "once": "^1.3.2" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/npmlog": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-6.0.2.tgz", - "integrity": "sha512-/vBvz5Jfr9dT/aFWd0FIRf+T/Q2WBsLENygUaFUqstqsycmZAP/t5BvFJTK0viFmSUxiUKTUplWy5vt+rvKIxg==", - "dev": true, - "dependencies": { - "are-we-there-yet": "^3.0.0", - "console-control-strings": "^1.1.0", - "gauge": "^4.0.3", - "set-blocking": "^2.0.0" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" - } - }, - "node_modules/number-is-nan": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", - "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object-copy": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz", - "integrity": "sha1-fn2Fi3gb18mRpBupde04EnVOmYw=", - "dev": true, - "dependencies": { - "copy-descriptor": "^0.1.0", - "define-property": "^0.2.5", - "kind-of": "^3.0.3" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object-copy/node_modules/define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, - "dependencies": { - "is-descriptor": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object-copy/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object-keys": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", - "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", - "dev": true, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/object-visit": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz", - "integrity": "sha1-95xEk68MU3e1n+OdOV5BBC3QRbs=", - "dev": true, - "dependencies": { - "isobject": "^3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object.assign": { - "version": "4.1.4", - "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.4.tgz", - "integrity": "sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "has-symbols": "^1.0.3", - "object-keys": "^1.1.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/object.defaults": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/object.defaults/-/object.defaults-1.1.0.tgz", - "integrity": "sha512-c/K0mw/F11k4dEUBMW8naXUuBuhxRCfG7W+yFy8EcijU/rSmazOUd1XAEEe6bC0OuXY4HUKjTJv7xbxIMqdxrA==", - "dev": true, - "dependencies": { - "array-each": "^1.0.1", - "array-slice": "^1.0.0", - "for-own": "^1.0.0", - "isobject": "^3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object.map": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/object.map/-/object.map-1.0.1.tgz", - "integrity": "sha512-3+mAJu2PLfnSVGHwIWubpOFLscJANBKuB/6A4CxBstc4aqwQY0FWcsppuy4jU5GSB95yES5JHSI+33AWuS4k6w==", - "dev": true, - "dependencies": { - "for-own": "^1.0.0", - "make-iterator": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object.pick": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz", - "integrity": "sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c=", - "dev": true, - "dependencies": { - "isobject": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object.reduce": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/object.reduce/-/object.reduce-1.0.1.tgz", - "integrity": "sha512-naLhxxpUESbNkRqc35oQ2scZSJueHGQNUfMW/0U37IgN6tE2dgDWg3whf+NEliy3F/QysrO48XKUz/nGPe+AQw==", - "dev": true, - "dependencies": { - "for-own": "^1.0.0", - "make-iterator": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "dev": true, - "dependencies": { - "wrappy": "1" - } - }, - "node_modules/ordered-read-streams": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/ordered-read-streams/-/ordered-read-streams-1.0.1.tgz", - "integrity": "sha512-Z87aSjx3r5c0ZB7bcJqIgIRX5bxR7A4aSzvIbaxd0oTkWBCOoKfuGHiKj60CHVUgg1Phm5yMZzBdt8XqRs73Mw==", - "dev": true, - "dependencies": { - "readable-stream": "^2.0.1" - } - }, - "node_modules/os-locale": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-1.4.0.tgz", - "integrity": "sha512-PRT7ZORmwu2MEFt4/fv3Q+mEfN4zetKxufQrkShY2oGvUms9r8otu5HfdyIFHkYXjO7laNsoVGmM2MANfuTA8g==", - "dev": true, - "dependencies": { - "lcid": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "dependencies": { - "p-try": "^2.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dev": true, - "dependencies": { - "p-limit": "^2.2.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/p-map": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", - "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", - "dev": true, - "dependencies": { - "aggregate-error": "^3.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/pace-progress": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/pace-progress/-/pace-progress-1.0.2.tgz", - "integrity": "sha1-/cVlxX3ZFyWjFns2C/JXjTw7VI0=", - "deprecated": "Disclaimer, We no longer use this library internally and are focusing our efforts on open sourcing and maintaining projects that we do use and can meaningfully contribute to. Sorry for any frustrations with this project (we're happy to link to any fork that has an excited, commited maintainer)." - }, - "node_modules/parse-filepath": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/parse-filepath/-/parse-filepath-1.0.2.tgz", - "integrity": "sha512-FwdRXKCohSVeXqwtYonZTXtbGJKrn+HNyWDYVcp5yuJlesTwNH4rsmRZ+GrKAPJ5bLpRxESMeS+Rl0VCHRvB2Q==", - "dev": true, - "dependencies": { - "is-absolute": "^1.0.0", - "map-cache": "^0.2.0", - "path-root": "^0.1.1" - }, - "engines": { - "node": ">=0.8" - } - }, - "node_modules/parse-json": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", - "integrity": "sha512-QR/GGaKCkhwk1ePQNYDRKYZ3mwU9ypsKhB0XyFnLQdomyEqk3e8wpW3V5Jp88zbxK4n5ST1nqo+g9juTpownhQ==", - "dev": true, - "dependencies": { - "error-ex": "^1.2.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/parse-node-version": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parse-node-version/-/parse-node-version-1.0.1.tgz", - "integrity": "sha512-3YHlOa/JgH6Mnpr05jP9eDG254US9ek25LyIxZlDItp2iJtwyaXQb57lBYLdT3MowkUFYEV2XXNAYIPlESvJlA==", - "dev": true, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/parse-passwd": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/parse-passwd/-/parse-passwd-1.0.0.tgz", - "integrity": "sha1-bVuTSkVpk7I9N/QKOC1vFmao5cY=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/pascalcase": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz", - "integrity": "sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/path-dirname": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/path-dirname/-/path-dirname-1.0.2.tgz", - "integrity": "sha512-ALzNPpyNq9AqXMBjeymIjFDAkAFH06mHJH/cSBHAgU0s4vfpBn6b2nf8tiRLvagKD8RbTpq2FKTBg7cl9l3c7Q==", - "dev": true - }, - "node_modules/path-exists": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz", - "integrity": "sha512-yTltuKuhtNeFJKa1PiRzfLAU5182q1y4Eb4XCJ3PBqyzEDkAZRzBrKKBct682ls9reBVHf9udYLN5Nd+K1B9BQ==", - "dev": true, - "dependencies": { - "pinkie-promise": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/path-parse": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", - "dev": true - }, - "node_modules/path-root": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/path-root/-/path-root-0.1.1.tgz", - "integrity": "sha512-QLcPegTHF11axjfojBIoDygmS2E3Lf+8+jI6wOVmNVenrKSo3mFdSGiIgdSHenczw3wPtlVMQaFVwGmM7BJdtg==", - "dev": true, - "dependencies": { - "path-root-regex": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/path-root-regex": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/path-root-regex/-/path-root-regex-0.1.2.tgz", - "integrity": "sha512-4GlJ6rZDhQZFE0DPVKh0e9jmZ5egZfxTkp7bcRDuPlJXbAwhxcl2dINPUAsjLdejqaLsCeg8axcLjIbvBjN4pQ==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/path-to-regexp": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.8.0.tgz", - "integrity": "sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA==", - "dev": true, - "dependencies": { - "isarray": "0.0.1" - } - }, - "node_modules/path-type": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz", - "integrity": "sha512-S4eENJz1pkiQn9Znv33Q+deTOKmbl+jj1Fl+qiP/vYezj+S8x+J3Uo0ISrx/QoEvIlOaDWJhPaRd1flJ9HXZqg==", - "dev": true, - "dependencies": { - "graceful-fs": "^4.1.2", - "pify": "^2.0.0", - "pinkie-promise": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/perfect-scrollbar": { - "version": "1.5.5", - "resolved": "https://registry.npmjs.org/perfect-scrollbar/-/perfect-scrollbar-1.5.5.tgz", - "integrity": "sha512-dzalfutyP3e/FOpdlhVryN4AJ5XDVauVWxybSkLZmakFE2sS3y3pc4JnSprw8tGmHvkaG5Edr5T7LBTZ+WWU2g==", - "dev": true - }, - "node_modules/performance-now": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", - "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=", - "optional": true - }, - "node_modules/picocolors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", - "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", - "dev": true - }, - "node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true, - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/pify": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/pinkie": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", - "integrity": "sha512-MnUuEycAemtSaeFSjXKW/aroV7akBbY+Sv+RkyqFjgAe73F+MR0TBWKBRDkmfWq/HiFmdavfZ1G7h4SPZXaCSg==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/pinkie-promise": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", - "integrity": "sha512-0Gni6D4UcLTbv9c57DfxDGdr41XfgUjqWZu492f0cIGr16zDU06BWP/RAEvOuo7CQ0CNjHaLlM59YJJFm3NWlw==", - "dev": true, - "dependencies": { - "pinkie": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/pkg-dir": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", - "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", - "dev": true, - "dependencies": { - "find-up": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/pkg-dir/node_modules/find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dev": true, - "dependencies": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/pkg-dir/node_modules/path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/plur": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/plur/-/plur-4.0.0.tgz", - "integrity": "sha512-4UGewrYgqDFw9vV6zNV+ADmPAUAfJPKtGvb/VdpQAx25X5f3xXdGdyOEVFwkl8Hl/tl7+xbeHqSEM+D5/TirUg==", - "dev": true, - "dependencies": { - "irregular-plurals": "^3.2.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/popper.js": { - "version": "1.16.1", - "resolved": "https://registry.npmjs.org/popper.js/-/popper.js-1.16.1.tgz", - "integrity": "sha512-Wb4p1J4zyFTbM+u6WuO4XstYx4Ky9Cewe4DWrel7B0w6VVICvPwdOpotjzcf6eD8TsckVnIMNONQyPIUFOUbCQ==", - "deprecated": "You can find the new Popper v2 at @popperjs/core, this package is dedicated to the legacy v1", - "dev": true, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/popperjs" - } - }, - "node_modules/posix-character-classes": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz", - "integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/pretty-format": { - "version": "29.5.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.5.0.tgz", - "integrity": "sha512-V2mGkI31qdttvTFX7Mt4efOqHXqJWMu4/r66Xh3Z3BwZaPfPJgp6/gbwoujRpPUtfEF6AUUWx3Jim3GCw5g/Qw==", - "dev": true, - "dependencies": { - "@jest/schemas": "^29.4.3", - "ansi-styles": "^5.0.0", - "react-is": "^18.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/pretty-format/node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/pretty-format/node_modules/react-is": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", - "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", - "dev": true - }, - "node_modules/pretty-hrtime": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/pretty-hrtime/-/pretty-hrtime-1.0.3.tgz", - "integrity": "sha512-66hKPCr+72mlfiSjlEB1+45IjXSqvVAIy6mocupoww4tBFE9R9IhwwUGoI4G++Tc9Aq+2rxOt0RFU6gPcrte0A==", - "dev": true, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/process-nextick-args": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", - "dev": true - }, - "node_modules/promise-inflight": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz", - "integrity": "sha1-mEcocL8igTL8vdhoEputEsPAKeM=", - "dev": true - }, - "node_modules/promise-retry": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/promise-retry/-/promise-retry-2.0.1.tgz", - "integrity": "sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==", - "dev": true, - "dependencies": { - "err-code": "^2.0.2", - "retry": "^0.12.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/prop-types": { - "version": "15.7.2", - "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.7.2.tgz", - "integrity": "sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==", - "dev": true, - "dependencies": { - "loose-envify": "^1.4.0", - "object-assign": "^4.1.1", - "react-is": "^16.8.1" - } - }, - "node_modules/proxy-from-env": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", - "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", - "dev": true - }, - "node_modules/prr": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz", - "integrity": "sha1-0/wRS6BplaRexok/SEzrHXj19HY=", - "dev": true, - "optional": true - }, - "node_modules/pump": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/pump/-/pump-2.0.1.tgz", - "integrity": "sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA==", - "dev": true, - "dependencies": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - }, - "node_modules/pumpify": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/pumpify/-/pumpify-1.5.1.tgz", - "integrity": "sha512-oClZI37HvuUJJxSKKrC17bZ9Cu0ZYhEAGPsPUy9KlMUmv9dKX2o77RUmq7f3XjIxbwyGwYzbzQ1L2Ks8sIradQ==", - "dev": true, - "dependencies": { - "duplexify": "^3.6.0", - "inherits": "^2.0.3", - "pump": "^2.0.0" - } - }, - "node_modules/punycode": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/queue-microtask": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", - "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/quick-lru": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-4.0.1.tgz", - "integrity": "sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/raf": { - "version": "3.4.1", - "resolved": "https://registry.npmjs.org/raf/-/raf-3.4.1.tgz", - "integrity": "sha512-Sq4CW4QhwOHE8ucn6J34MqtZCeWFP2aQSmrlroYgqAV1PjStIhJXxYuTgUIfkEk7zTLjmIjLmU5q+fbD1NnOJA==", - "optional": true, - "dependencies": { - "performance-now": "^2.1.0" - } - }, - "node_modules/randombytes": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", - "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", - "dev": true, - "dependencies": { - "safe-buffer": "^5.1.0" - } - }, - "node_modules/react": { - "version": "16.14.0", - "resolved": "https://registry.npmjs.org/react/-/react-16.14.0.tgz", - "integrity": "sha512-0X2CImDkJGApiAlcf0ODKIneSwBPhqJawOa5wCtKbu7ZECrmS26NvtSILynQ66cgkT/RJ4LidJOc3bUESwmU8g==", - "dev": true, - "dependencies": { - "loose-envify": "^1.1.0", - "object-assign": "^4.1.1", - "prop-types": "^15.6.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/react-dom": { - "version": "16.14.0", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-16.14.0.tgz", - "integrity": "sha512-1gCeQXDLoIqMgqD3IO2Ah9bnf0w9kzhwN5q4FGnHZ67hBm9yePzB5JJAIQCc8x3pFnNlwFq4RidZggNAAkzWWw==", - "dev": true, - "dependencies": { - "loose-envify": "^1.1.0", - "object-assign": "^4.1.1", - "prop-types": "^15.6.2", - "scheduler": "^0.19.1" - }, - "peerDependencies": { - "react": "^16.14.0" - } - }, - "node_modules/react-dom-factories": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/react-dom-factories/-/react-dom-factories-1.0.2.tgz", - "integrity": "sha1-63cFxNs2+1AbOqOP91lhaqD/luA=" - }, - "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 - }, - "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 - }, - "node_modules/react-router": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/react-router/-/react-router-4.3.1.tgz", - "integrity": "sha512-yrvL8AogDh2X42Dt9iknk4wF4V8bWREPirFfS9gLU1huk6qK41sg7Z/1S81jjTrGHxa3B8R3J6xIkDAA6CVarg==", - "dev": true, - "dependencies": { - "history": "^4.7.2", - "hoist-non-react-statics": "^2.5.0", - "invariant": "^2.2.4", - "loose-envify": "^1.3.1", - "path-to-regexp": "^1.7.0", - "prop-types": "^15.6.1", - "warning": "^4.0.1" - }, - "peerDependencies": { - "react": ">=15" - } - }, - "node_modules/read-pkg": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz", - "integrity": "sha512-7BGwRHqt4s/uVbuyoeejRn4YmFnYZiFl4AuaeXHlgZf3sONF0SOGlxs2Pw8g6hCKupo08RafIO5YXFNOKTfwsQ==", - "dev": true, - "dependencies": { - "load-json-file": "^1.0.0", - "normalize-package-data": "^2.3.2", - "path-type": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/read-pkg-up": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-1.0.1.tgz", - "integrity": "sha512-WD9MTlNtI55IwYUS27iHh9tK3YoIVhxis8yKhLpTqWtml739uXc9NWTpxoHkfZf3+DkCCsXox94/VWZniuZm6A==", - "dev": true, - "dependencies": { - "find-up": "^1.0.0", - "read-pkg": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "dev": true, - "dependencies": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "node_modules/readable-stream/node_modules/isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "dev": true - }, - "node_modules/readable-stream/node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true - }, - "node_modules/readdirp": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.2.1.tgz", - "integrity": "sha512-1JU/8q+VgFZyxwrJ+SVIOsh+KywWGpds3NTqikiKpDMZWScmAYyKIgqkO+ARvNWJfXeXR1zxz7aHF4u4CyH6vQ==", - "dev": true, - "dependencies": { - "graceful-fs": "^4.1.11", - "micromatch": "^3.1.10", - "readable-stream": "^2.0.2" - }, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/rechoir": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz", - "integrity": "sha512-HFM8rkZ+i3zrV+4LQjwQ0W+ez98pApMGM3HUrN04j3CqzPOzl9nmP15Y8YXNm8QHGv/eacOVEjqhmWpkRV0NAw==", - "dev": true, - "dependencies": { - "resolve": "^1.1.6" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/redent": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", - "integrity": "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==", - "dev": true, - "dependencies": { - "indent-string": "^4.0.0", - "strip-indent": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/regenerator-runtime": { - "version": "0.13.7", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz", - "integrity": "sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew==", - "devOptional": true - }, - "node_modules/regex-not": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz", - "integrity": "sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==", - "dev": true, - "dependencies": { - "extend-shallow": "^3.0.2", - "safe-regex": "^1.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/remove-bom-buffer": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/remove-bom-buffer/-/remove-bom-buffer-3.0.0.tgz", - "integrity": "sha512-8v2rWhaakv18qcvNeli2mZ/TMTL2nEyAKRvzo1WtnZBl15SHyEhrCu2/xKlJyUFKHiHgfXIyuY6g2dObJJycXQ==", - "dev": true, - "dependencies": { - "is-buffer": "^1.1.5", - "is-utf8": "^0.2.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/remove-bom-stream": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/remove-bom-stream/-/remove-bom-stream-1.2.0.tgz", - "integrity": "sha512-wigO8/O08XHb8YPzpDDT+QmRANfW6vLqxfaXm1YXhnFf3AkSLyjfG3GEFg4McZkmgL7KvCj5u2KczkvSP6NfHA==", - "dev": true, - "dependencies": { - "remove-bom-buffer": "^3.0.0", - "safe-buffer": "^5.1.0", - "through2": "^2.0.3" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/remove-trailing-separator": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", - "integrity": "sha512-/hS+Y0u3aOfIETiaiirUFwDBDzmXPvO+jAfKTitUngIPzdKc6Z0LoFjM/CK5PL4C+eKwHohlHAb6H0VFfmmUsw==", - "dev": true - }, - "node_modules/repeat-element": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.3.tgz", - "integrity": "sha512-ahGq0ZnV5m5XtZLMb+vP76kcAM5nkLqk0lpqAuojSKGgQtn4eRi4ZZGm2olo2zKFH+sMsWaqOCW1dqAnOru72g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/repeat-string": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", - "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=", - "dev": true, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/replace-ext": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-1.0.1.tgz", - "integrity": "sha512-yD5BHCe7quCgBph4rMQ+0KkIRKwWCrHDOX1p1Gp6HwjPM5kVoCdKGNhN7ydqqsX6lJEnQDKZ/tFMiEdQ1dvPEw==", - "dev": true, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/replace-homedir": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/replace-homedir/-/replace-homedir-1.0.0.tgz", - "integrity": "sha512-CHPV/GAglbIB1tnQgaiysb8H2yCy8WQ7lcEwQ/eT+kLj0QHV8LnJW0zpqpE7RSkrMSRoa+EBoag86clf7WAgSg==", - "dev": true, - "dependencies": { - "homedir-polyfill": "^1.0.1", - "is-absolute": "^1.0.0", - "remove-trailing-separator": "^1.1.0" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/require-main-filename": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-1.0.1.tgz", - "integrity": "sha512-IqSUtOVP4ksd1C/ej5zeEh/BIP2ajqpn8c5x+q99gvcIG/Qf0cud5raVnE/Dwd0ua9TXYDoDc0RE5hBSdz22Ug==", - "dev": true - }, - "node_modules/resolve": { - "version": "1.22.1", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz", - "integrity": "sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==", - "dev": true, - "dependencies": { - "is-core-module": "^2.9.0", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" - }, - "bin": { - "resolve": "bin/resolve" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/resolve-cwd": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", - "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", - "dev": true, - "dependencies": { - "resolve-from": "^5.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/resolve-dir": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/resolve-dir/-/resolve-dir-1.0.1.tgz", - "integrity": "sha1-eaQGRMNivoLybv/nOcm7U4IEb0M=", - "dev": true, - "dependencies": { - "expand-tilde": "^2.0.0", - "global-modules": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "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, - "engines": { - "node": ">=8" - } - }, - "node_modules/resolve-options": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/resolve-options/-/resolve-options-1.1.0.tgz", - "integrity": "sha512-NYDgziiroVeDC29xq7bp/CacZERYsA9bXYd1ZmcJlF3BcrZv5pTb4NG7SjdyKDnXZ84aC4vo2u6sNKIA1LCu/A==", - "dev": true, - "dependencies": { - "value-or-function": "^3.0.0" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/resolve-pathname": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/resolve-pathname/-/resolve-pathname-3.0.0.tgz", - "integrity": "sha512-C7rARubxI8bXFNB/hqcp/4iUeIXJhJZvFPFPiSPRnhU5UPxzMFIl+2E6yY6c4k9giDJAhtV+enfA+G89N6Csng==", - "dev": true - }, - "node_modules/resolve-url": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", - "integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=", - "deprecated": "https://github.com/lydell/resolve-url#deprecated", - "dev": true - }, - "node_modules/ret": { - "version": "0.1.15", - "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz", - "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==", - "dev": true, - "engines": { - "node": ">=0.12" - } - }, - "node_modules/retry": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", - "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==", - "dev": true, - "engines": { - "node": ">= 4" - } - }, - "node_modules/reusify": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", - "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", - "dev": true, - "engines": { - "iojs": ">=1.0.0", - "node": ">=0.10.0" - } - }, - "node_modules/rgbcolor": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/rgbcolor/-/rgbcolor-1.0.1.tgz", - "integrity": "sha512-9aZLIrhRaD97sgVhtJOW6ckOEh6/GnvQtdVNfdZ6s67+3/XwLS9lBcQYzEEhYVeUowN7pRzMLsyGhK2i/xvWbw==", - "optional": true, - "engines": { - "node": ">= 0.8.15" - } - }, - "node_modules/robust-predicates": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/robust-predicates/-/robust-predicates-3.0.1.tgz", - "integrity": "sha512-ndEIpszUHiG4HtDsQLeIuMvRsDnn8c8rYStabochtUeCvfuvNptb5TUbVD68LRAILPX7p9nqQGh4xJgn3EHS/g==" - }, - "node_modules/run-parallel": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", - "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "dependencies": { - "queue-microtask": "^1.2.2" - } - }, - "node_modules/rw": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/rw/-/rw-1.3.3.tgz", - "integrity": "sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==" - }, - "node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/safe-regex": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz", - "integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=", - "dev": true, - "dependencies": { - "ret": "~0.1.10" - } - }, - "node_modules/safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" - }, - "node_modules/sass-graph": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/sass-graph/-/sass-graph-4.0.1.tgz", - "integrity": "sha512-5YCfmGBmxoIRYHnKK2AKzrAkCoQ8ozO+iumT8K4tXJXRVCPf+7s1/9KxTSW3Rbvf+7Y7b4FR3mWyLnQr3PHocA==", - "dev": true, - "dependencies": { - "glob": "^7.0.0", - "lodash": "^4.17.11", - "scss-tokenizer": "^0.4.3", - "yargs": "^17.2.1" - }, - "bin": { - "sassgraph": "bin/sassgraph" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/sass-graph/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/sass-graph/node_modules/cliui": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", - "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", - "dev": true, - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.1", - "wrap-ansi": "^7.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/sass-graph/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true - }, - "node_modules/sass-graph/node_modules/get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "dev": true, - "engines": { - "node": "6.* || 8.* || >= 10.*" - } - }, - "node_modules/sass-graph/node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/sass-graph/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/sass-graph/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/sass-graph/node_modules/wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/sass-graph/node_modules/y18n": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "dev": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/sass-graph/node_modules/yargs": { - "version": "17.6.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.6.2.tgz", - "integrity": "sha512-1/9UrdHjDZc0eOU0HxOHoS78C69UD3JRMvzlJ7S79S2nTaWRA/whGCTV8o9e/N/1Va9YIV7Q4sOxD8VV4pCWOw==", - "dev": true, - "dependencies": { - "cliui": "^8.0.1", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.3", - "y18n": "^5.0.5", - "yargs-parser": "^21.1.1" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/sass-graph/node_modules/yargs-parser": { - "version": "21.1.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", - "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", - "dev": true, - "engines": { - "node": ">=12" - } - }, - "node_modules/scheduler": { - "version": "0.19.1", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.19.1.tgz", - "integrity": "sha512-n/zwRWRYSUj0/3g/otKDRPMh6qv2SYMWNq85IEa8iZyAv8od9zDYpGSnpBEjNgcMNq6Scbu5KfIPxNF72R/2EA==", - "dev": true, - "dependencies": { - "loose-envify": "^1.1.0", - "object-assign": "^4.1.1" - } - }, - "node_modules/schema-utils": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.1.tgz", - "integrity": "sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw==", - "dev": true, - "dependencies": { - "@types/json-schema": "^7.0.8", - "ajv": "^6.12.5", - "ajv-keywords": "^3.5.2" - }, - "engines": { - "node": ">= 10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - } - }, - "node_modules/scss-tokenizer": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/scss-tokenizer/-/scss-tokenizer-0.4.3.tgz", - "integrity": "sha512-raKLgf1LI5QMQnG+RxHz6oK0sL3x3I4FN2UDLqgLOGO8hodECNnNh5BXn7fAyBxrA8zVzdQizQ6XjNJQ+uBwMw==", - "dev": true, - "dependencies": { - "js-base64": "^2.4.9", - "source-map": "^0.7.3" - } - }, - "node_modules/scss-tokenizer/node_modules/source-map": { - "version": "0.7.4", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", - "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==", - "dev": true, - "engines": { - "node": ">= 8" - } - }, - "node_modules/semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "dev": true, - "bin": { - "semver": "bin/semver" - } - }, - "node_modules/semver-greatest-satisfied-range": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/semver-greatest-satisfied-range/-/semver-greatest-satisfied-range-1.1.0.tgz", - "integrity": "sha512-Ny/iyOzSSa8M5ML46IAx3iXc6tfOsYU2R4AXi2UpHk60Zrgyq6eqPj/xiOfS0rRl/iiQ/rdJkVjw/5cdUyCntQ==", - "dev": true, - "dependencies": { - "sver-compat": "^1.5.0" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/serialize-javascript": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.1.tgz", - "integrity": "sha512-owoXEFjWRllis8/M1Q+Cw5k8ZH40e3zhp/ovX+Xr/vi1qj6QesbyXXViFbpNvWvPNAD62SutwEXavefrLJWj7w==", - "dev": true, - "dependencies": { - "randombytes": "^2.1.0" - } - }, - "node_modules/set-blocking": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", - "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", - "dev": true - }, - "node_modules/set-value": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.1.tgz", - "integrity": "sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw==", - "dev": true, - "dependencies": { - "extend-shallow": "^2.0.1", - "is-extendable": "^0.1.1", - "is-plain-object": "^2.0.3", - "split-string": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/set-value/node_modules/extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "dependencies": { - "is-extendable": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/shallow-clone": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", - "integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==", - "dev": true, - "dependencies": { - "kind-of": "^6.0.2" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/signal-exit": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "dev": true - }, - "node_modules/simple-line-icons": { - "version": "2.5.5", - "resolved": "https://registry.npmjs.org/simple-line-icons/-/simple-line-icons-2.5.5.tgz", - "integrity": "sha512-v52iGG/qFZTSD/70yOfA1lYoN6zmjEfDjzFT6U6jNSCsh/aeVjy+8sYyTXWz1w7tLIkN2XeMmG+cLJp/0zYK4Q==", - "dev": true, - "dependencies": { - "less": "^3.12.2" - } - }, - "node_modules/slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/smart-buffer": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", - "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", - "dev": true, - "engines": { - "node": ">= 6.0.0", - "npm": ">= 3.0.0" - } - }, - "node_modules/snapdragon": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz", - "integrity": "sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==", - "dev": true, - "dependencies": { - "base": "^0.11.1", - "debug": "^2.2.0", - "define-property": "^0.2.5", - "extend-shallow": "^2.0.1", - "map-cache": "^0.2.2", - "source-map": "^0.5.6", - "source-map-resolve": "^0.5.0", - "use": "^3.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon-node": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/snapdragon-node/-/snapdragon-node-2.1.1.tgz", - "integrity": "sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==", - "dev": true, - "dependencies": { - "define-property": "^1.0.0", - "isobject": "^3.0.0", - "snapdragon-util": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon-node/node_modules/define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", - "dev": true, - "dependencies": { - "is-descriptor": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon-node/node_modules/is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "dev": true, - "dependencies": { - "kind-of": "^6.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon-node/node_modules/is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "dev": true, - "dependencies": { - "kind-of": "^6.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon-node/node_modules/is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "dev": true, - "dependencies": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon-util": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/snapdragon-util/-/snapdragon-util-3.0.1.tgz", - "integrity": "sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==", - "dev": true, - "dependencies": { - "kind-of": "^3.2.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon-util/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/snapdragon/node_modules/define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, - "dependencies": { - "is-descriptor": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon/node_modules/extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "dependencies": { - "is-extendable": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon/node_modules/source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/socks": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/socks/-/socks-2.7.1.tgz", - "integrity": "sha512-7maUZy1N7uo6+WVEX6psASxtNlKaNVMlGQKkG/63nEDdLOWNbiUMoLK7X4uYoLhQstau72mLgfEWcXcwsaHbYQ==", - "dev": true, - "dependencies": { - "ip": "^2.0.0", - "smart-buffer": "^4.2.0" - }, - "engines": { - "node": ">= 10.13.0", - "npm": ">= 3.0.0" - } - }, - "node_modules/socks-proxy-agent": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-7.0.0.tgz", - "integrity": "sha512-Fgl0YPZ902wEsAyiQ+idGd1A7rSFx/ayC1CQVMw5P+EQx2V0SgpGtf6OKFhVjPflPUl9YMmEOnmfjCdMUsygww==", - "dev": true, - "dependencies": { - "agent-base": "^6.0.2", - "debug": "^4.3.3", - "socks": "^2.6.2" - }, - "engines": { - "node": ">= 10" - } - }, - "node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/source-map-resolve": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.3.tgz", - "integrity": "sha512-Htz+RnsXWk5+P2slx5Jh3Q66vhQj1Cllm0zvnaY98+NFx+Dv2CF/f5O/t8x+KaNdrdIAsruNzoh/KpialbqAnw==", - "deprecated": "See https://github.com/lydell/source-map-resolve#deprecated", - "dev": true, - "dependencies": { - "atob": "^2.1.2", - "decode-uri-component": "^0.2.0", - "resolve-url": "^0.2.1", - "source-map-url": "^0.4.0", - "urix": "^0.1.0" - } - }, - "node_modules/source-map-support": { - "version": "0.5.21", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", - "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", - "dev": true, - "dependencies": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } - }, - "node_modules/source-map-url": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.0.tgz", - "integrity": "sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM=", - "deprecated": "See https://github.com/lydell/source-map-url#deprecated", - "dev": true - }, - "node_modules/sparkles": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/sparkles/-/sparkles-1.0.1.tgz", - "integrity": "sha512-dSO0DDYUahUt/0/pD/Is3VIm5TGJjludZ0HVymmhYF6eNA53PVLhnUk0znSYbH8IYBuJdCE+1luR22jNLMaQdw==", - "dev": true, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/spdx-correct": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.1.tgz", - "integrity": "sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w==", - "dev": true, - "dependencies": { - "spdx-expression-parse": "^3.0.0", - "spdx-license-ids": "^3.0.0" - } - }, - "node_modules/spdx-exceptions": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz", - "integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==", - "dev": true - }, - "node_modules/spdx-expression-parse": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", - "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", - "dev": true, - "dependencies": { - "spdx-exceptions": "^2.1.0", - "spdx-license-ids": "^3.0.0" - } - }, - "node_modules/spdx-license-ids": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.5.tgz", - "integrity": "sha512-J+FWzZoynJEXGphVIS+XEh3kFSjZX/1i9gFBaWQcB+/tmpe2qUsSBABpcxqxnAxFdiUFEgAX1bjYGQvIZmoz9Q==", - "dev": true - }, - "node_modules/split-string": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz", - "integrity": "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==", - "dev": true, - "dependencies": { - "extend-shallow": "^3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/stack-trace": { - "version": "0.0.10", - "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz", - "integrity": "sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg==", - "dev": true, - "engines": { - "node": "*" - } - }, - "node_modules/stackblur-canvas": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/stackblur-canvas/-/stackblur-canvas-2.5.0.tgz", - "integrity": "sha512-EeNzTVfj+1In7aSLPKDD03F/ly4RxEuF/EX0YcOG0cKoPXs+SLZxDawQbexQDBzwROs4VKLWTOaZQlZkGBFEIQ==", - "optional": true, - "engines": { - "node": ">=0.1.14" - } - }, - "node_modules/static-extend": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz", - "integrity": "sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY=", - "dev": true, - "dependencies": { - "define-property": "^0.2.5", - "object-copy": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/static-extend/node_modules/define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, - "dependencies": { - "is-descriptor": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/stdout-stream": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/stdout-stream/-/stdout-stream-1.4.1.tgz", - "integrity": "sha512-j4emi03KXqJWcIeF8eIXkjMFN1Cmb8gUlDYGeBALLPo5qdyTfA9bOtl8m33lRoC+vFMkP3gl0WsDr6+gzxbbTA==", - "dev": true, - "dependencies": { - "readable-stream": "^2.0.1" - } - }, - "node_modules/stream-exhaust": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/stream-exhaust/-/stream-exhaust-1.0.2.tgz", - "integrity": "sha512-b/qaq/GlBK5xaq1yrK9/zFcyRSTNxmcZwFLGSTG0mXgZl/4Z6GgiyYOXOvY7N3eEvFRAG1bkDRz5EPGSvPYQlw==", - "dev": true - }, - "node_modules/stream-shift": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.1.tgz", - "integrity": "sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ==", - "dev": true - }, - "node_modules/string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, - "dependencies": { - "safe-buffer": "~5.1.0" - } - }, - "node_modules/string_decoder/node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true - }, - "node_modules/string-width": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", - "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", - "dev": true, - "dependencies": { - "code-point-at": "^1.0.0", - "is-fullwidth-code-point": "^1.0.0", - "strip-ansi": "^3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", - "dev": true, - "dependencies": { - "ansi-regex": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/strip-bom": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz", - "integrity": "sha512-kwrX1y7czp1E69n2ajbG65mIo9dqvJ+8aBQXOGVxqwvNbsXdFM6Lq37dLAY3mknUwru8CfcCbfOLL/gMo+fi3g==", - "dev": true, - "dependencies": { - "is-utf8": "^0.2.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/strip-indent": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz", - "integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==", - "dev": true, - "dependencies": { - "min-indent": "^1.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/supports-color/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/supports-hyperlinks": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-2.3.0.tgz", - "integrity": "sha512-RpsAZlpWcDwOPQA22aCH4J0t7L8JmAvsCxfOSEwm7cQs3LshN36QaTkwd70DnBOXDWGssw2eUoc8CaRWT0XunA==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0", - "supports-color": "^7.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/supports-hyperlinks/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/supports-preserve-symlinks-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", - "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", - "dev": true, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/sver-compat": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/sver-compat/-/sver-compat-1.5.0.tgz", - "integrity": "sha512-aFTHfmjwizMNlNE6dsGmoAM4lHjL0CyiobWaFiXWSlD7cIxshW422Nb8KbXCmR6z+0ZEPY+daXJrDyh/vuwTyg==", - "dev": true, - "dependencies": { - "es6-iterator": "^2.0.1", - "es6-symbol": "^3.1.1" - } - }, - "node_modules/svg-pathdata": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/svg-pathdata/-/svg-pathdata-6.0.3.tgz", - "integrity": "sha512-qsjeeq5YjBZ5eMdFuUa4ZosMLxgr5RZ+F+Y1OrDhuOCEInRMA3x74XdBtggJcj9kOeInz0WE+LgCPDkZFlBYJw==", - "optional": true, - "engines": { - "node": ">=12.0.0" - } - }, - "node_modules/tapable": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", - "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/tar": { - "version": "6.1.13", - "resolved": "https://registry.npmjs.org/tar/-/tar-6.1.13.tgz", - "integrity": "sha512-jdIBIN6LTIe2jqzay/2vtYLlBHa3JF42ot3h1dW8Q0PaAG4v8rm0cvpVePtau5C6OKXGGcgO9q2AMNSWxiLqKw==", - "dev": true, - "dependencies": { - "chownr": "^2.0.0", - "fs-minipass": "^2.0.0", - "minipass": "^4.0.0", - "minizlib": "^2.1.1", - "mkdirp": "^1.0.3", - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/tar/node_modules/chownr": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", - "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", - "dev": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/tar/node_modules/minipass": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-4.0.3.tgz", - "integrity": "sha512-OW2r4sQ0sI+z5ckEt5c1Tri4xTgZwYDxpE54eqWlQloQRoWtXjqt9udJ5Z4dSv7wK+nfFI7FRXyCpBSft+gpFw==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/tar/node_modules/mkdirp": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", - "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", - "dev": true, - "bin": { - "mkdirp": "bin/cmd.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/terser": { - "version": "5.16.6", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.16.6.tgz", - "integrity": "sha512-IBZ+ZQIA9sMaXmRZCUMDjNH0D5AQQfdn4WUjHL0+1lF4TP1IHRJbrhb6fNaXWikrYQTSkb7SLxkeXAiy1p7mbg==", - "dev": true, - "dependencies": { - "@jridgewell/source-map": "^0.3.2", - "acorn": "^8.5.0", - "commander": "^2.20.0", - "source-map-support": "~0.5.20" - }, - "bin": { - "terser": "bin/terser" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/terser-webpack-plugin": { - "version": "5.3.7", - "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.7.tgz", - "integrity": "sha512-AfKwIktyP7Cu50xNjXF/6Qb5lBNzYaWpU6YfoX3uZicTx0zTy0stDDCsvjDapKsSDvOeWo5MEq4TmdBy2cNoHw==", - "dev": true, - "dependencies": { - "@jridgewell/trace-mapping": "^0.3.17", - "jest-worker": "^27.4.5", - "schema-utils": "^3.1.1", - "serialize-javascript": "^6.0.1", - "terser": "^5.16.5" - }, - "engines": { - "node": ">= 10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "webpack": "^5.1.0" - }, - "peerDependenciesMeta": { - "@swc/core": { - "optional": true - }, - "esbuild": { - "optional": true - }, - "uglify-js": { - "optional": true - } - } - }, - "node_modules/text-segmentation": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/text-segmentation/-/text-segmentation-1.0.3.tgz", - "integrity": "sha512-iOiPUo/BGnZ6+54OsWxZidGCsdU8YbE4PSpdPinp7DeMtUJNJBoJ/ouUSTJjHkh1KntHaltHl/gDs2FC4i5+Nw==", - "devOptional": true, - "dependencies": { - "utrie": "^1.0.2" - } - }, - "node_modules/through2": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", - "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", - "dev": true, - "dependencies": { - "readable-stream": "~2.3.6", - "xtend": "~4.0.1" - } - }, - "node_modules/through2-filter": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/through2-filter/-/through2-filter-3.0.0.tgz", - "integrity": "sha512-jaRjI2WxN3W1V8/FMZ9HKIBXixtiqs3SQSX4/YGIiP3gL6djW48VoZq9tDqeCWs3MT8YY5wb/zli8VW8snY1CA==", - "dev": true, - "dependencies": { - "through2": "~2.0.0", - "xtend": "~4.0.0" - } - }, - "node_modules/time-stamp": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/time-stamp/-/time-stamp-1.1.0.tgz", - "integrity": "sha512-gLCeArryy2yNTRzTGKbZbloctj64jkZ57hj5zdraXue6aFgd6PmvVtEyiUU+hvU0v7q08oVv8r8ev0tRo6bvgw==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/tiny-invariant": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.1.0.tgz", - "integrity": "sha512-ytxQvrb1cPc9WBEI/HSeYYoGD0kWnGEOR8RY6KomWLBVhqz0RgTwVO9dLrGz7dC+nN9llyI7OKAgRq8Vq4ZBSw==", - "dev": true - }, - "node_modules/tiny-warning": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz", - "integrity": "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==", - "dev": true - }, - "node_modules/to-absolute-glob": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/to-absolute-glob/-/to-absolute-glob-2.0.2.tgz", - "integrity": "sha512-rtwLUQEwT8ZeKQbyFJyomBRYXyE16U5VKuy0ftxLMK/PZb2fkOsg5r9kHdauuVDbsNdIBoC/HCthpidamQFXYA==", - "dev": true, - "dependencies": { - "is-absolute": "^1.0.0", - "is-negated-glob": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/to-object-path": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz", - "integrity": "sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68=", - "dev": true, - "dependencies": { - "kind-of": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/to-object-path/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/to-regex": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/to-regex/-/to-regex-3.0.2.tgz", - "integrity": "sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==", - "dev": true, - "dependencies": { - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "regex-not": "^1.0.2", - "safe-regex": "^1.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/to-regex-range": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", - "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", - "dev": true, - "dependencies": { - "is-number": "^3.0.0", - "repeat-string": "^1.6.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/to-through": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/to-through/-/to-through-2.0.0.tgz", - "integrity": "sha512-+QIz37Ly7acM4EMdw2PRN389OneM5+d844tirkGp4dPKzI5OE72V9OsbFp+CIYJDahZ41ZV05hNtcPAQUAm9/Q==", - "dev": true, - "dependencies": { - "through2": "^2.0.3" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/trim-newlines": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-3.0.1.tgz", - "integrity": "sha512-c1PTsA3tYrIsLGkJkzHF+w9F2EyxfXGo4UyJc4pFL++FMjnq0HJS69T3M7d//gKrFKwy429bouPescbjecU+Zw==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/true-case-path": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/true-case-path/-/true-case-path-2.2.1.tgz", - "integrity": "sha512-0z3j8R7MCjy10kc/g+qg7Ln3alJTodw9aDuVWZa3uiWqfuBMKeAeP2ocWcxoyM3D73yz3Jt/Pu4qPr4wHSdB/Q==", - "dev": true - }, - "node_modules/ts-loader": { - "version": "9.4.2", - "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-9.4.2.tgz", - "integrity": "sha512-OmlC4WVmFv5I0PpaxYb+qGeGOdm5giHU7HwDDUjw59emP2UYMHy9fFSDcYgSNoH8sXcj4hGCSEhlDZ9ULeDraA==", - "dev": true, - "dependencies": { - "chalk": "^4.1.0", - "enhanced-resolve": "^5.0.0", - "micromatch": "^4.0.0", - "semver": "^7.3.4" - }, - "engines": { - "node": ">=12.0.0" - }, - "peerDependencies": { - "typescript": "*", - "webpack": "^5.0.0" - } - }, - "node_modules/ts-loader/node_modules/braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "dev": true, - "dependencies": { - "fill-range": "^7.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/ts-loader/node_modules/fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "dev": true, - "dependencies": { - "to-regex-range": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/ts-loader/node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true, - "engines": { - "node": ">=0.12.0" - } - }, - "node_modules/ts-loader/node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/ts-loader/node_modules/micromatch": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", - "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", - "dev": true, - "dependencies": { - "braces": "^3.0.2", - "picomatch": "^2.3.1" - }, - "engines": { - "node": ">=8.6" - } - }, - "node_modules/ts-loader/node_modules/semver": { - "version": "7.3.8", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", - "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", - "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/ts-loader/node_modules/to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "dependencies": { - "is-number": "^7.0.0" - }, - "engines": { - "node": ">=8.0" - } - }, - "node_modules/tsd": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/tsd/-/tsd-0.28.0.tgz", - "integrity": "sha512-LWYyNoxmpp1PVfmSo84Uup304vclm4NHfS/YXUzNVRgvWrpkoSEmZuM0O5j58E+Z7fjjGRo6HlefGziKbD0zgw==", - "dev": true, - "dependencies": { - "@tsd/typescript": "~5.0.2", - "eslint-formatter-pretty": "^4.1.0", - "globby": "^11.0.1", - "jest-diff": "^29.0.3", - "meow": "^9.0.0", - "path-exists": "^4.0.0", - "read-pkg-up": "^7.0.0" - }, - "bin": { - "tsd": "dist/cli.js" - }, - "engines": { - "node": ">=14.16" - } - }, - "node_modules/tsd/node_modules/find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dev": true, - "dependencies": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/tsd/node_modules/parse-json": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", - "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.0.0", - "error-ex": "^1.3.1", - "json-parse-even-better-errors": "^2.3.0", - "lines-and-columns": "^1.1.6" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/tsd/node_modules/path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/tsd/node_modules/read-pkg": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz", - "integrity": "sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==", - "dev": true, - "dependencies": { - "@types/normalize-package-data": "^2.4.0", - "normalize-package-data": "^2.5.0", - "parse-json": "^5.0.0", - "type-fest": "^0.6.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/tsd/node_modules/read-pkg-up": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-7.0.1.tgz", - "integrity": "sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==", - "dev": true, - "dependencies": { - "find-up": "^4.1.0", - "read-pkg": "^5.2.0", - "type-fest": "^0.8.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/tsd/node_modules/read-pkg-up/node_modules/type-fest": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", - "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/tsd/node_modules/read-pkg/node_modules/type-fest": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.6.0.tgz", - "integrity": "sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/tslib": { - "version": "1.13.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.13.0.tgz", - "integrity": "sha512-i/6DQjL8Xf3be4K/E6Wgpekn5Qasl1usyw++dAA35Ue5orEn65VIxOA+YvNNl9HV3qv70T7CNwjODHZrLwvd1Q==", - "dev": true - }, - "node_modules/type": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/type/-/type-1.2.0.tgz", - "integrity": "sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg==", - "dev": true - }, - "node_modules/type-fest": { - "version": "0.21.3", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", - "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/typedarray": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", - "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==", - "dev": true - }, - "node_modules/typescript": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.0.2.tgz", - "integrity": "sha512-wVORMBGO/FAs/++blGNeAVdbNKtIh1rbBL2EyQ1+J9lClJ93KiiKe8PmFIVdXhHcyv44SL9oglmfeSsndo0jRw==", - "dev": true, - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=12.20" - } - }, - "node_modules/unc-path-regex": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/unc-path-regex/-/unc-path-regex-0.1.2.tgz", - "integrity": "sha512-eXL4nmJT7oCpkZsHZUOJo8hcX3GbsiDOa0Qu9F646fi8dT3XuSVopVqAcEiVzSKKH7UoDti23wNX3qGFxcW5Qg==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/undertaker": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/undertaker/-/undertaker-1.3.0.tgz", - "integrity": "sha512-/RXwi5m/Mu3H6IHQGww3GNt1PNXlbeCuclF2QYR14L/2CHPz3DFZkvB5hZ0N/QUkiXWCACML2jXViIQEQc2MLg==", - "dev": true, - "dependencies": { - "arr-flatten": "^1.0.1", - "arr-map": "^2.0.0", - "bach": "^1.0.0", - "collection-map": "^1.0.0", - "es6-weak-map": "^2.0.1", - "fast-levenshtein": "^1.0.0", - "last-run": "^1.1.0", - "object.defaults": "^1.0.0", - "object.reduce": "^1.0.0", - "undertaker-registry": "^1.0.0" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/undertaker-registry": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/undertaker-registry/-/undertaker-registry-1.0.1.tgz", - "integrity": "sha512-UR1khWeAjugW3548EfQmL9Z7pGMlBgXteQpr1IZeZBtnkCJQJIJ1Scj0mb9wQaPvUZ9Q17XqW6TIaPchJkyfqw==", - "dev": true, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/union-value": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.1.tgz", - "integrity": "sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg==", - "dev": true, - "dependencies": { - "arr-union": "^3.1.0", - "get-value": "^2.0.6", - "is-extendable": "^0.1.1", - "set-value": "^2.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/unique-filename": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-1.1.1.tgz", - "integrity": "sha512-Vmp0jIp2ln35UTXuryvjzkjGdRyf9b2lTXuSYUiPmzRcl3FDtYqAwOnTJkAngD9SWhnoJzDbTKwaOrZ+STtxNQ==", - "dev": true, - "dependencies": { - "unique-slug": "^2.0.0" - } - }, - "node_modules/unique-slug": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-2.0.2.tgz", - "integrity": "sha512-zoWr9ObaxALD3DOPfjPSqxt4fnZiWblxHIgeWqW8x7UqDzEtHEQLzji2cuJYQFCU6KmoJikOYAZlrTHHebjx2w==", - "dev": true, - "dependencies": { - "imurmurhash": "^0.1.4" - } - }, - "node_modules/unique-stream": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/unique-stream/-/unique-stream-2.3.1.tgz", - "integrity": "sha512-2nY4TnBE70yoxHkDli7DMazpWiP7xMdCYqU2nBRO0UB+ZpEkGsSija7MvmvnZFUeC+mrgiUfcHSr3LmRFIg4+A==", - "dev": true, - "dependencies": { - "json-stable-stringify-without-jsonify": "^1.0.1", - "through2-filter": "^3.0.0" - } - }, - "node_modules/unset-value": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz", - "integrity": "sha1-g3aHP30jNRef+x5vw6jtDfyKtVk=", - "dev": true, - "dependencies": { - "has-value": "^0.3.1", - "isobject": "^3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/unset-value/node_modules/has-value": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/has-value/-/has-value-0.3.1.tgz", - "integrity": "sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8=", - "dev": true, - "dependencies": { - "get-value": "^2.0.3", - "has-values": "^0.1.4", - "isobject": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/unset-value/node_modules/has-value/node_modules/isobject": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", - "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", - "dev": true, - "dependencies": { - "isarray": "1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/unset-value/node_modules/has-values": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/has-values/-/has-values-0.1.4.tgz", - "integrity": "sha1-bWHeldkd/Km5oCCJrThL/49it3E=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/unset-value/node_modules/isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "dev": true - }, - "node_modules/upath": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/upath/-/upath-1.2.0.tgz", - "integrity": "sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg==", - "dev": true, - "engines": { - "node": ">=4", - "yarn": "*" - } - }, - "node_modules/update-browserslist-db": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.10.tgz", - "integrity": "sha512-OztqDenkfFkbSG+tRxBeAnCVPckDBcvibKd35yDONx6OU8N7sqgwc7rCbkJ/WcYtVRZ4ba68d6byhC21GFh7sQ==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - } - ], - "dependencies": { - "escalade": "^3.1.1", - "picocolors": "^1.0.0" - }, - "bin": { - "browserslist-lint": "cli.js" - }, - "peerDependencies": { - "browserslist": ">= 4.21.0" - } - }, - "node_modules/uri-js": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", - "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==", - "dev": true, - "dependencies": { - "punycode": "^2.1.0" - } - }, - "node_modules/urix": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz", - "integrity": "sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=", - "deprecated": "Please see https://github.com/lydell/urix#deprecated", - "dev": true - }, - "node_modules/use": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz", - "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", - "dev": true - }, - "node_modules/utrie": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/utrie/-/utrie-1.0.2.tgz", - "integrity": "sha512-1MLa5ouZiOmQzUbjbu9VmjLzn1QLXBhwpUa7kdLUQK+KQ5KA9I1vk5U4YHe/X2Ch7PYnJfWuWT+VbuxbGwljhw==", - "devOptional": true, - "dependencies": { - "base64-arraybuffer": "^1.0.2" - } - }, - "node_modules/utrie/node_modules/base64-arraybuffer": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz", - "integrity": "sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ==", - "devOptional": true, - "engines": { - "node": ">= 0.6.0" - } - }, - "node_modules/v8flags": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/v8flags/-/v8flags-3.2.0.tgz", - "integrity": "sha512-mH8etigqMfiGWdeXpaaqGfs6BndypxusHHcv2qSHyZkGEznCd/qAXCWWRzeowtL54147cktFOC4P5y+kl8d8Jg==", - "dev": true, - "dependencies": { - "homedir-polyfill": "^1.0.1" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/validate-npm-package-license": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", - "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", - "dev": true, - "dependencies": { - "spdx-correct": "^3.0.0", - "spdx-expression-parse": "^3.0.0" - } - }, - "node_modules/value-equal": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/value-equal/-/value-equal-1.0.1.tgz", - "integrity": "sha512-NOJ6JZCAWr0zlxZt+xqCHNTEKOsrks2HQd4MqhP1qy4z1SkbEP467eNx6TgDKXMvUOb+OENfJCZwM+16n7fRfw==", - "dev": true - }, - "node_modules/value-or-function": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/value-or-function/-/value-or-function-3.0.0.tgz", - "integrity": "sha512-jdBB2FrWvQC/pnPtIqcLsMaQgjhdb6B7tk1MMyTKapox+tQZbdRP4uLxu/JY0t7fbfDCUMnuelzEYv5GsxHhdg==", - "dev": true, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/vinyl": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-2.2.1.tgz", - "integrity": "sha512-LII3bXRFBZLlezoG5FfZVcXflZgWP/4dCwKtxd5ky9+LOtM4CS3bIRQsmR1KMnMW07jpE8fqR2lcxPZ+8sJIcw==", - "dev": true, - "dependencies": { - "clone": "^2.1.1", - "clone-buffer": "^1.0.0", - "clone-stats": "^1.0.0", - "cloneable-readable": "^1.0.0", - "remove-trailing-separator": "^1.0.1", - "replace-ext": "^1.0.0" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/vinyl-fs": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/vinyl-fs/-/vinyl-fs-3.0.3.tgz", - "integrity": "sha512-vIu34EkyNyJxmP0jscNzWBSygh7VWhqun6RmqVfXePrOwi9lhvRs//dOaGOTRUQr4tx7/zd26Tk5WeSVZitgng==", - "dev": true, - "dependencies": { - "fs-mkdirp-stream": "^1.0.0", - "glob-stream": "^6.1.0", - "graceful-fs": "^4.0.0", - "is-valid-glob": "^1.0.0", - "lazystream": "^1.0.0", - "lead": "^1.0.0", - "object.assign": "^4.0.4", - "pumpify": "^1.3.5", - "readable-stream": "^2.3.3", - "remove-bom-buffer": "^3.0.0", - "remove-bom-stream": "^1.2.0", - "resolve-options": "^1.1.0", - "through2": "^2.0.0", - "to-through": "^2.0.0", - "value-or-function": "^3.0.0", - "vinyl": "^2.0.0", - "vinyl-sourcemap": "^1.1.0" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/vinyl-sourcemap": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/vinyl-sourcemap/-/vinyl-sourcemap-1.1.0.tgz", - "integrity": "sha512-NiibMgt6VJGJmyw7vtzhctDcfKch4e4n9TBeoWlirb7FMg9/1Ov9k+A5ZRAtywBpRPiyECvQRQllYM8dECegVA==", - "dev": true, - "dependencies": { - "append-buffer": "^1.0.2", - "convert-source-map": "^1.5.0", - "graceful-fs": "^4.1.6", - "normalize-path": "^2.1.1", - "now-and-later": "^2.0.0", - "remove-bom-buffer": "^3.0.0", - "vinyl": "^2.0.0" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/vinyl-sourcemap/node_modules/normalize-path": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", - "integrity": "sha512-3pKJwH184Xo/lnH6oyP1q2pMd7HcypqqmRs91/6/i2CGtWwIKGCkOOMTm/zXbgTEWHw1uNpNi/igc3ePOYHb6w==", - "dev": true, - "dependencies": { - "remove-trailing-separator": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/warning": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz", - "integrity": "sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==", - "dev": true, - "dependencies": { - "loose-envify": "^1.0.0" - } - }, - "node_modules/watchpack": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz", - "integrity": "sha512-Lcvm7MGST/4fup+ifyKi2hjyIAwcdI4HRgtvTpIUxBRhB+RFtUh8XtDOxUfctVCnhVi+QQj49i91OyvzkJl6cg==", - "dev": true, - "dependencies": { - "glob-to-regexp": "^0.4.1", - "graceful-fs": "^4.1.2" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/webpack": { - "version": "5.76.2", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.76.2.tgz", - "integrity": "sha512-Th05ggRm23rVzEOlX8y67NkYCHa9nTNcwHPBhdg+lKG+mtiW7XgggjAeeLnADAe7mLjJ6LUNfgHAuRRh+Z6J7w==", - "dev": true, - "dependencies": { - "@types/eslint-scope": "^3.7.3", - "@types/estree": "^0.0.51", - "@webassemblyjs/ast": "1.11.1", - "@webassemblyjs/wasm-edit": "1.11.1", - "@webassemblyjs/wasm-parser": "1.11.1", - "acorn": "^8.7.1", - "acorn-import-assertions": "^1.7.6", - "browserslist": "^4.14.5", - "chrome-trace-event": "^1.0.2", - "enhanced-resolve": "^5.10.0", - "es-module-lexer": "^0.9.0", - "eslint-scope": "5.1.1", - "events": "^3.2.0", - "glob-to-regexp": "^0.4.1", - "graceful-fs": "^4.2.9", - "json-parse-even-better-errors": "^2.3.1", - "loader-runner": "^4.2.0", - "mime-types": "^2.1.27", - "neo-async": "^2.6.2", - "schema-utils": "^3.1.0", - "tapable": "^2.1.1", - "terser-webpack-plugin": "^5.1.3", - "watchpack": "^2.4.0", - "webpack-sources": "^3.2.3" - }, - "bin": { - "webpack": "bin/webpack.js" - }, - "engines": { - "node": ">=10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependenciesMeta": { - "webpack-cli": { - "optional": true - } - } - }, - "node_modules/webpack-cli": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-5.0.1.tgz", - "integrity": "sha512-S3KVAyfwUqr0Mo/ur3NzIp6jnerNpo7GUO6so51mxLi1spqsA17YcMXy0WOIJtBSnj748lthxC6XLbNKh/ZC+A==", - "dev": true, - "dependencies": { - "@discoveryjs/json-ext": "^0.5.0", - "@webpack-cli/configtest": "^2.0.1", - "@webpack-cli/info": "^2.0.1", - "@webpack-cli/serve": "^2.0.1", - "colorette": "^2.0.14", - "commander": "^9.4.1", - "cross-spawn": "^7.0.3", - "envinfo": "^7.7.3", - "fastest-levenshtein": "^1.0.12", - "import-local": "^3.0.2", - "interpret": "^3.1.1", - "rechoir": "^0.8.0", - "webpack-merge": "^5.7.3" - }, - "bin": { - "webpack-cli": "bin/cli.js" - }, - "engines": { - "node": ">=14.15.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "webpack": "5.x.x" - }, - "peerDependenciesMeta": { - "@webpack-cli/generators": { - "optional": true - }, - "webpack-bundle-analyzer": { - "optional": true - }, - "webpack-dev-server": { - "optional": true - } - } - }, - "node_modules/webpack-cli/node_modules/commander": { - "version": "9.5.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-9.5.0.tgz", - "integrity": "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==", - "dev": true, - "engines": { - "node": "^12.20.0 || >=14" - } - }, - "node_modules/webpack-cli/node_modules/interpret": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/interpret/-/interpret-3.1.1.tgz", - "integrity": "sha512-6xwYfHbajpoF0xLW+iwLkhwgvLoZDfjYfoFNu8ftMoXINzwuymNLd9u/KmwtdT2GbR+/Cz66otEGEVVUHX9QLQ==", - "dev": true, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/webpack-cli/node_modules/rechoir": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.8.0.tgz", - "integrity": "sha512-/vxpCXddiX8NGfGO/mTafwjq4aFa/71pvamip0++IQk3zG8cbCj0fifNPrjjF1XMXUne91jL9OoxmdykoEtifQ==", - "dev": true, - "dependencies": { - "resolve": "^1.20.0" - }, - "engines": { - "node": ">= 10.13.0" - } - }, - "node_modules/webpack-merge": { - "version": "5.8.0", - "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-5.8.0.tgz", - "integrity": "sha512-/SaI7xY0831XwP6kzuwhKWVKDP9t1QY1h65lAFLbZqMPIuYcD9QAW4u9STIbU9kaJbPBB/geU/gLr1wDjOhQ+Q==", - "dev": true, - "dependencies": { - "clone-deep": "^4.0.1", - "wildcard": "^2.0.0" - }, - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/webpack-sources": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.3.tgz", - "integrity": "sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==", - "dev": true, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/webpack/node_modules/@types/estree": { - "version": "0.0.51", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.51.tgz", - "integrity": "sha512-CuPgU6f3eT/XgKKPqKd/gLZV1Xmvf1a2R5POBOGQa6uv82xpls89HU5zKeVoyR8XzHd1RGNOlQlvUe3CFkjWNQ==", - "dev": true - }, - "node_modules/which": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", - "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", - "dev": true, - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "which": "bin/which" - } - }, - "node_modules/which-module": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/which-module/-/which-module-1.0.0.tgz", - "integrity": "sha512-F6+WgncZi/mJDrammbTuHe1q0R5hOXv/mBaiNA2TCNT/LTHusX0V+CJnj9XT8ki5ln2UZyyddDgHfCzyrOH7MQ==", - "dev": true - }, - "node_modules/wide-align": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz", - "integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==", - "dev": true, - "dependencies": { - "string-width": "^1.0.2 || 2 || 3 || 4" - } - }, - "node_modules/wildcard": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/wildcard/-/wildcard-2.0.0.tgz", - "integrity": "sha512-JcKqAHLPxcdb9KM49dufGXn2x3ssnfjbcaQdLlfZsL9rH9wgDQjUtDxbo8NE0F6SFvydeu1VhZe7hZuHsB2/pw==", - "dev": true - }, - "node_modules/wrap-ansi": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", - "integrity": "sha512-vAaEaDM946gbNpH5pLVNR+vX2ht6n0Bt3GXwVB1AuAqZosOvHNF3P7wDnh8KLkSqgUh0uh77le7Owgoz+Z9XBw==", - "dev": true, - "dependencies": { - "string-width": "^1.0.1", - "strip-ansi": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", - "dev": true - }, - "node_modules/xtend": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", - "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", - "dev": true, - "engines": { - "node": ">=0.4" - } - }, - "node_modules/y18n": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-3.2.2.tgz", - "integrity": "sha512-uGZHXkHnhF0XeeAPgnKfPv1bgKAYyVvmNL1xlKsPYZPaIHxGti2hHqvOCQv71XMsLxu1QjergkqogUnms5D3YQ==", - "dev": true - }, - "node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - }, - "node_modules/yargs": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-7.1.2.tgz", - "integrity": "sha512-ZEjj/dQYQy0Zx0lgLMLR8QuaqTihnxirir7EwUHp1Axq4e3+k8jXU5K0VLbNvedv1f4EWtBonDIZm0NUr+jCcA==", - "dev": true, - "dependencies": { - "camelcase": "^3.0.0", - "cliui": "^3.2.0", - "decamelize": "^1.1.1", - "get-caller-file": "^1.0.1", - "os-locale": "^1.4.0", - "read-pkg-up": "^1.0.1", - "require-directory": "^2.1.1", - "require-main-filename": "^1.0.1", - "set-blocking": "^2.0.0", - "string-width": "^1.0.2", - "which-module": "^1.0.0", - "y18n": "^3.2.1", - "yargs-parser": "^5.0.1" - } - }, - "node_modules/yargs-parser": { - "version": "20.2.9", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", - "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", - "dev": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/yargs/node_modules/yargs-parser": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-5.0.1.tgz", - "integrity": "sha512-wpav5XYiddjXxirPoCTUPbqM0PXvJ9hiBMvuJgInvo4/lAOTZzUprArw17q2O1P2+GHhbBr18/iQwjL5Z9BqfA==", - "dev": true, - "dependencies": { - "camelcase": "^3.0.0", - "object.assign": "^4.1.0" - } - } - } -} diff --git a/src/AccountGoWeb/package.json b/src/AccountGoWeb/package.json deleted file mode 100644 index 1ba677bd3..000000000 --- a/src/AccountGoWeb/package.json +++ /dev/null @@ -1,50 +0,0 @@ -{ - "version": "0.0.1", - "name": "accountgoweb", - "private": true, - "scripts": { - "webpack": "webpack", - "gulp": "gulp", - "tsc": "tsc", - "css": "npm run css-compile-minified", - "css-compile-minified": "node-sass --output-style compressed --source-map true --source-map-contents true --precision 6 Scss -o wwwroot/css" - }, - "devDependencies": { - "@coreui/coreui": "^2.1.16", - "@coreui/icons": "^0.3.0", - "ag-grid-community": "^19.1.4", - "ajv": "^6.12.6", - "axios": "^1.3.4", - "bootstrap": "^5.2.3", - "gulp": "^4.0.2", - "html2canvas": "^1.4.1", - "ionicons": "^4.6.3", - "jquery": "^3.6.4", - "mobx": "^5.15.7", - "mobx-react": "^5.4.4", - "mobx-react-devtools": "^6.1.1", - "node-sass": "^8.0.0", - "perfect-scrollbar": "^1.5.5", - "popper.js": "^1.16.1", - "react": "^16.14.0", - "react-dom": "^16.14.0", - "react-router": "^4.3.1", - "simple-line-icons": "^2.5.5", - "ts-loader": "^9.4.2", - "tsd": "^0.28.0", - "typescript": "^5.0.2", - "webpack": "^5.76.2", - "webpack-cli": "^5.0.1" - }, - "dependencies": { - "accounting": "0.4.1", - "ag-grid": "18.1.2", - "d3": "7.8.2", - "font-awesome": "^4.7.0", - "jspdf": "2.5.1", - "knockout": "^3.5.0", - "knockout-mapping": "2.6.0", - "pace-progress": "^1.0.2", - "react-dom-factories": "1.0.2" - } -} diff --git a/src/AccountGoWeb/tsconfig.json b/src/AccountGoWeb/tsconfig.json deleted file mode 100644 index fd164f5ac..000000000 --- a/src/AccountGoWeb/tsconfig.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "compilerOptions": { - "target": "es2017", - "module": "commonjs", - "jsx": "react", - "declaration": false, - "noImplicitAny": false, - "removeComments": true, - "sourceMap": true, - "outDir": "wwwroot/tsxbuild", - "rootDir": "Scripts", - "experimentalDecorators": true - }, - "compileOnSave": true, - "exclude": [ - "node_modules", - "wwwroot" - ] -} diff --git a/src/AccountGoWeb/tsd.json b/src/AccountGoWeb/tsd.json deleted file mode 100644 index 725f3e956..000000000 --- a/src/AccountGoWeb/tsd.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "version": "v4", - "repo": "borisyankov/DefinitelyTyped", - "ref": "master", - "path": "typings", - "bundle": "typings/tsd.d.ts", - "installed": { - "react/react.d.ts": { - "commit": "edb64e4a35896510ce02b93c0bca5ec3878db738" - }, - "react/react-dom.d.ts": { - "commit": "edb64e4a35896510ce02b93c0bca5ec3878db738" - }, - "axios/axios.d.ts": { - "commit": "1315d46f8066eec8c197914da88e98629ebc2254" - }, - "d3/d3.d.ts": { - "commit": "77b1b1709315b03b9b1b67c589d599bebeeef2ee" - }, - "accounting/accounting.d.ts": { - "commit": "90995d48413f4c53d9c43fce2b47eec44c7276da" - } - } -} diff --git a/src/AccountGoWeb/typings/accounting/accounting.d.ts b/src/AccountGoWeb/typings/accounting/accounting.d.ts deleted file mode 100644 index b8187e41c..000000000 --- a/src/AccountGoWeb/typings/accounting/accounting.d.ts +++ /dev/null @@ -1,79 +0,0 @@ -// Type definitions for accounting.js 0.3.2 -// Project: http://josscrowcroft.github.io/accounting.js/ -// Definitions by: Sergey Gerasimov -// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped - -interface IAccountingCurrencyFormat { - pos: string; // for positive values, eg. "$ 1.00" - neg?: string; // for negative values, eg. "$ (1.00)" - zero?: string; // for zero values, eg. "$ --" -} - -interface IAccountingCurrencySettings { - symbol?: string; // default currency symbol is '$' - format?: TFormat; // controls output: %s = symbol, %v = value/number - decimal?: string; // decimal point separator - thousand?: string; // thousands separator - precision?: number // decimal places -} - -interface IAccountingNumberSettings { - precision?: number; // default precision on numbers is 0 - thousand?: string; - decimal?: string; -} - -interface IAccountingSettings { - currency: IAccountingCurrencySettings; // IAccountingCurrencySettings or IAccountingCurrencySettings - number: IAccountingNumberSettings; -} - -interface IAccountingStatic { - // format any number into currency - formatMoney(number: number, symbol?: string, precision?: number, thousand?: string, decimal?: string, format?: string): string; - formatMoney(number: number, options: IAccountingCurrencySettings): string; - formatMoney(number: number, options: IAccountingCurrencySettings): string; - - formatMoney(numbers: number[], symbol?: string, precision?: number, thousand?: string, decimal?: string, format?: string): string[]; - formatMoney(numbers: number[], options: IAccountingCurrencySettings): string[]; - formatMoney(numbers: number[], options: IAccountingCurrencySettings): string[]; - - // generic case (any array of numbers) - formatMoney(numbers: any[], symbol?: string, precision?: number, thousand?: string, decimal?: string, format?: string): any[]; - formatMoney(numbers: any[], options: IAccountingCurrencySettings): any[]; - formatMoney(numbers: any[], options: IAccountingCurrencySettings): any[]; - - // format a list of values for column-display - formatColumn(numbers: number[], symbol?: string, precision?: number, thousand?: string, decimal?: string, format?: string): string[]; - formatColumn(numbers: number[], options: IAccountingCurrencySettings): string[]; - formatColumn(numbers: number[], options: IAccountingCurrencySettings): string[]; - - formatColumn(numbers: number[][], symbol?: string, precision?: number, thousand?: string, decimal?: string, format?: string): string[][]; - formatColumn(numbers: number[][], options: IAccountingCurrencySettings): string[][]; - formatColumn(numbers: number[][], options: IAccountingCurrencySettings): string[][]; - - // format a number with custom precision and localisation - formatNumber(number: number, precision?: number, thousand?: string, decimal?: string): string; - formatNumber(number: number, options: IAccountingNumberSettings): string; - - formatNumber(number: number[], precision?: number, thousand?: string, decimal?: string): string[]; - formatNumber(number: number[], options: IAccountingNumberSettings): string[]; - - formatNumber(number: any[], precision?: number, thousand?: string, decimal?: string): any[]; - formatNumber(number: any[], options: IAccountingNumberSettings): any[]; - - // better rounding for floating point numbers - toFixed(number: number, precision?: number): string; - - // get a value from any formatted number/currency string - unformat(string: string, decimal?: string): number; - - // settings object that controls default parameters for library methods - settings: IAccountingSettings; -} - -declare var accounting: IAccountingStatic; - -declare module "accounting" { - export = accounting; -} diff --git a/src/AccountGoWeb/typings/axios/axios.d.ts b/src/AccountGoWeb/typings/axios/axios.d.ts deleted file mode 100644 index 44083f796..000000000 --- a/src/AccountGoWeb/typings/axios/axios.d.ts +++ /dev/null @@ -1,292 +0,0 @@ -// Type definitions for axios 0.9.1 -// Project: https://github.com/mzabriskie/axios -// Definitions by: Marcel Buesing -// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped - -declare namespace Axios { - - interface IThenable { - then(onFulfilled?: (value: R) => U | IThenable, onRejected?: (error: any) => U | IThenable): IThenable; - then(onFulfilled?: (value: R) => U | IThenable, onRejected?: (error: any) => void): IThenable; - } - - interface IPromise extends IThenable { - then(onFulfilled?: (value: R) => U | IThenable, onRejected?: (error: any) => U | IThenable): IPromise; - then(onFulfilled?: (value: R) => U | IThenable, onRejected?: (error: any) => void): IPromise; - catch(onRejected?: (error: any) => U | IThenable): IPromise; - } - - /** - * HTTP Basic auth details - */ - interface AxiosHttpBasicAuth { - username: string; - password: string; - } - - /** - * Common axios XHR config interface - * - request body data type - */ - interface AxiosXHRConfigBase { - /** - * will be prepended to `url` unless `url` is absolute. - * It can be convenient to set `baseURL` for an instance - * of axios to pass relative URLs to methods of that instance. - */ - baseURL?: string; - - /** - * custom headers to be sent - */ - headers?: Object; - - /** - * URL parameters to be sent with the request - */ - params?: Object; - - /** - * optional function in charge of serializing `params` - * (e.g. https://www.npmjs.com/package/qs, http://api.jquery.com/jquery.param/) - */ - paramsSerializer?: (params: Object) => string; - - /** - * specifies the number of milliseconds before the request times out. - * If the request takes longer than `timeout`, the request will be aborted. - */ - timeout?: number; - - /** - * indicates whether or not cross-site Access-Control requests - * should be made using credentials - */ - withCredentials?: boolean; - - /** - * indicates that HTTP Basic auth should be used, and supplies - * credentials. This will set an `Authorization` header, - * overwriting any existing `Authorization` custom headers you have - * set using `headers`. - */ - auth?: AxiosHttpBasicAuth; - - /** - * indicates the type of data that the server will respond with - * options are 'arraybuffer', 'blob', 'document', 'json', 'text' - */ - responseType?: string; - - /** - * name of the cookie to use as a value for xsrf token - */ - xsrfCookieName?: string; - - /** - * name of the http header that carries the xsrf token value - */ - xsrfHeaderName?: string; - - /** - * Change the request data before it is sent to the server. - * This is only applicable for request methods 'PUT', 'POST', and 'PATCH' - * The last function in the array must return a string or an ArrayBuffer - */ - transformRequest?: ((data: T) => U) | [(data: T) => U]; - - /** - * change the response data to be made before it is passed to then/catch - */ - transformResponse?: (data: T) => U; - } - - /** - * - request body data type - */ - interface AxiosXHRConfig extends AxiosXHRConfigBase { - /** - * server URL that will be used for the request, options are: - * GET, PUT, POST, DELETE, CONNECT, HEAD, OPTIONS, TRACE, PATCH - */ - url: string; - - /** - * request method to be used when making the request - */ - method?: string; - - /** - * data to be sent as the request body - * Only applicable for request methods 'PUT', 'POST', and 'PATCH' - * When no `transformRequest` is set, must be a string, an ArrayBuffer or a hash - */ - data?: T; - } - - /** - * - expected response type, - * - request body data type - */ - interface AxiosXHR { - /** - * Response that was provided by the server - */ - data: T; - - /** - * HTTP status code from the server response - */ - status: number; - - /** - * HTTP status message from the server response - */ - statusText: string; - - /** - * headers that the server responded with - */ - headers: Object; - - /** - * config that was provided to `axios` for the request - */ - config: AxiosXHRConfig; - } - - interface Interceptor { - /** - * intercept request before it is sent - */ - request: RequestInterceptor; - - /** - * intercept response of request when it is received. - */ - response: ResponseInterceptor - } - - type InterceptorId = number; - - interface RequestInterceptor { - /** - * - request body data type - */ - - use(fulfilledFn: (config: AxiosXHRConfig) => AxiosXHRConfig): InterceptorId; - - use(fulfilledFn: (config: AxiosXHRConfig) => AxiosXHRConfig, - rejectedFn: (error: any) => any) - : InterceptorId; - - eject(interceptorId: InterceptorId): void; - } - - interface ResponseInterceptor { - /** - * - expected response type - */ - - use(fulfilledFn: (config: Axios.AxiosXHR) => Axios.AxiosXHR): InterceptorId; - - use(fulfilledFn: (config: Axios.AxiosXHR) => Axios.AxiosXHR, - rejectedFn: (error: any) => any) - : InterceptorId; - - eject(interceptorId: InterceptorId): void; - } - - /** - * - expected response type, - * - request body data type - */ - interface AxiosInstance { - - /** - * Send request as configured - */ - (config: AxiosXHRConfig): IPromise>; - - /** - * Send request as configured - */ - new (config: AxiosXHRConfig): IPromise>; - - /** - * Send request as configured - */ - request(config: AxiosXHRConfig): IPromise>; - - /** - * intercept requests or responses before they are handled by then or catch - */ - interceptors: Interceptor; - - /** - * equivalent to `Promise.all` - */ - all(values: [T1 | IPromise>, T2 | IPromise>, T3 | IPromise>, T4 | IPromise>, T5 | IPromise>, T6 | IPromise>, T7 | IPromise>, T8 | IPromise>, T9 | IPromise>, T10 | IPromise>]): IPromise<[AxiosXHR, AxiosXHR, AxiosXHR, AxiosXHR, AxiosXHR, AxiosXHR, AxiosXHR, AxiosXHR, AxiosXHR, AxiosXHR]>; - all(values: [T1 | IPromise>, T2 | IPromise>, T3 | IPromise>, T4 | IPromise>, T5 | IPromise>, T6 | IPromise>, T7 | IPromise>, T8 | IPromise>, T9 | IPromise>]): IPromise<[AxiosXHR, AxiosXHR, AxiosXHR, AxiosXHR, AxiosXHR, AxiosXHR, AxiosXHR, AxiosXHR, AxiosXHR]>; - all(values: [T1 | IPromise>, T2 | IPromise>, T3 | IPromise>, T4 | IPromise>, T5 | IPromise>, T6 | IPromise>, T7 | IPromise>, T8 | IPromise>]): IPromise<[AxiosXHR, AxiosXHR, AxiosXHR, AxiosXHR, AxiosXHR, AxiosXHR, AxiosXHR, AxiosXHR]>; - all(values: [T1 | IPromise>, T2 | IPromise>, T3 | IPromise>, T4 | IPromise>, T5 | IPromise>, T6 | IPromise>, T7 | IPromise>]): IPromise<[AxiosXHR, AxiosXHR, AxiosXHR, AxiosXHR, AxiosXHR, AxiosXHR, AxiosXHR]>; - all(values: [T1 | IPromise>, T2 | IPromise>, T3 | IPromise>, T4 | IPromise>, T5 | IPromise>, T6 | IPromise>]): IPromise<[AxiosXHR, AxiosXHR, AxiosXHR, AxiosXHR, AxiosXHR, AxiosXHR]>; - all(values: [T1 | IPromise>, T2 | IPromise>, T3 | IPromise>, T4 | IPromise>, T5 | IPromise>]): IPromise<[AxiosXHR, AxiosXHR, AxiosXHR, AxiosXHR, AxiosXHR]>; - all(values: [T1 | IPromise>, T2 | IPromise>, T3 | IPromise>, T4 | IPromise>]): IPromise<[AxiosXHR, AxiosXHR, AxiosXHR, AxiosXHR]>; - all(values: [T1 | IPromise>, T2 | IPromise>, T3 | IPromise>]): IPromise<[AxiosXHR, AxiosXHR, AxiosXHR]>; - all(values: [T1 | IPromise>, T2 | IPromise>]): IPromise<[AxiosXHR, AxiosXHR]>; - - /** - * spread array parameter to `fn`. - * note: alternative to `spread`, destructuring assignment. - */ - spread(fn: (t1: T1, t2: T2) => U): (arr: ([T1, T2])) => U; - - /** - * convenience alias, method = GET - */ - get(url: string, config?: AxiosXHRConfigBase): IPromise>; - - - /** - * convenience alias, method = DELETE - */ - delete(url: string, config?: AxiosXHRConfigBase): IPromise>; - - /** - * convenience alias, method = HEAD - */ - head(url: string, config?: AxiosXHRConfigBase): IPromise>; - - /** - * convenience alias, method = POST - */ - post(url: string, data?: any, config?: AxiosXHRConfigBase): IPromise>; - - /** - * convenience alias, method = PUT - */ - put(url: string, data?: any, config?: AxiosXHRConfigBase): IPromise>; - - /** - * convenience alias, method = PATCH - */ - patch(url: string, data?: any, config?: AxiosXHRConfigBase): IPromise>; - } - - /** - * - expected response type, - */ - interface AxiosStatic extends AxiosInstance { - /** - * create a new instance of axios with a custom config - */ - create(config: AxiosXHRConfigBase): AxiosInstance; - } -} - -declare var axios: Axios.AxiosStatic; - -declare module "axios" { - export = axios; -} diff --git a/src/AccountGoWeb/typings/d3/d3.d.ts b/src/AccountGoWeb/typings/d3/d3.d.ts deleted file mode 100644 index 1da10a78a..000000000 --- a/src/AccountGoWeb/typings/d3/d3.d.ts +++ /dev/null @@ -1,3373 +0,0 @@ -// Type definitions for d3JS -// Project: http://d3js.org/ -// Definitions by: Alex Ford , Boris Yankov -// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped - -declare namespace d3 { - /** - * The current version of D3.js. - */ - export var version: string; - - /** - * Find the first element that matches the given selector string. - */ - export function select(selector: string): Selection; - - /** - * Create a selection from the given node reference. - */ - export function select(node: EventTarget): Selection; - - /** - * Find all elements that match the given selector string. - */ - export function selectAll(selector: string): Selection; - - /** - * Create a selection from the given list of nodes. - */ - export function selectAll(nodes: EventTarget[]): Selection; - - /** - * Returns the root selection (as if by d3.select(document.documentElement)). This function may be used for 'instanceof' tests, and extending its prototype will add properties to all selections. - */ - export function selection(): Selection; - - namespace selection { - export var prototype: Selection; - - /** - * Selections are grouped into arrays of nodes, with the parent tracked in the 'parentNode' property. - */ - interface Group extends Array { - parentNode: EventTarget; - } - - interface Update { - /** - * Retrieve a grouped selection. - */ - [index: number]: Group; - - /** - * The number of groups in this selection. - */ - length: number; - - /** - * Retrieve the value of the given attribute for the first node in the selection. - * - * @param name The attribute name to query. May be prefixed (see d3.ns.prefix). - */ - attr(name: string): string; - - /** - * For all nodes, set the attribute to the specified constant value. Use null to remove. - * - * @param name The attribute name, optionally prefixed. - * @param value The attribute value to use. Note that this is coerced to a string automatically. - */ - attr(name: string, value: Primitive): Update; - - /** - * Derive an attribute value for each node in the selection based on bound data. - * - * @param name The attribute name, optionally prefixed. - * @param value The function of the datum (the bound data item), index (the position in the subgrouping), and outer index (overall position in nested selections) which computes the attribute value. If the function returns null, the attribute is removed. - */ - attr(name: string, value: (datum: Datum, index: number, outerIndex: number) => Primitive): Update; - - /** - * Set multiple properties at once using an Object. D3 iterates over all enumerable properties and either sets or computes the attribute's value based on the corresponding entry in the Object. - * - * @param obj A key-value mapping corresponding to attributes and values. If the value is a simple string or number, it is taken as a constant. Otherwise, it is a function that derives the attribute value. - */ - attr(obj: { [key: string]: Primitive | ((datum: Datum, index: number, outerIndex: number) => Primitive) }): Update; - - /** - * Returns true if the first node in this selection has the given class list. If multiple classes are specified (i.e., "foo bar"), then returns true only if all classes match. - * - * @param name The class list to query. - */ - classed(name: string): boolean; - - /** - * Adds (or removes) the given class list. - * - * @param name The class list to toggle. Spaces separate class names: "foo bar" is a list of two classes. - * @param value If true, add the classes. If false, remove them. - */ - classed(name: string, value: boolean): Update; - - /** - * Determine if the given class list should be toggled for each node in the selection. - * - * @param name The class list. Spaces separate multiple class names. - * @param value The function to run for each node. Should return true to add the class to the node, or false to remove it. - */ - classed(name: string, value: (datum: Datum, index: number, outerIndex: number) => boolean): Update; - - /** - * Set or derive classes for multiple class lists at once. - * - * @param obj An Object mapping class lists to values that are either plain booleans or functions that return booleans. - */ - classed(obj: { [key: string]: boolean | ((datum: Datum, index: number, outerIndex: number) => boolean) }): Update; - - /** - * Retrieve the computed style value for the first node in the selection. - * @param name The CSS property name to query - */ - style(name: string): string; - - /** - * Set a style property for all nodes in the selection. - * @param name the CSS property name - * @param value the property value - * @param priority if specified, either null or the string "important" (no exclamation mark) - */ - style(name: string, value: Primitive, priority?: string): Update; - - /** - * Derive a property value for each node in the selection. - * @param name the CSS property name - * @param value the function to derive the value - * @param priority if specified, either null or the string "important" (no exclamation mark) - */ - style(name: string, value: (datum: Datum, index: number, outerIndex: number) => Primitive, priority?: string): Update; - - /** - * Set a large number of CSS properties from an object. - * - * @param obj an Object whose keys correspond to CSS property names and values are either constants or functions that derive property values - * @param priority if specified, either null or the string "important" (no exclamation mark) - */ - style(obj: { [key: string]: Primitive | ((datum: Datum, index: number, outerIndex: number) => Primitive) }, priority?: string): Update; - - /** - * Retrieve an arbitrary node property such as the 'checked' property of checkboxes, or the 'value' of text boxes. - * - * @param name the node's property to retrieve - */ - property(name: string): any; - - /** - * For each node, set the property value. Internally, this sets the node property directly (e.g., node[name] = value), so take care not to mutate special properties like __proto__. - * - * @param name the property name - * @param value the property value - */ - property(name: string, value: any): Update; - - /** - * For each node, derive the property value. Internally, this sets the node property directly (e.g., node[name] = value), so take care not to mutate special properties like __proto__. - * - * @param name the property name - * @param value the function used to derive the property's value - */ - property(name: string, value: (datum: Datum, index: number, outerIndex: number) => any): Update; - - /** - * Set multiple node properties. Caveats apply: take care not to mutate special properties like __proto__. - * - * @param obj an Object whose keys correspond to node properties and values are either constants or functions that will compute a value. - */ - property(obj: { [key: string]: any | ((datum: Datum, index: number, outerIndex: number) => any) }): Update; - - /** - * Retrieve the textContent of the first node in the selection. - */ - text(): string; - - /** - * Set the textContent of each node in the selection. - * @param value the text to use for all nodes - */ - text(value: Primitive): Update; - - /** - * Compute the textContent of each node in the selection. - * @param value the function which will compute the text - */ - text(value: (datum: Datum, index: number, outerIndex: number) => Primitive): Update; - - /** - * Retrieve the HTML content of the first node in the selection. Uses 'innerHTML' internally and will not work with SVG or other elements without a polyfill. - */ - html(): string; - - /** - * Set the HTML content of every node in the selection. Uses 'innerHTML' internally and thus will not work with SVG or other elements without a polyfill. - * @param value the HTML content to use. - */ - html(value: string): Selection; - - /** - * Compute the HTML content for each node in the selection. Uses 'innerHTML' internally and thus will not work with SVG or other elements without a polyfill. - * @param value the function to compute HTML content - */ - html(value: (datum: Datum, index: number, outerIndex: number) => string): Selection; - - /** - * Appends a new child to each node in the selection. This child will inherit the parent's data (if available). Returns a fresh selection consisting of the newly-appended children. - * - * @param name the element name to append. May be prefixed (see d3.ns.prefix). - */ - append(name: string): Selection; - - /** - * Appends a new child to each node in the selection by computing a new node. This child will inherit the parent's data (if available). Returns a fresh selection consisting of the newly-appended children. - * - * @param name the function to compute a new element - */ - append(name: (datum: Datum, index: number, outerIndex: number) => EventTarget): Update; - - /** - * Inserts a new child to each node in the selection. This child will inherit its parent's data (if available). Returns a fresh selection consisting of the newly-inserted children. - * @param name the element name to append. May be prefixed (see d3.ns.prefix). - * @param before the selector to determine position (e.g., ":first-child") - */ - insert(name: string, before: string): Update; - - /** - * Inserts a new child to each node in the selection. This child will inherit its parent's data (if available). Returns a fresh selection consisting of the newly-inserted children. - * @param name the element name to append. May be prefixed (see d3.ns.prefix). - * @param before a function to determine the node to use as the next sibling - */ - insert(name: string, before: (datum: Datum, index: number, outerIndex: number) => EventTarget): Update; - - /** - * Inserts a new child to the end of each node in the selection by computing a new node. This child will inherit its parent's data (if available). Returns a fresh selection consisting of the newly-inserted children. - * @param name the function to compute a new child - * @param before the selector to determine position (e.g., ":first-child") - */ - insert(name: (datum: Datum, index: number, outerIndex: number) => EventTarget, before: string): Update; - - /** - * Inserts a new child to the end of each node in the selection by computing a new node. This child will inherit its parent's data (if available). Returns a fresh selection consisting of the newly-inserted children. - * @param name the function to compute a new child - * @param before a function to determine the node to use as the next sibling - */ - insert(name: (datum: Datum, index: number, outerIndex: number) => EventTarget, before: (datum: Datum, index: number, outerIndex: number) => EventTarget): Update; - - /** - * Removes the elements from the DOM. They are in a detached state and may be re-added (though there is currently no dedicated API for doing so). - */ - remove(): Update; - - /** - * Retrieves the data bound to the first group in this selection. - */ - data(): Datum[]; - - /** - * Binds data to this selection. - * @param data the array of data to bind to this selection - * @param key the optional function to determine the unique key for each piece of data. When unspecified, uses the index of the element. - */ - data(data: NewDatum[], key?: (datum: NewDatum, index: number, outerIndex: number) => string): Update; - - /** - * Derives data to bind to this selection. - * @param data the function to derive data. Must return an array. - * @param key the optional function to determine the unique key for each data item. When unspecified, uses the index of the element. - */ - data(data: (datum: Datum, index: number, outerIndex: number) => NewDatum[], key?: (datum: NewDatum, index: number, outerIndex: number) => string): Update; - - /** - * Filters the selection, returning only those nodes that match the given CSS selector. - * @param selector the CSS selector - */ - filter(selector: string): Update; - - /** - * Filters the selection, returning only those nodes for which the given function returned true. - * @param selector the filter function - */ - filter(selector: (datum: Datum, index: number, outerIndex: number) => boolean): Update; - - /** - * Return the data item bound to the first element in the selection. - */ - datum(): Datum; - - /** - * Derive the data item for each node in the selection. Useful for situations such as the HTML5 'dataset' attribute. - * @param value the function to compute data for each node - */ - datum(value: (datum: Datum, index: number, outerIndex: number) => NewDatum): Update; - - /** - * Set the data item for each node in the selection. - * @param value the constant element to use for each node - */ - datum(value: NewDatum): Update; - - /** - * Reorders nodes in the selection based on the given comparator. Nodes are re-inserted into the document once sorted. - * @param comparator the comparison function, which defaults to d3.ascending - */ - sort(comparator?: (a: Datum, b: Datum) => number): Update; - - /** - * Reorders nodes in the document to match the selection order. More efficient than calling sort() if the selection is already ordered. - */ - order(): Update; - - /** - * Returns the listener (if any) for the given event. - * @param type the type of event to load the listener for. May have a namespace (e.g., ".foo") at the end. - */ - on(type: string): (datum: Datum, index: number, outerIndex: number) => any; - - /** - * Adds a listener for the specified event. If one was already registered, it is removed before the new listener is added. The return value of the listener function is ignored. - * @param type the of event to listen to. May have a namespace (e.g., ".foo") at the end. - * @param listener an event listener function, or null to unregister - * @param capture sets the DOM useCapture flag - */ - on(type: string, listener: (datum: Datum, index: number, outerIndex: number) => any, capture?: boolean): Update; - - /** - * Begins a new transition. Interrupts any active transitions of the same name. - * @param name the transition name (defaults to "") - */ - transition(name?: string): Transition; - - /** - * Interrupts the active transition of the provided name. Does not cancel scheduled transitions. - * @param name the transition name (defaults to "") - */ - interrupt(name?: string): Update; - - /** - * Creates a subselection by finding the first descendent matching the selector string. Bound data is inherited. - * @param selector the CSS selector to match against - */ - select(selector: string): Update; - - /** - * Creates a subselection by using a function to find descendent elements. Bound data is inherited. - * @param selector the function to find matching descendants - */ - select(selector: (datum: Datum, index: number, outerIndex: number) => EventTarget): Update; - - /** - * Creates a subselection by finding all descendents that match the given selector. Bound data is not inherited. - * @param selector the CSS selector to match against - */ - selectAll(selector: string): Update; - - /** - * Creates a subselection by using a function to find descendent elements. Bound data is not inherited. - * @param selector the function to find matching descendents - */ - selectAll(selector: (datum: Datum, index: number, outerIndex: number) => Array | NodeList): Update; - - /** - * Invoke the given function for each element in the selection. The return value of the function is ignored. - * @param func the function to invoke - */ - each(func: (datum: Datum, index: number, outerIndex: number) => any): Update; - - /** - * Call a function on the selection. sel.call(foo) is equivalent to foo(sel). - * @param func the function to call on the selection - * @param args any optional args - */ - call(func: (sel: Update, ...args: any[]) => any, ...args: any[]): Update; - - /** - * Returns true if the current selection is empty. - */ - empty(): boolean; - - /** - * Returns the first non-null element in the selection, or null otherwise. - */ - node(): Node; - - /** - * Returns the total number of elements in the selection. - */ - size(): number; - - /** - * Returns the placeholder nodes for each data element for which no corresponding DOM element was found. - */ - enter(): Enter; - - /** - * Returns a selection for those DOM nodes for which no new data element was found. - */ - exit(): Selection; - } - - interface Enter { - append(name: string): Selection; - append(name: (datum: Datum, index: number, outerIndex: number) => EventTarget): Selection; - - insert(name: string, before?: string): Selection; - insert(name: string, before: (datum: Datum, index: number, outerIndex: number) => EventTarget): Selection; - insert(name: (datum: Datum, index: number, outerIndex: number) => EventTarget, before?: string): Selection; - insert(name: (datum: Datum, index: number, outerIndex: number) => EventTarget, before: (datum: Datum, index: number, outerIndex: number) => EventTarget): Selection; - - select(name: (datum: Datum, index: number, outerIndex: number) => EventTarget): Selection; - call(func: (selection: Enter, ...args: any[]) => any, ...args: any[]): Enter; - - empty(): boolean; - size(): number; - } - } - - /** - * Administrivia: JavaScript primitive types, or "things that toString() predictably". - */ - export type Primitive = number | string | boolean; - - /** - * Administrivia: anything with a valueOf(): number method is comparable, so we allow it in numeric operations - */ - interface Numeric { - valueOf(): number; - } - - /** - * A grouped array of nodes. - * @param Datum the data bound to this selection. - */ - interface Selection { - /** - * Retrieve a grouped selection. - */ - [index: number]: selection.Group; - - /** - * The number of groups in this selection. - */ - length: number; - - /** - * Retrieve the value of the given attribute for the first node in the selection. - * - * @param name The attribute name to query. May be prefixed (see d3.ns.prefix). - */ - attr(name: string): string; - - /** - * For all nodes, set the attribute to the specified constant value. Use null to remove. - * - * @param name The attribute name, optionally prefixed. - * @param value The attribute value to use. Note that this is coerced to a string automatically. - */ - attr(name: string, value: Primitive): Selection; - - /** - * Derive an attribute value for each node in the selection based on bound data. - * - * @param name The attribute name, optionally prefixed. - * @param value The function of the datum (the bound data item), index (the position in the subgrouping), and outer index (overall position in nested selections) which computes the attribute value. If the function returns null, the attribute is removed. - */ - attr(name: string, value: (datum: Datum, index: number, outerIndex: number) => Primitive): Selection; - - /** - * Set multiple properties at once using an Object. D3 iterates over all enumerable properties and either sets or computes the attribute's value based on the corresponding entry in the Object. - * - * @param obj A key-value mapping corresponding to attributes and values. If the value is a simple string or number, it is taken as a constant. Otherwise, it is a function that derives the attribute value. - */ - attr(obj: { [key: string]: Primitive | ((datum: Datum, index: number, outerIndex: number) => Primitive) }): Selection; - - /** - * Returns true if the first node in this selection has the given class list. If multiple classes are specified (i.e., "foo bar"), then returns true only if all classes match. - * - * @param name The class list to query. - */ - classed(name: string): boolean; - - /** - * Adds (or removes) the given class list. - * - * @param name The class list to toggle. Spaces separate class names: "foo bar" is a list of two classes. - * @param value If true, add the classes. If false, remove them. - */ - classed(name: string, value: boolean): Selection; - - /** - * Determine if the given class list should be toggled for each node in the selection. - * - * @param name The class list. Spaces separate multiple class names. - * @param value The function to run for each node. Should return true to add the class to the node, or false to remove it. - */ - classed(name: string, value: (datum: Datum, index: number, outerIndex: number) => boolean): Selection; - - /** - * Set or derive classes for multiple class lists at once. - * - * @param obj An Object mapping class lists to values that are either plain booleans or functions that return booleans. - */ - classed(obj: { [key: string]: boolean | ((datum: Datum, index: number, outerIndex: number) => boolean) }): Selection; - - /** - * Retrieve the computed style value for the first node in the selection. - * @param name The CSS property name to query - */ - style(name: string): string; - - /** - * Set a style property for all nodes in the selection. - * @param name the CSS property name - * @param value the property value - * @param priority if specified, either null or the string "important" (no exclamation mark) - */ - style(name: string, value: Primitive, priority?: string): Selection; - - /** - * Derive a property value for each node in the selection. - * @param name the CSS property name - * @param value the function to derive the value - * @param priority if specified, either null or the string "important" (no exclamation mark) - */ - style(name: string, value: (datum: Datum, index: number, outerIndex: number) => Primitive, priority?: string): Selection; - - /** - * Set a large number of CSS properties from an object. - * - * @param obj an Object whose keys correspond to CSS property names and values are either constants or functions that derive property values - * @param priority if specified, either null or the string "important" (no exclamation mark) - */ - style(obj: { [key: string]: Primitive | ((datum: Datum, index: number, outerIndex: number) => Primitive) }, priority?: string): Selection; - - /** - * Retrieve an arbitrary node property such as the 'checked' property of checkboxes, or the 'value' of text boxes. - * - * @param name the node's property to retrieve - */ - property(name: string): any; - - /** - * For each node, set the property value. Internally, this sets the node property directly (e.g., node[name] = value), so take care not to mutate special properties like __proto__. - * - * @param name the property name - * @param value the property value - */ - property(name: string, value: any): Selection; - - /** - * For each node, derive the property value. Internally, this sets the node property directly (e.g., node[name] = value), so take care not to mutate special properties like __proto__. - * - * @param name the property name - * @param value the function used to derive the property's value - */ - property(name: string, value: (datum: Datum, index: number, outerIndex: number) => any): Selection; - - /** - * Set multiple node properties. Caveats apply: take care not to mutate special properties like __proto__. - * - * @param obj an Object whose keys correspond to node properties and values are either constants or functions that will compute a value. - */ - property(obj: { [key: string]: any | ((datum: Datum, index: number, innerInder: number) => any) }): Selection; - - /** - * Retrieve the textContent of the first node in the selection. - */ - text(): string; - - /** - * Set the textContent of each node in the selection. - * @param value the text to use for all nodes - */ - text(value: Primitive): Selection; - - /** - * Compute the textContent of each node in the selection. - * @param value the function which will compute the text - */ - text(value: (datum: Datum, index: number, outerIndex: number) => Primitive): Selection; - - /** - * Retrieve the HTML content of the first node in the selection. Uses 'innerHTML' internally and will not work with SVG or other elements without a polyfill. - */ - html(): string; - - /** - * Set the HTML content of every node in the selection. Uses 'innerHTML' internally and thus will not work with SVG or other elements without a polyfill. - * @param value the HTML content to use. - */ - html(value: string): Selection; - - /** - * Compute the HTML content for each node in the selection. Uses 'innerHTML' internally and thus will not work with SVG or other elements without a polyfill. - * @param value the function to compute HTML content - */ - html(value: (datum: Datum, index: number, outerIndex: number) => string): Selection; - - /** - * Appends a new child to each node in the selection. This child will inherit the parent's data (if available). Returns a fresh selection consisting of the newly-appended children. - * - * @param name the element name to append. May be prefixed (see d3.ns.prefix). - */ - append(name: string): Selection; - - /** - * Appends a new child to each node in the selection by computing a new node. This child will inherit the parent's data (if available). Returns a fresh selection consisting of the newly-appended children. - * - * @param name the function to compute a new element - */ - append(name: (datum: Datum, index: number, outerIndex: number) => EventTarget): Selection; - - /** - * Inserts a new child to each node in the selection. This child will inherit its parent's data (if available). Returns a fresh selection consisting of the newly-inserted children. - * @param name the element name to append. May be prefixed (see d3.ns.prefix). - * @param before the selector to determine position (e.g., ":first-child") - */ - insert(name: string, before: string): Selection; - - /** - * Inserts a new child to each node in the selection. This child will inherit its parent's data (if available). Returns a fresh selection consisting of the newly-inserted children. - * @param name the element name to append. May be prefixed (see d3.ns.prefix). - * @param before a function to determine the node to use as the next sibling - */ - insert(name: string, before: (datum: Datum, index: number, outerIndex: number) => EventTarget): Selection; - - /** - * Inserts a new child to the end of each node in the selection by computing a new node. This child will inherit its parent's data (if available). Returns a fresh selection consisting of the newly-inserted children. - * @param name the function to compute a new child - * @param before the selector to determine position (e.g., ":first-child") - */ - insert(name: (datum: Datum, index: number, outerIndex: number) => EventTarget, before: string): Selection; - - /** - * Inserts a new child to the end of each node in the selection by computing a new node. This child will inherit its parent's data (if available). Returns a fresh selection consisting of the newly-inserted children. - * @param name the function to compute a new child - * @param before a function to determine the node to use as the next sibling - */ - insert(name: (datum: Datum, index: number, outerIndex: number) => EventTarget, before: (datum: Datum, index: number, outerIndex: number) => EventTarget): Selection; - - /** - * Removes the elements from the DOM. They are in a detached state and may be re-added (though there is currently no dedicated API for doing so). - */ - remove(): Selection; - - /** - * Retrieves the data bound to the first group in this selection. - */ - data(): Datum[]; - - /** - * Binds data to this selection. - * @param data the array of data to bind to this selection - * @param key the optional function to determine the unique key for each piece of data. When unspecified, uses the index of the element. - */ - data(data: NewDatum[], key?: (datum: NewDatum, index: number, outerIndex: number) => string): selection.Update; - - /** - * Derives data to bind to this selection. - * @param data the function to derive data. Must return an array. - * @param key the optional function to determine the unique key for each data item. When unspecified, uses the index of the element. - */ - data(data: (datum: Datum, index: number, outerIndex: number) => NewDatum[], key?: (datum: NewDatum, index: number, outerIndex: number) => string): selection.Update; - - /** - * Filters the selection, returning only those nodes that match the given CSS selector. - * @param selector the CSS selector - */ - filter(selector: string): Selection; - - /** - * Filters the selection, returning only those nodes for which the given function returned true. - * @param selector the filter function - */ - filter(selector: (datum: Datum, index: number, outerIndex: number) => boolean): Selection; - - /** - * Return the data item bound to the first element in the selection. - */ - datum(): Datum; - - /** - * Derive the data item for each node in the selection. Useful for situations such as the HTML5 'dataset' attribute. - * @param value the function to compute data for each node - */ - datum(value: (datum: Datum, index: number, outerIndex: number) => NewDatum): Selection; - - /** - * Set the data item for each node in the selection. - * @param value the constant element to use for each node - */ - datum(value: NewDatum): Selection; - - /** - * Reorders nodes in the selection based on the given comparator. Nodes are re-inserted into the document once sorted. - * @param comparator the comparison function, which defaults to d3.ascending - */ - sort(comparator?: (a: Datum, b: Datum) => number): Selection; - - /** - * Reorders nodes in the document to match the selection order. More efficient than calling sort() if the selection is already ordered. - */ - order(): Selection; - - /** - * Returns the listener (if any) for the given event. - * @param type the type of event to load the listener for. May have a namespace (e.g., ".foo") at the end. - */ - on(type: string): (datum: Datum, index: number, outerIndex: number) => any; - - /** - * Adds a listener for the specified event. If one was already registered, it is removed before the new listener is added. The return value of the listener function is ignored. - * @param type the of event to listen to. May have a namespace (e.g., ".foo") at the end. - * @param listener an event listener function, or null to unregister - * @param capture sets the DOM useCapture flag - */ - on(type: string, listener: (datum: Datum, index: number, outerIndex: number) => any, capture?: boolean): Selection; - - /** - * Begins a new transition. Interrupts any active transitions of the same name. - * @param name the transition name (defaults to "") - */ - transition(name?: string): Transition; - - /** - * Interrupts the active transition of the provided name. Does not cancel scheduled transitions. - * @param name the transition name (defaults to "") - */ - interrupt(name?: string): Selection; - - /** - * Creates a subselection by finding the first descendent matching the selector string. Bound data is inherited. - * @param selector the CSS selector to match against - */ - select(selector: string): Selection; - - /** - * Creates a subselection by using a function to find descendent elements. Bound data is inherited. - * @param selector the function to find matching descendants - */ - select(selector: (datum: Datum, index: number, outerIndex: number) => EventTarget): Selection; - - /** - * Creates a subselection by finding all descendents that match the given selector. Bound data is not inherited. - * @param selector the CSS selector to match against - */ - selectAll(selector: string): Selection; - - /** - * Creates a subselection by finding all descendants that match the given selector. Bound data is not inherited. - * - * Use this overload when data-binding a subselection (that is, sel.selectAll('.foo').data(d => ...)). The type will carry over. - */ - selectAll(selector: string): Selection; - - /** - * Creates a subselection by using a function to find descendent elements. Bound data is not inherited. - * @param selector the function to find matching descendents - */ - selectAll(selector: (datum: Datum, index: number, outerIndex: number) => Array | NodeList): Selection; - - /** - * Creates a subselection by using a function to find descendent elements. Bound data is not inherited. - * - * Use this overload when data-binding a subselection (that is, sel.selectAll('.foo').data(d => ...)). The type will carry over. - * @param selector the function to find matching descendents - */ - selectAll(selector: (datum: Datum, index: number, outerIndex: number) => Array | NodeList): Selection; - - /** - * Invoke the given function for each element in the selection. The return value of the function is ignored. - * @param func the function to invoke - */ - each(func: (datum: Datum, index: number, outerIndex: number) => any): Selection; - - /** - * Call a function on the selection. sel.call(foo) is equivalent to foo(sel). - * @param func the function to call on the selection - * @param args any optional args - */ - call(func: (sel: Selection, ...args: any[]) => any, ...args: any[]): Selection; - - /** - * Returns true if the current selection is empty. - */ - empty(): boolean; - - /** - * Returns the first non-null element in the selection, or null otherwise. - */ - node(): Node; - - /** - * Returns the total number of elements in the selection. - */ - size(): number; - } - - export function transition(): Transition; - namespace transition { - export var prototype: Transition; - } - - interface Transition { - - transition(): Transition; - - delay(): number; - delay(delay: number): Transition; - delay(delay: (datum: Datum, index: number, outerIndex: number) => number): Transition; - - duration(): number; - duration(duration: number): Transition; - duration(duration: (datum: Datum, index: number, outerIndex: number) => number): Transition; - - ease(): (t: number) => number; - ease(value: string, ...args: any[]): Transition; - ease(value: (t: number) => number): Transition; - - attr(name: string, value: Primitive): Transition; - attr(name: string, value: (datum: Datum, index: number, outerIndex: number) => Primitive): Transition; - attr(obj: { [key: string]: Primitive | ((datum: Datum, index: number, outerIndex: number) => Primitive) }): Transition; - - attrTween(name: string, tween: (datum: Datum, index: number, attr: string) => (t: number) => Primitive): Transition; - - style(name: string, value: Primitive, priority?: string): Transition; - style(name: string, value: (datum: Datum, index: number, outerIndex: number) => Primitive, priority?: string): Transition; - style(obj: { [key: string]: Primitive | ((datum: Datum, index: number, outerIndex: number) => Primitive) }, priority?: string): Transition; - - styleTween(name: string, tween: (datum: Datum, index: number, attr: string) => (t: number) => Primitive, priority?: string): Transition; - - text(value: Primitive): Transition; - text(value: (datum: Datum, index: number, outerIndex: number) => Primitive): Transition; - - tween(name: string, factory: () => (t: number) => any): Transition; - - remove(): Transition; - - select(selector: string): Transition; - select(selector: (d: Datum, i: number) => EventTarget): Transition; - - selectAll(selector: string): Transition; - selectAll(selector: (d: Datum, i: number) => EventTarget[]): Transition; - - filter(selector: string): Transition; - filter(selector: (d: Datum, i: number) => boolean): Transition; - - each(type: string, listener: (d: Datum, i: number) => any): Transition; - each(listener: (d: Datum, i: number) => any): Transition; - - call(func: (transition: Transition, ...args: any[]) => any, ...args: any[]): Transition; - - empty(): boolean; - node(): Node; - size(): number; - } - - export function ease(type: 'linear'): (t: number) => number; - export function ease(type: 'linear-in'): (t: number) => number; - export function ease(type: 'linear-out'): (t: number) => number; - export function ease(type: 'linear-in-out'): (t: number) => number; - export function ease(type: 'linear-out-in'): (t: number) => number; - - export function ease(type: 'poly', k: number): (t: number) => number; - export function ease(type: 'poly-in', k: number): (t: number) => number; - export function ease(type: 'poly-out', k: number): (t: number) => number; - export function ease(type: 'poly-in-out', k: number): (t: number) => number; - export function ease(type: 'poly-out-in', k: number): (t: number) => number; - - export function ease(type: 'quad'): (t: number) => number; - export function ease(type: 'quad-in'): (t: number) => number; - export function ease(type: 'quad-out'): (t: number) => number; - export function ease(type: 'quad-in-out'): (t: number) => number; - export function ease(type: 'quad-out-in'): (t: number) => number; - - export function ease(type: 'cubic'): (t: number) => number; - export function ease(type: 'cubic-in'): (t: number) => number; - export function ease(type: 'cubic-out'): (t: number) => number; - export function ease(type: 'cubic-in-out'): (t: number) => number; - export function ease(type: 'cubic-out-in'): (t: number) => number; - - export function ease(type: 'sin'): (t: number) => number; - export function ease(type: 'sin-in'): (t: number) => number; - export function ease(type: 'sin-out'): (t: number) => number; - export function ease(type: 'sin-in-out'): (t: number) => number; - export function ease(type: 'sin-out-in'): (t: number) => number; - - export function ease(type: 'circle'): (t: number) => number; - export function ease(type: 'circle-in'): (t: number) => number; - export function ease(type: 'circle-out'): (t: number) => number; - export function ease(type: 'circle-in-out'): (t: number) => number; - export function ease(type: 'circle-out-in'): (t: number) => number; - - export function ease(type: 'elastic', a?: number, b?: number): (t: number) => number; - export function ease(type: 'elastic-in', a?: number, b?: number): (t: number) => number; - export function ease(type: 'elastic-out', a?: number, b?: number): (t: number) => number; - export function ease(type: 'elastic-in-out', a?: number, b?: number): (t: number) => number; - export function ease(type: 'elastic-out-in', a?: number, b?: number): (t: number) => number; - - export function ease(type: 'back', s: number): (t: number) => number; - export function ease(type: 'back-in', s: number): (t: number) => number; - export function ease(type: 'back-out', s: number): (t: number) => number; - export function ease(type: 'back-in-out', s: number): (t: number) => number; - export function ease(type: 'back-out-in', s: number): (t: number) => number; - - export function ease(type: 'bounce'): (t: number) => number; - export function ease(type: 'bounce-in'): (t: number) => number; - export function ease(type: 'bounce-out'): (t: number) => number; - export function ease(type: 'bounce-in-out'): (t: number) => number; - export function ease(type: 'bounce-out-in'): (t: number) => number; - - export function ease(type: string, ...args: any[]): (t: number) => number; - - export function timer(func: () => any, delay?: number, time?: number): void; - - namespace timer { - export function flush(): void; - } - - interface BaseEvent { - type: string; - sourceEvent?: Event; - } - - /** - * Define a D3-specific ZoomEvent per https://github.com/mbostock/d3/wiki/Zoom-Behavior#event - */ - interface ZoomEvent extends BaseEvent { - scale: number; - translate: [number, number]; - } - - /** - * Define a D3-specific DragEvent per https://github.com/mbostock/d3/wiki/Drag-Behavior#on - */ - interface DragEvent extends BaseEvent { - x: number; - y: number; - dx: number; - dy: number; - } - - /** - * The current event's value. Use this variable in a handler registered with `selection.on`. - */ - export var event: Event | BaseEvent; - - /** - * Returns the x and y coordinates of the mouse relative to the provided container element, using d3.event for the mouse's position on the page. - * @param container the container element (e.g. an SVG element) - */ - export function mouse(container: EventTarget): [number, number]; - - /** - * Given a container element and a touch identifier, determine the x and y coordinates of the touch. - * @param container the container element (e.g., an SVG element) - * @param identifier the given touch identifier - */ - export function touch(container: EventTarget, identifer: number): [number, number]; - - /** - * Given a container element, a list of touches, and a touch identifier, determine the x and y coordinates of the touch. - * @param container the container element (e.g., an SVG element) - * @param identifier the given touch identifier - */ - export function touch(container: EventTarget, touches: TouchList, identifer: number): [number, number]; - - /** - * Given a container element and an optional list of touches, return the position of every touch relative to the container. - * @param container the container element - * @param touches an optional list of touches (defaults to d3.event.touches) - */ - export function touches(container: EventTarget, touches?: TouchList): Array<[number, number]>; - - // NB. this is limited to primitive values due to D3's use of the <, >, and >= operators. Results get weird for object instances. - /** - * Compares two primitive values for sorting (in ascending order). - */ - export function ascending(a: Primitive, b: Primitive): number; - - /** - * Compares two primitive values for sorting (in ascending order). - */ - export function descending(a: Primitive, b: Primitive): number; - - /** - * Return the minimum value in the array using natural order. - */ - export function min(array: number[]): number; - - /** - * Return the minimum value in the array using natural order. - */ - export function min(array: string[]): string; - - /** - * Return the minimum value in the array using natural order. - */ - export function min(array: T[]): T; - - /** - * Return the minimum value in the array using natural order. - */ - export function min(array: T[], accessor: (datum: T, index: number) => number): number; - - /** - * Return the minimum value in the array using natural order. - */ - export function min(array: T[], accessor: (datum: T, index: number) => string): string; - - /** - * Return the minimum value in the array using natural order. - */ - export function min(array: T[], accessor: (datum: T, index: number) => U): U; - - /** - * Return the maximum value in the array of numbers using natural order. - */ - export function max(array: number[]): number; - - /** - * Return the maximum value in the array of strings using natural order. - */ - export function max(array: string[]): string; - - /** - * Return the maximum value in the array of numbers using natural order. - */ - export function max(array: T[]): T; - - /** - * Return the maximum value in the array using natural order and a projection function to map values to numbers. - */ - export function max(array: T[], accessor: (datum: T, index: number) => number): number; - - /** - * Return the maximum value in the array using natural order and a projection function to map values to strings. - */ - export function max(array: T[], accessor: (datum: T, index: number) => string): string; - - /** - * Return the maximum value in the array using natural order and a projection function to map values to easily-sorted values. - */ - export function max(array: T[], accessor: (datum: T, index: number) => U): U; - - /** - * Return the min and max simultaneously. - */ - export function extent(array: number[]): [number, number]; - - /** - * Return the min and max simultaneously. - */ - export function extent(array: string[]): [string, string]; - - /** - * Return the min and max simultaneously. - */ - export function extent(array: T[]): [T, T]; - - /** - * Return the min and max simultaneously. - */ - export function extent(array: Array): [T | Primitive, T | Primitive]; - - /** - * Return the min and max simultaneously. - */ - export function extent(array: T[], accessor: (datum: T, index: number) => number): [number, number]; - - /** - * Return the min and max simultaneously. - */ - export function extent(array: T[], accessor: (datum: T, index: number) => string): [string, string]; - - /** - * Return the min and max simultaneously. - */ - export function extent(array: T[], accessor: (datum: T, index: number) => Date): [Date, Date]; - - /** - * Return the min and max simultaneously. - */ - export function extent(array: T[], accessor: (datum: T, index: number) => U): [U | Primitive, U | Primitive]; - - /** - * Compute the sum of an array of numbers. - */ - export function sum(array: number[]): number; - - /** - * Compute the sum of an array, using the given accessor to convert values to numbers. - */ - export function sum(array: T[], accessor: (datum: T, index: number) => number): number; - - export function mean(array: number[]): number; - export function mean(array: T[], accessor: (datum: T, index: number) => number): number; - - /** - * Compute the median of an array of numbers (the 0.5-quantile). - */ - export function median(array: number[]): number; - export function median(datum: T[], accessor: (datum: T, index: number) => number): number; - - export function quantile(array: number[], p: number): number; - - export function variance(array: number[]): number; - export function variance(array: T[], accessor: (datum: T, index: number) => number): number; - - export function deviation(array: number[]): number; - export function deviation(array: T[], accessor: (datum: T, index: number) => number): number; - - export function bisectLeft(array: T[], x: T, lo?: number, hi?: number): number; - - export var bisect: typeof bisectRight; - - export function bisectRight(array: T[], x: T, lo?: number, hi?: number): number; - - export function bisector(accessor: (x: T) => U): { - left: (array: T[], x: U, lo?: number, hi?: number) => number; - right: (array: T[], x: U, lo?: number, hi?: number) => number; - } - - export function bisector(comparator: (a: T, b: U) => number): { - left: (array: T[], x: U, lo?: number, hi?: number) => number; - right: (array: T[], x: U, lo?: number, hi?: number) => number; - } - - export function shuffle(array: T[], lo?: number, hi?: number): T[]; - - /** - * Returns the enumerable property names of the specified object. - * @param object a JavaScript object - */ - export function keys(object: Object): string[]; - - /** - * Returns an array containing the property values of the specified object. - */ - export function values(object: { [key: string]: T }): T[]; - /** - * Returns an array containing the property values of the specified object. - */ - export function values(object: { [key: number]: T }): T[]; - /** - * Returns an array containing the property values of the specified object. - */ - export function values(object: Object): any[]; - - /** - * Returns an array of key-value pairs containing the property values of the specified object. - */ - export function entries(object: { [key: string]: T }): { key: string; value: T }[]; - - /** - * Returns an array of key-value pairs containing the property values of the specified object. - */ - export function entries(object: { [key: number]: T }): { key: string; value: T }[]; - - /** - * Returns an array of key-value pairs containing the property values of the specified object. - */ - export function entries(object: Object): { key: string; value: any }[]; - - /** - * A shim for ES6 maps. The implementation uses a JavaScript object internally, and thus keys are limited to strings. - */ - interface Map { - /** - * Does the map contain the given key? - */ - has(key: string): boolean; - - /** - * Retrieve the value for the given key. Returns undefined if there is no value stored. - */ - get(key: string): T; - - /** - * Set the value for the given key. Returns the new value. - */ - set(key: string, value: T): T; - - /** - * Remove the value for the given key. Returns true if there was a value and false otherwise. - */ - remove(key: string): boolean; - - /** - * Returns an array of all keys in arbitrary order. - */ - keys(): string[]; - - /** - * Returns an array of all values in arbitrary order. - */ - values(): T[]; - - /** - * Returns an array of key-value objects in arbitrary order. - */ - entries(): { key: string; value: T }[]; - - /** - * Calls the function for each key and value pair in the map. The 'this' context is the map itself. - */ - forEach(func: (key: string, value: T) => any): void; - - /** - * Is this map empty? - */ - empty(): boolean; - - /** - * Returns the number of elements stored in the map. - */ - size(): number; - } - - /** - * Constructs an initially empty map. - */ - export function map(): Map; - - /** - * Construct a new map by copying keys and values from the given one. - */ - export function map(object: Map): Map; - - /** - * Construct a new map by copying enumerable properties and values from the given object. - */ - export function map(object: { [key: string]: T }): Map; - - /** - * Construct a new map by copying enumerable properties and values from the given object. - */ - export function map(object: { [key: number]: T }): Map; - - /** - * Construct a new map by copying elements from the array. The key function is used to identify each object. - */ - export function map(array: T[], key: (datum: T, index: number) => string): Map; - - /** - * Construct a new map by copying enumerable properties and values from the given object. - */ - export function map(object: Object): Map; - - /** - * A shim for ES6 sets. Is only able to store strings. - */ - interface Set { - /** - * Is the given string stored in this set? - */ - has(value: string): boolean; - - /** - * Add the string to this set. Returns the value. - */ - add(value: string): string; - - /** - * Remove the given value from the set. Returns true if it was stored, and false otherwise. - */ - remove(value: string): boolean; - - /** - * Returns an array of the strings stored in this set. - */ - values(): string[]; - - /** - * Calls a given function for each value in the set. The return value of the function is ignored. The this context of the function is the set itself. - */ - forEach(func: (value: string) => any): void; - - /** - * Is this set empty? - */ - empty(): boolean; - - /** - * Returns the number of values stored in this set. - */ - size(): number; - } - - /** - * Creates an initially-empty set. - */ - export function set(): Set; - - /** - * Initializes a set from the given array of strings. - */ - export function set(array: string[]): Set; - - /** - * Merges the specified arrays into a single array. - */ - export function merge(arrays: T[][]): T[]; - - /** - * Generates a 0-based numeric sequence. The output range does not include 'stop'. - */ - export function range(stop: number): number[]; - - /** - * Generates a numeric sequence starting from the given start and stop values. 'step' defaults to 1. The output range does not include 'stop'. - */ - export function range(start: number, stop: number, step?: number): number[]; - - /** - * Given the specified array, return an array corresponding to the list of indices in 'keys'. - */ - export function permute(array: { [key: number]: T }, keys: number[]): T[]; - - /** - * Given the specified object, return an array corresponding to the list of property names in 'keys'. - */ - export function permute(object: { [key: string]: T }, keys: string[]): T[]; - - // TODO construct n-tuples from n input arrays - export function zip(...arrays: T[][]): T[][]; - - export function transpose(matrix: T[][]): T[][]; - - /** - * For each adjacent pair of elements in the specified array, returns a new array of tuples of elements i and i - 1. - * Returns the empty array if the input array has fewer than two elements. - */ - export function pairs(array: T[]): Array<[T, T]>; - - interface Nest { - key(func: (datum: T) => string): Nest; - sortKeys(comparator: (a: string, b: string) => number): Nest; - sortValues(comparator: (a: T, b: T) => number): Nest; - rollup(func: (values: T[]) => U): Nest; - map(array: T[]): { [key: string]: any }; - map(array: T[], mapType: typeof d3.map): Map; - entries(array: T[]): { key: string; values: any }[]; - } - - export function nest(): Nest; - - export module random { - export function normal(mean?: number, deviation?: number): () => number; - export function logNormal(mean?: number, deviation?: number): () => number; - export function bates(count: number): () => number; - export function irwinHall(count: number): () => number; - } - - interface Transform { - rotate: number; - translate: [number, number]; - skew: number; - scale: [number, number]; - toString(): string; - } - - export function transform(transform: string): Transform; - - export function format(specifier: string): (n: number) => string; - - interface FormatPrefix { - symbol: string; - scale(n: number): number; - } - - export function formatPrefix(value: number, precision?: number): FormatPrefix; - - export function round(x: number, n?: number): number; - - export function requote(string: string): string; - - export var rgb: { - new (r: number, g: number, b: number): Rgb; - new (color: string): Rgb; - - (r: number, g: number, b: number): Rgb; - (color: string): Rgb; - }; - - interface Rgb extends Color { - r: number; - g: number; - b: number; - - brighter(k?: number): Rgb; - darker(k?: number): Rgb; - - hsl(): Hsl; - - toString(): string; - } - - export var hsl: { - new (h: number, s: number, l: number): Hsl; - new (color: string): Hsl; - - (h: number, s: number, l: number): Hsl; - (color: string): Hsl; - }; - - interface Hsl extends Color { - h: number; - s: number; - l: number; - - brighter(k?: number): Hsl; - darker(k?: number): Hsl; - - rgb(): Rgb; - - toString(): string; - } - - export var hcl: { - new (h: number, c: number, l: number): Hcl; - new (color: string): Hcl; - - (h: number, c: number, l: number): Hcl; - (color: string): Hcl; - }; - - interface Hcl extends Color { - h: number; - c: number; - l: number; - - brighter(k?: number): Hcl; - darker(k?: number): Hcl; - } - - export var lab: { - new (l: number, a: number, b: number): Lab; - new (color: string): Lab; - - (l: number, a: number, b: number): Lab; - (color: string): Lab; - } - - interface Lab extends Color { - l: number; - a: number; - b: number; - - brighter(k?: number): Lab; - darker(k?: number): Lab; - - rgb(): Rgb; - toString(): string; - } - - export var color: { - (): Color; - new (): Color; - }; - - interface Color { - rgb(): Rgb; - } - - export module ns { - interface Qualified { - space: string; - local: string; - } - - export var prefix: { [key: string]: string }; - export function qualify(name: string): Qualified | string; - } - - export function functor(value: T): T; - export function functor(value: T): () => T; - - export function rebind(target: {}, source: {}, ...names: string[]): any; - - export function dispatch(...names: string[]): Dispatch; - - interface Dispatch { - on(type: string): (...args: any[]) => void; - on(type: string, listener: (...args: any[]) => any): Dispatch; - [event: string]: (...args: any[]) => void; - } - - export module scale { - export function identity(): Identity; - - interface Identity { - (n: number): number; - invert(n: number): number; - - domain(): number[]; - domain(numbers: number[]): Identity; - - range(): number[]; - range(numbers: number[]): Identity; - - ticks(count?: number): number[]; - - tickFormat(count?: number, format?: string): (n: number) => string; - - copy(): Identity; - } - - export function linear(): Linear; - export function linear(): Linear; - export function linear(): Linear; - - interface Linear { - (x: number): Output; - invert(y: number): number; - - domain(): number[]; - domain(numbers: number[]): Linear; - - range(): Range[]; - range(values: Range[]): Linear; - - rangeRound(values: number[]): Linear; - - interpolate(): (a: Range, b: Range) => (t: number) => Output; - interpolate(factory: (a: Range, b: Range) => (t: number) => Output): Linear; - - clamp(): boolean; - clamp(clamp: boolean): Linear; - - nice(count?: number): Linear; - - ticks(count?: number): number[]; - - tickFormat(count?: number, format?: string): (n: number) => string; - - copy(): Linear; - } - - export function sqrt(): Pow; - export function sqrt(): Pow; - export function sqrt(): Pow; - - export function pow(): Pow; - export function pow(): Pow; - export function pow(): Pow; - - interface Pow { - (x: number): Output; - - invert(y: number): number; - - domain(): number[]; - domain(numbers: number[]): Pow; - - range(): Range[]; - range(values: Range[]): Pow; - - rangeRound(values: number[]): Pow; - - exponent(): number; - exponent(k: number): Pow; - - interpolate(): (a: Range, b: Range) => (t: number) => Output; - interpolate(factory: (a: Range, b: Range) => (t: number) => Output): Pow; - - clamp(): boolean; - clamp(clamp: boolean): Pow; - - nice(m?: number): Pow; - - ticks(count?: number): number[]; - - tickFormat(count?: number, format?: string): (n: number) => string; - - copy(): Pow; - } - - export function log(): Log; - export function log(): Log; - export function log(): Log; - - interface Log { - (x: number): Output; - - invert(y: number): number; - - domain(): number[]; - domain(numbers: number[]): Log; - - range(): Range[]; - range(values: Range[]): Log; - - rangeRound(values: number[]): Log; - - base(): number; - base(base: number): Log; - - interpolate(): (a: Range, b: Range) => (t: number) => Output; - interpolate(factory: (a: Range, b: Range) => (t: number) => Output): Log; - - clamp(): boolean; - clamp(clamp: boolean): Log; - - nice(): Log; - - ticks(): number[]; - - tickFormat(count?: number, format?: string): (t: number) => string; - - copy(): Log; - } - - export function quantize(): Quantize; - - interface Quantize { - (x: number): T; - - invertExtent(y: T): [number, number]; - - domain(): number[]; - domain(numbers: number[]): Quantize; - - range(): T[]; - range(values: T[]): Quantize; - - copy(): Quantize; - } - - export function quantile(): Quantile; - - interface Quantile { - (x: number): T; - - invertExtent(y: T): [number, number]; - - domain(): number[]; - domain(numbers: number[]): Quantile; - - range(): T[]; - range(values: T[]): Quantile; - - quantiles(): number[]; - - copy(): Quantile; - } - - export function threshold(): Threshold; - export function threshold(): Threshold; - - interface Threshold { - (x: number): Range; - - invertExtent(y: Range): [Domain, Domain]; - - domain(): Domain[]; - domain(domain: Domain[]): Threshold; - - range(): Range[]; - range(values: Range[]): Threshold; - - copy(): Threshold; - } - - export function ordinal(): Ordinal; - export function ordinal(): Ordinal; - export function category10(): Ordinal; - export function category10(): Ordinal; - export function category20(): Ordinal; - export function category20(): Ordinal; - export function category20b(): Ordinal; - export function category20b(): Ordinal; - export function category20c(): Ordinal; - export function category20c(): Ordinal; - - interface Ordinal { - (x: Domain): Range; - - domain(): Domain[]; - domain(values: Domain[]): Ordinal; - - range(): Range[]; - range(values: Range[]): Ordinal; - - rangePoints(interval: [number, number], padding?: number): Ordinal; - rangeRoundPoints(interval: [number, number], padding?: number): Ordinal; - - rangeBands(interval: [number, number], padding?: number, outerPadding?: number): Ordinal; - rangeRoundBands(interval: [number, number], padding?: number, outerPadding?: number): Ordinal; - - rangeBand(): number; - rangeExtent(): [number, number]; - - copy(): Ordinal; - } - } - - export function interpolate(a: number, b: number): (t: number) => number; - export function interpolate(a: string, b: string): (t: number) => string; - export function interpolate(a: string | Color, b: Color): (t: number) => string; - export function interpolate(a: Array, b: Color[]): (t: number) => string; - export function interpolate(a: Range[], b: Output[]): (t: number) => Output[]; - export function interpolate(a: Range[], b: Range[]): (t: number) => Output[]; - export function interpolate(a: { [key: string]: string | Color }, b: { [key: string]: Color }): (t: number) => { [key: string]: string }; - export function interpolate(a: { [key: string]: Range }, b: { [key: string]: Output }): (t: number) => { [key: string]: Output }; - export function interpolate(a: { [key: string]: Range }, b: { [key: string]: Range }): (t: number) => { [key: string]: Output }; - - export function interpolateNumber(a: number, b: number): (t: number) => number; - - export function interpolateRound(a: number, b: number): (t: number) => number; - - export function interpolateString(a: string, b: string): (t: number) => string; - - export function interpolateRgb(a: string | Color, b: string | Color): (t: number) => string; - - export function interpolateHsl(a: string | Color, b: string | Color): (t: number) => string; - - export function interpolateLab(a: string | Color, b: string | Color): (t: number) => string; - - export function interpolateHcl(a: string | Color, b: string | Color): (t: number) => string; - - export function interpolateArray(a: Array, b: Color[]): (t: number) => string[]; - export function interpolateArray(a: Range[], b: Range[]): (t: number) => Output[]; - export function interpolateArray(a: Range[], b: Output[]): (t: number) => Output[]; - - export function interpolateObject(a: { [key: string]: string | Color }, b: { [key: string]: Color }): (t: number) => { [key: string]: string }; - export function interpolateObject(a: { [key: string]: Range }, b: { [key: string]: Output }): (t: number) => { [key: string]: Output }; - export function interpolateObject(a: { [key: string]: Range }, b: { [key: string]: Range }): (t: number) => { [key: string]: Output }; - - export function interpolateTransform(a: string | Transform, b: string | Transform): (t: number) => string; - - export function interpolateZoom(a: [number, number, number], b: [number, number, number]): { - (t: number): [number, number, number]; - duration: number; - }; - - export var interpolators: Array<(a: any, b: any) => (t: number) => any>; - - export module time { - export var second: Interval; - export var minute: Interval; - export var hour: Interval; - export var day: Interval; - export var week: Interval; - export var sunday: Interval; - export var monday: Interval; - export var tuesday: Interval; - export var wednesday: Interval; - export var thursday: Interval; - export var friday: Interval; - export var saturday: Interval; - export var month: Interval; - export var year: Interval; - - interface Interval { - (d: Date): Date; - - floor(d: Date): Date; - - round(d: Date): Date; - - ceil(d: Date): Date; - - range(start: Date, stop: Date, step?: number): Date[]; - - offset(date: Date, step: number): Date; - - utc: { - (d: Date): Date; - - floor(d: Date): Date; - - round(d: Date): Date; - - ceil(d: Date): Date; - - range(start: Date, stop: Date, step?: number): Date[]; - - offset(date: Date, step: number): Date; - } - } - - export function seconds(start: Date, stop: Date, step?: number): Date[]; - export function minutes(start: Date, stop: Date, step?: number): Date[]; - export function hours(start: Date, stop: Date, step?: number): Date[]; - export function days(start: Date, stop: Date, step?: number): Date[]; - export function weeks(start: Date, stop: Date, step?: number): Date[]; - export function sundays(start: Date, stop: Date, step?: number): Date[]; - export function mondays(start: Date, stop: Date, step?: number): Date[]; - export function tuesdays(start: Date, stop: Date, step?: number): Date[]; - export function wednesdays(start: Date, stop: Date, step?: number): Date[]; - export function thursdays(start: Date, stop: Date, step?: number): Date[]; - export function fridays(start: Date, stop: Date, step?: number): Date[]; - export function saturdays(start: Date, stop: Date, step?: number): Date[]; - export function months(start: Date, stop: Date, step?: number): Date[]; - export function years(start: Date, stop: Date, step?: number): Date[]; - - export function dayOfYear(d: Date): number; - export function weekOfYear(d: Date): number; - export function sundayOfYear(d: Date): number; - export function mondayOfYear(d: Date): number; - export function tuesdayOfYear(d: Date): number; - export function wednesdayOfYear(d: Date): number; - export function fridayOfYear(d: Date): number; - export function saturdayOfYear(d: Date): number; - - export function format(specifier: string): Format; - - export module format { - export function multi(formats: Array<[string, (d: Date) => boolean|number]>): Format; - export function utc(specifier: string): Format; - namespace utc { - export function multi(formats: Array<[string, (d: Date) => boolean|number]>): Format; - } - - export var iso: Format; - } - - interface Format { - (d: Date): string; - parse(input: string): Date; - } - - export function scale(): Scale; - export function scale(): Scale; - export function scale(): Scale; - - export module scale { - export function utc(): Scale; - export function utc(): Scale; - export function utc(): Scale; - } - - - interface Scale { - (x: Date): Output; - - invert(y: number): Date; - - domain(): Date[]; - domain(dates: number[]): Scale; - domain(dates: Date[]): Scale; - - nice(): Scale; - nice(interval: Interval, step?: number): Scale; - - range(): Range[]; - range(values: Range[]): Scale; - - rangeRound(values: number[]): Scale; - - interpolate(): (a: Range, b: Range) => (t: number) => Output; - interpolate(factory: (a: Range, b: Range) => (t: number) => Output): Scale; - - clamp(): boolean; - clamp(clamp: boolean): Scale; - - ticks(): Date[]; - ticks(interval: Interval, step?: number): Date[]; - ticks(count: number): Date[]; - - tickFormat(count: number): (d: Date) => string; - - copy(): Scale; - } - } - - export module behavior { - export function drag(): Drag; - - interface Drag { - (selection: Selection): void; - - on(type: string): (d: Datum, i: number) => any; - on(type: string, listener: (d: Datum, i: number) => any): Drag; - - origin(): (d: Datum, i: number) => { x: number; y: number }; - origin(accessor: (d: Datum, i: number) => { x: number; y: number }): Drag; - } - - export function zoom(): Zoom; - - namespace zoom { - interface Scale { - domain(): number[]; - domain(values: number[]): Scale; - - invert(y: number): number; - - range(values: number[]): Scale; - range(): number[]; - } - } - - interface Zoom { - (selection: Selection): void; - - translate(): [number, number]; - translate(translate: [number, number]): Zoom; - - scale(): number; - scale(scale: number): Zoom; - - scaleExtent(): [number, number]; - scaleExtent(extent: [number, number]): Zoom; - - center(): [number, number]; - center(center: [number, number]): Zoom; - - size(): [number, number]; - size(size: [number, number]): Zoom; - - x(): zoom.Scale; - x(x: zoom.Scale): Zoom; - - y(): zoom.Scale; - y(y: zoom.Scale): Zoom; - - on(type: string): (d: Datum, i: number) => any; - on(type: string, listener: (d: Datum, i: number) => any): Zoom; - - event(selection: Selection): void; - event(transition: Transition): void; - } - } - - export module geo { - export function path(): Path; - - interface Path { - (feature: any, index?: number): string; - - area(feature: any): number; - - centroid(feature: any): [number, number]; - - bounds(feature: any): [[number, number], [number, number]]; - - projection(): Transform | ((coordinates: [number, number]) => [number, number]); - projection(stream: Transform): Path; - projection(projection: (coordinates: [number, number]) => [number, number]): Path; - - pointRadius(): number | ((datum: any, index: number) => number); - pointRadius(radius: number): Path; - pointRadius(radius: (datum: any, index: number) => number): Path; - - context(): CanvasRenderingContext2D; - context(context: CanvasRenderingContext2D): Path; - } - - export function graticule(): Graticule; - - interface Graticule { - (): any; - - lines(): any[]; - - outline(): any; - - extent(): [[number, number], [number, number]]; - extent(extent: [[number, number], [number, number]]): Graticule; - - majorExtent(): [[number, number], [number, number]]; - majorExtent(extent: [[number, number], [number, number]]): Graticule; - - minorExtent(): [[number, number], [number, number]]; - minorExtent(extent: [[number, number], [number, number]]): Graticule; - - step(): [number, number]; - step(step: [number, number]): Graticule; - - majorStep(): [number, number]; - majorStep(step: [number, number]): Graticule; - - minorStep(): [number, number]; - minorStep(step: [number, number]): Graticule; - - precision(): number; - precision(precision: number): Graticule; - } - - export function circle(): Circle; - - interface Circle { - (...args: any[]): any; - - origin(): [number, number] | ((...args: any[]) => [number, number]); - origin(origin: [number, number]): Circle; - origin(origin: (...args: any[]) => [number, number]): Circle; - - angle(): number; - angle(angle: number): Circle; - - precision(): number; - precision(precision: number): Circle; - } - - export function area(feature: any): number; - export function centroid(feature: any): [number, number]; - export function bounds(feature: any): [[number, number], [number, number]]; - export function distance(a: [number, number], b: [number, number]): number; - export function length(feature: any): number; - export function interpolate(a: [number, number], b: [number, number]): (t: number) => [number, number]; - - export function rotation(rotate: [number, number] | [number, number, number]): Rotation; - - interface Rotation { - (location: [number, number]): [number, number]; - invert(location: [number, number]): [number, number]; - } - - export function stream(object: any, listener: Listener): void; - - interface Listener { - point(x: number, y: number, z: number): void; - lineStart(): void; - lineEnd(): void; - polygonStart(): void; - polygonEnd(): void; - sphere(): void; - } - - export function transform(methods: TransformMethods): Transform; - - interface TransformMethods { - point?(x: number, y: number, z: number): void; - lineStart?(): void; - lineEnd?(): void; - polygonStart?(): void; - polygonEnd?(): void; - sphere?(): void; - } - - interface Transform { - stream(stream: Listener): Listener; - } - - export function clipExtent(): ClipExtent; - - interface ClipExtent extends Transform { - extent(): [[number, number], [number, number]]; - extent(extent: [[number, number], [number, number]]): ClipExtent; - } - - export function projection(raw: RawInvertibleProjection): InvertibleProjection; - export function projection(raw: RawProjection): Projection; - - export function projectionMutator(factory: (...args: any[]) => RawInvertibleProjection): (...args: any[]) => InvertibleProjection; - export function projectionMutator(factory: (...args: any[]) => RawProjection): (...args: any[]) => Projection; - - export function albers(): ConicProjection; - export function albersUsa(): ConicProjection; - export function azimuthalEqualArea(): InvertibleProjection; - namespace azimuthalEqualArea { - export function raw(lambda: number, phi: number): [number, number]; - namespace raw { - export function invert(x: number, y: number): [number, number]; - } - } - - export function azimuthalEquidistant(): InvertibleProjection; - namespace azimuthalEquidistant { - export function raw(lambda: number, phi: number): [number, number]; - namespace raw { - export function invert(x: number, y: number): [number, number]; - } - } - - export function conicConformal(): ConicProjection; - namespace conicConformal { - export function raw(phi0: number, phi1: number): RawInvertibleProjection; - } - - export function conicEqualArea(): ConicProjection; - namespace conicEqualArea { - export function raw(phi0: number, phi1: number): RawInvertibleProjection; - } - - export function conicEquidistant(): ConicProjection; - namespace conicEquidistant { - export function raw(phi0: number, phi1: number): RawInvertibleProjection; - } - - export function equirectangular(): InvertibleProjection; - namespace equirectangular { - export function raw(lambda: number, phi: number): [number, number]; - namespace raw { - export function invert(x: number, y: number): [number, number]; - } - } - - export function gnomonic(): InvertibleProjection; - namespace gnomonic { - export function raw(lambda: number, phi: number): [number, number]; - namespace raw { - export function invert(x: number, y: number): [number, number]; - } - } - - export function mercator(): InvertibleProjection; - namespace mercator { - export function raw(lambda: number, phi: number): [number, number]; - namespace raw { - export function invert(x: number, y: number): [number, number]; - } - } - - export function orthographic(): InvertibleProjection; - namespace orthographic { - export function raw(lambda: number, phi: number): [number, number]; - namespace raw { - export function invert(x: number, y: number): [number, number]; - } - } - - export function stereographic(): InvertibleProjection; - namespace stereographic { - export function raw(lambda: number, phi: number): [number, number]; - namespace raw { - export function invert(x: number, y: number): [number, number]; - } - } - - export function transverseMercator(): InvertibleProjection; - namespace transverseMercator { - export function raw(lambda: number, phi: number): [number, number]; - namespace raw { - export function invert(x: number, y: number): [number, number]; - } - } - - interface Projection { - (location: [number, number]): [number, number]; - - rotate(): [number, number, number]; - rotate(rotation: [number, number, number]): Projection; - - center(): [number, number]; - center(location: [number, number]): Projection; - - translate(): [number, number]; - translate(point: [number, number]): Projection; - - scale(): number; - scale(scale: number): Projection; - - clipAngle(): number; - clipAngle(angle: number): Projection; - - clipExtent(): [[number, number], [number, number]]; - clipExtent(extent: [[number, number], [number, number]]): Projection; - - precision(): number; - precision(precision: number): Projection; - - stream(listener: Listener): Listener; - } - - interface InvertibleProjection extends Projection { - invert(point: [number, number]): [number, number]; - } - - interface ConicProjection extends InvertibleProjection { - parallels(): [number, number]; - parallels(parallels: [number, number]): ConicProjection; - - rotate(): [number, number, number]; - rotate(rotation: [number, number, number]): ConicProjection; - - center(): [number, number]; - center(location: [number, number]): ConicProjection; - - translate(): [number, number]; - translate(point: [number, number]): ConicProjection; - - scale(): number; - scale(scale: number): ConicProjection; - - clipAngle(): number; - clipAngle(angle: number): ConicProjection; - - clipExtent(): [[number, number], [number, number]]; - clipExtent(extent: [[number, number], [number, number]]): ConicProjection; - - precision(): number; - precision(precision: number): ConicProjection; - } - - interface RawProjection { - (lambda: number, phi: number): [number, number]; - } - - interface RawInvertibleProjection extends RawProjection { - invert(x: number, y: number): [number, number]; - } - } - - namespace svg { - export function line(): Line<[number, number]>; - export function line(): Line; - - interface Line { - (data: T[]): string; - - x(): number | ((d: T, i: number) => number); - x(x: number): Line; - x(x: (d: T, i: number) => number): Line; - - y(): number | ((d: T, i: number) => number); - y(x: number): Line; - y(y: (d: T, i: number) => number): Line; - - interpolate(): string | ((points: Array<[number, number]>) => string); - interpolate(interpolate: "linear"): Line; - interpolate(interpolate: "linear-closed"): Line; - interpolate(interpolate: "step"): Line; - interpolate(interpolate: "step-before"): Line; - interpolate(interpolate: "step-after"): Line; - interpolate(interpolate: "basis"): Line; - interpolate(interpolate: "basis-open"): Line; - interpolate(interpolate: "basis-closed"): Line; - interpolate(interpolate: "bundle"): Line; - interpolate(interpolate: "cardinal"): Line; - interpolate(interpolate: "cardinal-open"): Line; - interpolate(interpolate: "cardinal-closed"): Line; - interpolate(interpolate: "monotone"): Line; - interpolate(interpolate: string | ((points: Array<[number, number]>) => string)): Line; - - tension(): number; - tension(tension: number): Line; - - defined(): (d: T, i: number) => boolean; - defined(defined: (d: T, i: number) => boolean): Line; - } - - namespace line { - export function radial(): Radial<[number, number]>; - export function radial(): Radial; - - interface Radial { - (data: T[]): string; - - radius(): number | ((d: T, i: number) => number); - radius(radius: number): Radial; - radius(radius: (d: T, i: number) => number): Radial; - - angle(): number | ((d: T, i: number) => number); - angle(angle: number): Radial; - angle(angle: (d: T, i: number) => number): Radial; - - interpolate(): string | ((points: Array<[number, number]>) => string); - interpolate(interpolate: "linear"): Radial; - interpolate(interpolate: "linear-closed"): Radial; - interpolate(interpolate: "step"): Radial; - interpolate(interpolate: "step-before"): Radial; - interpolate(interpolate: "step-after"): Radial; - interpolate(interpolate: "basis"): Radial; - interpolate(interpolate: "basis-open"): Radial; - interpolate(interpolate: "basis-closed"): Radial; - interpolate(interpolate: "bundle"): Radial; - interpolate(interpolate: "cardinal"): Radial; - interpolate(interpolate: "cardinal-open"): Radial; - interpolate(interpolate: "cardinal-closed"): Radial; - interpolate(interpolate: "monotone"): Radial; - interpolate(interpolate: string | ((points: Array<[number, number]>) => string)): Radial; - - tension(): number; - tension(tension: number): Radial; - - defined(): (d: T, i: number) => boolean; - defined(defined: (d: T, i: number) => boolean): Radial; - } - } - - export function area(): Area<[number, number]>; - export function area(): Area; - - interface Area { - (data: T[]): string; - - x(): number | ((d: T, i: number) => number); - x(x: number): Area; - x(x: (d: T, i: number) => number): Area; - - x0(): number | ((d: T, i: number) => number); - x0(x0: number): Area; - x0(x0: (d: T, i: number) => number): Area; - - x1(): number | ((d: T, i: number) => number); - x1(x1: number): Area; - x1(x1: (d: T, i: number) => number): Area; - - y(): number | ((d: T, i: number) => number); - y(y: number): Area; - y(y: (d: T, i: number) => number): Area; - - y0(): number | ((d: T, i: number) => number); - y0(y0: number): Area; - y0(y0: (d: T, i: number) => number): Area; - - y1(): number | ((d: T, i: number) => number); - y1(y1: number): Area; - y1(y1: (d: T, i: number) => number): Area; - - interpolate(): string | ((points: Array<[number, number]>) => string); - interpolate(interpolate: "linear"): Area; - interpolate(interpolate: "step"): Area; - interpolate(interpolate: "step-before"): Area; - interpolate(interpolate: "step-after"): Area; - interpolate(interpolate: "basis"): Area; - interpolate(interpolate: "basis-open"): Area; - interpolate(interpolate: "cardinal"): Area; - interpolate(interpolate: "cardinal-open"): Area; - interpolate(interpolate: "monotone"): Area; - interpolate(interpolate: string | ((points: Array<[number, number]>) => string)): Area; - - tension(): number; - tension(tension: number): Area; - - defined(): (d: T, i: number) => boolean; - defined(defined: (d: T, i: number) => boolean): Area; - } - - namespace area { - export function radial(): Radial<[number, number]>; - export function radial(): Radial; - - interface Radial { - (data: T[]): string; - - radius(): number | ((d: T, i: number) => number); - radius(radius: number): Radial; - radius(radius: (d: T, i: number) => number): Radial; - - innerRadius(): number | ((d: T, i: number) => number); - innerRadius(innerRadius: number): Radial; - innerRadius(innerRadius: (d: T, i: number) => number): Radial; - - outerRadius(): number | ((d: T, i: number) => number); - outerRadius(outerRadius: number): Radial; - outerRadius(outerRadius: (d: T, i: number) => number): Radial; - - angle(): number | ((d: T, i: number) => number); - angle(angle: number): Radial; - angle(angle: (d: T, i: number) => number): Radial; - - startAngle(): number | ((d: T, i: number) => number); - startAngle(startAngle: number): Radial; - startAngle(startAngle: (d: T, i: number) => number): Radial; - - endAngle(): number | ((d: T, i: number) => number); - endAngle(endAngle: number): Radial; - endAngle(endAngle: (d: T, i: number) => number): Radial; - - interpolate(): string | ((points: Array<[number, number]>) => string); - interpolate(interpolate: "linear"): Radial; - interpolate(interpolate: "step"): Radial; - interpolate(interpolate: "step-before"): Radial; - interpolate(interpolate: "step-after"): Radial; - interpolate(interpolate: "basis"): Radial; - interpolate(interpolate: "basis-open"): Radial; - interpolate(interpolate: "cardinal"): Radial; - interpolate(interpolate: "cardinal-open"): Radial; - interpolate(interpolate: "monotone"): Radial; - interpolate(interpolate: string | ((points: Array<[number, number]>) => string)): Radial; - - tension(): number; - tension(tension: number): Radial; - - defined(): (d: T, i: number) => boolean; - defined(defined: (d: T, i: number) => boolean): Radial; - } - } - - export function arc(): Arc; - export function arc(): Arc; - - namespace arc { - interface Arc { - innerRadius: number; - outerRadius: number; - startAngle: number; - endAngle: number; - padAngle: number - } - } - - interface Arc { - (d: T, i?: number): string; - - innerRadius(): (d: T, i: number) => number; - innerRadius(radius: number): Arc; - innerRadius(radius: (d: T, i: number) => number): Arc; - - outerRadius(): (d: T, i: number) => number; - outerRadius(radius: number): Arc; - outerRadius(radius: (d: T, i: number) => number): Arc; - - cornerRadius(): (d: T, i: number) => number; - cornerRadius(radius: number): Arc; - cornerRadius(radius: (d: T, i: number) => number): Arc; - - padRadius(): string | ((d: T, i: number) => number); - padRadius(radius: "auto"): Arc; - padRadius(radius: string): Arc; - padRadius(radius: (d: T, i: number) => number): Arc; - - startAngle(): (d: T, i: number) => number; - startAngle(angle: number): Arc; - startAngle(angle: (d: T, i: number) => number): Arc; - - endAngle(): (d: T, i: number) => number; - endAngle(angle: number): Arc; - endAngle(angle: (d: T, i: number) => number): Arc; - - padAngle(): (d: T, i: number) => number; - padAngle(angle: number): Arc; - padAngle(angle: (d: T, i: number) => number): Arc; - - centroid(d: T, i?: number): [number, number]; - } - - export function symbol(): Symbol<{}>; - export function symbol(): Symbol; - - interface Symbol { - (d: T, i?: number): string; - - type(): (d: T, i: number) => string; - type(type: string): Symbol; - type(type: (d: T, i: number) => string): Symbol; - - size(): (d: T, i: string) => number; - size(size: number): Symbol; - size(size: (d: T, i: number) => number): Symbol; - } - - export var symbolTypes: string[]; - - export function chord(): Chord, chord.Node>; - export function chord(): Chord, Node>; - export function chord(): Chord; - - namespace chord { - interface Link { - source: Node; - target: Node; - } - - interface Node { - radius: number; - startAngle: number; - endAngle: number - } - } - - interface Chord { - (d: Link, i: number): string; - - source(): (d: Link, i: number) => Node; - source(source: Node): Chord; - source(source: (d: Link, i: number) => Node): Chord; - - target(): (d: Link, i: number) => Node; - target(target: Node): Chord; - target(target: (d: Link, i: number) => Node): Chord; - - radius(): (d: Node, i: number) => number; - radius(radius: number): Chord; - radius(radius: (d: Node, i: number) => number): Chord; - - startAngle(): (d: Node, i: number) => number; - startAngle(angle: number): Chord; - startAngle(angle: (d: Node, i: number) => number): Chord; - - endAngle(): (d: Node, i: number) => number; - endAngle(angle: number): Chord; - endAngle(angle: (d: Node, i: number) => number): Chord; - } - - export function diagonal(): Diagonal, diagonal.Node>; - export function diagonal(): Diagonal, Node>; - export function diagonal(): Diagonal; - - namespace diagonal { - interface Link { - source: Node; - target: Node; - } - - interface Node { - x: number; - y: number; - } - } - - interface Diagonal { - (d: Link, i?: number): string; - - source(): (d: Link, i: number) => Node; - source(source: Node): Diagonal; - source(source: (d: Link, i: number) => { x: number; y: number; }): Diagonal; - - target(): (d: Link, i: number) => Node; - target(target: Node): Diagonal; - target(target: (d: Link, i: number) => { x: number; y: number; }): Diagonal; - - projection(): (d: Node, i: number) => [number, number]; - projection(projection: (d: Node, i: number) => [number, number]): Diagonal; - } - - namespace diagonal { - export function radial(): Radial, Node>; - export function radial(): Radial, Node>; - export function radial(): Radial; - - interface Radial { - (d: Link, i: number): string; - - source(): (d: Link, i: number) => Node; - source(source: Node): Radial; - source(source: (d: Link, i: number) => Node): Radial; - - target(): (d: Link, i: number) => Node; - target(target: Node): Radial; - target(target: (d: Link, i: number) => Node): Radial; - - projection(): (d: Node, i: number) => [number, number]; - projection(projection: (d: Node, i: number) => [number, number]): Radial; - } - } - - export function axis(): Axis; - - interface Axis { - (selection: Selection): void; - (selection: Transition): void; - - scale(): any; - scale(scale: any): Axis; - - orient(): string; - orient(orientation: string): Axis; - - ticks(): any[]; - ticks(...args: any[]): Axis; - - tickValues(): any[]; - tickValues(values: any[]): Axis; - - tickSize(): number; - tickSize(size: number): Axis; - tickSize(inner: number, outer: number): Axis; - - innerTickSize(): number; - innerTickSize(size: number): Axis; - - outerTickSize(): number; - outerTickSize(size: number): Axis; - - tickPadding(): number; - tickPadding(padding: number): Axis; - - tickFormat(): (t: any) => string; - tickFormat(format: (t: any) => string): Axis; - tickFormat(format:string): Axis; - } - - export function brush(): Brush; - export function brush(): Brush; - - namespace brush { - interface Scale { - domain(): number[]; - domain(domain: number[]): Scale; - - range(): number[]; - range(range: number[]): Scale; - - invert?(y: number): number; - } - } - - interface Brush { - (selection: Selection): void; - (selection: Transition): void; - - event(selection: Selection): void; - event(selection: Transition): void; - - x(): brush.Scale; - x(x: brush.Scale): Brush; - - y(): brush.Scale; - y(y: brush.Scale): Brush; - - extent(): [number, number] | [[number, number], [number, number]]; - extent(extent: [number, number] | [[number, number], [number, number]]): Brush; - - clamp(): boolean | [boolean, boolean]; - clamp(clamp: boolean | [boolean, boolean]): Brush; - - clear(): void; - - empty(): boolean; - - on(type: 'brushstart'): (datum: T, index: number) => void; - on(type: 'brush'): (datum: T, index: number) => void; - on(type: 'brushend'): (datum: T, index: number) => void; - on(type: string): (datum: T, index: number) => void; - - on(type: 'brushstart', listener: (datum: T, index: number) => void): Brush; - on(type: 'brush', listener: (datum: T, index: number) => void): Brush; - on(type: 'brushend', listener: (datum: T, index: number) => void): Brush; - on(type: string, listener: (datum: T, index: number) => void): Brush; - } - } - - export function xhr(url: string, mimeType?: string, callback?: (err: any, data: any) => void): Xhr; - export function xhr(url: string, callback: (err: any, data: any) => void): Xhr; - - interface Xhr { - header(name: string): string; - header(name: string, value: string): Xhr; - - mimeType(): string; - mimeType(type: string): Xhr; - - responseType(): string; - responseType(type: string): Xhr; - - response(): (request: XMLHttpRequest) => any; - response(value: (request: XMLHttpRequest) => any): Xhr; - - get(callback?: (err: any, data: any) => void): Xhr; - - post(data?: any, callback?: (err: any, data: any) => void): Xhr; - post(callback: (err: any, data: any) => void): Xhr; - - send(method: string, data?: any, callback?: (err: any, data: any) => void): Xhr; - send(method: string, callback: (err: any, data: any) => void): Xhr; - - abort(): Xhr; - - on(type: "beforesend"): (request: XMLHttpRequest) => void; - on(type: "progress"): (request: XMLHttpRequest) => void; - on(type: "load"): (response: any) => void; - on(type: "error"): (err: any) => void; - on(type: string): (...args: any[]) => void; - - on(type: "beforesend", listener: (request: XMLHttpRequest) => void): Xhr; - on(type: "progress", listener: (request: XMLHttpRequest) => void): Xhr; - on(type: "load", listener: (response: any) => void): Xhr; - on(type: "error", listener: (err: any) => void): Xhr; - on(type: string, listener: (...args: any[]) => void): Xhr; - } - - export function text(url: string, mimeType?: string, callback?: (err: any, data: string) => void): Xhr; - export function text(url: string, callback: (err: any, data: string) => void): Xhr; - - export function json(url: string, callback?: (err: any, data: any) => void): Xhr; - - export function xml(url: string, mimeType?: string, callback?: (err: any, data: any) => void): Xhr; - export function xml(url: string, callback: (err: any, data: any) => void): Xhr; - - export function html(url: string, callback?: (err: any, data: DocumentFragment) => void): Xhr; - - export var csv: Dsv; - export var tsv: Dsv; - export function dsv(delimiter: string, mimeType: string): Dsv; - - interface Dsv { - (url: string, callback: (rows: { [key: string]: string }[]) => void): DsvXhr<{ [key: string]: string }>; - (url: string, callback: (error: any, rows: { [key: string]: string }[]) => void): DsvXhr<{ [key: string]: string }>; - (url: string): DsvXhr<{ [key: string]: string }>; - (url: string, accessor: (row: { [key: string]: string }) => T, callback: (rows: T[]) => void): DsvXhr; - (url: string, accessor: (row: { [key: string]: string }) => T, callback: (error: any, rows: T[]) => void): DsvXhr; - (url: string, accessor: (row: { [key: string]: string }) => T): DsvXhr; - - parse(string: string): { [key: string]: string }[]; - parse(string: string, accessor: (row: { [key: string]: string }, index: number) => T): T[]; - - parseRows(string: string): string[][]; - parseRows(string: string, accessor: (row: string[], index: number) => T): T[]; - - format(rows: Object[]): string; - - formatRows(rows: string[][]): string; - } - - interface DsvXhr extends Xhr { - row(): (row: { [key: string]: string }) => T; - row(accessor: (row: { [key: string]: string }) => U): DsvXhr; - - header(name: string): string; - header(name: string, value: string): DsvXhr; - - mimeType(): string; - mimeType(type: string): DsvXhr; - - responseType(): string; - responseType(type: string): DsvXhr; - - response(): (request: XMLHttpRequest) => any; - response(value: (request: XMLHttpRequest) => any): DsvXhr; - - get(callback?: (err: XMLHttpRequest, data: T[]) => void): DsvXhr; - post(data?: any, callback?: (err: XMLHttpRequest, data: T[]) => void): DsvXhr; - post(callback: (err: XMLHttpRequest, data: T[]) => void): DsvXhr; - - send(method: string, data?: any, callback?: (err: XMLHttpRequest, data: T[]) => void): DsvXhr; - send(method: string, callback: (err: XMLHttpRequest, data: T[]) => void): DsvXhr; - - abort(): DsvXhr; - - on(type: "beforesend"): (request: XMLHttpRequest) => void; - on(type: "progress"): (request: XMLHttpRequest) => void; - on(type: "load"): (response: T[]) => void; - on(type: "error"): (err: any) => void; - on(type: string): (...args: any[]) => void; - - on(type: "beforesend", listener: (request: XMLHttpRequest) => void): DsvXhr; - on(type: "progress", listener: (request: XMLHttpRequest) => void): DsvXhr; - on(type: "load", listener: (response: T[]) => void): DsvXhr; - on(type: "error", listener: (err: any) => void): DsvXhr; - on(type: string, listener: (...args: any[]) => void): DsvXhr; - } - - export function locale(definition: LocaleDefinition): Locale; - - interface LocaleDefinition { - decimal: string; - thousands: string; - grouping: number[]; - currency: [string, string]; - dateTime: string; - date: string; - time: string; - periods: [string, string]; - days: [string, string, string, string, string, string, string]; - shortDays: [string, string, string, string, string, string, string]; - months: [string, string, string, string, string, string, string, string, string, string, string, string]; - shortMonths: [string, string, string, string, string, string, string, string, string, string, string, string]; - } - - interface Locale { - numberFormat(specifier: string): (n: number) => string; - timeFormat: { - (specifier: string): time.Format; - utc(specifier: string): time.Format; - multi(formats: Array<[string, (d: Date) => boolean|number]>): time.Format; - } - } - - namespace layout { - export function bundle(): Bundle; - export function bundle(): Bundle - - namespace bundle { - interface Node { - parent: Node; - } - - interface Link { - source: T; - target: T; - } - } - - interface Bundle { - (links: bundle.Link[]): T[][]; - } - - export function chord(): Chord; - - namespace chord { - interface Link { - source: Node; - target: Node; - } - - interface Node { - index: number; - subindex: number; - startAngle: number; - endAngle: number; - value: number; - } - - interface Group { - index: number; - startAngle: number; - endAngle: number; - value: number; - } - } - - interface Chord { - matrix(): number[][]; - matrix(matrix: number[][]): Chord; - - padding(): number; - padding(padding: number): Chord; - - sortGroups(): (a: number, b: number) => number; - sortGroups(comparator: (a: number, b: number) => number): Chord; - - sortSubgroups(): (a: number, b: number) => number; - sortSubgroups(comparator: (a: number, b: number) => number): Chord; - - sortChords(): (a: number, b: number) => number; - sortChords(comparator: (a: number, b: number) => number): Chord; - - chords(): chord.Link[]; - groups(): chord.Group[]; - } - - export function cluster(): Cluster; - export function cluster(): Cluster; - - namespace cluster { - interface Result { - parent?: Result; - children?: Result[]; - depth?: number; - x?: number; - y?: number; - } - - interface Link { - source: T; - target: T; - } - } - - interface Cluster { - (root: T): T[]; - - nodes(root: T): T[]; - - links(nodes: T[]): cluster.Link[]; - - children(): (node: T) => T[]; - children(accessor: (node: T) => T[]): Cluster; - - sort(): (a: T, b: T) => number; - sort(comparator: (a: T, b: T) => number): Cluster; - - separation(): (a: T, b: T) => number; - separation(separation: (a: T, b: T) => number): Cluster; - - size(): [number, number]; - size(size: [number, number]): Cluster; - - nodeSize(): [number, number]; - nodeSize(nodeSize: [number, number]): Cluster; - - value(): (a: T) => number; - value(value: (a: T) => number): Cluster; - } - - export function force(): Force, force.Node>; - export function force(): Force, Node>; - export function force, Node extends force.Node>(): Force; - - namespace force { - interface Link { - source: T; - target: T; - } - - interface Node { - index?: number; - x?: number; - y?: number; - px?: number; - py?: number; - fixed?: boolean; - weight?: number; - } - - interface Event { - type: string; - alpha: number; - } - } - - interface Force, Node extends force.Node> { - size(): [number, number]; - size(size: [number, number]): Force; - - linkDistance(): number | ((link: Link, index: number) => number); - linkDistance(distance: number): Force; - linkDistance(distance: (link: Link, index: number) => number): Force; - - linkStrength(): number | ((link: Link, index: number) => number); - linkStrength(strength: number): Force; - linkStrength(strength: (link: Link, index: number) => number): Force; - - friction(): number; - friction(friction: number): Force; - - charge(): number | ((node: Node, index: number) => number); - charge(charge: number): Force; - charge(charge: (node: Node, index: number) => number): Force; - - chargeDistance(): number; - chargeDistance(distance: number): Force; - - theta(): number; - theta(theta: number): Force; - - gravity(): number; - gravity(gravity: number): Force; - - nodes(): Node[]; - nodes(nodes: Node[]): Force; - - links(): Link[]; - links(links: { source: number; target: number }[]): Force; - links(links: Link[]): Force; - - start(): Force; - - alpha(): number; - alpha(value: number): Force; - - resume(): Force; - - stop(): Force; - - on(type: string): (event: force.Event) => void; - on(type: string, listener: (event: force.Event) => void): Force; - - drag(): behavior.Drag; - drag(selection: Selection): void; - } - - export function hierarchy(): Hierarchy; - export function hierarchy(): Hierarchy; - - namespace hierarchy { - interface Result { - parent?: Result; - children?: Result[]; - value?: number; - depth?: number; - } - } - - interface Hierarchy { - (root: T): T[]; - - children(): (node: T) => T[]; - children(accessor: (node: T) => T[]): Hierarchy; - - sort(): (a: T, b: T) => number; - sort(comparator: (a: T, b: T) => number): Hierarchy; - - value(): (node: T) => number; - value(accessor: (node: T) => number): Hierarchy; - - revalue(root: T): T[]; - } - - export function histogram(): Histogram; - export function histogram(): Histogram; - - namespace histogram { - interface Bin extends Array { - x: number; - dx: number; - y: number; - } - } - - interface Histogram { - (values: T[], index?: number): histogram.Bin[]; - - value(): (datum: T, index: number) => number; - value(value: (datum: T, index: number) => number): Histogram; - - range(): (values: T[], index: number) => [number, number]; - range(range: (values: T[], index: number) => [number, number]): Histogram; - - bins(): (range: [number, number], values: T[], index: number) => number[]; - bins(count: number): Histogram; - bins(thresholds: number[]): Histogram; - bins(func: (range: [number, number], values: T[], index: number) => number[]): Histogram; - - frequency(): boolean; - frequency(frequency: boolean): Histogram; - } - - export function pack(): Pack; - export function pack(): Pack; - - namespace pack { - interface Node { - parent?: Node; - children?: Node[]; - value?: number; - depth?: number; - x?: number; - y?: number; - r?: number; - } - - interface Link { - source: Node; - target: Node; - } - } - - interface Pack { - (root: T): T[]; - - nodes(root: T): T[]; - - links(nodes: T[]): pack.Link[]; - - children(): (node: T, depth: number) => T[]; - children(children: (node: T, depth: number) => T[]): Pack; - - sort(): (a: T, b: T) => number; - sort(comparator: (a: T, b: T) => number): Pack; - - value(): (node: T) => number; - value(value: (node: T) => number): Pack; - - size(): [number, number]; - size(size: [number, number]): Pack; - - radius(): number | ((node: T) => number); - radius(radius: number): Pack; - radius(radius: (node: T) => number): Pack; - - padding(): number; - padding(padding: number): Pack; - } - - export function partition(): Partition; - export function partition(): Partition; - - namespace partition { - interface Link { - source: T; - target: T; - } - - interface Node { - parent?: Node; - children?: number; - value?: number; - depth?: number; - x?: number; - y?: number; - dx?: number; - dy?: number; - } - - } - - export interface Partition { - (root: T): T[]; - - nodes(root: T): T[]; - - links(nodes: T[]): partition.Link[]; - - children(): (node: T, depth: number) => T[]; - children(children: (node: T, depth: number) => T[]): Partition; - - sort(): (a: T, b: T) => number; - sort(comparator: (a: T, b: T) => number): Partition; - - value(): (node: T) => number; - value(value: (node: T) => number): Partition; - - size(): [number, number]; - size(size: [number, number]): Partition; - } - - export function pie(): Pie; - export function pie(): Pie; - - namespace pie { - interface Arc { - value: number; - startAngle: number; - endAngle: number; - padAngle: number; - data: T; - } - } - - interface Pie { - (data: T[], index?: number): pie.Arc[]; - - value(): (datum: T, index: number) => number; - value(accessor: (datum: T, index: number) => number): Pie; - - sort(): (a: T, b: T) => number; - sort(comparator: (a: T, b: T) => number): Pie; - - startAngle(): number | ((data: T[], index: number) => number); - startAngle(angle: number): Pie; - startAngle(angle: (data: T[], index: number) => number): Pie; - - endAngle(): number | ((data: T[], index: number) => number); - endAngle(angle: number): Pie; - endAngle(angle: (data: T[], index: number) => number): Pie; - - padAngle(): number | ((data: T[], index: number) => number); - padAngle(angle: number): Pie; - padAngle(angle: (data: T[], index: number) => number): Pie; - } - - export function stack(): Stack; - export function stack(): Stack; - export function stack(): Stack; - namespace stack { - interface Value { - x: number; - y: number; - y0?: number; - } - } - - interface Stack { - (layers: Series[], index?: number): Series[]; - - values(): (layer: Series, index: number) => Value[]; - values(accessor: (layer: Series, index: number) => Value[]): Stack; - - offset(): (data: Array<[number, number]>) => number[]; - offset(offset: "silhouette"): Stack; - offset(offset: "wiggle"): Stack; - offset(offset: "expand"): Stack; - offset(offset: "zero"): Stack; - offset(offset: string): Stack; - offset(offset: (data: Array<[number, number]>) => number[]): Stack; - - order(): (data: Array<[number, number]>) => number[]; - order(order: "inside-out"): Stack; - order(order: "reverse"): Stack; - order(order: "default"): Stack; - order(order: string): Stack; - order(order: (data: Array<[number, number]>) => number[]): Stack; - - x(): (value: Value, index: number) => number; - x(accessor: (value: Value, index: number) => number): Stack; - - y(): (value: Value, index: number) => number; - y(accesor: (value: Value, index: number) => number): Stack; - - out(): (value: Value, y0: number, y: number) => void; - out(setter: (value: Value, y0: number, y: number) => void): Stack; - } - - export function tree(): Tree; - export function tree(): Tree; - - namespace tree { - interface Link { - source: T; - target: T; - } - - interface Node { - parent?: Node; - children?: Node[]; - depth?: number; - x?: number; - y?: number; - } - } - - interface Tree { - (root: T, index?: number): T[]; - - nodes(root: T, index?: number): T[]; - - links(nodes: T[]): tree.Link[]; - - children(): (datum: T, index: number) => T[]; - children(children: (datum: T, index: number) => T[]): Tree; - - separation(): (a: T, b: T) => number; - separation(separation: (a: T, b: T) => number): Tree; - - size(): [number, number]; - size(size: [number, number]): Tree; - - nodeSize(): [number, number]; - nodeSize(size: [number, number]): Tree; - - sort(): (a: T, b: T) => number; - sort(comparator: (a: T, b: T) => number): Tree; - - value(): (datum: T, index: number) => number; - value(value: (datum: T, index: number) => number): Tree; - } - - export function treemap(): Treemap; - export function treemap(): Treemap; - - namespace treemap { - interface Node { - parent?: Node; - children?: Node[]; - value?: number; - depth?: number; - x?: number; - y?: number; - dx?: number; - dy?: number; - } - - interface Link { - source: T; - target: T; - } - - type Padding = number | [number, number, number, number]; - } - - interface Treemap { - (root: T, index?: number): T[]; - - nodes(root: T, index?: number): T[]; - - links(nodes: T[]): treemap.Link[]; - - children(): (node: T, depth: number) => T[]; - children(children: (node: T, depth: number) => T[]): Treemap; - - sort(): (a: T, b: T) => number; - sort(comparator: (a: T, b: T) => number): Treemap; - - value(): (node: T, index: number) => number; - value(value: (node: T, index: number) => number): Treemap; - - size(): [number, number]; - size(size: [number, number]): Treemap; - - padding(): (node: T, depth: number) => treemap.Padding; - padding(padding: treemap.Padding): Treemap; - padding(padding: (node: T, depth: number) => treemap.Padding): Treemap; - - round(): boolean; - round(round: boolean): Treemap; - - sticky(): boolean; - sticky(sticky: boolean): boolean; - - mode(): string; - mode(mode: "squarify"): Treemap; - mode(mode: "slice"): Treemap; - mode(mode: "dice"): Treemap; - mode(mode: "slice-dice"): Treemap; - mode(mode: string): Treemap; - - ratio(): number; - ratio(ratio: number): Treemap; - } - } - - namespace geom { - export function voronoi(): Voronoi<[number, number]>; - export function voronoi(): Voronoi; - - namespace voronoi { - interface Link { - source: T; - target: T; - } - } - - interface Voronoi { - (data: T[]): Array<[number, number]>; - - x(): (vertex: T) => number; - x(x: (vertex: T) => number): Voronoi; - - y(): (vertex: T) => number; - y(y: (vertex: T) => number): Voronoi; - - clipExtent(): [[number, number], [number, number]]; - clipExtent(extent: [[number, number], [number, number]]): Voronoi; - - links(data: T[]): voronoi.Link[]; - - triangles(data: T[]): Array<[T, T, T]>; - } - - /** - * @deprecated use d3.geom.voronoi().triangles() instead - */ - export function delaunay(vertices: Array<[number, number]>): Array<[[number, number], [number, number], [number, number]]>; - - export function quadtree(): Quadtree<[number, number]>; - export function quadtree(nodes: T[], x1?: number, y1?: number, x2?: number, y2?: number): quadtree.Quadtree; - - namespace quadtree { - interface Node { - nodes: [Node, Node, Node, Node]; - leaf: boolean; - point: T; - x: number; - y: number; - } - - interface Quadtree extends Node { - add(point: T): void; - visit(callback: (node: Node, x1: number, y1: number, x2: number, y2: number) => boolean | void): void; - find(point: [number, number]): T; - } - } - - interface Quadtree { - (points: T[]): quadtree.Quadtree; - - x(): (datum: T, index: number) => number; - x(x: number): Quadtree; - x(x: (datum: T, index: number) => number): Quadtree; - - y(): (datum: T, index: number) => number; - y(y: number): Quadtree; - y(y: (datum: T, index: number) => number): Quadtree; - - extent(): [[number, number], [number, number]]; - extent(extent: [[number, number], [number, number]]): Quadtree; - } - - export function hull(vertices: Array<[number, number]>): Array<[number, number]>; - export function hull(): Hull<[number, number]>; - export function hull(): Hull; - - interface Hull { - (vertices: T[]): Array<[number, number]>; - - x(): (datum: T) => number; - x(x: (datum: T) => number): Hull; - - y(): (datum: T) => number; - y(y: (datum: T) => number): Hull; - } - - export function polygon(vertices: Array<[number, number]>): Polygon; - - interface Polygon { - area(): number; - - centroid(): [number, number]; - - clip(subject: Array<[number, number]>): Array<[number, number]>; - } - } -} - -// we need this to exist -interface TouchList { } - -declare module 'd3' { - export = d3; -} diff --git a/src/AccountGoWeb/typings/react/react-dom.d.ts b/src/AccountGoWeb/typings/react/react-dom.d.ts deleted file mode 100644 index 1f6ca6efd..000000000 --- a/src/AccountGoWeb/typings/react/react-dom.d.ts +++ /dev/null @@ -1,75 +0,0 @@ -// Type definitions for React v0.14 (react-dom) -// Project: http://facebook.github.io/react/ -// Definitions by: Asana , AssureSign , Microsoft -// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped - -/// - -declare namespace __React { - namespace __DOM { - function findDOMNode(instance: ReactInstance): E; - function findDOMNode(instance: ReactInstance): Element; - - function render

    ( - element: DOMElement, - container: Element, - callback?: (element: T) => any): T; - function render

    ( - element: SFCElement

    , - container: Element, - callback?: () => any): void; - function render>( - element: CElement, - container: Element, - callback?: (component: T) => any): T; - function render

    ( - element: ReactElement

    , - container: Element, - callback?: (component?: Component | Element) => any): Component | Element | void; - - function unmountComponentAtNode(container: Element): boolean; - - var version: string; - - function unstable_batchedUpdates(callback: (a: A, b: B) => any, a: A, b: B): void; - function unstable_batchedUpdates(callback: (a: A) => any, a: A): void; - function unstable_batchedUpdates(callback: () => any): void; - - function unstable_renderSubtreeIntoContainer

    ( - parentComponent: Component, - element: DOMElement, - container: Element, - callback?: (element: T) => any): T; - function unstable_renderSubtreeIntoContainer>( - parentComponent: Component, - element: CElement, - container: Element, - callback?: (component: T) => any): T; - function render

    ( - parentComponent: Component, - element: SFCElement

    , - container: Element, - callback?: () => any): void; - function unstable_renderSubtreeIntoContainer

    ( - parentComponent: Component, - element: ReactElement

    , - container: Element, - callback?: (component?: Component | Element) => any): Component | Element | void; - } - - namespace __DOMServer { - function renderToString(element: ReactElement): string; - function renderToStaticMarkup(element: ReactElement): string; - var version: string; - } -} - -declare module "react-dom" { - import DOM = __React.__DOM; - export = DOM; -} - -declare module "react-dom/server" { - import DOMServer = __React.__DOMServer; - export = DOMServer; -} diff --git a/src/AccountGoWeb/typings/react/react.d.ts b/src/AccountGoWeb/typings/react/react.d.ts deleted file mode 100644 index 5bc4c168f..000000000 --- a/src/AccountGoWeb/typings/react/react.d.ts +++ /dev/null @@ -1,2475 +0,0 @@ -// Type definitions for React v0.14 -// Project: http://facebook.github.io/react/ -// Definitions by: Asana , AssureSign , Microsoft -// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped - -declare namespace __React { - - // - // React Elements - // ---------------------------------------------------------------------- - - type ReactType = string | ComponentClass | StatelessComponent; - - type Key = string | number; - type Ref = string | ((instance: T) => any); - type ComponentState = {} | void; - - interface Attributes { - key?: Key; - } - interface ClassAttributes extends Attributes { - ref?: Ref; - } - - interface ReactElement

    { - type: string | ComponentClass

    | SFC

    ; - props: P; - key?: Key; - } - - interface SFCElement

    extends ReactElement

    { - type: SFC

    ; - } - - type CElement> = ComponentElement; - interface ComponentElement> extends ReactElement

    { - type: ComponentClass

    ; - ref?: Ref; - } - - type ClassicElement

    = CElement>; - - interface DOMElement

    extends ReactElement

    { - type: string; - ref: Ref; - } - - interface ReactHTMLElement extends DOMElement { - } - - interface ReactSVGElement extends DOMElement { - } - - // - // Factories - // ---------------------------------------------------------------------- - - interface Factory

    { - (props?: P & Attributes, ...children: ReactNode[]): ReactElement

    ; - } - - interface SFCFactory

    { - (props?: P & Attributes, ...children: ReactNode[]): SFCElement

    ; - } - - interface ComponentFactory> { - (props?: P & ClassAttributes, ...children: ReactNode[]): CElement; - } - - type CFactory> = ComponentFactory; - type ClassicFactory

    = CFactory>; - - interface DOMFactory

    { - (props?: P & ClassAttributes, ...children: ReactNode[]): DOMElement; - } - - interface HTMLFactory extends DOMFactory { - } - - interface SVGFactory extends DOMFactory { - } - - // - // React Nodes - // http://facebook.github.io/react/docs/glossary.html - // ---------------------------------------------------------------------- - - type ReactText = string | number; - type ReactChild = ReactElement | ReactText; - - // Should be Array but type aliases cannot be recursive - type ReactFragment = {} | Array; - type ReactNode = ReactChild | ReactFragment | boolean; - - // - // Top Level API - // ---------------------------------------------------------------------- - - function createClass(spec: ComponentSpec): ClassicComponentClass

    ; - - function createFactory

    ( - type: string): DOMFactory; - function createFactory

    (type: SFC

    ): SFCFactory

    ; - function createFactory

    ( - type: ClassType, ClassicComponentClass

    >): CFactory>; - function createFactory, C extends ComponentClass

    >( - type: ClassType): CFactory; - function createFactory

    (type: ComponentClass

    | SFC

    ): Factory

    ; - - function createElement

    ( - type: string, - props?: P & ClassAttributes, - ...children: ReactNode[]): DOMElement; - function createElement

    ( - type: SFC

    , - props?: P & Attributes, - ...children: ReactNode[]): SFCElement

    ; - function createElement

    ( - type: ClassType, ClassicComponentClass

    >, - props?: P & ClassAttributes>, - ...children: ReactNode[]): CElement>; - function createElement, C extends ComponentClass

    >( - type: ClassType, - props?: P & ClassAttributes, - ...children: ReactNode[]): CElement; - function createElement

    ( - type: ComponentClass

    | SFC

    , - props?: P & Attributes, - ...children: ReactNode[]): ReactElement

    ; - - function cloneElement

    ( - element: DOMElement, - props?: P & ClassAttributes, - ...children: ReactNode[]): DOMElement; - function cloneElement

    ( - element: SFCElement

    , - props?: Q, // should be Q & Attributes, but then Q is inferred as {} - ...children: ReactNode[]): SFCElement

    ; - function cloneElement

    >( - element: CElement, - props?: Q, // should be Q & ClassAttributes - ...children: ReactNode[]): CElement; - function cloneElement

    ( - element: ReactElement

    , - props?: Q, // should be Q & Attributes - ...children: ReactNode[]): ReactElement

    ; - - function isValidElement

    (object: {}): object is ReactElement

    ; - - var DOM: ReactDOM; - var PropTypes: ReactPropTypes; - var Children: ReactChildren; - var version: string; - - // - // Component API - // ---------------------------------------------------------------------- - - type ReactInstance = Component | Element; - - // Base component for plain JS classes - class Component { - constructor(props?: P, context?: any); - setState(f: (prevState: S, props: P) => S, callback?: () => any): void; - setState(state: S, callback?: () => any): void; - forceUpdate(callBack?: () => any): void; - render(): JSX.Element; - - // React.Props is now deprecated, which means that the `children` - // property is not available on `P` by default, even though you can - // always pass children as variadic arguments to `createElement`. - // In the future, if we can define its call signature conditionally - // on the existence of `children` in `P`, then we should remove this. - props: P & { children?: ReactNode }; - state: S; - context: {}; - refs: { - [key: string]: ReactInstance - }; - } - - interface ClassicComponent extends Component { - replaceState(nextState: S, callback?: () => any): void; - isMounted(): boolean; - getInitialState?(): S; - } - - interface ChildContextProvider { - getChildContext(): CC; - } - - // - // Class Interfaces - // ---------------------------------------------------------------------- - - type SFC

    = StatelessComponent

    ; - interface StatelessComponent

    { - (props?: P, context?: any): ReactElement; - propTypes?: ValidationMap

    ; - contextTypes?: ValidationMap; - defaultProps?: P; - displayName?: string; - } - - interface ComponentClass

    { - new(props?: P, context?: any): Component; - propTypes?: ValidationMap

    ; - contextTypes?: ValidationMap; - childContextTypes?: ValidationMap; - defaultProps?: P; - displayName?: string; - } - - interface ClassicComponentClass

    extends ComponentClass

    { - new(props?: P, context?: any): ClassicComponent; - getDefaultProps?(): P; - } - - /** - * We use an intersection type to infer multiple type parameters from - * a single argument, which is useful for many top-level API defs. - * See https://github.com/Microsoft/TypeScript/issues/7234 for more info. - */ - type ClassType, C extends ComponentClass

    > = - C & - (new() => T) & - (new() => { props: P }); - - // - // Component Specs and Lifecycle - // ---------------------------------------------------------------------- - - interface ComponentLifecycle { - componentWillMount?(): void; - componentDidMount?(): void; - componentWillReceiveProps?(nextProps: P, nextContext: any): void; - shouldComponentUpdate?(nextProps: P, nextState: S, nextContext: any): boolean; - componentWillUpdate?(nextProps: P, nextState: S, nextContext: any): void; - componentDidUpdate?(prevProps: P, prevState: S, prevContext: any): void; - componentWillUnmount?(): void; - } - - interface Mixin extends ComponentLifecycle { - mixins?: Mixin; - statics?: { - [key: string]: any; - }; - - displayName?: string; - propTypes?: ValidationMap; - contextTypes?: ValidationMap; - childContextTypes?: ValidationMap; - - getDefaultProps?(): P; - getInitialState?(): S; - } - - interface ComponentSpec extends Mixin { - render(): ReactElement; - - [propertyName: string]: any; - } - - // - // Event System - // ---------------------------------------------------------------------- - - interface SyntheticEvent { - bubbles: boolean; - cancelable: boolean; - currentTarget: EventTarget; - defaultPrevented: boolean; - eventPhase: number; - isTrusted: boolean; - nativeEvent: Event; - preventDefault(): void; - stopPropagation(): void; - target: EventTarget; - timeStamp: Date; - type: string; - } - - interface ClipboardEvent extends SyntheticEvent { - clipboardData: DataTransfer; - } - - interface CompositionEvent extends SyntheticEvent { - data: string; - } - - interface DragEvent extends SyntheticEvent { - dataTransfer: DataTransfer; - } - - interface FocusEvent extends SyntheticEvent { - relatedTarget: EventTarget; - } - - interface FormEvent extends SyntheticEvent { - } - - interface KeyboardEvent extends SyntheticEvent { - altKey: boolean; - charCode: number; - ctrlKey: boolean; - getModifierState(key: string): boolean; - key: string; - keyCode: number; - locale: string; - location: number; - metaKey: boolean; - repeat: boolean; - shiftKey: boolean; - which: number; - } - - interface MouseEvent extends SyntheticEvent { - altKey: boolean; - button: number; - buttons: number; - clientX: number; - clientY: number; - ctrlKey: boolean; - getModifierState(key: string): boolean; - metaKey: boolean; - pageX: number; - pageY: number; - relatedTarget: EventTarget; - screenX: number; - screenY: number; - shiftKey: boolean; - } - - interface TouchEvent extends SyntheticEvent { - altKey: boolean; - changedTouches: TouchList; - ctrlKey: boolean; - getModifierState(key: string): boolean; - metaKey: boolean; - shiftKey: boolean; - targetTouches: TouchList; - touches: TouchList; - } - - interface UIEvent extends SyntheticEvent { - detail: number; - view: AbstractView; - } - - interface WheelEvent extends SyntheticEvent { - deltaMode: number; - deltaX: number; - deltaY: number; - deltaZ: number; - } - - // - // Event Handler Types - // ---------------------------------------------------------------------- - - interface EventHandler { - (event: E): void; - } - - type ReactEventHandler = EventHandler; - - type ClipboardEventHandler = EventHandler; - type CompositionEventHandler = EventHandler; - type DragEventHandler = EventHandler; - type FocusEventHandler = EventHandler; - type FormEventHandler = EventHandler; - type KeyboardEventHandler = EventHandler; - type MouseEventHandler = EventHandler; - type TouchEventHandler = EventHandler; - type UIEventHandler = EventHandler; - type WheelEventHandler = EventHandler; - - // - // Props / DOM Attributes - // ---------------------------------------------------------------------- - - /** - * @deprecated. This was used to allow clients to pass `ref` and `key` - * to `createElement`, which is no longer necessary due to intersection - * types. If you need to declare a props object before passing it to - * `createElement` or a factory, use `ClassAttributes`: - * - * ```ts - * var b: Button; - * var props: ButtonProps & ClassAttributes + +@code { + private int currentCount = 0; + + private void IncrementCount() + { + currentCount++; + } +} diff --git a/src/BlazorGDB/BlazorGDB.Client/Pages/Financials/ChartOfAccounts.razor b/src/BlazorGDB/BlazorGDB.Client/Pages/Financials/ChartOfAccounts.razor new file mode 100644 index 000000000..73bb4fe1a --- /dev/null +++ b/src/BlazorGDB/BlazorGDB.Client/Pages/Financials/ChartOfAccounts.razor @@ -0,0 +1,184 @@ +@page "/x/financials/chart-of-accounts" +@using System.Text.Json +@inject Microsoft.JSInterop.IJSRuntime JSRuntime +@inject HttpClient Http + +@* @rendermode InteractiveServer *@ +@rendermode InteractiveAuto + +Chart of Accounts + +@*

    Chart Of Accounts

    *@ + +@if (getError || accounts is null) +{ +

    Unable to get data. Please try again later.

    +} +else +{ +
      + @foreach (var item in accounts) + { +
    • @item.AccountName
    • + } +
    + + +
    + + + + + + + + + + + + + + @for (int accountIdx = 0; accountIdx < accounts.Count(); ++accountIdx) + { + var account = accounts.ToList()[accountIdx]; + var accountTargetId = $"asset-{accountIdx}"; + + + + + + + + + + + + + } + +
    CodeNameBalanceDebitCredit
    + +
    +
    + +} + +@code { + private AccountViewModel[]? accounts = []; + private bool getError; + private bool shouldRender = true; + private bool jsInteropCalled = false; // Flag to ensure JS interop is called only once + private string url = string.Empty; + JsonElement data; + string error = string.Empty; + + protected override bool ShouldRender() => shouldRender; + + protected override async Task OnInitializedAsync() + { + var options = new JsonSerializerOptions + { + PropertyNameCaseInsensitive = true + }; + try + { + error += $"BASE URL: {Http.BaseAddress}\n"; + url = $"{Constants.API_URL}financials/accounts"; + //accounts = await Http.GetFromJsonAsync(url); + error += "1\n"; + data = await Http.GetFromJsonAsync(url); + error += "2\n"; + // get total property from JSON response data + error += "3\n"; + error += data.GetProperty("accountName").GetInt32(); + } + catch (Exception ex) + { + getError = true; + Console.WriteLine(ex.Message); + } + } + + protected override async Task OnAfterRenderAsync(bool firstRender) + { + if (firstRender && !jsInteropCalled) + { + await JSRuntime.InvokeVoidAsync( + "console.log", + $"accounts: {accounts}\n", + $"url: {url}\n", + $"getError: {getError}\n", + $"error: {error}\n" + ); + //await JSRuntime.InvokeVoidAsync("getError", getError); + jsInteropCalled = true; // Ensure this block runs only once + } + } +} diff --git a/src/BlazorGDB/BlazorGDB.Client/Pages/Financials/ChartOfAccounts2.razor b/src/BlazorGDB/BlazorGDB.Client/Pages/Financials/ChartOfAccounts2.razor new file mode 100644 index 000000000..c66ecadff --- /dev/null +++ b/src/BlazorGDB/BlazorGDB.Client/Pages/Financials/ChartOfAccounts2.razor @@ -0,0 +1,228 @@ +@page "/b/financials/chart-of-accounts" +@inject HttpClient _client +@inject Microsoft.JSInterop.IJSRuntime JSRuntime +@using System.Net.Http.Json +@using System.Text.Json +@using System.Text.Json.Serialization +@using System.Net.Http + + +Chart of Accounts + +

    Chart Of Accounts

    + +@if (getError || accounts is null) +{ +

    Unable to get data. Please try again later.

    +} +else +{ +
      + @foreach (var item in accounts) + { +
    • @item.AccountName
    • + } +
    + + +
    + + + + + + + + + + + + + + @for (int accountIdx = 0; accountIdx < accounts.Count(); ++accountIdx) + { + var account = accounts.ToList()[accountIdx]; + var accountTargetId = $"asset-{accountIdx}"; + + + + + + + + + + + + + } + +
    CodeNameBalanceDebitCredit
    + +
    +
    + +} + +@code { + private List? accounts = []; + private bool getError = false; + private bool shouldRender = false; + private bool jsInteropCalled = false; // Flag to ensure JS interop is called only once + private string apiurl = string.Empty; + private HttpResponseMessage responseMessage = new(); + private string error = string.Empty; + + protected override bool ShouldRender() => shouldRender; + + protected override async Task OnInitializedAsync() + { + // apiurl = $"{Constants.API_URL}financials/accounts"; + // Set the BaseAddress dynamically + _client.BaseAddress = new Uri(Constants.API_URL); + apiurl = "financials/accounts"; + + var request = new HttpRequestMessage(HttpMethod.Get, apiurl); + request.Headers.Add("Accept", "application/json"); + + error += "HERE 1\n"; + + _client.DefaultRequestHeaders.Accept.Clear(); + _client.DefaultRequestHeaders.Clear(); + _client.DefaultRequestHeaders.CacheControl = new System.Net.Http.Headers.CacheControlHeaderValue() { NoCache = true }; + _client.DefaultRequestHeaders.Accept.Add(new System.Net.Http.Headers.MediaTypeWithQualityHeaderValue("application/json")); + + try + { + error += "HERE 2\n"; + var responseMessage = await _client.SendAsync(request).ConfigureAwait(false); + error += "HERE 3\n"; + if (responseMessage.IsSuccessStatusCode) + { + var responseString = await responseMessage.Content.ReadAsStringAsync(); + + // Ensure JsonSerializerOptions are set correctly + var options = new JsonSerializerOptions + { + PropertyNameCaseInsensitive = true, // Adjust based on your JSON + DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, + }; + error += "HERE 4\n"; + + try + { + error += "HERE 5\n"; + accounts = JsonSerializer.Deserialize>(responseString, options); + error += "HERE 6\n"; + } + catch (JsonException ex) + { + // Log the exception to diagnose deserialization issues + //await JSRuntime.InvokeVoidAsync("logToConsole", $"JSON Deserialization error: {ex.Message}"); + error += $"JSON Deserialization error: {ex.Message}\n"; + getError = true; + } + } + else + { + // Log the status code and reason phrase for debugging + //await JSRuntime.InvokeVoidAsync("logToConsole", $"HTTP Request failed. Status Code: {responseMessage.StatusCode}, Reason: {responseMessage.ReasonPhrase}"); + error += $"HTTP Request failed. Status Code: {responseMessage.StatusCode}, Reason: {responseMessage.ReasonPhrase}\n"; + getError = true; + } + } + catch (Exception ex) + { + error += "HERE 7\n"; + // Log the exception to diagnose request issues + //await JSRuntime.InvokeVoidAsync("logToConsole", $"HTTP Request error: {ex.Message}"); + error += $"HTTP Request error: {ex.Message}\n"; + getError = true; + } + error += "HERE 8\n"; + shouldRender = true; + } + + protected override async Task OnAfterRenderAsync(bool firstRender) + { + if (firstRender && !jsInteropCalled) + { + await JSRuntime.InvokeVoidAsync( + "console.log", + $"accounts: {accounts}\n", + $"url: {apiurl}\n", + $"getError: {getError}\n", + $"responseMessage: {responseMessage!.Content.ToString()}\n", + $"error: {error}\n" + ); + jsInteropCalled = true; // Ensure this block runs only once + } + } +} diff --git a/src/BlazorGDB/BlazorGDB.Client/Pages/Financials/ChartOfAccounts3.razor b/src/BlazorGDB/BlazorGDB.Client/Pages/Financials/ChartOfAccounts3.razor new file mode 100644 index 000000000..686c270e5 --- /dev/null +++ b/src/BlazorGDB/BlazorGDB.Client/Pages/Financials/ChartOfAccounts3.razor @@ -0,0 +1,193 @@ +@page "/a/financials/chart-of-accounts" +@using System.Text.Json +@inject Microsoft.JSInterop.IJSRuntime JSRuntime +@inject HttpClient _httpClient + +@rendermode InteractiveAuto + +Chart of Accounts + +

    Chart Of Accounts

    + +@if (getError || accounts is null) +{ +

    Unable to get data. Please try again later.

    +} +else +{ +
      + @foreach (var item in accounts) + { +
    • @item.AccountName
    • + } +
    + + +
    + + + + + + + + + + + + + + @for (int accountIdx = 0; accountIdx < accounts.Count(); ++accountIdx) + { + var account = accounts.ToList()[accountIdx]; + var accountTargetId = $"asset-{accountIdx}"; + + + + + + + + + + + + + } + +
    CodeNameBalanceDebitCredit
    + +
    +
    + +} + +@code { + private AccountViewModel[]? accounts = []; + private bool getError = false; + private bool shouldRender = true; + private bool jsInteropCalled = false; // Flag to ensure JS interop is called only once + private string url = string.Empty; + JsonElement data; + string error = string.Empty; + + protected override bool ShouldRender() => shouldRender; + + protected override async Task OnInitializedAsync() + { + error += "HERE 1\n"; + using var httpResponse = await _httpClient.GetAsync($"{Constants.API_URL}financials/accounts"); + error += "HERE 2\n"; + if (!httpResponse.IsSuccessStatusCode) + { + error += "HERE 3\n"; + // set error message for display, log to console and return + var errorMessage = httpResponse.ReasonPhrase; + Console.WriteLine($"There was an error! {errorMessage}"); + return; + } + + var options = new JsonSerializerOptions + { + PropertyNameCaseInsensitive = true + }; + error += "HERE 4\n"; + // convert http response data to UsersResponse object + accounts = await httpResponse.Content.ReadFromJsonAsync(options); + + // capture raw JSON response and deserialize to JsonElement for debugging / inspection + var jsonResponse = await httpResponse.Content.ReadAsStringAsync(); + data = JsonSerializer.Deserialize(jsonResponse); + Console.WriteLine(data); + + // allow the component to render now that data is available + shouldRender = true; + StateHasChanged(); + + error += "HERE 5\n"; + } + + + protected override async Task OnAfterRenderAsync(bool firstRender) + { + if (firstRender && !jsInteropCalled) + { + await JSRuntime.InvokeVoidAsync( + "console.log", + $"accounts: {accounts}\n", + $"url: {url}\n", + $"getError: {getError}\n", + $"error: {error}\n" + ); + //await JSRuntime.InvokeVoidAsync("getError", getError); + jsInteropCalled = true; // Ensure this block runs only once + } + } +} diff --git a/src/BlazorGDB/BlazorGDB.Client/PersistentAuthenticationStateProvider.cs b/src/BlazorGDB/BlazorGDB.Client/PersistentAuthenticationStateProvider.cs new file mode 100644 index 000000000..e9b56405c --- /dev/null +++ b/src/BlazorGDB/BlazorGDB.Client/PersistentAuthenticationStateProvider.cs @@ -0,0 +1,40 @@ +using System.Security.Claims; +using Microsoft.AspNetCore.Components; +using Microsoft.AspNetCore.Components.Authorization; + +namespace BlazorGDB.Client; + +// This is a client-side AuthenticationStateProvider that determines the user's authentication state by +// looking for data persisted in the page when it was rendered on the server. This authentication state will +// be fixed for the lifetime of the WebAssembly application. So, if the user needs to log in or out, a full +// page reload is required. +// +// This only provides a user name and email for display purposes. It does not actually include any tokens +// that authenticate to the server when making subsequent requests. That works separately using a +// cookie that will be included on HttpClient requests to the server. +internal class PersistentAuthenticationStateProvider : AuthenticationStateProvider +{ + private static readonly Task defaultUnauthenticatedTask = + Task.FromResult(new AuthenticationState(new ClaimsPrincipal(new ClaimsIdentity()))); + + private readonly Task authenticationStateTask = defaultUnauthenticatedTask; + + public PersistentAuthenticationStateProvider(PersistentComponentState state) + { + if (!state.TryTakeFromJson(nameof(UserInfo), out var userInfo) || userInfo is null) + { + return; + } + + Claim[] claims = [ + new Claim(ClaimTypes.NameIdentifier, userInfo.UserId), + new Claim(ClaimTypes.Name, userInfo.Email), + new Claim(ClaimTypes.Email, userInfo.Email) ]; + + authenticationStateTask = Task.FromResult( + new AuthenticationState(new ClaimsPrincipal(new ClaimsIdentity(claims, + authenticationType: nameof(PersistentAuthenticationStateProvider))))); + } + + public override Task GetAuthenticationStateAsync() => authenticationStateTask; +} diff --git a/src/BlazorGDB/BlazorGDB.Client/Program.cs b/src/BlazorGDB/BlazorGDB.Client/Program.cs new file mode 100644 index 000000000..13a85ede5 --- /dev/null +++ b/src/BlazorGDB/BlazorGDB.Client/Program.cs @@ -0,0 +1,16 @@ +using System.Reflection.Metadata; +using BlazorGDB.Client; +using BlazorGDB.Client.Models; +using Microsoft.AspNetCore.Components.Authorization; +using Microsoft.AspNetCore.Components.WebAssembly.Hosting; +using Microsoft.Extensions.DependencyInjection; + +var builder = WebAssemblyHostBuilder.CreateDefault(args); + +builder.Services.AddAuthorizationCore(); +builder.Services.AddCascadingAuthenticationState(); +builder.Services.AddSingleton(); +builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(Constants.API_URL) }); +//builder.Services.AddScoped(sp => new HttpClient()); + +await builder.Build().RunAsync(); diff --git a/src/BlazorGDB/BlazorGDB.Client/RedirectToLogin.razor b/src/BlazorGDB/BlazorGDB.Client/RedirectToLogin.razor new file mode 100644 index 000000000..c8b8eff4a --- /dev/null +++ b/src/BlazorGDB/BlazorGDB.Client/RedirectToLogin.razor @@ -0,0 +1,8 @@ +@inject NavigationManager NavigationManager + +@code { + protected override void OnInitialized() + { + NavigationManager.NavigateTo($"Account/Login?returnUrl={Uri.EscapeDataString(NavigationManager.Uri)}", forceLoad: true); + } +} diff --git a/src/BlazorGDB/BlazorGDB.Client/UserInfo.cs b/src/BlazorGDB/BlazorGDB.Client/UserInfo.cs new file mode 100644 index 000000000..afb642b17 --- /dev/null +++ b/src/BlazorGDB/BlazorGDB.Client/UserInfo.cs @@ -0,0 +1,9 @@ +namespace BlazorGDB.Client; + +// Add properties to this class and update the server and client AuthenticationStateProviders +// to expose more information about the authenticated user to the client. +public class UserInfo +{ + public required string UserId { get; set; } + public required string Email { get; set; } +} diff --git a/src/BlazorGDB/BlazorGDB.Client/_Imports.razor b/src/BlazorGDB/BlazorGDB.Client/_Imports.razor new file mode 100644 index 000000000..db16aacf7 --- /dev/null +++ b/src/BlazorGDB/BlazorGDB.Client/_Imports.razor @@ -0,0 +1,12 @@ +@using System.Net.Http +@using System.Net.Http.Json +@using Microsoft.AspNetCore.Components.Authorization +@using Microsoft.AspNetCore.Components.Forms +@using Microsoft.AspNetCore.Components.Routing +@using Microsoft.AspNetCore.Components.Web +@using static Microsoft.AspNetCore.Components.Web.RenderMode +@using Microsoft.AspNetCore.Components.Web.Virtualization +@using Microsoft.JSInterop +@using BlazorGDB.Client +@using BlazorGDB.Client.Models + diff --git a/src/BlazorGDB/BlazorGDB.Client/wwwroot/appsettings.Development.json b/src/BlazorGDB/BlazorGDB.Client/wwwroot/appsettings.Development.json new file mode 100644 index 000000000..0c208ae91 --- /dev/null +++ b/src/BlazorGDB/BlazorGDB.Client/wwwroot/appsettings.Development.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/src/BlazorGDB/BlazorGDB.Client/wwwroot/appsettings.json b/src/BlazorGDB/BlazorGDB.Client/wwwroot/appsettings.json new file mode 100644 index 000000000..0c208ae91 --- /dev/null +++ b/src/BlazorGDB/BlazorGDB.Client/wwwroot/appsettings.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/src/BlazorGDB/BlazorGDB/BlazorGDB.csproj b/src/BlazorGDB/BlazorGDB/BlazorGDB.csproj new file mode 100644 index 000000000..177e6cf2f --- /dev/null +++ b/src/BlazorGDB/BlazorGDB/BlazorGDB.csproj @@ -0,0 +1,32 @@ + + + net10.0 + enable + enable + aspnet-BlazorGDB-1148a8f8-fb21-46ba-ad7a-f4bc4cd1bf04 + + + + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/BlazorGDB/BlazorGDB/Components/Account/IdentityComponentsEndpointRouteBuilderExtensions.cs b/src/BlazorGDB/BlazorGDB/Components/Account/IdentityComponentsEndpointRouteBuilderExtensions.cs new file mode 100644 index 000000000..d23bd0aca --- /dev/null +++ b/src/BlazorGDB/BlazorGDB/Components/Account/IdentityComponentsEndpointRouteBuilderExtensions.cs @@ -0,0 +1,112 @@ +using System.Security.Claims; +using System.Text.Json; +using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.Components.Authorization; +using Microsoft.AspNetCore.Http.Extensions; +using Microsoft.AspNetCore.Identity; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Primitives; +using BlazorGDB.Components.Account.Pages; +using BlazorGDB.Components.Account.Pages.Manage; +using BlazorGDB.Data; + +namespace Microsoft.AspNetCore.Routing; + +internal static class IdentityComponentsEndpointRouteBuilderExtensions +{ + // These endpoints are required by the Identity Razor components defined in the /Components/Account/Pages directory of this project. + public static IEndpointConventionBuilder MapAdditionalIdentityEndpoints(this IEndpointRouteBuilder endpoints) + { + ArgumentNullException.ThrowIfNull(endpoints); + + var accountGroup = endpoints.MapGroup("/Account"); + + accountGroup.MapPost("/PerformExternalLogin", ( + HttpContext context, + [FromServices] SignInManager signInManager, + [FromForm] string provider, + [FromForm] string returnUrl) => + { + IEnumerable> query = [ + new("ReturnUrl", returnUrl), + new("Action", ExternalLogin.LoginCallbackAction)]; + + var redirectUrl = UriHelper.BuildRelative( + context.Request.PathBase, + "/Account/ExternalLogin", + QueryString.Create(query)); + + var properties = signInManager.ConfigureExternalAuthenticationProperties(provider, redirectUrl); + return TypedResults.Challenge(properties, [provider]); + }); + + accountGroup.MapPost("/Logout", async ( + ClaimsPrincipal user, + SignInManager signInManager, + [FromForm] string returnUrl) => + { + await signInManager.SignOutAsync(); + return TypedResults.LocalRedirect($"~/{returnUrl}"); + }); + + var manageGroup = accountGroup.MapGroup("/Manage").RequireAuthorization(); + + manageGroup.MapPost("/LinkExternalLogin", async ( + HttpContext context, + [FromServices] SignInManager signInManager, + [FromForm] string provider) => + { + // Clear the existing external cookie to ensure a clean login process + await context.SignOutAsync(IdentityConstants.ExternalScheme); + + var redirectUrl = UriHelper.BuildRelative( + context.Request.PathBase, + "/Account/Manage/ExternalLogins", + QueryString.Create("Action", ExternalLogins.LinkLoginCallbackAction)); + + var properties = signInManager.ConfigureExternalAuthenticationProperties(provider, redirectUrl, signInManager.UserManager.GetUserId(context.User)); + return TypedResults.Challenge(properties, [provider]); + }); + + var loggerFactory = endpoints.ServiceProvider.GetRequiredService(); + var downloadLogger = loggerFactory.CreateLogger("DownloadPersonalData"); + + manageGroup.MapPost("/DownloadPersonalData", async ( + HttpContext context, + [FromServices] UserManager userManager, + [FromServices] AuthenticationStateProvider authenticationStateProvider) => + { + var user = await userManager.GetUserAsync(context.User); + if (user is null) + { + return Results.NotFound($"Unable to load user with ID '{userManager.GetUserId(context.User)}'."); + } + + var userId = await userManager.GetUserIdAsync(user); + downloadLogger.LogInformation("User with ID '{UserId}' asked for their personal data.", userId); + + // Only include personal data for download + var personalData = new Dictionary(); + var personalDataProps = typeof(ApplicationUser).GetProperties().Where( + prop => Attribute.IsDefined(prop, typeof(PersonalDataAttribute))); + foreach (var p in personalDataProps) + { + personalData.Add(p.Name, p.GetValue(user)?.ToString() ?? "null"); + } + + var logins = await userManager.GetLoginsAsync(user); + foreach (var l in logins) + { + personalData.Add($"{l.LoginProvider} external login provider key", l.ProviderKey); + } + + personalData.Add("Authenticator Key", (await userManager.GetAuthenticatorKeyAsync(user))!); + var fileBytes = JsonSerializer.SerializeToUtf8Bytes(personalData); + + context.Response.Headers.TryAdd("Content-Disposition", "attachment; filename=PersonalData.json"); + return TypedResults.File(fileBytes, contentType: "application/json", fileDownloadName: "PersonalData.json"); + }); + + return accountGroup; + } +} diff --git a/src/BlazorGDB/BlazorGDB/Components/Account/IdentityNoOpEmailSender.cs b/src/BlazorGDB/BlazorGDB/Components/Account/IdentityNoOpEmailSender.cs new file mode 100644 index 000000000..274c4363b --- /dev/null +++ b/src/BlazorGDB/BlazorGDB/Components/Account/IdentityNoOpEmailSender.cs @@ -0,0 +1,20 @@ +using Microsoft.AspNetCore.Identity; +using Microsoft.AspNetCore.Identity.UI.Services; +using BlazorGDB.Data; + +namespace BlazorGDB.Components.Account; + +// Remove the "else if (EmailSender is IdentityNoOpEmailSender)" block from RegisterConfirmation.razor after updating with a real implementation. +internal sealed class IdentityNoOpEmailSender : IEmailSender +{ + private readonly IEmailSender emailSender = new NoOpEmailSender(); + + public Task SendConfirmationLinkAsync(ApplicationUser user, string email, string confirmationLink) => + emailSender.SendEmailAsync(email, "Confirm your email", $"Please confirm your account by
    clicking here."); + + public Task SendPasswordResetLinkAsync(ApplicationUser user, string email, string resetLink) => + emailSender.SendEmailAsync(email, "Reset your password", $"Please reset your password by clicking here."); + + public Task SendPasswordResetCodeAsync(ApplicationUser user, string email, string resetCode) => + emailSender.SendEmailAsync(email, "Reset your password", $"Please reset your password using the following code: {resetCode}"); +} diff --git a/src/BlazorGDB/BlazorGDB/Components/Account/IdentityRedirectManager.cs b/src/BlazorGDB/BlazorGDB/Components/Account/IdentityRedirectManager.cs new file mode 100644 index 000000000..c9c579248 --- /dev/null +++ b/src/BlazorGDB/BlazorGDB/Components/Account/IdentityRedirectManager.cs @@ -0,0 +1,58 @@ +using System.Diagnostics.CodeAnalysis; +using Microsoft.AspNetCore.Components; + +namespace BlazorGDB.Components.Account; + +internal sealed class IdentityRedirectManager(NavigationManager navigationManager) +{ + public const string StatusCookieName = "Identity.StatusMessage"; + + private static readonly CookieBuilder StatusCookieBuilder = new() + { + SameSite = SameSiteMode.Strict, + HttpOnly = true, + IsEssential = true, + MaxAge = TimeSpan.FromSeconds(5), + }; + + [DoesNotReturn] + public void RedirectTo(string? uri) + { + uri ??= ""; + + // Prevent open redirects. + if (!Uri.IsWellFormedUriString(uri, UriKind.Relative)) + { + uri = navigationManager.ToBaseRelativePath(uri); + } + + // During static rendering, NavigateTo throws a NavigationException which is handled by the framework as a redirect. + // So as long as this is called from a statically rendered Identity component, the InvalidOperationException is never thrown. + navigationManager.NavigateTo(uri); + throw new InvalidOperationException($"{nameof(IdentityRedirectManager)} can only be used during static rendering."); + } + + [DoesNotReturn] + public void RedirectTo(string uri, Dictionary queryParameters) + { + var uriWithoutQuery = navigationManager.ToAbsoluteUri(uri).GetLeftPart(UriPartial.Path); + var newUri = navigationManager.GetUriWithQueryParameters(uriWithoutQuery, queryParameters); + RedirectTo(newUri); + } + + [DoesNotReturn] + public void RedirectToWithStatus(string uri, string message, HttpContext context) + { + context.Response.Cookies.Append(StatusCookieName, message, StatusCookieBuilder.Build(context)); + RedirectTo(uri); + } + + private string CurrentPath => navigationManager.ToAbsoluteUri(navigationManager.Uri).GetLeftPart(UriPartial.Path); + + [DoesNotReturn] + public void RedirectToCurrentPage() => RedirectTo(CurrentPath); + + [DoesNotReturn] + public void RedirectToCurrentPageWithStatus(string message, HttpContext context) + => RedirectToWithStatus(CurrentPath, message, context); +} diff --git a/src/BlazorGDB/BlazorGDB/Components/Account/IdentityUserAccessor.cs b/src/BlazorGDB/BlazorGDB/Components/Account/IdentityUserAccessor.cs new file mode 100644 index 000000000..99b2a384d --- /dev/null +++ b/src/BlazorGDB/BlazorGDB/Components/Account/IdentityUserAccessor.cs @@ -0,0 +1,19 @@ +using Microsoft.AspNetCore.Identity; +using BlazorGDB.Data; + +namespace BlazorGDB.Components.Account; + +internal sealed class IdentityUserAccessor(UserManager userManager, IdentityRedirectManager redirectManager) +{ + public async Task GetRequiredUserAsync(HttpContext context) + { + var user = await userManager.GetUserAsync(context.User); + + if (user is null) + { + redirectManager.RedirectToWithStatus("Account/InvalidUser", $"Error: Unable to load user with ID '{userManager.GetUserId(context.User)}'.", context); + } + + return user; + } +} diff --git a/src/BlazorGDB/BlazorGDB/Components/Account/Pages/AccessDenied.razor b/src/BlazorGDB/BlazorGDB/Components/Account/Pages/AccessDenied.razor new file mode 100644 index 000000000..905dec348 --- /dev/null +++ b/src/BlazorGDB/BlazorGDB/Components/Account/Pages/AccessDenied.razor @@ -0,0 +1,8 @@ +@page "/Account/AccessDenied" + +Access denied + +
    +

    Access denied

    +

    You do not have access to this resource.

    +
    diff --git a/src/BlazorGDB/BlazorGDB/Components/Account/Pages/ConfirmEmail.razor b/src/BlazorGDB/BlazorGDB/Components/Account/Pages/ConfirmEmail.razor new file mode 100644 index 000000000..7ad7b1c73 --- /dev/null +++ b/src/BlazorGDB/BlazorGDB/Components/Account/Pages/ConfirmEmail.razor @@ -0,0 +1,48 @@ +@page "/Account/ConfirmEmail" + +@using System.Text +@using Microsoft.AspNetCore.Identity +@using Microsoft.AspNetCore.WebUtilities +@using BlazorGDB.Data + +@inject UserManager UserManager +@inject IdentityRedirectManager RedirectManager + +Confirm email + +

    Confirm email

    + + +@code { + private string? statusMessage; + + [CascadingParameter] + private HttpContext HttpContext { get; set; } = default!; + + [SupplyParameterFromQuery] + private string? UserId { get; set; } + + [SupplyParameterFromQuery] + private string? Code { get; set; } + + protected override async Task OnInitializedAsync() + { + if (UserId is null || Code is null) + { + RedirectManager.RedirectTo(""); + } + + var user = await UserManager.FindByIdAsync(UserId); + if (user is null) + { + HttpContext.Response.StatusCode = StatusCodes.Status404NotFound; + statusMessage = $"Error loading user with ID {UserId}"; + } + else + { + var code = Encoding.UTF8.GetString(WebEncoders.Base64UrlDecode(Code)); + var result = await UserManager.ConfirmEmailAsync(user, code); + statusMessage = result.Succeeded ? "Thank you for confirming your email." : "Error confirming your email."; + } + } +} diff --git a/src/BlazorGDB/BlazorGDB/Components/Account/Pages/ConfirmEmailChange.razor b/src/BlazorGDB/BlazorGDB/Components/Account/Pages/ConfirmEmailChange.razor new file mode 100644 index 000000000..647ad2231 --- /dev/null +++ b/src/BlazorGDB/BlazorGDB/Components/Account/Pages/ConfirmEmailChange.razor @@ -0,0 +1,68 @@ +@page "/Account/ConfirmEmailChange" + +@using System.Text +@using Microsoft.AspNetCore.Identity +@using Microsoft.AspNetCore.WebUtilities +@using BlazorGDB.Data + +@inject UserManager UserManager +@inject SignInManager SignInManager +@inject IdentityRedirectManager RedirectManager + +Confirm email change + +

    Confirm email change

    + + + +@code { + private string? message; + + [CascadingParameter] + private HttpContext HttpContext { get; set; } = default!; + + [SupplyParameterFromQuery] + private string? UserId { get; set; } + + [SupplyParameterFromQuery] + private string? Email { get; set; } + + [SupplyParameterFromQuery] + private string? Code { get; set; } + + protected override async Task OnInitializedAsync() + { + if (UserId is null || Email is null || Code is null) + { + RedirectManager.RedirectToWithStatus( + "Account/Login", "Error: Invalid email change confirmation link.", HttpContext); + } + + var user = await UserManager.FindByIdAsync(UserId); + if (user is null) + { + message = "Unable to find user with Id '{userId}'"; + return; + } + + var code = Encoding.UTF8.GetString(WebEncoders.Base64UrlDecode(Code)); + var result = await UserManager.ChangeEmailAsync(user, Email, code); + if (!result.Succeeded) + { + message = "Error changing email."; + return; + } + + // In our UI email and user name are one and the same, so when we update the email + // we need to update the user name. + var setUserNameResult = await UserManager.SetUserNameAsync(user, Email); + if (!setUserNameResult.Succeeded) + { + message = "Error changing user name."; + return; + } + + await SignInManager.RefreshSignInAsync(user); + message = "Thank you for confirming your email change."; + } +} diff --git a/src/BlazorGDB/BlazorGDB/Components/Account/Pages/ExternalLogin.razor b/src/BlazorGDB/BlazorGDB/Components/Account/Pages/ExternalLogin.razor new file mode 100644 index 000000000..811979271 --- /dev/null +++ b/src/BlazorGDB/BlazorGDB/Components/Account/Pages/ExternalLogin.razor @@ -0,0 +1,195 @@ +@page "/Account/ExternalLogin" + +@using System.ComponentModel.DataAnnotations +@using System.Security.Claims +@using System.Text +@using System.Text.Encodings.Web +@using Microsoft.AspNetCore.Identity +@using Microsoft.AspNetCore.WebUtilities +@using BlazorGDB.Data + +@inject SignInManager SignInManager +@inject UserManager UserManager +@inject IUserStore UserStore +@inject IEmailSender EmailSender +@inject NavigationManager NavigationManager +@inject IdentityRedirectManager RedirectManager +@inject ILogger Logger + +Register + + +

    Register

    +

    Associate your @ProviderDisplayName account.

    +
    + +
    + You've successfully authenticated with @ProviderDisplayName. + Please enter an email address for this site below and click the Register button to finish + logging in. +
    + +
    +
    + + + +
    + + + +
    + +
    +
    +
    + +@code { + public const string LoginCallbackAction = "LoginCallback"; + + private string? message; + private ExternalLoginInfo externalLoginInfo = default!; + + [CascadingParameter] + private HttpContext HttpContext { get; set; } = default!; + + [SupplyParameterFromForm] + private InputModel Input { get; set; } = default!; + + [SupplyParameterFromQuery] + private string? RemoteError { get; set; } + + [SupplyParameterFromQuery] + private string? ReturnUrl { get; set; } + + [SupplyParameterFromQuery] + private string? Action { get; set; } + + private string? ProviderDisplayName => externalLoginInfo.ProviderDisplayName; + + protected override async Task OnInitializedAsync() + { + if (RemoteError is not null) + { + RedirectManager.RedirectToWithStatus("Account/Login", $"Error from external provider: {RemoteError}", HttpContext); + } + + var info = await SignInManager.GetExternalLoginInfoAsync(); + if (info is null) + { + RedirectManager.RedirectToWithStatus("Account/Login", "Error loading external login information.", HttpContext); + } + + externalLoginInfo = info; + + if (HttpMethods.IsGet(HttpContext.Request.Method)) + { + if (Action == LoginCallbackAction) + { + await OnLoginCallbackAsync(); + return; + } + + // We should only reach this page via the login callback, so redirect back to + // the login page if we get here some other way. + RedirectManager.RedirectTo("Account/Login"); + } + } + + private async Task OnLoginCallbackAsync() + { + // Sign in the user with this external login provider if the user already has a login. + var result = await SignInManager.ExternalLoginSignInAsync( + externalLoginInfo.LoginProvider, + externalLoginInfo.ProviderKey, + isPersistent: false, + bypassTwoFactor: true); + + if (result.Succeeded) + { + Logger.LogInformation( + "{Name} logged in with {LoginProvider} provider.", + externalLoginInfo.Principal.Identity?.Name, + externalLoginInfo.LoginProvider); + RedirectManager.RedirectTo(ReturnUrl); + } + else if (result.IsLockedOut) + { + RedirectManager.RedirectTo("Account/Lockout"); + } + + // If the user does not have an account, then ask the user to create an account. + if (externalLoginInfo.Principal.HasClaim(c => c.Type == ClaimTypes.Email)) + { + Input.Email = externalLoginInfo.Principal.FindFirstValue(ClaimTypes.Email) ?? ""; + } + } + + private async Task OnValidSubmitAsync() + { + var emailStore = GetEmailStore(); + var user = CreateUser(); + + await UserStore.SetUserNameAsync(user, Input.Email, CancellationToken.None); + await emailStore.SetEmailAsync(user, Input.Email, CancellationToken.None); + + var result = await UserManager.CreateAsync(user); + if (result.Succeeded) + { + result = await UserManager.AddLoginAsync(user, externalLoginInfo); + if (result.Succeeded) + { + Logger.LogInformation("User created an account using {Name} provider.", externalLoginInfo.LoginProvider); + + var userId = await UserManager.GetUserIdAsync(user); + var code = await UserManager.GenerateEmailConfirmationTokenAsync(user); + code = WebEncoders.Base64UrlEncode(Encoding.UTF8.GetBytes(code)); + + var callbackUrl = NavigationManager.GetUriWithQueryParameters( + NavigationManager.ToAbsoluteUri("Account/ConfirmEmail").AbsoluteUri, + new Dictionary { ["userId"] = userId, ["code"] = code }); + await EmailSender.SendConfirmationLinkAsync(user, Input.Email, HtmlEncoder.Default.Encode(callbackUrl)); + + // If account confirmation is required, we need to show the link if we don't have a real email sender + if (UserManager.Options.SignIn.RequireConfirmedAccount) + { + RedirectManager.RedirectTo("Account/RegisterConfirmation", new() { ["email"] = Input.Email }); + } + + await SignInManager.SignInAsync(user, isPersistent: false, externalLoginInfo.LoginProvider); + RedirectManager.RedirectTo(ReturnUrl); + } + } + + message = $"Error: {string.Join(",", result.Errors.Select(error => error.Description))}"; + } + + private ApplicationUser CreateUser() + { + try + { + return Activator.CreateInstance(); + } + catch + { + throw new InvalidOperationException($"Can't create an instance of '{nameof(ApplicationUser)}'. " + + $"Ensure that '{nameof(ApplicationUser)}' is not an abstract class and has a parameterless constructor"); + } + } + + private IUserEmailStore GetEmailStore() + { + if (!UserManager.SupportsUserEmail) + { + throw new NotSupportedException("The default UI requires a user store with email support."); + } + return (IUserEmailStore)UserStore; + } + + private sealed class InputModel + { + [Required] + [EmailAddress] + public string Email { get; set; } = ""; + } +} diff --git a/src/BlazorGDB/BlazorGDB/Components/Account/Pages/ForgotPassword.razor b/src/BlazorGDB/BlazorGDB/Components/Account/Pages/ForgotPassword.razor new file mode 100644 index 000000000..97d5dd406 --- /dev/null +++ b/src/BlazorGDB/BlazorGDB/Components/Account/Pages/ForgotPassword.razor @@ -0,0 +1,68 @@ +@page "/Account/ForgotPassword" + +@using System.ComponentModel.DataAnnotations +@using System.Text +@using System.Text.Encodings.Web +@using Microsoft.AspNetCore.Identity +@using Microsoft.AspNetCore.WebUtilities +@using BlazorGDB.Data + +@inject UserManager UserManager +@inject IEmailSender EmailSender +@inject NavigationManager NavigationManager +@inject IdentityRedirectManager RedirectManager + +Forgot your password? + +

    Forgot your password?

    +

    Enter your email.

    +
    +
    +
    + + + + +
    + + + +
    + +
    +
    +
    + +@code { + [SupplyParameterFromForm] + private InputModel Input { get; set; } = default!; + + private async Task OnValidSubmitAsync() + { + var user = await UserManager.FindByEmailAsync(Input.Email); + if (user is null || !(await UserManager.IsEmailConfirmedAsync(user))) + { + // Don't reveal that the user does not exist or is not confirmed + RedirectManager.RedirectTo("Account/ForgotPasswordConfirmation"); + } + + // For more information on how to enable account confirmation and password reset please + // visit https://go.microsoft.com/fwlink/?LinkID=532713 + var code = await UserManager.GeneratePasswordResetTokenAsync(user); + code = WebEncoders.Base64UrlEncode(Encoding.UTF8.GetBytes(code)); + var callbackUrl = NavigationManager.GetUriWithQueryParameters( + NavigationManager.ToAbsoluteUri("Account/ResetPassword").AbsoluteUri, + new Dictionary { ["code"] = code }); + + await EmailSender.SendPasswordResetLinkAsync(user, Input.Email, HtmlEncoder.Default.Encode(callbackUrl)); + + RedirectManager.RedirectTo("Account/ForgotPasswordConfirmation"); + } + + private sealed class InputModel + { + [Required] + [EmailAddress] + public string Email { get; set; } = ""; + } +} diff --git a/src/BlazorGDB/BlazorGDB/Components/Account/Pages/ForgotPasswordConfirmation.razor b/src/BlazorGDB/BlazorGDB/Components/Account/Pages/ForgotPasswordConfirmation.razor new file mode 100644 index 000000000..38de01d1e --- /dev/null +++ b/src/BlazorGDB/BlazorGDB/Components/Account/Pages/ForgotPasswordConfirmation.razor @@ -0,0 +1,8 @@ +@page "/Account/ForgotPasswordConfirmation" + +Forgot password confirmation + +

    Forgot password confirmation

    +

    + Please check your email to reset your password. +

    diff --git a/src/BlazorGDB/BlazorGDB/Components/Account/Pages/InvalidPasswordReset.razor b/src/BlazorGDB/BlazorGDB/Components/Account/Pages/InvalidPasswordReset.razor new file mode 100644 index 000000000..509578bbf --- /dev/null +++ b/src/BlazorGDB/BlazorGDB/Components/Account/Pages/InvalidPasswordReset.razor @@ -0,0 +1,8 @@ +@page "/Account/InvalidPasswordReset" + +Invalid password reset + +

    Invalid password reset

    +

    + The password reset link is invalid. +

    diff --git a/src/BlazorGDB/BlazorGDB/Components/Account/Pages/InvalidUser.razor b/src/BlazorGDB/BlazorGDB/Components/Account/Pages/InvalidUser.razor new file mode 100644 index 000000000..e61fe5def --- /dev/null +++ b/src/BlazorGDB/BlazorGDB/Components/Account/Pages/InvalidUser.razor @@ -0,0 +1,7 @@ +@page "/Account/InvalidUser" + +Invalid user + +

    Invalid user

    + + diff --git a/src/BlazorGDB/BlazorGDB/Components/Account/Pages/Lockout.razor b/src/BlazorGDB/BlazorGDB/Components/Account/Pages/Lockout.razor new file mode 100644 index 000000000..a8d1e0afc --- /dev/null +++ b/src/BlazorGDB/BlazorGDB/Components/Account/Pages/Lockout.razor @@ -0,0 +1,8 @@ +@page "/Account/Lockout" + +Locked out + +
    +

    Locked out

    +

    This account has been locked out, please try again later.

    +
    diff --git a/src/BlazorGDB/BlazorGDB/Components/Account/Pages/Login.razor b/src/BlazorGDB/BlazorGDB/Components/Account/Pages/Login.razor new file mode 100644 index 000000000..bbcdad956 --- /dev/null +++ b/src/BlazorGDB/BlazorGDB/Components/Account/Pages/Login.razor @@ -0,0 +1,131 @@ +@page "/Account/Login" +@* @rendermode InteractiveServer *@ +@* @rendermode InteractiveWebAssembly *@ +@rendermode InteractiveAuto + +@using System.ComponentModel.DataAnnotations +@using Microsoft.AspNetCore.Authentication +@using Microsoft.AspNetCore.Identity +@using BlazorGDB.Data + +@inject SignInManager SignInManager +@inject ILogger Logger +@inject NavigationManager NavigationManager +@inject IdentityRedirectManager RedirectManager + +Log in + +

    Log in

    +
    +
    +
    + + + +

    Use a local account to log in.

    +
    + +
    + + + +
    +
    + + + +
    +
    + +
    +
    + +
    + +
    +
    +
    +
    +
    +

    Use another service to log in.

    +
    + +
    +
    +
    + +@code { + private string? errorMessage; + + [CascadingParameter] + private HttpContext HttpContext { get; set; } = default!; + + [SupplyParameterFromForm] + private InputModel? Input { get; set; } + + [SupplyParameterFromQuery] + private string? ReturnUrl { get; set; } + + protected override async Task OnInitializedAsync() + { + if (HttpMethods.IsGet(HttpContext.Request.Method)) + { + // Clear the existing external cookie to ensure a clean login process + await HttpContext.SignOutAsync(IdentityConstants.ExternalScheme); + } + } + + public async Task LoginUser() + { + // This doesn't count login failures towards account lockout + // To enable password failures to trigger account lockout, set lockoutOnFailure: true + var result = await SignInManager.PasswordSignInAsync(Input!.Email, Input.Password, Input.RememberMe, lockoutOnFailure: false); + if (result.Succeeded) + { + Logger.LogInformation("User logged in."); + RedirectManager.RedirectTo(ReturnUrl); + } + else if (result.RequiresTwoFactor) + { + RedirectManager.RedirectTo( + "Account/LoginWith2fa", + new() { ["returnUrl"] = ReturnUrl, ["rememberMe"] = Input.RememberMe }); + } + else if (result.IsLockedOut) + { + Logger.LogWarning("User account locked out."); + RedirectManager.RedirectTo("Account/Lockout"); + } + else + { + errorMessage = "Error: Invalid login attempt."; + } + } + + private sealed class InputModel + { + [Required] + [EmailAddress] + public string Email { get; set; } = ""; + + [Required] + [DataType(DataType.Password)] + public string Password { get; set; } = ""; + + [Display(Name = "Remember me?")] + public bool RememberMe { get; set; } + } +} diff --git a/src/BlazorGDB/BlazorGDB/Components/Account/Pages/LoginWith2fa.razor b/src/BlazorGDB/BlazorGDB/Components/Account/Pages/LoginWith2fa.razor new file mode 100644 index 000000000..b6781bbba --- /dev/null +++ b/src/BlazorGDB/BlazorGDB/Components/Account/Pages/LoginWith2fa.razor @@ -0,0 +1,101 @@ +@page "/Account/LoginWith2fa" + +@using System.ComponentModel.DataAnnotations +@using Microsoft.AspNetCore.Identity +@using BlazorGDB.Data + +@inject SignInManager SignInManager +@inject UserManager UserManager +@inject IdentityRedirectManager RedirectManager +@inject ILogger Logger + +Two-factor authentication + +

    Two-factor authentication

    +
    + +

    Your login is protected with an authenticator app. Enter your authenticator code below.

    +
    +
    + + + + + +
    + + + +
    +
    + +
    +
    + +
    +
    +
    +
    +

    + Don't have access to your authenticator device? You can + log in with a recovery code. +

    + +@code { + private string? message; + private ApplicationUser user = default!; + + [SupplyParameterFromForm] + private InputModel Input { get; set; } = default!; + + [SupplyParameterFromQuery] + private string? ReturnUrl { get; set; } + + [SupplyParameterFromQuery] + private bool RememberMe { get; set; } + + protected override async Task OnInitializedAsync() + { + // Ensure the user has gone through the username & password screen first + user = await SignInManager.GetTwoFactorAuthenticationUserAsync() ?? + throw new InvalidOperationException("Unable to load two-factor authentication user."); + } + + private async Task OnValidSubmitAsync() + { + var authenticatorCode = Input.TwoFactorCode!.Replace(" ", string.Empty).Replace("-", string.Empty); + var result = await SignInManager.TwoFactorAuthenticatorSignInAsync(authenticatorCode, RememberMe, Input.RememberMachine); + var userId = await UserManager.GetUserIdAsync(user); + + if (result.Succeeded) + { + Logger.LogInformation("User with ID '{UserId}' logged in with 2fa.", userId); + RedirectManager.RedirectTo(ReturnUrl); + } + else if (result.IsLockedOut) + { + Logger.LogWarning("User with ID '{UserId}' account locked out.", userId); + RedirectManager.RedirectTo("Account/Lockout"); + } + else + { + Logger.LogWarning("Invalid authenticator code entered for user with ID '{UserId}'.", userId); + message = "Error: Invalid authenticator code."; + } + } + + private sealed class InputModel + { + [Required] + [StringLength(7, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 6)] + [DataType(DataType.Text)] + [Display(Name = "Authenticator code")] + public string? TwoFactorCode { get; set; } + + [Display(Name = "Remember this machine")] + public bool RememberMachine { get; set; } + } +} diff --git a/src/BlazorGDB/BlazorGDB/Components/Account/Pages/LoginWithRecoveryCode.razor b/src/BlazorGDB/BlazorGDB/Components/Account/Pages/LoginWithRecoveryCode.razor new file mode 100644 index 000000000..177eeba49 --- /dev/null +++ b/src/BlazorGDB/BlazorGDB/Components/Account/Pages/LoginWithRecoveryCode.razor @@ -0,0 +1,85 @@ +@page "/Account/LoginWithRecoveryCode" + +@using System.ComponentModel.DataAnnotations +@using Microsoft.AspNetCore.Identity +@using BlazorGDB.Data + +@inject SignInManager SignInManager +@inject UserManager UserManager +@inject IdentityRedirectManager RedirectManager +@inject ILogger Logger + +Recovery code verification + +

    Recovery code verification

    +
    + +

    + You have requested to log in with a recovery code. This login will not be remembered until you provide + an authenticator app code at log in or disable 2FA and log in again. +

    +
    +
    + + + +
    + + + +
    + +
    +
    +
    + +@code { + private string? message; + private ApplicationUser user = default!; + + [SupplyParameterFromForm] + private InputModel Input { get; set; } = default!; + + [SupplyParameterFromQuery] + private string? ReturnUrl { get; set; } + + protected override async Task OnInitializedAsync() + { + // Ensure the user has gone through the username & password screen first + user = await SignInManager.GetTwoFactorAuthenticationUserAsync() ?? + throw new InvalidOperationException("Unable to load two-factor authentication user."); + } + + private async Task OnValidSubmitAsync() + { + var recoveryCode = Input.RecoveryCode.Replace(" ", string.Empty); + + var result = await SignInManager.TwoFactorRecoveryCodeSignInAsync(recoveryCode); + + var userId = await UserManager.GetUserIdAsync(user); + + if (result.Succeeded) + { + Logger.LogInformation("User with ID '{UserId}' logged in with a recovery code.", userId); + RedirectManager.RedirectTo(ReturnUrl); + } + else if (result.IsLockedOut) + { + Logger.LogWarning("User account locked out."); + RedirectManager.RedirectTo("Account/Lockout"); + } + else + { + Logger.LogWarning("Invalid recovery code entered for user with ID '{UserId}' ", userId); + message = "Error: Invalid recovery code entered."; + } + } + + private sealed class InputModel + { + [Required] + [DataType(DataType.Text)] + [Display(Name = "Recovery Code")] + public string RecoveryCode { get; set; } = ""; + } +} diff --git a/src/BlazorGDB/BlazorGDB/Components/Account/Pages/Manage/ChangePassword.razor b/src/BlazorGDB/BlazorGDB/Components/Account/Pages/Manage/ChangePassword.razor new file mode 100644 index 000000000..743d7d05c --- /dev/null +++ b/src/BlazorGDB/BlazorGDB/Components/Account/Pages/Manage/ChangePassword.razor @@ -0,0 +1,96 @@ +@page "/Account/Manage/ChangePassword" + +@using System.ComponentModel.DataAnnotations +@using Microsoft.AspNetCore.Identity +@using BlazorGDB.Data + +@inject UserManager UserManager +@inject SignInManager SignInManager +@inject IdentityUserAccessor UserAccessor +@inject IdentityRedirectManager RedirectManager +@inject ILogger Logger + +Change password + +

    Change password

    + +
    +
    + + + +
    + + + +
    +
    + + + +
    +
    + + + +
    + +
    +
    +
    + +@code { + private string? message; + private ApplicationUser user = default!; + private bool hasPassword; + + [CascadingParameter] + private HttpContext HttpContext { get; set; } = default!; + + [SupplyParameterFromForm] + private InputModel? Input { get; set; } + + protected override async Task OnInitializedAsync() + { + user = await UserAccessor.GetRequiredUserAsync(HttpContext); + hasPassword = await UserManager.HasPasswordAsync(user); + if (!hasPassword) + { + RedirectManager.RedirectTo("Account/Manage/SetPassword"); + } + } + + private async Task OnValidSubmitAsync() + { + var changePasswordResult = await UserManager.ChangePasswordAsync(user, Input!.OldPassword, Input.NewPassword); + if (!changePasswordResult.Succeeded) + { + message = $"Error: {string.Join(",", changePasswordResult.Errors.Select(error => error.Description))}"; + return; + } + + await SignInManager.RefreshSignInAsync(user); + Logger.LogInformation("User changed their password successfully."); + + RedirectManager.RedirectToCurrentPageWithStatus("Your password has been changed", HttpContext); + } + + private sealed class InputModel + { + [Required] + [DataType(DataType.Password)] + [Display(Name = "Current password")] + public string OldPassword { get; set; } = ""; + + [Required] + [StringLength(100, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 6)] + [DataType(DataType.Password)] + [Display(Name = "New password")] + public string NewPassword { get; set; } = ""; + + [DataType(DataType.Password)] + [Display(Name = "Confirm new password")] + [Compare("NewPassword", ErrorMessage = "The new password and confirmation password do not match.")] + public string ConfirmPassword { get; set; } = ""; + } +} diff --git a/src/BlazorGDB/BlazorGDB/Components/Account/Pages/Manage/DeletePersonalData.razor b/src/BlazorGDB/BlazorGDB/Components/Account/Pages/Manage/DeletePersonalData.razor new file mode 100644 index 000000000..b7afcbd37 --- /dev/null +++ b/src/BlazorGDB/BlazorGDB/Components/Account/Pages/Manage/DeletePersonalData.razor @@ -0,0 +1,89 @@ +@page "/Account/Manage/DeletePersonalData" + +@using System.ComponentModel.DataAnnotations +@using Microsoft.AspNetCore.Identity +@using BlazorGDB.Data + +@inject UserManager UserManager +@inject SignInManager SignInManager +@inject IdentityUserAccessor UserAccessor +@inject IdentityRedirectManager RedirectManager +@inject ILogger Logger + +Delete Personal Data + + + +

    Delete Personal Data

    + + + +
    + + + + @if (requirePassword) + { +
    + + + +
    + } + +
    +
    + +@code { + private string? message; + private ApplicationUser user = default!; + private bool requirePassword; + + [CascadingParameter] + private HttpContext HttpContext { get; set; } = default!; + + [SupplyParameterFromForm] + private InputModel? Input { get; set; } + + protected override async Task OnInitializedAsync() + { + if (Input == null) + { + Input = new InputModel(); + } + user = await UserAccessor.GetRequiredUserAsync(HttpContext); + requirePassword = await UserManager.HasPasswordAsync(user); + } + + private async Task OnValidSubmitAsync() + { + if (requirePassword && !await UserManager.CheckPasswordAsync(user, Input!.Password)) + { + message = "Error: Incorrect password."; + return; + } + + var result = await UserManager.DeleteAsync(user); + if (!result.Succeeded) + { + throw new InvalidOperationException("Unexpected error occurred deleting user."); + } + + await SignInManager.SignOutAsync(); + + var userId = await UserManager.GetUserIdAsync(user); + Logger.LogInformation("User with ID '{UserId}' deleted themselves.", userId); + + RedirectManager.RedirectToCurrentPage(); + } + + private sealed class InputModel + { + [DataType(DataType.Password)] + public string Password { get; set; } = ""; + } +} diff --git a/src/BlazorGDB/BlazorGDB/Components/Account/Pages/Manage/Disable2fa.razor b/src/BlazorGDB/BlazorGDB/Components/Account/Pages/Manage/Disable2fa.razor new file mode 100644 index 000000000..18069c385 --- /dev/null +++ b/src/BlazorGDB/BlazorGDB/Components/Account/Pages/Manage/Disable2fa.razor @@ -0,0 +1,64 @@ +@page "/Account/Manage/Disable2fa" + +@using Microsoft.AspNetCore.Identity +@using BlazorGDB.Data + +@inject UserManager UserManager +@inject IdentityUserAccessor UserAccessor +@inject IdentityRedirectManager RedirectManager +@inject ILogger Logger + +Disable two-factor authentication (2FA) + + +

    Disable two-factor authentication (2FA)

    + + + +
    +
    + + + +
    + +@code { + private ApplicationUser user = default!; + + [CascadingParameter] + private HttpContext HttpContext { get; set; } = default!; + + protected override async Task OnInitializedAsync() + { + user = await UserAccessor.GetRequiredUserAsync(HttpContext); + + if (HttpMethods.IsGet(HttpContext.Request.Method) && !await UserManager.GetTwoFactorEnabledAsync(user)) + { + throw new InvalidOperationException("Cannot disable 2FA for user as it's not currently enabled."); + } + } + + private async Task OnSubmitAsync() + { + var disable2faResult = await UserManager.SetTwoFactorEnabledAsync(user, false); + if (!disable2faResult.Succeeded) + { + throw new InvalidOperationException("Unexpected error occurred disabling 2FA."); + } + + var userId = await UserManager.GetUserIdAsync(user); + Logger.LogInformation("User with ID '{UserId}' has disabled 2fa.", userId); + RedirectManager.RedirectToWithStatus( + "Account/Manage/TwoFactorAuthentication", + "2fa has been disabled. You can reenable 2fa when you setup an authenticator app", + HttpContext); + } +} diff --git a/src/BlazorGDB/BlazorGDB/Components/Account/Pages/Manage/Email.razor b/src/BlazorGDB/BlazorGDB/Components/Account/Pages/Manage/Email.razor new file mode 100644 index 000000000..295997555 --- /dev/null +++ b/src/BlazorGDB/BlazorGDB/Components/Account/Pages/Manage/Email.razor @@ -0,0 +1,123 @@ +@page "/Account/Manage/Email" + +@using System.ComponentModel.DataAnnotations +@using System.Text +@using System.Text.Encodings.Web +@using Microsoft.AspNetCore.Identity +@using Microsoft.AspNetCore.WebUtilities +@using BlazorGDB.Data + +@inject UserManager UserManager +@inject IEmailSender EmailSender +@inject IdentityUserAccessor UserAccessor +@inject NavigationManager NavigationManager + +Manage email + +

    Manage email

    + + +
    +
    +
    + + + + + + @if (isEmailConfirmed) + { +
    + +
    + +
    + +
    + } + else + { +
    + + + +
    + } +
    + + + +
    + +
    +
    +
    + +@code { + private string? message; + private ApplicationUser user = default!; + private string? email; + private bool isEmailConfirmed; + + [CascadingParameter] + private HttpContext HttpContext { get; set; } = default!; + + [SupplyParameterFromForm(FormName = "change-email")] + private InputModel? Input { get; set; } + + protected override async Task OnInitializedAsync() + { + user = await UserAccessor.GetRequiredUserAsync(HttpContext); + email = await UserManager.GetEmailAsync(user); + isEmailConfirmed = await UserManager.IsEmailConfirmedAsync(user); + + Input!.NewEmail ??= email; + } + + private async Task OnValidSubmitAsync() + { + if (Input!.NewEmail is null || Input.NewEmail == email) + { + message = "Your email is unchanged."; + return; + } + + var userId = await UserManager.GetUserIdAsync(user); + var code = await UserManager.GenerateChangeEmailTokenAsync(user, Input.NewEmail); + code = WebEncoders.Base64UrlEncode(Encoding.UTF8.GetBytes(code)); + var callbackUrl = NavigationManager.GetUriWithQueryParameters( + NavigationManager.ToAbsoluteUri("Account/ConfirmEmailChange").AbsoluteUri, + new Dictionary { ["userId"] = userId, ["email"] = Input.NewEmail, ["code"] = code }); + + await EmailSender.SendConfirmationLinkAsync(user, Input.NewEmail, HtmlEncoder.Default.Encode(callbackUrl)); + + message = "Confirmation link to change email sent. Please check your email."; + } + + private async Task OnSendEmailVerificationAsync() + { + if (email is null) + { + return; + } + + var userId = await UserManager.GetUserIdAsync(user); + var code = await UserManager.GenerateEmailConfirmationTokenAsync(user); + code = WebEncoders.Base64UrlEncode(Encoding.UTF8.GetBytes(code)); + var callbackUrl = NavigationManager.GetUriWithQueryParameters( + NavigationManager.ToAbsoluteUri("Account/ConfirmEmail").AbsoluteUri, + new Dictionary { ["userId"] = userId, ["code"] = code }); + + await EmailSender.SendConfirmationLinkAsync(user, email, HtmlEncoder.Default.Encode(callbackUrl)); + + message = "Verification email sent. Please check your email."; + } + + private sealed class InputModel + { + [Required] + [EmailAddress] + [Display(Name = "New email")] + public string? NewEmail { get; set; } + } +} diff --git a/src/BlazorGDB/BlazorGDB/Components/Account/Pages/Manage/EnableAuthenticator.razor b/src/BlazorGDB/BlazorGDB/Components/Account/Pages/Manage/EnableAuthenticator.razor new file mode 100644 index 000000000..abc43b2b8 --- /dev/null +++ b/src/BlazorGDB/BlazorGDB/Components/Account/Pages/Manage/EnableAuthenticator.razor @@ -0,0 +1,173 @@ +@page "/Account/Manage/EnableAuthenticator" + +@using System.ComponentModel.DataAnnotations +@using System.Globalization +@using System.Text +@using System.Text.Encodings.Web +@using Microsoft.AspNetCore.Identity +@using BlazorGDB.Data + +@inject UserManager UserManager +@inject IdentityUserAccessor UserAccessor +@inject UrlEncoder UrlEncoder +@inject IdentityRedirectManager RedirectManager +@inject ILogger Logger + +Configure authenticator app + +@if (recoveryCodes is not null) +{ + +} +else +{ + +

    Configure authenticator app

    +
    +

    To use an authenticator app go through the following steps:

    +
      +
    1. +

      + Download a two-factor authenticator app like Microsoft Authenticator for + Android and + iOS or + Google Authenticator for + Android and + iOS. +

      +
    2. +
    3. +

      Scan the QR Code or enter this key @sharedKey into your two factor authenticator app. Spaces and casing do not matter.

      + +
      +
      +
    4. +
    5. +

      + Once you have scanned the QR code or input the key above, your two factor authentication app will provide you + with a unique code. Enter the code in the confirmation box below. +

      +
      +
      + + +
      + + + +
      + + +
      +
      +
      +
    6. +
    +
    +} + +@code { + private const string AuthenticatorUriFormat = "otpauth://totp/{0}:{1}?secret={2}&issuer={0}&digits=6"; + + private string? message; + private ApplicationUser user = default!; + private string? sharedKey; + private string? authenticatorUri; + private IEnumerable? recoveryCodes; + + [CascadingParameter] + private HttpContext HttpContext { get; set; } = default!; + + [SupplyParameterFromForm] + private InputModel? Input { get; set; } + + protected override async Task OnInitializedAsync() + { + Input = new InputModel(); + user = await UserAccessor.GetRequiredUserAsync(HttpContext); + + await LoadSharedKeyAndQrCodeUriAsync(user); + } + + private async Task OnValidSubmitAsync() + { + // Strip spaces and hyphens + var verificationCode = Input!.Code.Replace(" ", string.Empty).Replace("-", string.Empty); + + var is2faTokenValid = await UserManager.VerifyTwoFactorTokenAsync( + user, UserManager.Options.Tokens.AuthenticatorTokenProvider, verificationCode); + + if (!is2faTokenValid) + { + message = "Error: Verification code is invalid."; + return; + } + + await UserManager.SetTwoFactorEnabledAsync(user, true); + var userId = await UserManager.GetUserIdAsync(user); + Logger.LogInformation("User with ID '{UserId}' has enabled 2FA with an authenticator app.", userId); + + message = "Your authenticator app has been verified."; + + if (await UserManager.CountRecoveryCodesAsync(user) == 0) + { + recoveryCodes = await UserManager.GenerateNewTwoFactorRecoveryCodesAsync(user, 10); + } + else + { + RedirectManager.RedirectToWithStatus("Account/Manage/TwoFactorAuthentication", message, HttpContext); + } + } + + private async ValueTask LoadSharedKeyAndQrCodeUriAsync(ApplicationUser user) + { + // Load the authenticator key & QR code URI to display on the form + var unformattedKey = await UserManager.GetAuthenticatorKeyAsync(user); + if (string.IsNullOrEmpty(unformattedKey)) + { + await UserManager.ResetAuthenticatorKeyAsync(user); + unformattedKey = await UserManager.GetAuthenticatorKeyAsync(user); + } + + sharedKey = FormatKey(unformattedKey!); + + var email = await UserManager.GetEmailAsync(user); + authenticatorUri = GenerateQrCodeUri(email!, unformattedKey!); + } + + private string FormatKey(string unformattedKey) + { + var result = new StringBuilder(); + int currentPosition = 0; + while (currentPosition + 4 < unformattedKey.Length) + { + result.Append(unformattedKey.AsSpan(currentPosition, 4)).Append(' '); + currentPosition += 4; + } + if (currentPosition < unformattedKey.Length) + { + result.Append(unformattedKey.AsSpan(currentPosition)); + } + + return result.ToString().ToLowerInvariant(); + } + + private string GenerateQrCodeUri(string email, string unformattedKey) + { + return string.Format( + CultureInfo.InvariantCulture, + AuthenticatorUriFormat, + UrlEncoder.Encode("Microsoft.AspNetCore.Identity.UI"), + UrlEncoder.Encode(email), + unformattedKey); + } + + private sealed class InputModel + { + [Required] + [StringLength(7, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 6)] + [DataType(DataType.Text)] + [Display(Name = "Verification Code")] + public string Code { get; set; } = ""; + } +} diff --git a/src/BlazorGDB/BlazorGDB/Components/Account/Pages/Manage/ExternalLogins.razor b/src/BlazorGDB/BlazorGDB/Components/Account/Pages/Manage/ExternalLogins.razor new file mode 100644 index 000000000..bc7df5565 --- /dev/null +++ b/src/BlazorGDB/BlazorGDB/Components/Account/Pages/Manage/ExternalLogins.razor @@ -0,0 +1,140 @@ +@page "/Account/Manage/ExternalLogins" + +@using Microsoft.AspNetCore.Authentication +@using Microsoft.AspNetCore.Identity +@using BlazorGDB.Data + +@inject UserManager UserManager +@inject SignInManager SignInManager +@inject IdentityUserAccessor UserAccessor +@inject IUserStore UserStore +@inject IdentityRedirectManager RedirectManager + +Manage your external logins + + +@if (currentLogins?.Count > 0) +{ +

    Registered Logins

    + + + @foreach (var login in currentLogins) + { + + + + + } + +
    @login.ProviderDisplayName + @if (showRemoveButton) + { +
    + +
    + + + +
    + + } + else + { + @:   + } +
    +} +@if (otherLogins?.Count > 0) +{ +

    Add another service to log in.

    +
    +
    + +
    +

    + @foreach (var provider in otherLogins) + { + + } +

    +
    + +} + +@code { + public const string LinkLoginCallbackAction = "LinkLoginCallback"; + + private ApplicationUser user = default!; + private IList? currentLogins; + private IList? otherLogins; + private bool showRemoveButton; + + [CascadingParameter] + private HttpContext HttpContext { get; set; } = default!; + + [SupplyParameterFromForm] + private string? LoginProvider { get; set; } + + [SupplyParameterFromForm] + private string? ProviderKey { get; set; } + + [SupplyParameterFromQuery] + private string? Action { get; set; } + + protected override async Task OnInitializedAsync() + { + user = await UserAccessor.GetRequiredUserAsync(HttpContext); + currentLogins = await UserManager.GetLoginsAsync(user); + otherLogins = (await SignInManager.GetExternalAuthenticationSchemesAsync()) + .Where(auth => currentLogins.All(ul => auth.Name != ul.LoginProvider)) + .ToList(); + + string? passwordHash = null; + if (UserStore is IUserPasswordStore userPasswordStore) + { + passwordHash = await userPasswordStore.GetPasswordHashAsync(user, HttpContext.RequestAborted); + } + + showRemoveButton = passwordHash is not null || currentLogins.Count > 1; + + if (HttpMethods.IsGet(HttpContext.Request.Method) && Action == LinkLoginCallbackAction) + { + await OnGetLinkLoginCallbackAsync(); + } + } + + private async Task OnSubmitAsync() + { + var result = await UserManager.RemoveLoginAsync(user, LoginProvider!, ProviderKey!); + if (!result.Succeeded) + { + RedirectManager.RedirectToCurrentPageWithStatus("Error: The external login was not removed.", HttpContext); + } + + await SignInManager.RefreshSignInAsync(user); + RedirectManager.RedirectToCurrentPageWithStatus("The external login was removed.", HttpContext); + } + + private async Task OnGetLinkLoginCallbackAsync() + { + var userId = await UserManager.GetUserIdAsync(user); + var info = await SignInManager.GetExternalLoginInfoAsync(userId); + if (info is null) + { + RedirectManager.RedirectToCurrentPageWithStatus("Error: Could not load external login info.", HttpContext); + } + + var result = await UserManager.AddLoginAsync(user, info); + if (!result.Succeeded) + { + RedirectManager.RedirectToCurrentPageWithStatus("Error: The external login was not added. External logins can only be associated with one account.", HttpContext); + } + + // Clear the existing external cookie to ensure a clean login process + await HttpContext.SignOutAsync(IdentityConstants.ExternalScheme); + + RedirectManager.RedirectToCurrentPageWithStatus("The external login was added.", HttpContext); + } +} diff --git a/src/BlazorGDB/BlazorGDB/Components/Account/Pages/Manage/GenerateRecoveryCodes.razor b/src/BlazorGDB/BlazorGDB/Components/Account/Pages/Manage/GenerateRecoveryCodes.razor new file mode 100644 index 000000000..6ae658a3d --- /dev/null +++ b/src/BlazorGDB/BlazorGDB/Components/Account/Pages/Manage/GenerateRecoveryCodes.razor @@ -0,0 +1,68 @@ +@page "/Account/Manage/GenerateRecoveryCodes" + +@using Microsoft.AspNetCore.Identity +@using BlazorGDB.Data + +@inject UserManager UserManager +@inject IdentityUserAccessor UserAccessor +@inject IdentityRedirectManager RedirectManager +@inject ILogger Logger + +Generate two-factor authentication (2FA) recovery codes + +@if (recoveryCodes is not null) +{ + +} +else +{ +

    Generate two-factor authentication (2FA) recovery codes

    + +
    +
    + + + +
    +} + +@code { + private string? message; + private ApplicationUser user = default!; + private IEnumerable? recoveryCodes; + + [CascadingParameter] + private HttpContext HttpContext { get; set; } = default!; + + protected override async Task OnInitializedAsync() + { + user = await UserAccessor.GetRequiredUserAsync(HttpContext); + + var isTwoFactorEnabled = await UserManager.GetTwoFactorEnabledAsync(user); + if (!isTwoFactorEnabled) + { + throw new InvalidOperationException("Cannot generate recovery codes for user because they do not have 2FA enabled."); + } + } + + private async Task OnSubmitAsync() + { + var userId = await UserManager.GetUserIdAsync(user); + recoveryCodes = await UserManager.GenerateNewTwoFactorRecoveryCodesAsync(user, 10); + message = "You have generated new recovery codes."; + + Logger.LogInformation("User with ID '{UserId}' has generated new 2FA recovery codes.", userId); + } +} diff --git a/src/BlazorGDB/BlazorGDB/Components/Account/Pages/Manage/Index.razor b/src/BlazorGDB/BlazorGDB/Components/Account/Pages/Manage/Index.razor new file mode 100644 index 000000000..67b0ed926 --- /dev/null +++ b/src/BlazorGDB/BlazorGDB/Components/Account/Pages/Manage/Index.razor @@ -0,0 +1,78 @@ +@page "/Account/Manage" + +@using System.ComponentModel.DataAnnotations +@using Microsoft.AspNetCore.Identity +@using BlazorGDB.Data + +@inject UserManager UserManager +@inject SignInManager SignInManager +@inject IdentityUserAccessor UserAccessor +@inject IdentityRedirectManager RedirectManager + +Profile + +

    Profile

    + + +
    +
    + + + +
    + + +
    +
    + + + +
    + +
    +
    +
    + +@code { + private ApplicationUser user = default!; + private string? username; + private string? phoneNumber; + + [CascadingParameter] + private HttpContext HttpContext { get; set; } = default!; + + [SupplyParameterFromForm] + private InputModel? Input { get; set; } + + protected override async Task OnInitializedAsync() + { + user = await UserAccessor.GetRequiredUserAsync(HttpContext); + username = await UserManager.GetUserNameAsync(user); + phoneNumber = await UserManager.GetPhoneNumberAsync(user); + + Input ??= new InputModel(); + Input.PhoneNumber ??= phoneNumber; + } + + private async Task OnValidSubmitAsync() + { + if (Input!.PhoneNumber != phoneNumber) + { + var setPhoneResult = await UserManager.SetPhoneNumberAsync(user, Input.PhoneNumber); + if (!setPhoneResult.Succeeded) + { + RedirectManager.RedirectToCurrentPageWithStatus("Error: Failed to set phone number.", HttpContext); + } + } + + await SignInManager.RefreshSignInAsync(user); + RedirectManager.RedirectToCurrentPageWithStatus("Your profile has been updated", HttpContext); + } + + private sealed class InputModel + { + [Phone] + [Display(Name = "Phone number")] + public string? PhoneNumber { get; set; } + } +} diff --git a/src/BlazorGDB/BlazorGDB/Components/Account/Pages/Manage/PersonalData.razor b/src/BlazorGDB/BlazorGDB/Components/Account/Pages/Manage/PersonalData.razor new file mode 100644 index 000000000..851eb54c8 --- /dev/null +++ b/src/BlazorGDB/BlazorGDB/Components/Account/Pages/Manage/PersonalData.razor @@ -0,0 +1,34 @@ +@page "/Account/Manage/PersonalData" + +@inject IdentityUserAccessor UserAccessor + +Personal Data + + +

    Personal Data

    + +
    +
    +

    Your account contains personal data that you have given us. This page allows you to download or delete that data.

    +

    + Deleting this data will permanently remove your account, and this cannot be recovered. +

    +
    + + + +

    + Delete +

    +
    +
    + +@code { + [CascadingParameter] + private HttpContext HttpContext { get; set; } = default!; + + protected override async Task OnInitializedAsync() + { + _ = await UserAccessor.GetRequiredUserAsync(HttpContext); + } +} diff --git a/src/BlazorGDB/BlazorGDB/Components/Account/Pages/Manage/ResetAuthenticator.razor b/src/BlazorGDB/BlazorGDB/Components/Account/Pages/Manage/ResetAuthenticator.razor new file mode 100644 index 000000000..35f7422a8 --- /dev/null +++ b/src/BlazorGDB/BlazorGDB/Components/Account/Pages/Manage/ResetAuthenticator.razor @@ -0,0 +1,52 @@ +@page "/Account/Manage/ResetAuthenticator" + +@using Microsoft.AspNetCore.Identity +@using BlazorGDB.Data + +@inject UserManager UserManager +@inject SignInManager SignInManager +@inject IdentityUserAccessor UserAccessor +@inject IdentityRedirectManager RedirectManager +@inject ILogger Logger + +Reset authenticator key + + +

    Reset authenticator key

    + +
    +
    + + + +
    + +@code { + [CascadingParameter] + private HttpContext HttpContext { get; set; } = default!; + + private async Task OnSubmitAsync() + { + var user = await UserAccessor.GetRequiredUserAsync(HttpContext); + await UserManager.SetTwoFactorEnabledAsync(user, false); + await UserManager.ResetAuthenticatorKeyAsync(user); + var userId = await UserManager.GetUserIdAsync(user); + Logger.LogInformation("User with ID '{UserId}' has reset their authentication app key.", userId); + + await SignInManager.RefreshSignInAsync(user); + + RedirectManager.RedirectToWithStatus( + "Account/Manage/EnableAuthenticator", + "Your authenticator app key has been reset, you will need to configure your authenticator app using the new key.", + HttpContext); + } +} diff --git a/src/BlazorGDB/BlazorGDB/Components/Account/Pages/Manage/SetPassword.razor b/src/BlazorGDB/BlazorGDB/Components/Account/Pages/Manage/SetPassword.razor new file mode 100644 index 000000000..231f3500a --- /dev/null +++ b/src/BlazorGDB/BlazorGDB/Components/Account/Pages/Manage/SetPassword.razor @@ -0,0 +1,88 @@ +@page "/Account/Manage/SetPassword" + +@using System.ComponentModel.DataAnnotations +@using Microsoft.AspNetCore.Identity +@using BlazorGDB.Data + +@inject UserManager UserManager +@inject SignInManager SignInManager +@inject IdentityUserAccessor UserAccessor +@inject IdentityRedirectManager RedirectManager + +Set password + +

    Set your password

    + +

    + You do not have a local username/password for this site. Add a local + account so you can log in without an external login. +

    +
    +
    + + + +
    + + + +
    +
    + + + +
    + +
    +
    +
    + +@code { + private string? message; + private ApplicationUser user = default!; + + [CascadingParameter] + private HttpContext HttpContext { get; set; } = default!; + + [SupplyParameterFromForm] + private InputModel Input { get; set; } = default!; + + protected override async Task OnInitializedAsync() + { + Input ??= new(); + user = await UserAccessor.GetRequiredUserAsync(HttpContext); + + var hasPassword = await UserManager.HasPasswordAsync(user); + if (hasPassword) + { + RedirectManager.RedirectTo("Account/Manage/ChangePassword"); + } + } + + private async Task OnValidSubmitAsync() + { + var addPasswordResult = await UserManager.AddPasswordAsync(user, Input.NewPassword!); + if (!addPasswordResult.Succeeded) + { + message = $"Error: {string.Join(",", addPasswordResult.Errors.Select(error => error.Description))}"; + return; + } + + await SignInManager.RefreshSignInAsync(user); + RedirectManager.RedirectToCurrentPageWithStatus("Your password has been set.", HttpContext); + } + + private sealed class InputModel + { + [Required] + [StringLength(100, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 6)] + [DataType(DataType.Password)] + [Display(Name = "New password")] + public string? NewPassword { get; set; } + + [DataType(DataType.Password)] + [Display(Name = "Confirm new password")] + [Compare("NewPassword", ErrorMessage = "The new password and confirmation password do not match.")] + public string? ConfirmPassword { get; set; } + } +} diff --git a/src/BlazorGDB/BlazorGDB/Components/Account/Pages/Manage/TwoFactorAuthentication.razor b/src/BlazorGDB/BlazorGDB/Components/Account/Pages/Manage/TwoFactorAuthentication.razor new file mode 100644 index 000000000..d6fe84935 --- /dev/null +++ b/src/BlazorGDB/BlazorGDB/Components/Account/Pages/Manage/TwoFactorAuthentication.razor @@ -0,0 +1,101 @@ +@page "/Account/Manage/TwoFactorAuthentication" + +@using Microsoft.AspNetCore.Http.Features +@using Microsoft.AspNetCore.Identity +@using BlazorGDB.Data + +@inject UserManager UserManager +@inject SignInManager SignInManager +@inject IdentityUserAccessor UserAccessor +@inject IdentityRedirectManager RedirectManager + +Two-factor authentication (2FA) + + +

    Two-factor authentication (2FA)

    +@if (canTrack) +{ + if (is2faEnabled) + { + if (recoveryCodesLeft == 0) + { +
    + You have no recovery codes left. +

    You must generate a new set of recovery codes before you can log in with a recovery code.

    +
    + } + else if (recoveryCodesLeft == 1) + { +
    + You have 1 recovery code left. +

    You can generate a new set of recovery codes.

    +
    + } + else if (recoveryCodesLeft <= 3) + { +
    + You have @recoveryCodesLeft recovery codes left. +

    You should generate a new set of recovery codes.

    +
    + } + + if (isMachineRemembered) + { +
    + + + + } + + Disable 2FA + Reset recovery codes + } + +

    Authenticator app

    + @if (!hasAuthenticator) + { + Add authenticator app + } + else + { + Set up authenticator app + Reset authenticator app + } +} +else +{ +
    + Privacy and cookie policy have not been accepted. +

    You must accept the policy before you can enable two factor authentication.

    +
    +} + +@code { + private bool canTrack; + private bool hasAuthenticator; + private int recoveryCodesLeft; + private bool is2faEnabled; + private bool isMachineRemembered; + + [CascadingParameter] + private HttpContext HttpContext { get; set; } = default!; + + protected override async Task OnInitializedAsync() + { + var user = await UserAccessor.GetRequiredUserAsync(HttpContext); + canTrack = HttpContext.Features.Get()?.CanTrack ?? true; + hasAuthenticator = await UserManager.GetAuthenticatorKeyAsync(user) is not null; + is2faEnabled = await UserManager.GetTwoFactorEnabledAsync(user); + isMachineRemembered = await SignInManager.IsTwoFactorClientRememberedAsync(user); + recoveryCodesLeft = await UserManager.CountRecoveryCodesAsync(user); + } + + private async Task OnSubmitForgetBrowserAsync() + { + await SignInManager.ForgetTwoFactorClientAsync(); + + RedirectManager.RedirectToCurrentPageWithStatus( + "The current browser has been forgotten. When you login again from this browser you will be prompted for your 2fa code.", + HttpContext); + } +} diff --git a/src/BlazorGDB/BlazorGDB/Components/Account/Pages/Manage/_Imports.razor b/src/BlazorGDB/BlazorGDB/Components/Account/Pages/Manage/_Imports.razor new file mode 100644 index 000000000..ada5bb010 --- /dev/null +++ b/src/BlazorGDB/BlazorGDB/Components/Account/Pages/Manage/_Imports.razor @@ -0,0 +1,2 @@ +@layout ManageLayout +@attribute [Microsoft.AspNetCore.Authorization.Authorize] diff --git a/src/BlazorGDB/BlazorGDB/Components/Account/Pages/Register.razor b/src/BlazorGDB/BlazorGDB/Components/Account/Pages/Register.razor new file mode 100644 index 000000000..9ea93c2c2 --- /dev/null +++ b/src/BlazorGDB/BlazorGDB/Components/Account/Pages/Register.razor @@ -0,0 +1,145 @@ +@page "/Account/Register" + +@using System.ComponentModel.DataAnnotations +@using System.Text +@using System.Text.Encodings.Web +@using Microsoft.AspNetCore.Identity +@using Microsoft.AspNetCore.WebUtilities +@using BlazorGDB.Data + +@inject UserManager UserManager +@inject IUserStore UserStore +@inject SignInManager SignInManager +@inject IEmailSender EmailSender +@inject ILogger Logger +@inject NavigationManager NavigationManager +@inject IdentityRedirectManager RedirectManager + +Register + +

    Register

    + +
    +
    + + + +

    Create a new account.

    +
    + +
    + + + +
    +
    + + + +
    +
    + + + +
    + +
    +
    +
    +
    +

    Use another service to register.

    +
    + +
    +
    +
    + +@code { + private IEnumerable? identityErrors; + + [SupplyParameterFromForm] + private InputModel Input { get; set; } = null!; + + [SupplyParameterFromQuery] + private string? ReturnUrl { get; set; } + + private string? Message => identityErrors is null ? null : $"Error: {string.Join(", ", identityErrors.Select(error => error.Description))}"; + + public async Task RegisterUser(EditContext editContext) + { + var user = CreateUser(); + + await UserStore.SetUserNameAsync(user, Input.Email, CancellationToken.None); + var emailStore = GetEmailStore(); + await emailStore.SetEmailAsync(user, Input.Email, CancellationToken.None); + var result = await UserManager.CreateAsync(user, Input.Password); + + if (!result.Succeeded) + { + identityErrors = result.Errors; + return; + } + + Logger.LogInformation("User created a new account with password."); + + var userId = await UserManager.GetUserIdAsync(user); + var code = await UserManager.GenerateEmailConfirmationTokenAsync(user); + code = WebEncoders.Base64UrlEncode(Encoding.UTF8.GetBytes(code)); + var callbackUrl = NavigationManager.GetUriWithQueryParameters( + NavigationManager.ToAbsoluteUri("Account/ConfirmEmail").AbsoluteUri, + new Dictionary { ["userId"] = userId, ["code"] = code, ["returnUrl"] = ReturnUrl }); + + await EmailSender.SendConfirmationLinkAsync(user, Input.Email, HtmlEncoder.Default.Encode(callbackUrl)); + + if (UserManager.Options.SignIn.RequireConfirmedAccount) + { + RedirectManager.RedirectTo( + "Account/RegisterConfirmation", + new() { ["email"] = Input.Email, ["returnUrl"] = ReturnUrl }); + } + + await SignInManager.SignInAsync(user, isPersistent: false); + RedirectManager.RedirectTo(ReturnUrl); + } + + private ApplicationUser CreateUser() + { + try + { + return Activator.CreateInstance(); + } + catch + { + throw new InvalidOperationException($"Can't create an instance of '{nameof(ApplicationUser)}'. " + + $"Ensure that '{nameof(ApplicationUser)}' is not an abstract class and has a parameterless constructor."); + } + } + + private IUserEmailStore GetEmailStore() + { + if (!UserManager.SupportsUserEmail) + { + throw new NotSupportedException("The default UI requires a user store with email support."); + } + return (IUserEmailStore)UserStore; + } + + private sealed class InputModel + { + [Required] + [EmailAddress] + [Display(Name = "Email")] + public string Email { get; set; } = ""; + + [Required] + [StringLength(100, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 6)] + [DataType(DataType.Password)] + [Display(Name = "Password")] + public string Password { get; set; } = ""; + + [DataType(DataType.Password)] + [Display(Name = "Confirm password")] + [Compare("Password", ErrorMessage = "The password and confirmation password do not match.")] + public string ConfirmPassword { get; set; } = ""; + } +} diff --git a/src/BlazorGDB/BlazorGDB/Components/Account/Pages/RegisterConfirmation.razor b/src/BlazorGDB/BlazorGDB/Components/Account/Pages/RegisterConfirmation.razor new file mode 100644 index 000000000..8cedde260 --- /dev/null +++ b/src/BlazorGDB/BlazorGDB/Components/Account/Pages/RegisterConfirmation.razor @@ -0,0 +1,68 @@ +@page "/Account/RegisterConfirmation" + +@using System.Text +@using Microsoft.AspNetCore.Identity +@using Microsoft.AspNetCore.WebUtilities +@using BlazorGDB.Data + +@inject UserManager UserManager +@inject IEmailSender EmailSender +@inject NavigationManager NavigationManager +@inject IdentityRedirectManager RedirectManager + +Register confirmation + +

    Register confirmation

    + + + +@if (emailConfirmationLink is not null) +{ +

    + This app does not currently have a real email sender registered, see these docs for how to configure a real email sender. + Normally this would be emailed: Click here to confirm your account +

    +} +else +{ +

    Please check your email to confirm your account.

    +} + +@code { + private string? emailConfirmationLink; + private string? statusMessage; + + [CascadingParameter] + private HttpContext HttpContext { get; set; } = default!; + + [SupplyParameterFromQuery] + private string? Email { get; set; } + + [SupplyParameterFromQuery] + private string? ReturnUrl { get; set; } + + protected override async Task OnInitializedAsync() + { + if (Email is null) + { + RedirectManager.RedirectTo(""); + } + + var user = await UserManager.FindByEmailAsync(Email); + if (user is null) + { + HttpContext.Response.StatusCode = StatusCodes.Status404NotFound; + statusMessage = "Error finding user for unspecified email"; + } + else if (EmailSender is IdentityNoOpEmailSender) + { + // Once you add a real email sender, you should remove this code that lets you confirm the account + var userId = await UserManager.GetUserIdAsync(user); + var code = await UserManager.GenerateEmailConfirmationTokenAsync(user); + code = WebEncoders.Base64UrlEncode(Encoding.UTF8.GetBytes(code)); + emailConfirmationLink = NavigationManager.GetUriWithQueryParameters( + NavigationManager.ToAbsoluteUri("Account/ConfirmEmail").AbsoluteUri, + new Dictionary { ["userId"] = userId, ["code"] = code, ["returnUrl"] = ReturnUrl }); + } + } +} diff --git a/src/BlazorGDB/BlazorGDB/Components/Account/Pages/ResendEmailConfirmation.razor b/src/BlazorGDB/BlazorGDB/Components/Account/Pages/ResendEmailConfirmation.razor new file mode 100644 index 000000000..4e52337b5 --- /dev/null +++ b/src/BlazorGDB/BlazorGDB/Components/Account/Pages/ResendEmailConfirmation.razor @@ -0,0 +1,68 @@ +@page "/Account/ResendEmailConfirmation" + +@using System.ComponentModel.DataAnnotations +@using System.Text +@using System.Text.Encodings.Web +@using Microsoft.AspNetCore.Identity +@using Microsoft.AspNetCore.WebUtilities +@using BlazorGDB.Data + +@inject UserManager UserManager +@inject IEmailSender EmailSender +@inject NavigationManager NavigationManager +@inject IdentityRedirectManager RedirectManager + +Resend email confirmation + +

    Resend email confirmation

    +

    Enter your email.

    +
    + +
    +
    + + + +
    + + + +
    + +
    +
    +
    + +@code { + private string? message; + + [SupplyParameterFromForm] + private InputModel? Input { get; set; } + + private async Task OnValidSubmitAsync() + { + var user = await UserManager.FindByEmailAsync(Input?.Email!); + if (user is null) + { + message = "Verification email sent. Please check your email."; + return; + } + + var userId = await UserManager.GetUserIdAsync(user); + var code = await UserManager.GenerateEmailConfirmationTokenAsync(user); + code = WebEncoders.Base64UrlEncode(Encoding.UTF8.GetBytes(code)); + var callbackUrl = NavigationManager.GetUriWithQueryParameters( + NavigationManager.ToAbsoluteUri("Account/ConfirmEmail").AbsoluteUri, + new Dictionary { ["userId"] = userId, ["code"] = code }); + await EmailSender.SendConfirmationLinkAsync(user, Input!.Email, HtmlEncoder.Default.Encode(callbackUrl)); + + message = "Verification email sent. Please check your email."; + } + + private sealed class InputModel + { + [Required] + [EmailAddress] + public string Email { get; set; } = ""; + } +} diff --git a/src/BlazorGDB/BlazorGDB/Components/Account/Pages/ResetPassword.razor b/src/BlazorGDB/BlazorGDB/Components/Account/Pages/ResetPassword.razor new file mode 100644 index 000000000..a413b64e4 --- /dev/null +++ b/src/BlazorGDB/BlazorGDB/Components/Account/Pages/ResetPassword.razor @@ -0,0 +1,103 @@ +@page "/Account/ResetPassword" + +@using System.ComponentModel.DataAnnotations +@using System.Text +@using Microsoft.AspNetCore.Identity +@using Microsoft.AspNetCore.WebUtilities +@using BlazorGDB.Data + +@inject IdentityRedirectManager RedirectManager +@inject UserManager UserManager + +Reset password + +

    Reset password

    +

    Reset your password.

    +
    +
    +
    + + + + + + +
    + + + +
    +
    + + + +
    +
    + + + +
    + +
    +
    +
    + +@code { + private IEnumerable? identityErrors; + + [SupplyParameterFromForm] + private InputModel? Input { get; set; } + + [SupplyParameterFromQuery] + private string? Code { get; set; } + + private string? Message => identityErrors is null ? null : $"Error: {string.Join(", ", identityErrors.Select(error => error.Description))}"; + + protected override void OnInitialized() + { + if (Code is null) + { + RedirectManager.RedirectTo("Account/InvalidPasswordReset"); + } + + Input!.Code = Encoding.UTF8.GetString(WebEncoders.Base64UrlDecode(Code)); + } + + private async Task OnValidSubmitAsync() + { + var user = await UserManager.FindByEmailAsync(Input!.Email); + if (user is null) + { + // Don't reveal that the user does not exist + RedirectManager.RedirectTo("Account/ResetPasswordConfirmation"); + } + + var result = await UserManager.ResetPasswordAsync(user, Input.Code, Input.Password); + if (result.Succeeded) + { + RedirectManager.RedirectTo("Account/ResetPasswordConfirmation"); + } + + identityErrors = result.Errors; + } + + private sealed class InputModel + { + [Required] + [EmailAddress] + public string Email { get; set; } = ""; + + [Required] + [StringLength(100, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 6)] + [DataType(DataType.Password)] + public string Password { get; set; } = ""; + + [DataType(DataType.Password)] + [Display(Name = "Confirm password")] + [Compare("Password", ErrorMessage = "The password and confirmation password do not match.")] + public string ConfirmPassword { get; set; } = ""; + + [Required] + public string Code { get; set; } = ""; + } +} diff --git a/src/BlazorGDB/BlazorGDB/Components/Account/Pages/ResetPasswordConfirmation.razor b/src/BlazorGDB/BlazorGDB/Components/Account/Pages/ResetPasswordConfirmation.razor new file mode 100644 index 000000000..7f7347dcc --- /dev/null +++ b/src/BlazorGDB/BlazorGDB/Components/Account/Pages/ResetPasswordConfirmation.razor @@ -0,0 +1,7 @@ +@page "/Account/ResetPasswordConfirmation" +Reset password confirmation + +

    Reset password confirmation

    +

    + Your password has been reset. Please click here to log in. +

    diff --git a/src/BlazorGDB/BlazorGDB/Components/Account/Pages/_Imports.razor b/src/BlazorGDB/BlazorGDB/Components/Account/Pages/_Imports.razor new file mode 100644 index 000000000..3427de42c --- /dev/null +++ b/src/BlazorGDB/BlazorGDB/Components/Account/Pages/_Imports.razor @@ -0,0 +1,2 @@ +@using BlazorGDB.Components.Account.Shared +@layout AccountLayout diff --git a/src/BlazorGDB/BlazorGDB/Components/Account/PersistingRevalidatingAuthenticationStateProvider.cs b/src/BlazorGDB/BlazorGDB/Components/Account/PersistingRevalidatingAuthenticationStateProvider.cs new file mode 100644 index 000000000..bb188c405 --- /dev/null +++ b/src/BlazorGDB/BlazorGDB/Components/Account/PersistingRevalidatingAuthenticationStateProvider.cs @@ -0,0 +1,109 @@ +using System.Diagnostics; +using System.Security.Claims; +using Microsoft.AspNetCore.Components; +using Microsoft.AspNetCore.Components.Authorization; +using Microsoft.AspNetCore.Components.Server; +using Microsoft.AspNetCore.Components.Web; +using Microsoft.AspNetCore.Identity; +using Microsoft.Extensions.Options; +using BlazorGDB.Client; +using BlazorGDB.Data; + +namespace BlazorGDB.Components.Account; + +// This is a server-side AuthenticationStateProvider that revalidates the security stamp for the connected user +// every 30 minutes an interactive circuit is connected. It also uses PersistentComponentState to flow the +// authentication state to the client which is then fixed for the lifetime of the WebAssembly application. +internal sealed class PersistingRevalidatingAuthenticationStateProvider : RevalidatingServerAuthenticationStateProvider +{ + private readonly IServiceScopeFactory scopeFactory; + private readonly PersistentComponentState state; + private readonly IdentityOptions options; + + private readonly PersistingComponentStateSubscription subscription; + + private Task? authenticationStateTask; + + public PersistingRevalidatingAuthenticationStateProvider( + ILoggerFactory loggerFactory, + IServiceScopeFactory serviceScopeFactory, + PersistentComponentState persistentComponentState, + IOptions optionsAccessor) + : base(loggerFactory) + { + scopeFactory = serviceScopeFactory; + state = persistentComponentState; + options = optionsAccessor.Value; + + AuthenticationStateChanged += OnAuthenticationStateChanged; + subscription = state.RegisterOnPersisting(OnPersistingAsync, RenderMode.InteractiveWebAssembly); + } + + protected override TimeSpan RevalidationInterval => TimeSpan.FromMinutes(30); + + protected override async Task ValidateAuthenticationStateAsync( + AuthenticationState authenticationState, CancellationToken cancellationToken) + { + // Get the user manager from a new scope to ensure it fetches fresh data + await using var scope = scopeFactory.CreateAsyncScope(); + var userManager = scope.ServiceProvider.GetRequiredService>(); + return await ValidateSecurityStampAsync(userManager, authenticationState.User); + } + + private async Task ValidateSecurityStampAsync(UserManager userManager, ClaimsPrincipal principal) + { + var user = await userManager.GetUserAsync(principal); + if (user is null) + { + return false; + } + else if (!userManager.SupportsUserSecurityStamp) + { + return true; + } + else + { + var principalStamp = principal.FindFirstValue(options.ClaimsIdentity.SecurityStampClaimType); + var userStamp = await userManager.GetSecurityStampAsync(user); + return principalStamp == userStamp; + } + } + + private void OnAuthenticationStateChanged(Task task) + { + authenticationStateTask = task; + } + + private async Task OnPersistingAsync() + { + if (authenticationStateTask is null) + { + throw new UnreachableException($"Authentication state not set in {nameof(OnPersistingAsync)}()."); + } + + var authenticationState = await authenticationStateTask; + var principal = authenticationState.User; + + if (principal.Identity?.IsAuthenticated == true) + { + var userId = principal.FindFirst(options.ClaimsIdentity.UserIdClaimType)?.Value; + var email = principal.FindFirst(options.ClaimsIdentity.EmailClaimType)?.Value; + + if (userId != null && email != null) + { + state.PersistAsJson(nameof(UserInfo), new UserInfo + { + UserId = userId, + Email = email, + }); + } + } + } + + protected override void Dispose(bool disposing) + { + subscription.Dispose(); + AuthenticationStateChanged -= OnAuthenticationStateChanged; + base.Dispose(disposing); + } +} diff --git a/src/BlazorGDB/BlazorGDB/Components/Account/Shared/AccountLayout.razor b/src/BlazorGDB/BlazorGDB/Components/Account/Shared/AccountLayout.razor new file mode 100644 index 000000000..2d92fc33e --- /dev/null +++ b/src/BlazorGDB/BlazorGDB/Components/Account/Shared/AccountLayout.razor @@ -0,0 +1,28 @@ +@inherits LayoutComponentBase +@layout BlazorGDB.Components.Layout.MainLayout +@inject NavigationManager NavigationManager + +@if (HttpContext is null) +{ +

    Loading...

    +} +else +{ + @Body +} + +@code { + [CascadingParameter] + private HttpContext? HttpContext { get; set; } + + protected override void OnParametersSet() + { + if (HttpContext is null) + { + // If this code runs, we're currently rendering in interactive mode, so there is no HttpContext. + // The identity pages need to set cookies, so they require an HttpContext. To achieve this we + // must transition back from interactive mode to a server-rendered page. + NavigationManager.Refresh(forceReload: true); + } + } +} diff --git a/src/BlazorGDB/BlazorGDB/Components/Account/Shared/ExternalLoginPicker.razor b/src/BlazorGDB/BlazorGDB/Components/Account/Shared/ExternalLoginPicker.razor new file mode 100644 index 000000000..4cf82d5f5 --- /dev/null +++ b/src/BlazorGDB/BlazorGDB/Components/Account/Shared/ExternalLoginPicker.razor @@ -0,0 +1,43 @@ +@using Microsoft.AspNetCore.Authentication +@using Microsoft.AspNetCore.Identity +@using BlazorGDB.Data + +@inject SignInManager SignInManager +@inject IdentityRedirectManager RedirectManager + +@if (externalLogins.Length == 0) +{ +
    +

    + There are no external authentication services configured. See this article + about setting up this ASP.NET application to support logging in via external services. +

    +
    +} +else +{ +
    +
    + + +

    + @foreach (var provider in externalLogins) + { + + } +

    +
    +
    +} + +@code { + private AuthenticationScheme[] externalLogins = []; + + [SupplyParameterFromQuery] + private string? ReturnUrl { get; set; } + + protected override async Task OnInitializedAsync() + { + externalLogins = (await SignInManager.GetExternalAuthenticationSchemesAsync()).ToArray(); + } +} diff --git a/src/BlazorGDB/BlazorGDB/Components/Account/Shared/ManageLayout.razor b/src/BlazorGDB/BlazorGDB/Components/Account/Shared/ManageLayout.razor new file mode 100644 index 000000000..949bc9221 --- /dev/null +++ b/src/BlazorGDB/BlazorGDB/Components/Account/Shared/ManageLayout.razor @@ -0,0 +1,17 @@ +@inherits LayoutComponentBase +@layout AccountLayout + +

    Manage your account

    + +
    +

    Change your account settings

    +
    +
    +
    + +
    +
    + @Body +
    +
    +
    diff --git a/src/BlazorGDB/BlazorGDB/Components/Account/Shared/ManageNavMenu.razor b/src/BlazorGDB/BlazorGDB/Components/Account/Shared/ManageNavMenu.razor new file mode 100644 index 000000000..8c89d6e28 --- /dev/null +++ b/src/BlazorGDB/BlazorGDB/Components/Account/Shared/ManageNavMenu.razor @@ -0,0 +1,37 @@ +@using Microsoft.AspNetCore.Identity +@using BlazorGDB.Data + +@inject SignInManager SignInManager + + + +@code { + private bool hasExternalLogins; + + protected override async Task OnInitializedAsync() + { + hasExternalLogins = (await SignInManager.GetExternalAuthenticationSchemesAsync()).Any(); + } +} diff --git a/src/BlazorGDB/BlazorGDB/Components/Account/Shared/ShowRecoveryCodes.razor b/src/BlazorGDB/BlazorGDB/Components/Account/Shared/ShowRecoveryCodes.razor new file mode 100644 index 000000000..aa92e1194 --- /dev/null +++ b/src/BlazorGDB/BlazorGDB/Components/Account/Shared/ShowRecoveryCodes.razor @@ -0,0 +1,28 @@ + +

    Recovery codes

    + +
    +
    + @foreach (var recoveryCode in RecoveryCodes) + { +
    + @recoveryCode +
    + } +
    +
    + +@code { + [Parameter] + public string[] RecoveryCodes { get; set; } = []; + + [Parameter] + public string? StatusMessage { get; set; } +} diff --git a/src/BlazorGDB/BlazorGDB/Components/Account/Shared/StatusMessage.razor b/src/BlazorGDB/BlazorGDB/Components/Account/Shared/StatusMessage.razor new file mode 100644 index 000000000..12cd544cf --- /dev/null +++ b/src/BlazorGDB/BlazorGDB/Components/Account/Shared/StatusMessage.razor @@ -0,0 +1,29 @@ +@if (!string.IsNullOrEmpty(DisplayMessage)) +{ + var statusMessageClass = DisplayMessage.StartsWith("Error") ? "danger" : "success"; + +} + +@code { + private string? messageFromCookie; + + [Parameter] + public string? Message { get; set; } + + [CascadingParameter] + private HttpContext HttpContext { get; set; } = default!; + + private string? DisplayMessage => Message ?? messageFromCookie; + + protected override void OnInitialized() + { + messageFromCookie = HttpContext.Request.Cookies[IdentityRedirectManager.StatusCookieName]; + + if (messageFromCookie is not null) + { + HttpContext.Response.Cookies.Delete(IdentityRedirectManager.StatusCookieName); + } + } +} diff --git a/src/BlazorGDB/BlazorGDB/Components/App.razor b/src/BlazorGDB/BlazorGDB/Components/App.razor new file mode 100644 index 000000000..149133d0d --- /dev/null +++ b/src/BlazorGDB/BlazorGDB/Components/App.razor @@ -0,0 +1,29 @@ + + + + + + + + + + + @* *@ + + + + + + + + + + + + + + + + + + diff --git a/src/BlazorGDB/BlazorGDB/Components/Layout/MainLayout.razor b/src/BlazorGDB/BlazorGDB/Components/Layout/MainLayout.razor new file mode 100644 index 000000000..64048e7ac --- /dev/null +++ b/src/BlazorGDB/BlazorGDB/Components/Layout/MainLayout.razor @@ -0,0 +1,115 @@ +@inherits LayoutComponentBase + +@*
    + + +
    +
    + About +
    + +
    + @Body +
    +
    +
    + +
    + An unhandled error has occurred. + Reload + 🗙 +
    *@ + +
    + + + +
    +
    + About +
    + +
    +
    @Body
    +
    +
    + +
    + +@code { + Sidebar? sidebar; + IEnumerable? navItems; + + private async Task SidebarDataProvider(SidebarDataProviderRequest request) + { + if (navItems is null) + navItems = GetNavItems(); + + return await Task.FromResult(request.ApplyTo(navItems)); + } + + // https://icons.getbootstrap.com/ + + private IEnumerable GetNavItems() + { + navItems = new List + { + new NavItem { Id = "10", Href = "/", IconName = IconName.HouseDoorFill, Text = "Dashboard", Match=NavLinkMatch.All}, + + new NavItem { Id = "20", IconName = IconName.Activity, Text = "Accounts Receivable" }, + new NavItem { Id = "21", Href = "/receivables/sales-quotations", CustomIconName = "bi bi-currency-dollar", Text = "Sales Quotations", ParentId="20"}, + new NavItem { Id = "22", Href = "/receivables/sales-orders", IconName = IconName.Alarm, Text = "Sales Orders", ParentId="20"}, + new NavItem { Id = "23", Href = "/receivables/sales-receipts", IconName = IconName.CheckAll, Text = "Sales Receipts", ParentId="20"}, + new NavItem { Id = "24", Href = "/receivables/sales-invoices", IconName = IconName.CheckAll, Text = "Sales Invoices", ParentId="20"}, + new NavItem { Id = "25", Href = "/receivables/donation-invoices", IconName = IconName.CheckAll, Text = "Donation Invoices", ParentId="20"}, + new NavItem { Id = "26", Href = "/receivables/customers", IconName = IconName.CheckAll, Text = "Customers", ParentId="20"}, + + new NavItem { Id = "30", IconName = IconName.WindowPlus, Text = "Accounts Payable" }, + new NavItem { Id = "31", Href = "/payables/purchase-orders", CustomIconName = "bi bi-pencil", Text = "Purchase Orders", ParentId="30"}, + new NavItem { Id = "32", Href = "/payables/purchase-invoices", CustomIconName = "bi bi-laptop-fill", Text = "Purchase Invoices", ParentId="30"}, + new NavItem { Id = "33", Href = "/payables/vendors", CustomIconName = "bi bi-globe2", Text = "Vendors", ParentId="30"}, + + new NavItem { Id = "40", IconName = IconName.WindowPlus, Text = "Inventory" }, + new NavItem { Id = "41", Href = "/inventory/inventory-control-journal", CustomIconName = "bi bi-pencil", Text = "Inventory Control Journal", ParentId="40"}, + new NavItem { Id = "42", Href = "/inventory/items", CustomIconName = "bi bi-laptop-fill", Text = "Items", ParentId="40"}, + + new NavItem { Id = "50", IconName = IconName.WindowPlus, Text = "Financials" }, + new NavItem { Id = "51", Href = "/financials/journal-entries", CustomIconName = "bi bi-currency-dollar", Text = "Journal Entries", ParentId="50"}, + new NavItem { Id = "52", Href = "/financials/general-ledgers", IconName = IconName.Alarm, Text = "General Ledgers", ParentId="50"}, + new NavItem { Id = "53", Href = "/financials/taxes", IconName = IconName.CheckAll, Text = "Taxes", ParentId="50"}, + new NavItem { Id = "54", Href = "/financials/chart-of-accounts", IconName = IconName.CheckAll, Text = "Chart of Accounts", ParentId="50"}, + new NavItem { Id = "55", Href = "/b/financials/chart-of-accounts", IconName = IconName.CheckAll, Text = "Chart of Accounts (wasm)", ParentId="50"}, + new NavItem { Id = "56", Href = "/financials/banks", IconName = IconName.CheckAll, Text = "Banks", ParentId="50"}, + + new NavItem { Id = "60", IconName = IconName.WindowPlus, Text = "Reports" }, + new NavItem { Id = "61", Href = "/reports/balance-sheet", CustomIconName = "bi bi-pencil", Text = "Balance Sheet", ParentId="60"}, + new NavItem { Id = "62", Href = "/reports/income-statement", CustomIconName = "bi bi-laptop-fill", Text = "Income Statement", ParentId="60"}, + new NavItem { Id = "63", Href = "/reports/trial-balance", CustomIconName = "bi bi-globe2", Text = "Trial Balance", ParentId="60"}, + + new NavItem { Id = "70", IconName = IconName.WindowPlus, Text = "Organization" }, + new NavItem { Id = "71", Href = "/organization/company", CustomIconName = "bi bi-pencil", Text = "Company", ParentId="70"}, + new NavItem { Id = "72", Href = "/organization/settings", CustomIconName = "bi bi-laptop-fill", Text = "Settings", ParentId="70"}, + + new NavItem { Id = "80", IconName = IconName.WindowPlus, Text = "System Administration" }, + new NavItem { Id = "81", Href = "/system-administration/security-users", IconName = IconName.People, Text = "Security Users", ParentId="80"}, + new NavItem { Id = "82", Href = "/system-administration/security-roles", IconName = IconName.PersonRolodex, Text = "Security Roles", ParentId="80"}, + new NavItem { Id = "83", Href = "/system-administration/security-groups", IconName = IconName.CheckAll, Text = "Security Groups", ParentId="80"}, + new NavItem { Id = "84", Href = "/system-administration/audit-logs", IconName = IconName.CheckAll, Text = "Audit Logs", ParentId="80"}, + + new NavItem { Id = "90", Href = "/logout", IconName = IconName.FullscreenExit, Text = "Logout", Match=NavLinkMatch.All}, + + new NavItem { Id = "100", Href = "Account/Login", CustomIconName = "bi bi-person-badge-nav-menu", Text = "Login"}, + + new NavItem { Id = "110", Href = "Account/Register", CustomIconName = "bi bi-person-nav-menu", Text = "Register"}, + + }; + + return navItems; + } +} + diff --git a/src/BlazorGDB/BlazorGDB/Components/Layout/MainLayout.razor.css b/src/BlazorGDB/BlazorGDB/Components/Layout/MainLayout.razor.css new file mode 100644 index 000000000..038baf178 --- /dev/null +++ b/src/BlazorGDB/BlazorGDB/Components/Layout/MainLayout.razor.css @@ -0,0 +1,96 @@ +.page { + position: relative; + display: flex; + flex-direction: column; +} + +main { + flex: 1; +} + +.sidebar { + background-image: linear-gradient(180deg, rgb(5, 39, 103) 0%, #3a0647 70%); +} + +.top-row { + background-color: #f7f7f7; + border-bottom: 1px solid #d6d5d5; + justify-content: flex-end; + height: 3.5rem; + display: flex; + align-items: center; +} + + .top-row ::deep a, .top-row ::deep .btn-link { + white-space: nowrap; + margin-left: 1.5rem; + text-decoration: none; + } + + .top-row ::deep a:hover, .top-row ::deep .btn-link:hover { + text-decoration: underline; + } + + .top-row ::deep a:first-child { + overflow: hidden; + text-overflow: ellipsis; + } + +@media (max-width: 640.98px) { + .top-row { + justify-content: space-between; + } + + .top-row ::deep a, .top-row ::deep .btn-link { + margin-left: 0; + } +} + +@media (min-width: 641px) { + .page { + flex-direction: row; + } + + .sidebar { + width: 250px; + height: 100vh; + position: sticky; + top: 0; + } + + .top-row { + position: sticky; + top: 0; + z-index: 1; + } + + .top-row.auth ::deep a:first-child { + flex: 1; + text-align: right; + width: 0; + } + + .top-row, article { + padding-left: 2rem !important; + padding-right: 1.5rem !important; + } +} + +#blazor-error-ui { + background: lightyellow; + bottom: 0; + box-shadow: 0 -1px 2px rgba(0, 0, 0, 0.2); + display: none; + left: 0; + padding: 0.6rem 1.25rem 0.7rem 1.25rem; + position: fixed; + width: 100%; + z-index: 1000; +} + + #blazor-error-ui .dismiss { + cursor: pointer; + position: absolute; + right: 0.75rem; + top: 0.5rem; + } diff --git a/src/BlazorGDB/BlazorGDB/Components/Layout/NavMenu.razor b/src/BlazorGDB/BlazorGDB/Components/Layout/NavMenu.razor new file mode 100644 index 000000000..595478328 --- /dev/null +++ b/src/BlazorGDB/BlazorGDB/Components/Layout/NavMenu.razor @@ -0,0 +1,102 @@ +@implements IDisposable + +@inject NavigationManager NavigationManager + + + + + + + +@code { + private string? currentUrl; + + protected override void OnInitialized() + { + currentUrl = NavigationManager.ToBaseRelativePath(NavigationManager.Uri); + NavigationManager.LocationChanged += OnLocationChanged; + } + + private void OnLocationChanged(object? sender, LocationChangedEventArgs e) + { + currentUrl = NavigationManager.ToBaseRelativePath(e.Location); + StateHasChanged(); + } + + public void Dispose() + { + NavigationManager.LocationChanged -= OnLocationChanged; + } +} + diff --git a/src/BlazorGDB/BlazorGDB/Components/Layout/NavMenu.razor.css b/src/BlazorGDB/BlazorGDB/Components/Layout/NavMenu.razor.css new file mode 100644 index 000000000..16700eba7 --- /dev/null +++ b/src/BlazorGDB/BlazorGDB/Components/Layout/NavMenu.razor.css @@ -0,0 +1,125 @@ +.navbar-toggler { + appearance: none; + cursor: pointer; + width: 3.5rem; + height: 2.5rem; + color: white; + position: absolute; + top: 0.5rem; + right: 1rem; + border: 1px solid rgba(255, 255, 255, 0.1); + background: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='rgba%28255, 255, 255, 0.55%29' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e") no-repeat center/1.75rem rgba(255, 255, 255, 0.1); +} + +.navbar-toggler:checked { + background-color: rgba(255, 255, 255, 0.5); +} + +.top-row { + height: 3.5rem; + background-color: rgba(0,0,0,0.4); +} + +.navbar-brand { + font-size: 1.1rem; +} + +.bi { + display: inline-block; + position: relative; + width: 1.25rem; + height: 1.25rem; + margin-right: 0.75rem; + top: -1px; + background-size: cover; +} + +.bi-house-door-fill-nav-menu { + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='white' class='bi bi-house-door-fill' viewBox='0 0 16 16'%3E%3Cpath d='M6.5 14.5v-3.505c0-.245.25-.495.5-.495h2c.25 0 .5.25.5.5v3.5a.5.5 0 0 0 .5.5h4a.5.5 0 0 0 .5-.5v-7a.5.5 0 0 0-.146-.354L13 5.793V2.5a.5.5 0 0 0-.5-.5h-1a.5.5 0 0 0-.5.5v1.293L8.354 1.146a.5.5 0 0 0-.708 0l-6 6A.5.5 0 0 0 1.5 7.5v7a.5.5 0 0 0 .5.5h4a.5.5 0 0 0 .5-.5Z'/%3E%3C/svg%3E"); +} + +.bi-plus-square-fill-nav-menu { + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='white' class='bi bi-plus-square-fill' viewBox='0 0 16 16'%3E%3Cpath d='M2 0a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V2a2 2 0 0 0-2-2H2zm6.5 4.5v3h3a.5.5 0 0 1 0 1h-3v3a.5.5 0 0 1-1 0v-3h-3a.5.5 0 0 1 0-1h3v-3a.5.5 0 0 1 1 0z'/%3E%3C/svg%3E"); +} + +.bi-list-nested-nav-menu { + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='white' class='bi bi-list-nested' viewBox='0 0 16 16'%3E%3Cpath fill-rule='evenodd' d='M4.5 11.5A.5.5 0 0 1 5 11h10a.5.5 0 0 1 0 1H5a.5.5 0 0 1-.5-.5zm-2-4A.5.5 0 0 1 3 7h10a.5.5 0 0 1 0 1H3a.5.5 0 0 1-.5-.5zm-2-4A.5.5 0 0 1 1 3h10a.5.5 0 0 1 0 1H1a.5.5 0 0 1-.5-.5z'/%3E%3C/svg%3E"); +} + +.bi-lock-nav-menu { + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='white' class='bi bi-list-nested' viewBox='0 0 16 16'%3E%3Cpath d='M8 1a2 2 0 0 1 2 2v4H6V3a2 2 0 0 1 2-2zm3 6V3a3 3 0 0 0-6 0v4a2 2 0 0 0-2 2v5a2 2 0 0 0 2 2h6a2 2 0 0 0 2-2V9a2 2 0 0 0-2-2zM5 8h6a1 1 0 0 1 1 1v5a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1V9a1 1 0 0 1 1-1z'/%3E%3C/svg%3E"); +} + +.bi-person-nav-menu { + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='white' class='bi bi-person' viewBox='0 0 16 16'%3E%3Cpath d='M8 8a3 3 0 1 0 0-6 3 3 0 0 0 0 6Zm2-3a2 2 0 1 1-4 0 2 2 0 0 1 4 0Zm4 8c0 1-1 1-1 1H3s-1 0-1-1 1-4 6-4 6 3 6 4Zm-1-.004c-.001-.246-.154-.986-.832-1.664C11.516 10.68 10.289 10 8 10c-2.29 0-3.516.68-4.168 1.332-.678.678-.83 1.418-.832 1.664h10Z'/%3E%3C/svg%3E"); +} + +.bi-person-badge-nav-menu { + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='white' class='bi bi-person-badge' viewBox='0 0 16 16'%3E%3Cpath d='M6.5 2a.5.5 0 0 0 0 1h3a.5.5 0 0 0 0-1h-3zM11 8a3 3 0 1 1-6 0 3 3 0 0 1 6 0z'/%3E%3Cpath d='M4.5 0A2.5 2.5 0 0 0 2 2.5V14a2 2 0 0 0 2 2h8a2 2 0 0 0 2-2V2.5A2.5 2.5 0 0 0 11.5 0h-7zM3 2.5A1.5 1.5 0 0 1 4.5 1h7A1.5 1.5 0 0 1 13 2.5v10.795a4.2 4.2 0 0 0-.776-.492C11.392 12.387 10.063 12 8 12s-3.392.387-4.224.803a4.2 4.2 0 0 0-.776.492V2.5z'/%3E%3C/svg%3E"); +} + +.bi-person-fill-nav-menu { + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='white' class='bi bi-person-fill' viewBox='0 0 16 16'%3E%3Cpath d='M3 14s-1 0-1-1 1-4 6-4 6 3 6 4-1 1-1 1H3Zm5-6a3 3 0 1 0 0-6 3 3 0 0 0 0 6Z'/%3E%3C/svg%3E"); +} + +.bi-arrow-bar-left-nav-menu { + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='white' class='bi bi-arrow-bar-left' viewBox='0 0 16 16'%3E%3Cpath d='M12.5 15a.5.5 0 0 1-.5-.5v-13a.5.5 0 0 1 1 0v13a.5.5 0 0 1-.5.5ZM10 8a.5.5 0 0 1-.5.5H3.707l2.147 2.146a.5.5 0 0 1-.708.708l-3-3a.5.5 0 0 1 0-.708l3-3a.5.5 0 1 1 .708.708L3.707 7.5H9.5a.5.5 0 0 1 .5.5Z'/%3E%3C/svg%3E"); +} + +.nav-item { + font-size: 0.9rem; + padding-bottom: 0.5rem; +} + + .nav-item:first-of-type { + padding-top: 1rem; + } + + .nav-item:last-of-type { + padding-bottom: 1rem; + } + + .nav-item ::deep .nav-link { + color: #d7d7d7; + background: none; + border: none; + border-radius: 4px; + height: 3rem; + display: flex; + align-items: center; + line-height: 3rem; + width: 100%; + } + +.nav-item ::deep a.active { + background-color: rgba(255,255,255,0.37); + color: white; +} + +.nav-item ::deep .nav-link:hover { + background-color: rgba(255,255,255,0.1); + color: white; +} + +.nav-scrollable { + display: none; +} + +.navbar-toggler:checked ~ .nav-scrollable { + display: block; +} + +@media (min-width: 641px) { + .navbar-toggler { + display: none; + } + + .nav-scrollable { + /* Never collapse the sidebar for wide screens */ + display: block; + + /* Allow sidebar to scroll for tall menus */ + height: calc(100vh - 3.5rem); + overflow-y: auto; + } +} diff --git a/src/BlazorGDB/BlazorGDB/Components/Pages/Error.razor b/src/BlazorGDB/BlazorGDB/Components/Pages/Error.razor new file mode 100644 index 000000000..576cc2d2f --- /dev/null +++ b/src/BlazorGDB/BlazorGDB/Components/Pages/Error.razor @@ -0,0 +1,36 @@ +@page "/Error" +@using System.Diagnostics + +Error + +

    Error.

    +

    An error occurred while processing your request.

    + +@if (ShowRequestId) +{ +

    + Request ID: @RequestId +

    +} + +

    Development Mode

    +

    + Swapping to Development environment will display more detailed information about the error that occurred. +

    +

    + The Development environment shouldn't be enabled for deployed applications. + It can result in displaying sensitive information from exceptions to end users. + For local debugging, enable the Development environment by setting the ASPNETCORE_ENVIRONMENT environment variable to Development + and restarting the app. +

    + +@code{ + [CascadingParameter] + private HttpContext? HttpContext { get; set; } + + private string? RequestId { get; set; } + private bool ShowRequestId => !string.IsNullOrEmpty(RequestId); + + protected override void OnInitialized() => + RequestId = Activity.Current?.Id ?? HttpContext?.TraceIdentifier; +} diff --git a/src/BlazorGDB/BlazorGDB/Components/Pages/Financials/AddNewItemTaxGroup.razor b/src/BlazorGDB/BlazorGDB/Components/Pages/Financials/AddNewItemTaxGroup.razor new file mode 100644 index 000000000..af02f5886 --- /dev/null +++ b/src/BlazorGDB/BlazorGDB/Components/Pages/Financials/AddNewItemTaxGroup.razor @@ -0,0 +1,67 @@ +@page "/financials/itemtaxgroups/add" +@rendermode InteractiveServer +@inject NavigationManager Navigation + +Add New Item Tax Group + +
    +

    Add New Item Tax Group

    + +
    + Note: Item Tax Groups are currently created automatically when creating or editing taxes. + This standalone creation page is not yet supported by the API. +
    + + + + +
    +
    +
    + +
    + + +
    +
    + +
    + +
    + +
    +
    +
    +
    + +
    + @if (errorMessage != null) + { +
    @errorMessage
    + } +
    + +
    + + Back to Taxes +
    +
    +
    + +@code { + private Dto.TaxSystem.ItemTaxGroup itemTaxGroup = new() + { + IsFullyExempt = false + }; + + private string? errorMessage = "This feature requires API support for standalone item tax group creation."; + + private Task HandleSubmit() + { + // This would require a dedicated API endpoint + errorMessage = "API endpoint for creating item tax groups independently is not available. Please create item tax groups through the Tax creation/edit process."; + return Task.CompletedTask; + } +} diff --git a/src/BlazorGDB/BlazorGDB/Components/Pages/Financials/AddNewTax.razor b/src/BlazorGDB/BlazorGDB/Components/Pages/Financials/AddNewTax.razor new file mode 100644 index 000000000..44a20ca2e --- /dev/null +++ b/src/BlazorGDB/BlazorGDB/Components/Pages/Financials/AddNewTax.razor @@ -0,0 +1,139 @@ +@page "/financials/taxes/add" +@rendermode InteractiveServer +@inject IHttpClientFactory HttpClientFactory +@inject NavigationManager Navigation +@inject IConfiguration Configuration + +Add New Tax + +
    +

    Add New Tax

    + + + + +
    +
    +
    + +
    + + +
    +
    + +
    + +
    + + +
    +
    + +
    + +
    + + +
    +
    + +
    + +
    + +
    +
    + +
    + +
    + + Default: 20300 (Sales Tax) +
    +
    + +
    + +
    + + Default: 50700 (Purchase Tax) +
    +
    +
    +
    + +
    + @if (errorMessage != null) + { +
    @errorMessage
    + } + @if (successMessage != null) + { +
    @successMessage
    + } +
    + +
    + + Cancel +
    +
    +
    + +@code { + private Dto.TaxSystem.TaxForCreation taxForCreation = new() + { + IsActive = true, + SalesAccountId = 20300, + PurchaseAccountId = 50700 + }; + + private bool isSubmitting = false; + private string? errorMessage; + private string? successMessage; + + private async Task HandleSubmit() + { + isSubmitting = true; + errorMessage = null; + successMessage = null; + + try + { + var baseUri = Configuration["ApiUrl"]; + var httpClient = HttpClientFactory.CreateClient(); + + var json = System.Text.Json.JsonSerializer.Serialize(taxForCreation); + var content = new StringContent(json, System.Text.Encoding.UTF8, "application/json"); + + var response = await httpClient.PostAsync($"{baseUri}tax/addnewtax", content); + + if (response.IsSuccessStatusCode) + { + Navigation.NavigateTo("/financials/taxes"); + } + else + { + var errorContent = await response.Content.ReadAsStringAsync(); + errorMessage = $"Error creating tax: {response.StatusCode} - {errorContent}"; + Console.WriteLine(errorMessage); + } + } + catch (Exception ex) + { + errorMessage = $"Error: {ex.Message}"; + Console.WriteLine($"Error creating tax: {ex.Message}"); + } + finally + { + isSubmitting = false; + } + } +} diff --git a/src/BlazorGDB/BlazorGDB/Components/Pages/Financials/AddNewTaxGroup.razor b/src/BlazorGDB/BlazorGDB/Components/Pages/Financials/AddNewTaxGroup.razor new file mode 100644 index 000000000..3023f65e0 --- /dev/null +++ b/src/BlazorGDB/BlazorGDB/Components/Pages/Financials/AddNewTaxGroup.razor @@ -0,0 +1,74 @@ +@page "/financials/taxgroups/add" +@rendermode InteractiveServer +@inject NavigationManager Navigation + +Add New Tax Group + +
    +

    Add New Tax Group

    + +
    + Note: Tax Groups are currently created automatically when creating or editing taxes. + This standalone creation page is not yet supported by the API. +
    + + + + +
    +
    +
    + +
    + + +
    +
    + +
    + +
    + +
    +
    + +
    + +
    + +
    +
    +
    +
    + +
    + @if (errorMessage != null) + { +
    @errorMessage
    + } +
    + +
    + + Back to Taxes +
    +
    +
    + +@code { + private Dto.TaxSystem.TaxGroup taxGroup = new() + { + IsActive = true + }; + + private string? errorMessage = "This feature requires API support for standalone tax group creation."; + + private Task HandleSubmit() + { + // This would require a dedicated API endpoint + errorMessage = "API endpoint for creating tax groups independently is not available. Please create tax groups through the Tax creation/edit process."; + return Task.CompletedTask; + } +} diff --git a/src/BlazorGDB/BlazorGDB/Components/Pages/Financials/Banks.razor b/src/BlazorGDB/BlazorGDB/Components/Pages/Financials/Banks.razor new file mode 100644 index 000000000..bbd7814a3 --- /dev/null +++ b/src/BlazorGDB/BlazorGDB/Components/Pages/Financials/Banks.razor @@ -0,0 +1,9 @@ +@page "/financials/banks" + +Banks + +

    Banks

    + +@code { + +} diff --git a/src/BlazorGDB/BlazorGDB/Components/Pages/Financials/ChartOfAccounts.razor b/src/BlazorGDB/BlazorGDB/Components/Pages/Financials/ChartOfAccounts.razor new file mode 100644 index 000000000..c8fc84adc --- /dev/null +++ b/src/BlazorGDB/BlazorGDB/Components/Pages/Financials/ChartOfAccounts.razor @@ -0,0 +1,202 @@ +@page "/financials/chart-of-accounts" +@using System.Text.Json +@using System.Text.Json.Serialization +@using LibraryGDB.Models.Financial +@using Microsoft.JSInterop +@using Microsoft.Net.Http.Headers +@inject IHttpClientFactory ClientFactory +@inject Microsoft.JSInterop.IJSRuntime JSRuntime + +Chart of Accounts + +

    Chart Of Accounts

    + +@if (getError || accounts is null) +{ +

    Unable to get data. Please try again later.

    +} +else +{ +@*
      + @foreach (var item in accounts) + { +
    • @item.AccountName
    • + } +
    *@ + + +
    + + + + + + + + + + + + + + @for (int accountIdx = 0; accountIdx < accounts.Count(); ++accountIdx) + { + var account = accounts.ToList()[accountIdx]; + var accountTargetId = $"asset-{accountIdx}"; + + + + + + + + + + + + + } + +
    CodeNameBalanceDebitCredit
    + +
    +
    + +} + +@code { + private IEnumerable? accounts = []; + private bool getError; + private bool shouldRender; + private bool jsInteropCalled = false; // Flag to ensure JS interop is called only once + + + protected override bool ShouldRender() => shouldRender; + + protected override async Task OnInitializedAsync() + { + string apiurl = System.Environment.GetEnvironmentVariable("APIURL") ?? "http://localhost:8001/api/"; + + var request = new HttpRequestMessage(HttpMethod.Get, $"{apiurl}financials/accounts"); + request.Headers.Add("Accept", "application/json"); + + var _client = ClientFactory.CreateClient(); + + _client.DefaultRequestHeaders.Accept.Clear(); + _client.DefaultRequestHeaders.Clear(); + _client.DefaultRequestHeaders.CacheControl = new System.Net.Http.Headers.CacheControlHeaderValue() { NoCache = true }; + _client.DefaultRequestHeaders.Accept.Add(new System.Net.Http.Headers.MediaTypeWithQualityHeaderValue("application/json")); + + var response = await _client.SendAsync(request).ConfigureAwait(false); + + if (response.IsSuccessStatusCode) + { + var responseString = await response.Content.ReadAsStringAsync(); + + // Log the response string to verify the JSON structure + // Console.WriteLine($"Response JSON: {responseString}"); + + // Ensure JsonSerializerOptions are set correctly + var options = new JsonSerializerOptions + { + PropertyNameCaseInsensitive = true, // Adjust based on your JSON + DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, + }; + + try + { + accounts = JsonSerializer.Deserialize>(responseString, options); + } + catch (JsonException ex) + { + // Log the exception to diagnose deserialization issues + Console.WriteLine($"JSON Deserialization error: {ex.Message}"); + getError = true; + } + + // await JSRuntime.InvokeVoidAsync("console.log", accounts); + } + else + { + getError = true; + } + + shouldRender = true; + } + + protected override async Task OnAfterRenderAsync(bool firstRender) + { + if (firstRender && !jsInteropCalled) + { + await JSRuntime.InvokeVoidAsync("console.log", accounts); + jsInteropCalled = true; // Ensure this block runs only once + } + } +} diff --git a/src/BlazorGDB/BlazorGDB/Components/Pages/Financials/EditItemTaxGroup.razor b/src/BlazorGDB/BlazorGDB/Components/Pages/Financials/EditItemTaxGroup.razor new file mode 100644 index 000000000..5b344d5b4 --- /dev/null +++ b/src/BlazorGDB/BlazorGDB/Components/Pages/Financials/EditItemTaxGroup.razor @@ -0,0 +1,136 @@ +@page "/financials/itemtaxgroups/edit/{GroupId:int}" +@rendermode InteractiveServer +@inject IHttpClientFactory HttpClientFactory +@inject NavigationManager Navigation +@inject IConfiguration Configuration + +Edit Item Tax Group + +
    +

    Edit Item Tax Group

    + +
    + Note: Item Tax Groups are currently edited through the tax editing process. + Standalone editing is not yet supported by the API. +
    + + @if (isLoading) + { +
    +
    + Loading... +
    +
    + } + else if (itemTaxGroup != null) + { + + + +
    +
    +
    + +
    + +
    +
    + +
    + +
    + +
    +
    + + @if (itemTaxGroup.Taxes?.Any() == true) + { +
    +
    +
    Associated Taxes
    +
      + @foreach (var tax in itemTaxGroup.Taxes) + { +
    • Tax ID: @tax.TaxId
    • + } +
    +
    +
    + } +
    +
    + +
    + @if (errorMessage != null) + { +
    @errorMessage
    + } +
    + +
    + + Back to Taxes +
    +
    + } +
    + +@code { + [Parameter] + public int GroupId { get; set; } + + private Dto.TaxSystem.ItemTaxGroup? itemTaxGroup; + private bool isLoading = true; + private string? errorMessage = "This feature requires API support for standalone item tax group editing."; + + protected override async Task OnInitializedAsync() + { + await LoadItemTaxGroup(); + } + + private async Task LoadItemTaxGroup() + { + isLoading = true; + try + { + var baseUri = Configuration["ApiUrl"]; + var httpClient = HttpClientFactory.CreateClient(); + + var response = await httpClient.GetAsync($"{baseUri}tax/taxes"); + + if (response.IsSuccessStatusCode) + { + var content = await response.Content.ReadAsStringAsync(); + var taxSystemDto = System.Text.Json.JsonSerializer.Deserialize( + content, + new System.Text.Json.JsonSerializerOptions { PropertyNameCaseInsensitive = true } + ); + + itemTaxGroup = taxSystemDto?.ItemTaxGroups?.FirstOrDefault(itg => itg.Id == GroupId); + + if (itemTaxGroup == null) + { + errorMessage = "Item Tax Group not found."; + } + } + } + catch (Exception ex) + { + errorMessage = $"Error loading item tax group: {ex.Message}"; + Console.WriteLine($"Error loading item tax group: {ex.Message}"); + } + finally + { + isLoading = false; + } + } + + private Task HandleSubmit() + { + errorMessage = "API endpoint for updating item tax groups independently is not available. Please edit item tax groups through the Tax editing process."; + return Task.CompletedTask; + } +} \ No newline at end of file diff --git a/src/BlazorGDB/BlazorGDB/Components/Pages/Financials/EditTax.razor b/src/BlazorGDB/BlazorGDB/Components/Pages/Financials/EditTax.razor new file mode 100644 index 000000000..3c1cd233e --- /dev/null +++ b/src/BlazorGDB/BlazorGDB/Components/Pages/Financials/EditTax.razor @@ -0,0 +1,206 @@ +@page "/financials/taxes/edit/{TaxId:int}" +@rendermode InteractiveServer +@inject IHttpClientFactory HttpClientFactory +@inject NavigationManager Navigation +@inject IConfiguration Configuration + +Edit Tax + +
    +

    Edit Tax

    + + @if (isLoading) + { +
    +
    + Loading... +
    +
    + } + else if (taxForUpdate != null) + { + + + +
    +
    +
    + +
    + +
    +
    + +
    + +
    + +
    +
    + +
    + +
    + +
    +
    + +
    + +
    + +
    +
    + +
    + +
    + +
    +
    + +
    + +
    + +
    +
    +
    +
    + +
    + @if (errorMessage != null) + { +
    @errorMessage
    + } + @if (successMessage != null) + { +
    @successMessage
    + } +
    + +
    + + Cancel +
    +
    + } +
    + +@code { + [Parameter] + public int TaxId { get; set; } + + private Dto.TaxSystem.TaxForUpdate? taxForUpdate; + private bool isLoading = true; + private bool isSubmitting = false; + private string? errorMessage; + private string? successMessage; + + protected override async Task OnInitializedAsync() + { + await LoadTax(); + } + + private async Task LoadTax() + { + isLoading = true; + try + { + var baseUri = Configuration["ApiUrl"]; + var httpClient = HttpClientFactory.CreateClient(); + + // Load all taxes to find the one we need + var response = await httpClient.GetAsync($"{baseUri}tax/taxes"); + + if (response.IsSuccessStatusCode) + { + var content = await response.Content.ReadAsStringAsync(); + var taxSystemDto = System.Text.Json.JsonSerializer.Deserialize( + content, + new System.Text.Json.JsonSerializerOptions { PropertyNameCaseInsensitive = true } + ); + + var tax = taxSystemDto?.Taxes?.FirstOrDefault(t => t.Id == TaxId); + + if (tax != null) + { + // Find the tax group and item tax group that contain this tax + var taxGroup = taxSystemDto?.TaxGroups?.FirstOrDefault(tg => + tg.Taxes?.Any(t => t.TaxId == TaxId) ?? false); + + var itemTaxGroup = taxSystemDto?.ItemTaxGroups?.FirstOrDefault(itg => + itg.Taxes?.Any(t => t.TaxId == TaxId) ?? false); + + taxForUpdate = new Dto.TaxSystem.TaxForUpdate + { + SalesAccountId = 20300, + PurchaseAccountId = 50700, + Tax = tax, + TaxGroup = taxGroup ?? new Dto.TaxSystem.TaxGroup { Description = "Default", IsActive = true }, + ItemTaxGroup = itemTaxGroup ?? new Dto.TaxSystem.ItemTaxGroup { Name = "Default", IsFullyExempt = false } + }; + } + else + { + errorMessage = "Tax not found."; + } + } + } + catch (Exception ex) + { + errorMessage = $"Error loading tax: {ex.Message}"; + Console.WriteLine($"Error loading tax: {ex.Message}"); + } + finally + { + isLoading = false; + } + } + + private async Task HandleSubmit() + { + if (taxForUpdate?.Tax == null) return; + + isSubmitting = true; + errorMessage = null; + successMessage = null; + + try + { + var baseUri = Configuration["ApiUrl"]; + var httpClient = HttpClientFactory.CreateClient(); + + var json = System.Text.Json.JsonSerializer.Serialize(taxForUpdate); + var content = new StringContent(json, System.Text.Encoding.UTF8, "application/json"); + + var response = await httpClient.PutAsync($"{baseUri}tax/edittax", content); + + if (response.IsSuccessStatusCode) + { + Navigation.NavigateTo("/financials/taxes"); + } + else + { + var errorContent = await response.Content.ReadAsStringAsync(); + errorMessage = $"Error updating tax: {response.StatusCode} - {errorContent}"; + Console.WriteLine(errorMessage); + } + } + catch (Exception ex) + { + errorMessage = $"Error: {ex.Message}"; + Console.WriteLine($"Error updating tax: {ex.Message}"); + } + finally + { + isSubmitting = false; + } + } +} diff --git a/src/BlazorGDB/BlazorGDB/Components/Pages/Financials/EditTaxGroup.razor b/src/BlazorGDB/BlazorGDB/Components/Pages/Financials/EditTaxGroup.razor new file mode 100644 index 000000000..4098a106b --- /dev/null +++ b/src/BlazorGDB/BlazorGDB/Components/Pages/Financials/EditTaxGroup.razor @@ -0,0 +1,142 @@ +@page "/financials/taxgroups/edit/{GroupId:int}" +@rendermode InteractiveServer +@inject IHttpClientFactory HttpClientFactory +@inject NavigationManager Navigation +@inject IConfiguration Configuration + +Edit Tax Group + +
    +

    Edit Tax Group

    + +
    + Note: Tax Groups are currently edited through the tax editing process. + Standalone editing is not yet supported by the API. +
    + + @if (isLoading) + { +
    +
    + Loading... +
    +
    + } + else if (taxGroup != null) + { + + + +
    +
    +
    + +
    + +
    +
    + +
    + +
    + +
    +
    + +
    + +
    + +
    +
    + + @if (taxGroup.Taxes?.Any() == true) + { +
    +
    +
    Associated Taxes
    +
      + @foreach (var tax in taxGroup.Taxes) + { +
    • Tax ID: @tax.TaxId
    • + } +
    +
    +
    + } +
    +
    + +
    + @if (errorMessage != null) + { +
    @errorMessage
    + } +
    + +
    + + Back to Taxes +
    +
    + } +
    + +@code { + [Parameter] + public int GroupId { get; set; } + + private Dto.TaxSystem.TaxGroup? taxGroup; + private bool isLoading = true; + private string? errorMessage = "This feature requires API support for standalone tax group editing."; + + protected override async Task OnInitializedAsync() + { + await LoadTaxGroup(); + } + + private async Task LoadTaxGroup() + { + isLoading = true; + try + { + var baseUri = Configuration["ApiUrl"]; + var httpClient = HttpClientFactory.CreateClient(); + + var response = await httpClient.GetAsync($"{baseUri}tax/taxes"); + + if (response.IsSuccessStatusCode) + { + var content = await response.Content.ReadAsStringAsync(); + var taxSystemDto = System.Text.Json.JsonSerializer.Deserialize( + content, + new System.Text.Json.JsonSerializerOptions { PropertyNameCaseInsensitive = true } + ); + + taxGroup = taxSystemDto?.TaxGroups?.FirstOrDefault(tg => tg.Id == GroupId); + + if (taxGroup == null) + { + errorMessage = "Tax Group not found."; + } + } + } + catch (Exception ex) + { + errorMessage = $"Error loading tax group: {ex.Message}"; + Console.WriteLine($"Error loading tax group: {ex.Message}"); + } + finally + { + isLoading = false; + } + } + + private Task HandleSubmit() + { + errorMessage = "API endpoint for updating tax groups independently is not available. Please edit tax groups through the Tax editing process."; + return Task.CompletedTask; + } +} diff --git a/src/BlazorGDB/BlazorGDB/Components/Pages/Financials/GeneralLedgers.razor b/src/BlazorGDB/BlazorGDB/Components/Pages/Financials/GeneralLedgers.razor new file mode 100644 index 000000000..6a19d7c22 --- /dev/null +++ b/src/BlazorGDB/BlazorGDB/Components/Pages/Financials/GeneralLedgers.razor @@ -0,0 +1,9 @@ +@page "/financials/general-ledgers" + +General Ledgers + +

    General Ledgers

    + +@code { + +} diff --git a/src/BlazorGDB/BlazorGDB/Components/Pages/Financials/JournalEntries.razor b/src/BlazorGDB/BlazorGDB/Components/Pages/Financials/JournalEntries.razor new file mode 100644 index 000000000..a7178b364 --- /dev/null +++ b/src/BlazorGDB/BlazorGDB/Components/Pages/Financials/JournalEntries.razor @@ -0,0 +1,9 @@ +@page "/financials/journal-entries" + +Journal Entries + +

    Journal Entries

    + +@code { + +} diff --git a/src/BlazorGDB/BlazorGDB/Components/Pages/Financials/Taxes.razor b/src/BlazorGDB/BlazorGDB/Components/Pages/Financials/Taxes.razor new file mode 100644 index 000000000..a52ad76a1 --- /dev/null +++ b/src/BlazorGDB/BlazorGDB/Components/Pages/Financials/Taxes.razor @@ -0,0 +1,499 @@ +@page "/financials/taxes" +@rendermode InteractiveServer +@inject IHttpClientFactory HttpClientFactory +@inject NavigationManager Navigation +@inject IConfiguration Configuration +@inject IJSRuntime JSRuntime + +Taxes + +
    +

    Tax Management

    + + + +
    + @if (activeTab == "taxes") + { +
    + +
    + + + + + + + + + + + @if (taxes != null && taxes.Any()) + { + @foreach (var tax in taxes) + { + + + + + + + } + } + else if (isLoading) + { + + + + } + else + { + + + + } + +
    CodeNameRate (%)Actions
    @tax.TaxCode@tax.TaxName@tax.Rate + + +
    +
    + Loading... +
    +
    No taxes found.
    +
    +
    + } + else if (activeTab == "tax-groups") + { +
    +
    + + + + + + + + + + + @if (taxGroups != null && taxGroups.Any()) + { + @foreach (var group in taxGroups) + { + + + + + + + } + } + else if (isLoading) + { + + + + } + else + { + + + + } + +
    DescriptionTax Applied To ShippingActiveActions
    @group.Description + + + + + +
    +
    + Loading... +
    +
    No tax groups found.
    +
    + + @if (selectedTaxGroup != null) + { +
    +
    +
    Tax(es) included: @GetTaxGroupTaxCount(selectedTaxGroup)
    +
    +
    +
    + + + + + + + + + + @foreach (var groupTax in GetTaxGroupTaxes(selectedTaxGroup)) + { + + + + + + } + +
    CodeNameRate (%)
    @groupTax.TaxCode@groupTax.TaxName@groupTax.Rate
    +
    +
    +
    + } +
    + } + else if (activeTab == "item-tax-groups") + { +
    +
    + + + + + + + + + + @if (itemTaxGroups != null && itemTaxGroups.Any()) + { + @foreach (var itemGroup in itemTaxGroups) + { + + + + + + } + } + else if (isLoading) + { + + + + } + else + { + + + + } + +
    NameFully ExemptActions
    @itemGroup.Name + + + +
    +
    + Loading... +
    +
    No item tax groups found.
    +
    + + @if (selectedItemTaxGroup != null) + { +
    +
    +
    Tax(es) included: @GetItemTaxGroupTaxCount(selectedItemTaxGroup)
    +
    +
    +
    + + + + + + + + + + @foreach (var groupTax in GetItemTaxGroupTaxes(selectedItemTaxGroup)) + { + + + + + + } + +
    CodeNameRate (%)
    @groupTax.TaxCode@groupTax.TaxName@groupTax.Rate
    +
    +
    +
    + } +
    + } +
    +
    + +@code { + private string activeTab = "taxes"; + private bool isLoading = true; + + private List? taxes; + private List? taxGroups; + private List? itemTaxGroups; + + private Dto.TaxSystem.TaxGroup? selectedTaxGroup; + private Dto.TaxSystem.ItemTaxGroup? selectedItemTaxGroup; + + protected override async Task OnInitializedAsync() + { + await LoadTaxData(); + } + + private async Task LoadTaxData() + { + isLoading = true; + try + { + var baseUri = Configuration["ApiUrl"]; + if (string.IsNullOrEmpty(baseUri)) + { + Console.WriteLine("Error: ApiUrl not configured in appsettings.json"); + return; + } + + var httpClient = HttpClientFactory.CreateClient(); + var apiUrl = $"{baseUri}tax/taxes"; + + Console.WriteLine($"Loading tax data from: {apiUrl}"); + + var response = await httpClient.GetAsync(apiUrl); + + Console.WriteLine($"Response status: {response.StatusCode}"); + + if (response.IsSuccessStatusCode) + { + var content = await response.Content.ReadAsStringAsync(); + Console.WriteLine($"Response content length: {content.Length}"); + + var taxSystemDto = System.Text.Json.JsonSerializer.Deserialize( + content, + new System.Text.Json.JsonSerializerOptions { PropertyNameCaseInsensitive = true } + ); + + taxes = taxSystemDto?.Taxes?.ToList(); + taxGroups = taxSystemDto?.TaxGroups?.ToList(); + itemTaxGroups = taxSystemDto?.ItemTaxGroups?.ToList(); + + Console.WriteLine($"Loaded {taxes?.Count ?? 0} taxes, {taxGroups?.Count ?? 0} tax groups, {itemTaxGroups?.Count ?? 0} item tax groups"); + } + else + { + var errorContent = await response.Content.ReadAsStringAsync(); + Console.WriteLine($"Error response: {errorContent}"); + } + } + catch (Exception ex) + { + Console.WriteLine($"Error loading tax data: {ex.Message}"); + Console.WriteLine($"Stack trace: {ex.StackTrace}"); + } + finally + { + isLoading = false; + } + } + + private void SetActiveTab(string tab) + { + activeTab = tab; + selectedTaxGroup = null; + selectedItemTaxGroup = null; + } + + private void NavigateToAddTax() + { + Navigation.NavigateTo("/financials/taxes/add"); + } + + private void EditTax(Dto.TaxSystem.Tax tax) + { + Navigation.NavigateTo($"/financials/taxes/edit/{tax.Id}"); + } + + private async Task ConfirmDeleteTax(int taxId) + { + bool confirmed = await JSRuntime.InvokeAsync("confirm", "Are you sure you want to delete this tax?"); + if (confirmed) + { + await DeleteTax(taxId); + } + } + + private async Task DeleteTax(int taxId) + { + try + { + var baseUri = Configuration["ApiUrl"]; + var httpClient = HttpClientFactory.CreateClient(); + + var response = await httpClient.DeleteAsync($"{baseUri}tax/deletetax/{taxId}"); + + if (response.IsSuccessStatusCode) + { + await LoadTaxData(); // Reload data after successful delete + } + else + { + var errorContent = await response.Content.ReadAsStringAsync(); + Console.WriteLine($"Error deleting tax: {errorContent}"); + } + } + catch (Exception ex) + { + Console.WriteLine($"Error deleting tax: {ex.Message}"); + } + } + + private void SelectTaxGroup(Dto.TaxSystem.TaxGroup group) + { + selectedTaxGroup = group; + } + + private async Task ConfirmDeleteTaxGroup(int groupId) + { + bool confirmed = await JSRuntime.InvokeAsync("confirm", "Are you sure you want to delete this tax group?"); + if (confirmed) + { + await DeleteTaxGroup(groupId); + } + } + + private async Task DeleteTaxGroup(int groupId) + { + try + { + var baseUri = Configuration["ApiUrl"]; + var httpClient = HttpClientFactory.CreateClient(); + + var response = await httpClient.DeleteAsync($"{baseUri}tax/deletetaxgroup/{groupId}"); + + if (response.IsSuccessStatusCode) + { + await LoadTaxData(); + } + else + { + var errorContent = await response.Content.ReadAsStringAsync(); + Console.WriteLine($"Error deleting tax group: {errorContent}"); + } + } + catch (Exception ex) + { + Console.WriteLine($"Error deleting tax group: {ex.Message}"); + } + } + + private void SelectItemTaxGroup(Dto.TaxSystem.ItemTaxGroup group) + { + selectedItemTaxGroup = group; + } + + private async Task ConfirmDeleteItemTaxGroup(int groupId) + { + bool confirmed = await JSRuntime.InvokeAsync("confirm", "Are you sure you want to delete this item tax group?"); + if (confirmed) + { + await DeleteItemTaxGroup(groupId); + } + } + + private async Task DeleteItemTaxGroup(int groupId) + { + try + { + var baseUri = Configuration["ApiUrl"]; + var httpClient = HttpClientFactory.CreateClient(); + + var response = await httpClient.DeleteAsync($"{baseUri}tax/deleteitemtaxgroup/{groupId}"); + + if (response.IsSuccessStatusCode) + { + await LoadTaxData(); + } + else + { + var errorContent = await response.Content.ReadAsStringAsync(); + Console.WriteLine($"Error deleting item tax group: {errorContent}"); + } + } + catch (Exception ex) + { + Console.WriteLine($"Error deleting item tax group: {ex.Message}"); + } + } + + private int GetTaxGroupTaxCount(Dto.TaxSystem.TaxGroup group) + { + return group.Taxes?.Count() ?? 0; + } + + private IEnumerable GetTaxGroupTaxes(Dto.TaxSystem.TaxGroup group) + { + if (group.Taxes == null || taxes == null) return Enumerable.Empty(); + + return group.Taxes + .Select(gt => taxes.FirstOrDefault(t => t.Id == gt.TaxId)) + .Where(t => t != null) + .Cast(); + } + + private int GetItemTaxGroupTaxCount(Dto.TaxSystem.ItemTaxGroup group) + { + return group.Taxes?.Count() ?? 0; + } + + private IEnumerable GetItemTaxGroupTaxes(Dto.TaxSystem.ItemTaxGroup group) + { + if (group.Taxes == null || taxes == null) return Enumerable.Empty(); + + return group.Taxes + .Select(gt => taxes.FirstOrDefault(t => t.Id == gt.TaxId)) + .Where(t => t != null) + .Cast(); + } +} + diff --git a/src/BlazorGDB/BlazorGDB/Components/Pages/Home.razor b/src/BlazorGDB/BlazorGDB/Components/Pages/Home.razor new file mode 100644 index 000000000..9001e0bd2 --- /dev/null +++ b/src/BlazorGDB/BlazorGDB/Components/Pages/Home.razor @@ -0,0 +1,7 @@ +@page "/" + +Home + +

    Hello, world!

    + +Welcome to your new app. diff --git a/src/BlazorGDB/BlazorGDB/Components/Pages/Inventory/InventoryControlJournal.razor b/src/BlazorGDB/BlazorGDB/Components/Pages/Inventory/InventoryControlJournal.razor new file mode 100644 index 000000000..39ab6ce12 --- /dev/null +++ b/src/BlazorGDB/BlazorGDB/Components/Pages/Inventory/InventoryControlJournal.razor @@ -0,0 +1,9 @@ +@page "/inventory/inventory-control-journal" + +Inventory Control Journal + +

    Inventory Control Journal

    + +@code { + +} diff --git a/src/BlazorGDB/BlazorGDB/Components/Pages/Inventory/Items.razor b/src/BlazorGDB/BlazorGDB/Components/Pages/Inventory/Items.razor new file mode 100644 index 000000000..9afc03b0f --- /dev/null +++ b/src/BlazorGDB/BlazorGDB/Components/Pages/Inventory/Items.razor @@ -0,0 +1,243 @@ +@page "/inventory/items" +@using Dto.Inventory +@inject HttpClient Http +@inject NavigationManager Navigation +@rendermode InteractiveServer + +Items + +
    +
    +
    +

    Items

    +
    +
    + +
    +
    + + @if (selectedItem != null) + { + + } +
    +
    + + @if (isLoading) + { +
    +
    +
    + Loading... +
    +

    Loading items...

    +
    +
    + } + else if (errorMessage != null) + { +
    +
    + +
    +
    + } + else if (items == null || !items.Any()) + { +
    +
    + +
    +
    + } + else + { +
    +
    +
    + + + + + + + + + + + + + + + @foreach (var item in items) + { + + + + + + + + + + + } + +
    + Item @GetSortIcon(nameof(Item.Id)) + + Code @GetSortIcon(nameof(Item.Code)) + + Description @GetSortIcon(nameof(Item.Description)) + + Measurement @GetSortIcon(nameof(Item.Measurement)) + + Item Tax Group @GetSortIcon(nameof(Item.ItemTaxGroupName)) + + Cost @GetSortIcon(nameof(Item.Cost)) + + Price @GetSortIcon(nameof(Item.Price)) + + On Hand @GetSortIcon(nameof(Item.QuantityOnHand)) +
    + + @item.Id + + @item.Code@item.Description@item.Measurement@item.ItemTaxGroupName@item.Cost?.ToString("N2")@item.Price?.ToString("N2")@item.QuantityOnHand?.ToString("N2")
    +
    +
    +
    + +
    +
    +

    Total: @items.Count() item(s)

    +
    +
    + } +
    + +@code { + private List? items; + private List? allItems; + private Item? selectedItem; + private bool isLoading = true; + private string? errorMessage; + private string? sortColumn; + private bool sortAscending = true; + + protected override async Task OnInitializedAsync() + { + await LoadItems(); + } + + private async Task LoadItems() + { + isLoading = true; + errorMessage = null; + + try + { + var apiUrl = "http://localhost:8001/api/inventory/items"; + var response = await Http.GetAsync(apiUrl); + + if (response.IsSuccessStatusCode) + { + allItems = await response.Content.ReadFromJsonAsync>(); + items = allItems; + } + else + { + errorMessage = $"Failed to load items. Status: {response.StatusCode}"; + } + } + catch (HttpRequestException ex) + { + errorMessage = $"Network error: {ex.Message}. Please ensure the API is running on port 8001."; + } + catch (Exception ex) + { + errorMessage = $"Error loading items: {ex.Message}"; + } + finally + { + isLoading = false; + } + } + + private void SelectItem(Item item) + { + selectedItem = item; + } + + private void NavigateToNewItem() + { + Navigation.NavigateTo("/inventory/item/new"); + } + + private void NavigateToViewItem() + { + if (selectedItem != null) + { + Navigation.NavigateTo($"/inventory/item/{selectedItem.Id}"); + } + } + + private void SortBy(string column) + { + if (items == null) return; + + if (sortColumn == column) + { + sortAscending = !sortAscending; + } + else + { + sortColumn = column; + sortAscending = true; + } + + items = column switch + { + nameof(Item.Id) => sortAscending + ? items.OrderBy(i => i.Id).ToList() + : items.OrderByDescending(i => i.Id).ToList(), + nameof(Item.Code) => sortAscending + ? items.OrderBy(i => i.Code).ToList() + : items.OrderByDescending(i => i.Code).ToList(), + nameof(Item.Description) => sortAscending + ? items.OrderBy(i => i.Description).ToList() + : items.OrderByDescending(i => i.Description).ToList(), + nameof(Item.Measurement) => sortAscending + ? items.OrderBy(i => i.Measurement).ToList() + : items.OrderByDescending(i => i.Measurement).ToList(), + nameof(Item.ItemTaxGroupName) => sortAscending + ? items.OrderBy(i => i.ItemTaxGroupName).ToList() + : items.OrderByDescending(i => i.ItemTaxGroupName).ToList(), + nameof(Item.Cost) => sortAscending + ? items.OrderBy(i => i.Cost).ToList() + : items.OrderByDescending(i => i.Cost).ToList(), + nameof(Item.Price) => sortAscending + ? items.OrderBy(i => i.Price).ToList() + : items.OrderByDescending(i => i.Price).ToList(), + nameof(Item.QuantityOnHand) => sortAscending + ? items.OrderBy(i => i.QuantityOnHand).ToList() + : items.OrderByDescending(i => i.QuantityOnHand).ToList(), + _ => items + }; + } + + private string GetSortIcon(string column) + { + if (sortColumn != column) return ""; + return sortAscending ? "▲" : "▼"; + } +} \ No newline at end of file diff --git a/src/BlazorGDB/BlazorGDB/Components/Pages/Organization/Company.razor b/src/BlazorGDB/BlazorGDB/Components/Pages/Organization/Company.razor new file mode 100644 index 000000000..9d59cf08b --- /dev/null +++ b/src/BlazorGDB/BlazorGDB/Components/Pages/Organization/Company.razor @@ -0,0 +1,9 @@ +@page "/organization/company" + +Company + +

    Company

    + +@code { + +} diff --git a/src/BlazorGDB/BlazorGDB/Components/Pages/Organization/Settings.razor b/src/BlazorGDB/BlazorGDB/Components/Pages/Organization/Settings.razor new file mode 100644 index 000000000..fabf58a68 --- /dev/null +++ b/src/BlazorGDB/BlazorGDB/Components/Pages/Organization/Settings.razor @@ -0,0 +1,9 @@ +@page "/organization/settings" + +Settings + +

    Settings

    + +@code { + +} diff --git a/src/BlazorGDB/BlazorGDB/Components/Pages/Payables/PurchaseInvoiceDetail.razor b/src/BlazorGDB/BlazorGDB/Components/Pages/Payables/PurchaseInvoiceDetail.razor new file mode 100644 index 000000000..45f34655c --- /dev/null +++ b/src/BlazorGDB/BlazorGDB/Components/Pages/Payables/PurchaseInvoiceDetail.razor @@ -0,0 +1,489 @@ +@page "/payables/purchase-invoice/{id:int}" +@page "/payables/purchase-invoice/add" +@rendermode InteractiveServer +@using System.Text.Json +@using System.Text.Json.Serialization +@inject IHttpClientFactory ClientFactory +@inject NavigationManager Navigation + +@pageTitle + +

    @pageTitle

    + +@if (loadError) +{ + +} +else if (purchaseInvoice is null) +{ +

    Loading...

    +} +else +{ + + + + +
    +
    +
    + +
    + +
    +
    + +
    + +
    + @if (vendors != null && vendors.Any()) + { + + + @foreach (var vendor in vendors) + { + + } + + } + else + { + + } +
    +
    + +
    + +
    + +
    +
    + +
    + +
    + +
    +
    + +
    + +
    + +
    +
    + +
    + +
    + +
    +
    + +
    + +
    + +
    +
    + +
    + +
    + +
    +
    + +
    + +
    + +
    +
    + +

    Invoice Lines

    + + + + + + + + + @if (!isViewMode) + { + + } + + + + @if (purchaseInvoice.PurchaseInvoiceLines != null) + { + @for (int i = 0; i < purchaseInvoice.PurchaseInvoiceLines.Count; i++) + { + var line = purchaseInvoice.PurchaseInvoiceLines[i]; + var lineIndex = i; + + + + + + + @if (!isViewMode) + { + + } + + } + } + +
    ItemQuantityAmountDiscount %MeasurementActions
    + @if (items != null && items.Any()) + { + + + @foreach (var item in items) + { + + } + + } + else + { + + } + + + + + + + + @if (measurements != null && measurements.Any()) + { + + + @foreach (var measurement in measurements) + { + + } + + } + else + { + + } + + +
    + + @if (!isViewMode) + { + + } +
    +
    + +
    + @if (isViewMode) + { + + @if (!purchaseInvoice.IsPaid && purchaseInvoice.Posted) + { + + } + } + else + { + + } + +
    +
    + + @if (saveError) + { + + } + @if (saveSuccess) + { + + } +} + +@code { + [Parameter] + public int? Id { get; set; } + + private PurchaseInvoiceDto? purchaseInvoice; + private List? vendors; + private List? items; + private List? measurements; + private bool loadError; + private bool saveError; + private bool saveSuccess; + private bool isViewMode = true; + private string pageTitle = "Purchase Invoice"; + + protected override async Task OnInitializedAsync() + { + await LoadReferenceData(); + + if (Id.HasValue && Id.Value > 0) + { + pageTitle = "Purchase Invoice Details"; + await LoadPurchaseInvoice(Id.Value); + isViewMode = true; + } + else + { + pageTitle = "New Purchase Invoice"; + InitializeNewPurchaseInvoice(); + isViewMode = false; + } + } + + private void InitializeNewPurchaseInvoice() + { + purchaseInvoice = new PurchaseInvoiceDto + { + No = new Random().Next(1, 99999).ToString(), + InvoiceDate = DateTime.Now, + Posted = false, + IsPaid = false, + AmountPaid = 0, + PurchaseInvoiceLines = new List + { + new PurchaseInvoiceLineDto + { + Quantity = 1, + Amount = 0, + Discount = 0 + } + } + }; + } + + private async Task LoadReferenceData() + { + try + { + string apiurl = System.Environment.GetEnvironmentVariable("APIURL") ?? "http://localhost:8001/api/"; + var client = ClientFactory.CreateClient(); + + // Load vendors, items, measurements - simplified for now + // In a real scenario, you'd load these from API endpoints + vendors = new List(); + items = new List(); + measurements = new List(); + } + catch (Exception ex) + { + Console.WriteLine($"Error loading reference data: {ex.Message}"); + } + } + + private async Task LoadPurchaseInvoice(int id) + { + try + { + string apiurl = System.Environment.GetEnvironmentVariable("APIURL") ?? "http://localhost:8001/api/"; + + var request = new HttpRequestMessage(HttpMethod.Get, $"{apiurl}purchasing/purchaseinvoice?id={id}"); + request.Headers.Add("Accept", "application/json"); + + var client = ClientFactory.CreateClient(); + client.DefaultRequestHeaders.Accept.Clear(); + client.DefaultRequestHeaders.Clear(); + client.DefaultRequestHeaders.CacheControl = new System.Net.Http.Headers.CacheControlHeaderValue() { NoCache = true }; + client.DefaultRequestHeaders.Accept.Add(new System.Net.Http.Headers.MediaTypeWithQualityHeaderValue("application/json")); + + var response = await client.SendAsync(request); + + if (response.IsSuccessStatusCode) + { + var responseString = await response.Content.ReadAsStringAsync(); + var options = new JsonSerializerOptions + { + PropertyNameCaseInsensitive = true, + DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, + }; + + purchaseInvoice = JsonSerializer.Deserialize(responseString, options); + } + else + { + loadError = true; + } + } + catch (Exception ex) + { + Console.WriteLine($"Error loading purchase invoice: {ex.Message}"); + loadError = true; + } + } + + private async Task HandleSubmit() + { + try + { + saveError = false; + saveSuccess = false; + + string apiurl = System.Environment.GetEnvironmentVariable("APIURL") ?? "http://localhost:8001/api/"; + + var options = new JsonSerializerOptions + { + PropertyNameCaseInsensitive = true, + DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, + }; + + var json = JsonSerializer.Serialize(purchaseInvoice, options); + var content = new StringContent(json, System.Text.Encoding.UTF8, "application/json"); + + var client = ClientFactory.CreateClient(); + var response = await client.PostAsync($"{apiurl}purchasing/savepurchaseinvoice", content); + + if (response.IsSuccessStatusCode) + { + saveSuccess = true; + await Task.Delay(1000); + Navigation.NavigateTo("/payables/purchase-invoices"); + } + else + { + saveError = true; + } + } + catch (Exception ex) + { + Console.WriteLine($"Error saving purchase invoice: {ex.Message}"); + saveError = true; + } + } + + private void EnableEdit() + { + isViewMode = false; + } + + private void Cancel() + { + Navigation.NavigateTo("/payables/purchase-invoices"); + } + + private void NavigateToPayment() + { + if (purchaseInvoice != null) + { + Navigation.NavigateTo($"/payables/payment/{purchaseInvoice.Id}"); + } + } + + private void AddLine() + { + purchaseInvoice?.PurchaseInvoiceLines?.Add(new PurchaseInvoiceLineDto + { + Quantity = 1, + Amount = 0, + Discount = 0 + }); + } + + private void RemoveLine(int index) + { + if (purchaseInvoice?.PurchaseInvoiceLines != null && purchaseInvoice.PurchaseInvoiceLines.Count > 1) + { + purchaseInvoice.PurchaseInvoiceLines.RemoveAt(index); + StateHasChanged(); + } + } + + private void OnLineValueChanged(int index) + { + StateHasChanged(); + } + + // DTOs + public class PurchaseInvoiceDto + { + public int Id { get; set; } + public string? No { get; set; } + public int VendorId { get; set; } + public string? VendorName { get; set; } + public string? VendorInvoiceNo { get; set; } + public DateTime InvoiceDate { get; set; } + public decimal Amount + { + get + { + if (PurchaseInvoiceLines == null) return 0; + return PurchaseInvoiceLines.Sum(l => + { + var qty = l.Quantity ?? 0; + var amt = l.Amount ?? 0; + var disc = l.Discount ?? 0; + var lineTotal = qty * amt; + var discountAmount = lineTotal * (disc / 100); + return lineTotal - discountAmount; + }); + } + } + public decimal AmountPaid { get; set; } + public bool Posted { get; set; } + public bool IsPaid { get; set; } + public string? ReferenceNo { get; set; } + public List? PurchaseInvoiceLines { get; set; } + } + + public class PurchaseInvoiceLineDto + { + public int Id { get; set; } + public int? ItemId { get; set; } + public int? MeasurementId { get; set; } + public decimal? Quantity { get; set; } + public decimal? Amount { get; set; } + public decimal? Discount { get; set; } + } + + public class VendorDto + { + public int Id { get; set; } + public string? Name { get; set; } + } + + public class ItemDto + { + public int Id { get; set; } + public string? Name { get; set; } + } + + public class MeasurementDto + { + public int Id { get; set; } + public string? Name { get; set; } + } +} + diff --git a/src/BlazorGDB/BlazorGDB/Components/Pages/Payables/PurchaseInvoices.razor b/src/BlazorGDB/BlazorGDB/Components/Pages/Payables/PurchaseInvoices.razor new file mode 100644 index 000000000..c8efa7a57 --- /dev/null +++ b/src/BlazorGDB/BlazorGDB/Components/Pages/Payables/PurchaseInvoices.razor @@ -0,0 +1,198 @@ +@page "/payables/purchase-invoices" +@rendermode InteractiveServer +@using System.Text.Json +@using System.Text.Json.Serialization +@inject IHttpClientFactory ClientFactory +@inject NavigationManager Navigation + +Purchase Invoices + +

    Purchase Invoices

    + +@if (getError) +{ + +} +else if (purchaseInvoices is null) +{ +

    Loading...

    +} +else +{ +
    + + @if (selectedInvoice != null) + { + + @if (!selectedInvoice.IsPaid && selectedInvoice.Posted) + { + + } + } +
    + +
    + + + + + + + + + + + + + + + + @foreach (var invoice in purchaseInvoices) + { + + + + + + + + + + + + } + +
    NoVendor NameInvoice DateAmountAmount PaidRef NoPostedIs PaidActions
    @invoice.No@invoice.VendorName@invoice.InvoiceDate.ToString("yyyy-MM-dd")@invoice.Amount.ToString("C")@invoice.AmountPaid.ToString("C")@invoice.ReferenceNo + + + + + + @if (!invoice.IsPaid && invoice.Posted) + { + + } +
    +
    +} + +@code { + private List? purchaseInvoices; + private PurchaseInvoiceDto? selectedInvoice; + private bool getError; + private bool shouldRender = true; + + protected override bool ShouldRender() => shouldRender; + + protected override async Task OnInitializedAsync() + { + await LoadPurchaseInvoices(); + } + + private async Task LoadPurchaseInvoices() + { + try + { + string apiurl = System.Environment.GetEnvironmentVariable("APIURL") ?? "http://localhost:8001/api/"; + + var request = new HttpRequestMessage(HttpMethod.Get, $"{apiurl}purchasing/purchaseinvoices"); + request.Headers.Add("Accept", "application/json"); + + var client = ClientFactory.CreateClient(); + client.DefaultRequestHeaders.Accept.Clear(); + client.DefaultRequestHeaders.Clear(); + client.DefaultRequestHeaders.CacheControl = new System.Net.Http.Headers.CacheControlHeaderValue() { NoCache = true }; + client.DefaultRequestHeaders.Accept.Add(new System.Net.Http.Headers.MediaTypeWithQualityHeaderValue("application/json")); + + var response = await client.SendAsync(request); + + if (response.IsSuccessStatusCode) + { + var responseString = await response.Content.ReadAsStringAsync(); + var options = new JsonSerializerOptions + { + PropertyNameCaseInsensitive = true, + DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, + }; + + purchaseInvoices = JsonSerializer.Deserialize>(responseString, options); + } + else + { + getError = true; + } + } + catch (Exception ex) + { + Console.WriteLine($"Error loading purchase invoices: {ex.Message}"); + getError = true; + } + + shouldRender = true; + } + + private void SelectInvoice(PurchaseInvoiceDto invoice) + { + selectedInvoice = invoice; + StateHasChanged(); + } + + private void ViewInvoiceById(int id) + { + Navigation.NavigateTo($"/payables/purchase-invoice/{id}"); + } + + private void ViewInvoice() + { + if (selectedInvoice != null) + { + Navigation.NavigateTo($"/payables/purchase-invoice/{selectedInvoice.Id}"); + } + } + + private void NavigateToAdd() + { + Navigation.NavigateTo("/payables/purchase-invoice/add"); + } + + private void MakePaymentById(int id) + { + Navigation.NavigateTo($"/payables/payment/{id}"); + } + + private void MakePayment() + { + if (selectedInvoice != null) + { + Navigation.NavigateTo($"/payables/payment/{selectedInvoice.Id}"); + } + } + + public class PurchaseInvoiceDto + { + public int Id { get; set; } + public string? No { get; set; } + public int VendorId { get; set; } + public string? VendorName { get; set; } + public DateTime InvoiceDate { get; set; } + public decimal Amount { get; set; } + public decimal AmountPaid { get; set; } + public string? ReferenceNo { get; set; } + public bool Posted { get; set; } + public bool IsPaid { get; set; } + } +} diff --git a/src/BlazorGDB/BlazorGDB/Components/Pages/Payables/PurchaseOrderDetail.razor b/src/BlazorGDB/BlazorGDB/Components/Pages/Payables/PurchaseOrderDetail.razor new file mode 100644 index 000000000..51595845a --- /dev/null +++ b/src/BlazorGDB/BlazorGDB/Components/Pages/Payables/PurchaseOrderDetail.razor @@ -0,0 +1,449 @@ +@page "/payables/purchase-order/{id:int}" +@page "/payables/purchase-order/add" +@rendermode InteractiveServer +@using System.Text.Json +@using System.Text.Json.Serialization +@inject IHttpClientFactory ClientFactory +@inject NavigationManager Navigation + +@pageTitle + +

    @pageTitle

    + +@if (loadError) +{ + +} +else if (purchaseOrder is null) +{ +

    Loading...

    +} +else +{ + + + + +
    +
    +
    + +
    + +
    +
    + +
    + +
    + @if (vendors != null && vendors.Any()) + { + + + @foreach (var vendor in vendors) + { + + } + + } + else + { + + } +
    +
    + +
    + +
    + +
    +
    + +
    + +
    + +
    +
    + +
    + +
    + +
    +
    + +
    + +
    + +
    +
    + +

    Order Lines

    + + + + + + + + + @if (!isViewMode) + { + + } + + + + @if (purchaseOrder.PurchaseOrderLines != null) + { + @for (int i = 0; i < purchaseOrder.PurchaseOrderLines.Count; i++) + { + var line = purchaseOrder.PurchaseOrderLines[i]; + var lineIndex = i; + + + + + + + @if (!isViewMode) + { + + } + + } + } + +
    ItemQuantityAmountDiscount %MeasurementActions
    + @if (items != null && items.Any()) + { + + + @foreach (var item in items) + { + + } + + } + else + { + + } + + + + + + + + @if (measurements != null && measurements.Any()) + { + + + @foreach (var measurement in measurements) + { + + } + + } + else + { + + } + + +
    + + @if (!isViewMode) + { + + } +
    +
    + +
    + @if (isViewMode) + { + + } + else + { + + } + +
    +
    + + @if (saveError) + { + + } + @if (saveSuccess) + { + + } +} + +@code { + [Parameter] + public int? Id { get; set; } + + private PurchaseOrderDto? purchaseOrder; + private List? vendors; + private List? items; + private List? measurements; + private bool loadError; + private bool saveError; + private bool saveSuccess; + private bool isViewMode = true; + private string pageTitle = "Purchase Order"; + + protected override async Task OnInitializedAsync() + { + await LoadReferenceData(); + + if (Id.HasValue && Id.Value > 0) + { + pageTitle = "Purchase Order Details"; + await LoadPurchaseOrder(Id.Value); + isViewMode = true; + } + else + { + pageTitle = "New Purchase Order"; + InitializeNewPurchaseOrder(); + isViewMode = false; + } + } + + private void InitializeNewPurchaseOrder() + { + purchaseOrder = new PurchaseOrderDto + { + No = new Random().Next(1, 99999).ToString(), + OrderDate = DateTime.Now, + Completed = false, + PurchaseOrderLines = new List + { + new PurchaseOrderLineDto + { + Quantity = 1, + Amount = 0, + Discount = 0 + } + } + }; + } + + private async Task LoadReferenceData() + { + try + { + string apiurl = System.Environment.GetEnvironmentVariable("APIURL") ?? "http://localhost:8001/api/"; + var client = ClientFactory.CreateClient(); + + // Load vendors, items, measurements - simplified for now + // In a real scenario, you'd load these from API endpoints + vendors = new List(); + items = new List(); + measurements = new List(); + } + catch (Exception ex) + { + Console.WriteLine($"Error loading reference data: {ex.Message}"); + } + } + + private async Task LoadPurchaseOrder(int id) + { + try + { + string apiurl = System.Environment.GetEnvironmentVariable("APIURL") ?? "http://localhost:8001/api/"; + + var request = new HttpRequestMessage(HttpMethod.Get, $"{apiurl}purchasing/purchaseorder?id={id}"); + request.Headers.Add("Accept", "application/json"); + + var client = ClientFactory.CreateClient(); + client.DefaultRequestHeaders.Accept.Clear(); + client.DefaultRequestHeaders.Clear(); + client.DefaultRequestHeaders.CacheControl = new System.Net.Http.Headers.CacheControlHeaderValue() { NoCache = true }; + client.DefaultRequestHeaders.Accept.Add(new System.Net.Http.Headers.MediaTypeWithQualityHeaderValue("application/json")); + + var response = await client.SendAsync(request); + + if (response.IsSuccessStatusCode) + { + var responseString = await response.Content.ReadAsStringAsync(); + var options = new JsonSerializerOptions + { + PropertyNameCaseInsensitive = true, + DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, + }; + + purchaseOrder = JsonSerializer.Deserialize(responseString, options); + } + else + { + loadError = true; + } + } + catch (Exception ex) + { + Console.WriteLine($"Error loading purchase order: {ex.Message}"); + loadError = true; + } + } + + private async Task HandleSubmit() + { + try + { + saveError = false; + saveSuccess = false; + + string apiurl = System.Environment.GetEnvironmentVariable("APIURL") ?? "http://localhost:8001/api/"; + + var options = new JsonSerializerOptions + { + PropertyNameCaseInsensitive = true, + DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, + }; + + var json = JsonSerializer.Serialize(purchaseOrder, options); + var content = new StringContent(json, System.Text.Encoding.UTF8, "application/json"); + + var client = ClientFactory.CreateClient(); + var response = await client.PostAsync($"{apiurl}purchasing/savepurchaseorder", content); + + if (response.IsSuccessStatusCode) + { + saveSuccess = true; + await Task.Delay(1000); + Navigation.NavigateTo("/payables/purchase-orders"); + } + else + { + saveError = true; + } + } + catch (Exception ex) + { + Console.WriteLine($"Error saving purchase order: {ex.Message}"); + saveError = true; + } + } + + private void EnableEdit() + { + isViewMode = false; + } + + private void Cancel() + { + Navigation.NavigateTo("/payables/purchase-orders"); + } + + private void AddLine() + { + purchaseOrder?.PurchaseOrderLines?.Add(new PurchaseOrderLineDto + { + Quantity = 1, + Amount = 0, + Discount = 0 + }); + } + + private void RemoveLine(int index) + { + if (purchaseOrder?.PurchaseOrderLines != null && purchaseOrder.PurchaseOrderLines.Count > 1) + { + purchaseOrder.PurchaseOrderLines.RemoveAt(index); + StateHasChanged(); + } + } + + private void OnLineValueChanged(int index) + { + StateHasChanged(); + } + + // DTOs + public class PurchaseOrderDto + { + public int Id { get; set; } + public string? No { get; set; } + public int VendorId { get; set; } + public string? VendorName { get; set; } + public DateTime OrderDate { get; set; } + public decimal Amount + { + get + { + if (PurchaseOrderLines == null) return 0; + return PurchaseOrderLines.Sum(l => + { + var qty = l.Quantity ?? 0; + var amt = l.Amount ?? 0; + var disc = l.Discount ?? 0; + var lineTotal = qty * amt; + var discountAmount = lineTotal * (disc / 100); + return lineTotal - discountAmount; + }); + } + } + public bool Completed { get; set; } + public string? ReferenceNo { get; set; } + public List? PurchaseOrderLines { get; set; } + } + + public class PurchaseOrderLineDto + { + public int Id { get; set; } + public int? ItemId { get; set; } + public int? MeasurementId { get; set; } + public decimal? Quantity { get; set; } + public decimal? Amount { get; set; } + public decimal? Discount { get; set; } + } + + public class VendorDto + { + public int Id { get; set; } + public string? Name { get; set; } + } + + public class ItemDto + { + public int Id { get; set; } + public string? Name { get; set; } + } + + public class MeasurementDto + { + public int Id { get; set; } + public string? Name { get; set; } + } +} + diff --git a/src/BlazorGDB/BlazorGDB/Components/Pages/Payables/PurchaseOrders.razor b/src/BlazorGDB/BlazorGDB/Components/Pages/Payables/PurchaseOrders.razor new file mode 100644 index 000000000..18e24df09 --- /dev/null +++ b/src/BlazorGDB/BlazorGDB/Components/Pages/Payables/PurchaseOrders.razor @@ -0,0 +1,188 @@ +@page "/payables/purchase-orders" +@rendermode InteractiveServer +@using System.Text.Json +@using System.Text.Json.Serialization +@inject IHttpClientFactory ClientFactory +@inject NavigationManager Navigation + +Purchase Orders + +

    Purchase Orders

    + +@if (getError) +{ + +} +else if (purchaseOrders is null) +{ +

    Loading...

    +} +else +{ +
    + + @if (selectedPurchaseOrder != null) + { + + @if (selectedPurchaseOrder.StatusId != 3) + { + + } + } +
    + +
    + + + + + + + + + + + + + + @foreach (var order in purchaseOrders) + { + + + + + + + + + + } + +
    NoVendor NameOrder DateAmountRef NoStatusActions
    @order.No@order.VendorName@order.OrderDate.ToString("yyyy-MM-dd")@order.Amount.ToString("C")@order.ReferenceNo@GetStatusText(order.StatusId) + +
    +
    +} + +@code { + private List? purchaseOrders; + private PurchaseOrderDto? selectedPurchaseOrder; + private bool getError; + private bool shouldRender = true; + + protected override bool ShouldRender() => shouldRender; + + protected override async Task OnInitializedAsync() + { + await LoadPurchaseOrders(); + } + + private async Task LoadPurchaseOrders() + { + try + { + string apiurl = System.Environment.GetEnvironmentVariable("APIURL") ?? "http://localhost:8001/api/"; + + var request = new HttpRequestMessage(HttpMethod.Get, $"{apiurl}purchasing/purchaseorders"); + request.Headers.Add("Accept", "application/json"); + + var client = ClientFactory.CreateClient(); + client.DefaultRequestHeaders.Accept.Clear(); + client.DefaultRequestHeaders.Clear(); + client.DefaultRequestHeaders.CacheControl = new System.Net.Http.Headers.CacheControlHeaderValue() { NoCache = true }; + client.DefaultRequestHeaders.Accept.Add(new System.Net.Http.Headers.MediaTypeWithQualityHeaderValue("application/json")); + + var response = await client.SendAsync(request); + + if (response.IsSuccessStatusCode) + { + var responseString = await response.Content.ReadAsStringAsync(); + var options = new JsonSerializerOptions + { + PropertyNameCaseInsensitive = true, + DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, + }; + + purchaseOrders = JsonSerializer.Deserialize>(responseString, options); + } + else + { + getError = true; + } + } + catch (Exception ex) + { + Console.WriteLine($"Error loading purchase orders: {ex.Message}"); + getError = true; + } + + shouldRender = true; + } + + private void SelectOrder(PurchaseOrderDto order) + { + selectedPurchaseOrder = order; + StateHasChanged(); + } + + private void ViewOrder(int id) + { + Navigation.NavigateTo($"/payables/purchase-order/{id}"); + } + + private void ViewPurchaseOrder() + { + if (selectedPurchaseOrder != null) + { + Navigation.NavigateTo($"/payables/purchase-order/{selectedPurchaseOrder.Id}"); + } + } + + private void NavigateToAdd() + { + Navigation.NavigateTo("/payables/purchase-order/add"); + } + + private void CreateInvoice() + { + if (selectedPurchaseOrder != null) + { + Navigation.NavigateTo($"/payables/purchase-invoice/add?purchId={selectedPurchaseOrder.Id}"); + } + } + + private string GetStatusText(int statusId) + { + return statusId switch + { + 1 => "Open", + 2 => "Partially Invoiced", + 3 => "Fully Invoiced", + _ => "Unknown" + }; + } + + public class PurchaseOrderDto + { + public int Id { get; set; } + public string? No { get; set; } + public int VendorId { get; set; } + public string? VendorName { get; set; } + public DateTime OrderDate { get; set; } + public decimal Amount { get; set; } + public string? ReferenceNo { get; set; } + public int StatusId { get; set; } + } +} diff --git a/src/BlazorGDB/BlazorGDB/Components/Pages/Payables/Vendors.razor b/src/BlazorGDB/BlazorGDB/Components/Pages/Payables/Vendors.razor new file mode 100644 index 000000000..b517864c6 --- /dev/null +++ b/src/BlazorGDB/BlazorGDB/Components/Pages/Payables/Vendors.razor @@ -0,0 +1,229 @@ +@page "/payables/vendors" +@using Dto.Purchasing +@inject HttpClient Http +@inject NavigationManager Navigation +@rendermode InteractiveServer + +Vendors + +
    +
    +
    +

    Vendors

    +
    +
    + +
    +
    + + @if (selectedVendor != null) + { + + } +
    +
    + + @if (isLoading) + { +
    +
    +
    + Loading... +
    +

    Loading vendors...

    +
    +
    + } + else if (errorMessage != null) + { +
    +
    + +
    +
    + } + else if (vendors == null || !vendors.Any()) + { +
    +
    + +
    +
    + } + else + { +
    +
    +
    + + + + + + + + + + + + + @foreach (var vendor in vendors) + { + + + + + + + + + } + +
    + No @GetSortIcon(nameof(Vendor.No)) + + Name @GetSortIcon(nameof(Vendor.Name)) + + Phone @GetSortIcon(nameof(Vendor.Phone)) + + Contact @GetSortIcon(nameof(Vendor.Contact)) + + Tax Group @GetSortIcon(nameof(Vendor.TaxGroup)) + + Balance @GetSortIcon(nameof(Vendor.Balance)) +
    + + @vendor.No + + @vendor.Name@vendor.Phone@vendor.Contact@vendor.TaxGroup@vendor.Balance.ToString("N2")
    +
    +
    +
    + +
    +
    +

    Total: @vendors.Count() vendor(s)

    +
    +
    + } +
    + +@code { + private List? vendors; + private List? allVendors; + private Vendor? selectedVendor; + private bool isLoading = true; + private string? errorMessage; + private string? sortColumn; + private bool sortAscending = true; + + protected override async Task OnInitializedAsync() + { + await LoadVendors(); + } + + private async Task LoadVendors() + { + isLoading = true; + errorMessage = null; + + try + { + var apiUrl = "http://localhost:8001/api/purchasing/vendors"; + var response = await Http.GetAsync(apiUrl); + + if (response.IsSuccessStatusCode) + { + allVendors = await response.Content.ReadFromJsonAsync>(); + vendors = allVendors; + } + else + { + errorMessage = $"Failed to load vendors. Status: {response.StatusCode}"; + } + } + catch (HttpRequestException ex) + { + errorMessage = $"Network error: {ex.Message}. Please ensure the API is running on port 8001."; + } + catch (Exception ex) + { + errorMessage = $"Error loading vendors: {ex.Message}"; + } + finally + { + isLoading = false; + } + } + + private void SelectVendor(Vendor vendor) + { + selectedVendor = vendor; + } + + private void NavigateToNewVendor() + { + Navigation.NavigateTo("/payables/vendor/new"); + } + + private void NavigateToViewVendor() + { + if (selectedVendor != null) + { + Navigation.NavigateTo($"/payables/vendor/{selectedVendor.Id}"); + } + } + + private void SortBy(string column) + { + if (vendors == null) return; + + if (sortColumn == column) + { + sortAscending = !sortAscending; + } + else + { + sortColumn = column; + sortAscending = true; + } + + vendors = column switch + { + nameof(Vendor.No) => sortAscending + ? vendors.OrderBy(v => v.No).ToList() + : vendors.OrderByDescending(v => v.No).ToList(), + nameof(Vendor.Name) => sortAscending + ? vendors.OrderBy(v => v.Name).ToList() + : vendors.OrderByDescending(v => v.Name).ToList(), + nameof(Vendor.Phone) => sortAscending + ? vendors.OrderBy(v => v.Phone).ToList() + : vendors.OrderByDescending(v => v.Phone).ToList(), + nameof(Vendor.Contact) => sortAscending + ? vendors.OrderBy(v => v.Contact).ToList() + : vendors.OrderByDescending(v => v.Contact).ToList(), + nameof(Vendor.TaxGroup) => sortAscending + ? vendors.OrderBy(v => v.TaxGroup).ToList() + : vendors.OrderByDescending(v => v.TaxGroup).ToList(), + nameof(Vendor.Balance) => sortAscending + ? vendors.OrderBy(v => v.Balance).ToList() + : vendors.OrderByDescending(v => v.Balance).ToList(), + _ => vendors + }; + } + + private string GetSortIcon(string column) + { + if (sortColumn != column) return ""; + return sortAscending ? "▲" : "▼"; + } +} \ No newline at end of file diff --git a/src/BlazorGDB/BlazorGDB/Components/Pages/Receivables/AddCustomer.razor b/src/BlazorGDB/BlazorGDB/Components/Pages/Receivables/AddCustomer.razor new file mode 100644 index 000000000..8f708be66 --- /dev/null +++ b/src/BlazorGDB/BlazorGDB/Components/Pages/Receivables/AddCustomer.razor @@ -0,0 +1,452 @@ +@page "/receivables/customer/{Id:int}" +@page "/receivables/customer" +@using CustomerDto = Dto.Sales.Customer +@using Dto.Common +@inject HttpClient Http +@inject NavigationManager Navigation +@rendermode InteractiveServer + +@(Id == 0 ? "New" : "Edit") Customer + + + +
    +
    +
    + @if (Id != 0 && !isEditMode) + { + + + } +
    +
    + + @if (isLoading) + { +
    +
    +
    + Loading... +
    +

    Loading customer...

    +
    +
    + } + else if (errorMessage != null) + { +
    +
    + +
    +
    + } + else + { + + + + @* General Section *@ +
    +
    + General +
    +
    +
    +
    +
    +
    + +
    +
    + +
    +
    +
    +
    + +
    +
    + + +
    +
    +
    +
    +
    +
    + +
    +
    + +
    +
    +
    +
    + +
    +
    + +
    +
    +
    +
    + +
    +
    + +
    +
    +
    +
    +
    +
    + + @* Contact Section *@ +
    +
    + Contact + + @if (Id != 0) + { + + } +
    +
    +
    +
    +
    +
    + +
    +
    + +
    +
    +
    +
    +
    +
    + + @* Invoicing Section *@ +
    +
    + Invoicing +
    +
    +
    +
    +
    +
    + +
    +
    + + + @foreach (var account in Accounts) + { + + } + +
    +
    +
    +
    + +
    +
    + + + @foreach (var account in Accounts) + { + + } + +
    +
    +
    +
    + +
    +
    + + + @foreach (var account in Accounts) + { + + } + +
    +
    +
    +
    + +
    +
    + + + @foreach (var account in Accounts) + { + + } + +
    +
    +
    +
    +
    +
    + +
    +
    + + + @foreach (var taxGroup in TaxGroups) + { + + } + +
    +
    +
    +
    +
    +
    + + @* Payment Section *@ +
    +
    + Payment +
    +
    +
    +
    +
    +
    + +
    +
    + + + @foreach (var term in PaymentTerms) + { + + } + +
    +
    +
    +
    +
    +
    + +
    +
    + @if (isEditMode || Id == 0) + { + + } + +
    +
    +
    + } +
    + +@code { + [Parameter] + public int Id { get; set; } = 0; + + private CustomerDto Model { get; set; } = new(); + + private List Accounts { get; set; } = new(); + private List TaxGroups { get; set; } = new(); + private List PaymentTerms { get; set; } = new(); + + private bool isLoading = true; + private bool isEditMode = false; + private string? errorMessage; + + protected override async Task OnInitializedAsync() + { + isLoading = true; + + if (Id != 0) + { + await LoadCustomer(); + } + else + { + isEditMode = true; + await GenerateCustomerNumber(); + } + + await LoadDropdownData(); + + isLoading = false; + } + + private async Task GenerateCustomerNumber() + { + try + { + string baseApiUrl = Environment.GetEnvironmentVariable("APIURL") ?? "http://localhost:8001/api/"; + var apiUrl = baseApiUrl + "sales/customers"; + var response = await Http.GetAsync(apiUrl); + + if (response.IsSuccessStatusCode) + { + var customers = await response.Content.ReadFromJsonAsync>(); + if (customers != null && customers.Any()) + { + // Find the highest customer number + var maxNo = customers + .Select(c => int.TryParse(c.No, out int num) ? num : 0) + .DefaultIfEmpty(0) + .Max(); + + Model.No = (maxNo + 1).ToString(); + } + else + { + Model.No = "1"; + } + } + } + catch (Exception) + { + Model.No = "1"; + } + } + + private async Task LoadCustomer() + { + try + { + string baseApiUrl = Environment.GetEnvironmentVariable("APIURL") ?? "http://localhost:8001/api/"; + var apiUrl = $"{baseApiUrl}sales/customer?id={Id}"; + var response = await Http.GetAsync(apiUrl); + + if (response.IsSuccessStatusCode) + { + Model = await response.Content.ReadFromJsonAsync() ?? new(); + } + else + { + errorMessage = $"Failed to load customer. Status: {response.StatusCode}"; + } + } + catch (Exception ex) + { + errorMessage = $"Error loading customer: {ex.Message}"; + } + } + + private async Task LoadDropdownData() + { + try + { + string baseApiUrl = Environment.GetEnvironmentVariable("APIURL") ?? "http://localhost:8001/api/"; + + // Load accounts + var accountsResponse = await Http.GetAsync(baseApiUrl + "common/postingaccounts"); + if (accountsResponse.IsSuccessStatusCode) + { + var accounts = await accountsResponse.Content.ReadFromJsonAsync>(); + Accounts = accounts?.Select(a => new SelectListItem { Value = a.Id.ToString(), Text = a.AccountName! }).ToList() ?? new(); + } + + // Load tax groups + var taxGroupsResponse = await Http.GetAsync(baseApiUrl + "tax/taxgroups"); + if (taxGroupsResponse.IsSuccessStatusCode) + { + var taxGroups = await taxGroupsResponse.Content.ReadFromJsonAsync>(); + TaxGroups = taxGroups?.Select(tg => new SelectListItem { Value = tg.Id.ToString(), Text = tg.Description! }).ToList() ?? new(); + } + + // Load payment terms + var paymentTermsResponse = await Http.GetAsync(baseApiUrl + "common/paymentterms"); + if (paymentTermsResponse.IsSuccessStatusCode) + { + var paymentTerms = await paymentTermsResponse.Content.ReadFromJsonAsync>(); + PaymentTerms = paymentTerms?.Select(pt => new SelectListItem { Value = pt.Id.ToString(), Text = pt.Description }).ToList() ?? new(); + } + } + catch (Exception ex) + { + errorMessage = $"Error loading form data: {ex.Message}"; + } + } + + private async Task HandleValidSubmit() + { + try + { + string baseApiUrl = Environment.GetEnvironmentVariable("APIURL") ?? "http://localhost:8001/api/"; + var apiUrl = baseApiUrl + "sales/savecustomer"; + + var response = await Http.PostAsJsonAsync(apiUrl, Model); + + if (response.IsSuccessStatusCode) + { + Navigation.NavigateTo("/receivables/customers"); + } + else + { + var errorContent = await response.Content.ReadAsStringAsync(); + errorMessage = $"Failed to save customer. Status: {response.StatusCode}. {errorContent}"; + } + } + catch (Exception ex) + { + errorMessage = $"Error saving customer: {ex.Message}"; + } + } + + private void EnableEditMode() + { + isEditMode = true; + } + + private void NavigateToCustomerList() + { + Navigation.NavigateTo("/receivables/customers"); + } + + private void NavigateToAddContact() + { + Navigation.NavigateTo($"/receivables/contact?partyId={Model.Id}&partyType=1"); + } + + private void NavigateToContacts() + { + Navigation.NavigateTo($"/receivables/contacts?partyId={Model.Id}&partyType=1"); + } + + public class SelectListItem + { + public string Value { get; set; } = string.Empty; + public string Text { get; set; } = string.Empty; + } + + public class PaymentTermResponse + { + public int Id { get; set; } + public string Description { get; set; } = string.Empty; + } +} diff --git a/src/BlazorGDB/BlazorGDB/Components/Pages/Receivables/Contact.razor b/src/BlazorGDB/BlazorGDB/Components/Pages/Receivables/Contact.razor new file mode 100644 index 000000000..be015306a --- /dev/null +++ b/src/BlazorGDB/BlazorGDB/Components/Pages/Receivables/Contact.razor @@ -0,0 +1,232 @@ +@page "/receivables/contact/{Id:int}" +@page "/receivables/contact" +@using ContactDto = Dto.Common.Contact +@inject HttpClient Http +@inject NavigationManager Navigation +@rendermode InteractiveServer + +@(Id == 0 ? "New" : "Edit") Contact + + + +
    +
    +
    + @if (Id != 0 && !isEditMode) + { + + } +
    +
    + + @if (isLoading) + { +
    +
    +
    + Loading... +
    +

    Loading contact...

    +
    +
    + } + else if (errorMessage != null) + { +
    +
    + +
    +
    + } + else + { + + + +
    +
    + Contact +
    +
    +
    +
    + +
    +
    + + +
    +
    +
    +
    + +
    +
    + + +
    +
    +
    +
    + +
    +
    + +
    +
    +
    +
    + +
    +
    + +
    +
    +
    +
    + +
    +
    + +
    +
    +
    +
    + +
    +
    + @if (isEditMode || Id == 0) + { + + } + +
    +
    +
    + } +
    + +@code { + [Parameter] + public int Id { get; set; } = 0; + + [Parameter] + [SupplyParameterFromQuery] + public int? PartyId { get; set; } + + [Parameter] + [SupplyParameterFromQuery] + public int? PartyType { get; set; } + + private ContactDto Model { get; set; } = new(); + + private bool isLoading = true; + private bool isEditMode = false; + private string? errorMessage; + + protected override async Task OnInitializedAsync() + { + isLoading = true; + + if (Id != 0) + { + await LoadContact(); + } + else + { + isEditMode = true; + Model.HoldingPartyId = PartyId.GetValueOrDefault(); + Model.HoldingPartyType = PartyType.GetValueOrDefault(); + } + + isLoading = false; + } + + private async Task LoadContact() + { + try + { + string baseApiUrl = Environment.GetEnvironmentVariable("APIURL") ?? "http://localhost:8001/api/"; + var apiUrl = $"{baseApiUrl}contact/contact?id={Id}"; + var response = await Http.GetAsync(apiUrl); + + if (response.IsSuccessStatusCode) + { + Model = await response.Content.ReadFromJsonAsync() ?? new(); + } + else + { + errorMessage = $"Failed to load contact. Status: {response.StatusCode}"; + } + } + catch (Exception ex) + { + errorMessage = $"Error loading contact: {ex.Message}"; + } + } + + private async Task HandleValidSubmit() + { + try + { + string baseApiUrl = Environment.GetEnvironmentVariable("APIURL") ?? "http://localhost:8001/api/"; + var apiUrl = baseApiUrl + "contact/savecontact"; + + var response = await Http.PostAsJsonAsync(apiUrl, Model); + + if (response.IsSuccessStatusCode) + { + if (PartyId.HasValue && PartyType.HasValue) + { + Navigation.NavigateTo($"/receivables/contacts?partyId={PartyId}&partyType={PartyType}"); + } + else + { + Navigation.NavigateTo("/receivables/customers"); + } + } + else + { + var errorContent = await response.Content.ReadAsStringAsync(); + errorMessage = $"Failed to save contact. Status: {response.StatusCode}. {errorContent}"; + } + } + catch (Exception ex) + { + errorMessage = $"Error saving contact: {ex.Message}"; + } + } + + private void EnableEditMode() + { + isEditMode = true; + } + + private void NavigateBack() + { + if (PartyId.HasValue && PartyType.HasValue) + { + Navigation.NavigateTo($"/receivables/contacts?partyId={PartyId}&partyType={PartyType}"); + } + else if (PartyId.HasValue) + { + Navigation.NavigateTo($"/receivables/customer/{PartyId}"); + } + else + { + Navigation.NavigateTo("/receivables/customers"); + } + } +} diff --git a/src/BlazorGDB/BlazorGDB/Components/Pages/Receivables/Contacts.razor b/src/BlazorGDB/BlazorGDB/Components/Pages/Receivables/Contacts.razor new file mode 100644 index 000000000..063e29856 --- /dev/null +++ b/src/BlazorGDB/BlazorGDB/Components/Pages/Receivables/Contacts.razor @@ -0,0 +1,345 @@ +@page "/receivables/contacts" +@using ContactDto = Dto.Common.Contact +@inject HttpClient Http +@inject NavigationManager Navigation +@rendermode InteractiveServer + +Contacts + +
    +
    +
    +

    Contacts

    +
    +
    + +
    +
    + + @if (selectedContact != null) + { + + + } + +
    +
    + + @if (successMessage != null) + { +
    +
    + +
    +
    + } + + @if (isLoading) + { +
    +
    +
    + Loading... +
    +

    Loading contacts...

    +
    +
    + } + else if (errorMessage != null) + { +
    +
    + +
    +
    + } + else if (contacts == null || !contacts.Any()) + { +
    +
    + +
    +
    + } + else + { +
    +
    +
    + + + + + + + + + + + @foreach (var contact in contacts) + { + + + + + + + } + +
    + Id @GetSortIcon(nameof(ContactDto.Id)) + + First Name @GetSortIcon(nameof(ContactDto.FirstName)) + + Last Name @GetSortIcon(nameof(ContactDto.LastName)) + Primary
    + + @contact.Id + + @contact.FirstName@contact.LastName + @if (IsPrimaryContact(contact)) + { + + Primary + + } +
    +
    +
    +
    + +
    +
    +

    Total: @contacts.Count() contact(s)

    +
    +
    + } +
    + +@code { + [Parameter] + [SupplyParameterFromQuery] + public int? PartyId { get; set; } + + [Parameter] + [SupplyParameterFromQuery] + public int? PartyType { get; set; } + + private List? contacts; + private ContactDto? selectedContact; + private Dto.Sales.Customer? currentCustomer; + private bool isLoading = true; + private string? errorMessage; + private string? successMessage; + private string? sortColumn; + private bool sortAscending = true; + + protected override async Task OnInitializedAsync() + { + await LoadCustomer(); + await LoadContacts(); + } + + private async Task LoadCustomer() + { + if (!PartyId.HasValue) return; + + try + { + string baseApiUrl = Environment.GetEnvironmentVariable("APIURL") ?? "http://localhost:8001/api/"; + var customerUrl = $"{baseApiUrl}sales/customer?id={PartyId}"; + var customerResponse = await Http.GetAsync(customerUrl); + + if (customerResponse.IsSuccessStatusCode) + { + currentCustomer = await customerResponse.Content.ReadFromJsonAsync(); + } + } + catch (Exception) + { + // Silently fail - customer may not exist yet + } + } + + private async Task LoadContacts() + { + isLoading = true; + errorMessage = null; + + try + { + string baseApiUrl = Environment.GetEnvironmentVariable("APIURL") ?? "http://localhost:8001/api/"; + var apiUrl = baseApiUrl + $"contact/contacts?partyId={PartyId}&partyType={PartyType}"; + var response = await Http.GetAsync(apiUrl); + + if (response.IsSuccessStatusCode) + { + contacts = await response.Content.ReadFromJsonAsync>(); + } + else + { + errorMessage = $"Failed to load contacts. Status: {response.StatusCode}"; + } + } + catch (HttpRequestException ex) + { + errorMessage = $"Network error: {ex.Message}. Please ensure the API is running on port 8001."; + } + catch (Exception ex) + { + errorMessage = $"Error loading contacts: {ex.Message}"; + } + finally + { + isLoading = false; + } + } + + private void SelectContact(ContactDto contact) + { + selectedContact = contact; + } + + private async Task SetAsPrimaryContact() + { + if (selectedContact == null || !PartyId.HasValue) return; + + try + { + string baseApiUrl = Environment.GetEnvironmentVariable("APIURL") ?? "http://localhost:8001/api/"; + + // Load the customer + var customerUrl = $"{baseApiUrl}sales/customer?id={PartyId}"; + var customerResponse = await Http.GetAsync(customerUrl); + + if (customerResponse.IsSuccessStatusCode) + { + var customer = await customerResponse.Content.ReadFromJsonAsync(); + if (customer != null) + { + // Update customer's primary contact data + if (customer.PrimaryContact == null) + { + customer.PrimaryContact = new ContactDto(); + } + customer.PrimaryContact.FirstName = selectedContact.FirstName; + customer.PrimaryContact.LastName = selectedContact.LastName; + customer.PrimaryContact.Party = selectedContact.Party; + + // Save the customer + var saveUrl = baseApiUrl + "sales/savecustomer"; + var saveResponse = await Http.PostAsJsonAsync(saveUrl, customer); + + if (saveResponse.IsSuccessStatusCode) + { + successMessage = $"Set {selectedContact.FirstName} {selectedContact.LastName} as primary contact."; + currentCustomer = customer; + StateHasChanged(); + } + else + { + errorMessage = $"Failed to update primary contact. Status: {saveResponse.StatusCode}"; + } + } + } + else + { + errorMessage = $"Failed to load customer. Status: {customerResponse.StatusCode}"; + } + } + catch (Exception ex) + { + errorMessage = $"Error setting primary contact: {ex.Message}"; + } + } + + private void NavigateToViewContact() + { + if (selectedContact != null) + { + Navigation.NavigateTo($"/receivables/contact/{selectedContact.Id}?partyId={selectedContact.HoldingPartyId}&partyType={selectedContact.HoldingPartyType}"); + } + } + + private void NavigateToNewContact() + { + if (PartyId.HasValue && PartyType.HasValue) + { + Navigation.NavigateTo($"/receivables/contact?partyId={PartyId}&partyType={PartyType}"); + } + else + { + Navigation.NavigateTo("/receivables/contact"); + } + } + + private void NavigateToCustomers() + { + if (PartyId.HasValue) + { + Navigation.NavigateTo($"/receivables/customer/{PartyId}"); + } + else + { + Navigation.NavigateTo("/receivables/customers"); + } + } + + private void SortBy(string column) + { + if (contacts == null) return; + + if (sortColumn == column) + { + sortAscending = !sortAscending; + } + else + { + sortColumn = column; + sortAscending = true; + } + + contacts = column switch + { + nameof(ContactDto.Id) => sortAscending + ? contacts.OrderBy(c => c.Id).ToList() + : contacts.OrderByDescending(c => c.Id).ToList(), + nameof(ContactDto.FirstName) => sortAscending + ? contacts.OrderBy(c => c.FirstName).ToList() + : contacts.OrderByDescending(c => c.FirstName).ToList(), + nameof(ContactDto.LastName) => sortAscending + ? contacts.OrderBy(c => c.LastName).ToList() + : contacts.OrderByDescending(c => c.LastName).ToList(), + _ => contacts + }; + } + + private string GetSortIcon(string column) + { + if (sortColumn != column) return ""; + return sortAscending ? "▲" : "▼"; + } + + private bool IsPrimaryContact(ContactDto contact) + { + if (currentCustomer?.PrimaryContact == null) return false; + return currentCustomer.PrimaryContact.FirstName == contact.FirstName && + currentCustomer.PrimaryContact.LastName == contact.LastName; + } +} diff --git a/src/BlazorGDB/BlazorGDB/Components/Pages/Receivables/Customers.razor b/src/BlazorGDB/BlazorGDB/Components/Pages/Receivables/Customers.razor new file mode 100644 index 000000000..2a369dfc5 --- /dev/null +++ b/src/BlazorGDB/BlazorGDB/Components/Pages/Receivables/Customers.razor @@ -0,0 +1,230 @@ +@page "/receivables/customers" +@using CustomerDto = Dto.Sales.Customer +@inject HttpClient Http +@inject NavigationManager Navigation +@rendermode InteractiveServer + +Customers + +
    +
    +
    +

    Customers

    +
    +
    + +
    +
    + + @if (selectedCustomer != null) + { + + } +
    +
    + + @if (isLoading) + { +
    +
    +
    + Loading... +
    +

    Loading customers...

    +
    +
    + } + else if (errorMessage != null) + { +
    +
    + +
    +
    + } + else if (customers == null || !customers.Any()) + { +
    +
    + +
    +
    + } + else + { +
    +
    +
    + + + + + + + + + + + + + @foreach (var customer in customers) + { + + + + + + + + + } + +
    + No @GetSortIcon(nameof(CustomerDto.No)) + + Name @GetSortIcon(nameof(CustomerDto.Name)) + + Phone @GetSortIcon(nameof(CustomerDto.Phone)) + + Contact @GetSortIcon(nameof(CustomerDto.Contact)) + + Tax Group @GetSortIcon(nameof(CustomerDto.TaxGroup)) + + Balance @GetSortIcon(nameof(CustomerDto.Balance)) +
    + + @customer.No + + @customer.Name@customer.Phone@customer.Contact@customer.TaxGroup@customer.Balance.ToString("N2")
    +
    +
    +
    + +
    +
    +

    Total: @customers.Count() customer(s)

    +
    +
    + } +
    + +@code { + private List? customers; + private List? allCustomers; + private CustomerDto? selectedCustomer; + private bool isLoading = true; + private string? errorMessage; + private string? sortColumn; + private bool sortAscending = true; + + protected override async Task OnInitializedAsync() + { + await LoadCustomers(); + } + + private async Task LoadCustomers() + { + isLoading = true; + errorMessage = null; + + try + { + string baseApiUrl = Environment.GetEnvironmentVariable("APIURL") ?? "http://localhost:8001/api/"; + var apiUrl = baseApiUrl + "sales/customers"; + var response = await Http.GetAsync(apiUrl); + + if (response.IsSuccessStatusCode) + { + allCustomers = await response.Content.ReadFromJsonAsync>(); + customers = allCustomers; + } + else + { + errorMessage = $"Failed to load customers. Status: {response.StatusCode}"; + } + } + catch (HttpRequestException ex) + { + errorMessage = $"Network error: {ex.Message}. Please ensure the API is running on port 8001."; + } + catch (Exception ex) + { + errorMessage = $"Error loading customers: {ex.Message}"; + } + finally + { + isLoading = false; + } + } + + private void SelectCustomer(CustomerDto customer) + { + selectedCustomer = customer; + } + + private void NavigateToNewCustomer() + { + Navigation.NavigateTo("/receivables/customer"); + } + + private void NavigateToViewCustomer() + { + if (selectedCustomer != null) + { + Navigation.NavigateTo($"/receivables/customer/{selectedCustomer.Id}"); + } + } + + private void SortBy(string column) + { + if (customers == null) return; + + if (sortColumn == column) + { + sortAscending = !sortAscending; + } + else + { + sortColumn = column; + sortAscending = true; + } + + customers = column switch + { + nameof(CustomerDto.No) => sortAscending + ? customers.OrderBy(c => c.No).ToList() + : customers.OrderByDescending(c => c.No).ToList(), + nameof(CustomerDto.Name) => sortAscending + ? customers.OrderBy(c => c.Name).ToList() + : customers.OrderByDescending(c => c.Name).ToList(), + nameof(CustomerDto.Phone) => sortAscending + ? customers.OrderBy(c => c.Phone).ToList() + : customers.OrderByDescending(c => c.Phone).ToList(), + nameof(CustomerDto.Contact) => sortAscending + ? customers.OrderBy(c => c.Contact).ToList() + : customers.OrderByDescending(c => c.Contact).ToList(), + nameof(CustomerDto.TaxGroup) => sortAscending + ? customers.OrderBy(c => c.TaxGroup).ToList() + : customers.OrderByDescending(c => c.TaxGroup).ToList(), + nameof(CustomerDto.Balance) => sortAscending + ? customers.OrderBy(c => c.Balance).ToList() + : customers.OrderByDescending(c => c.Balance).ToList(), + _ => customers + }; + } + + private string GetSortIcon(string column) + { + if (sortColumn != column) return ""; + return sortAscending ? "▲" : "▼"; + } +} diff --git a/src/BlazorGDB/BlazorGDB/Components/Pages/Receivables/DonationInvoices.razor b/src/BlazorGDB/BlazorGDB/Components/Pages/Receivables/DonationInvoices.razor new file mode 100644 index 000000000..721081500 --- /dev/null +++ b/src/BlazorGDB/BlazorGDB/Components/Pages/Receivables/DonationInvoices.razor @@ -0,0 +1,9 @@ +@page "/receivables/donation-invoices" + +Donation Invoices + +

    Donation Invoices

    + +@code { + +} diff --git a/src/BlazorGDB/BlazorGDB/Components/Pages/Receivables/NewSalesReceipt.razor b/src/BlazorGDB/BlazorGDB/Components/Pages/Receivables/NewSalesReceipt.razor new file mode 100644 index 000000000..d3f54c374 --- /dev/null +++ b/src/BlazorGDB/BlazorGDB/Components/Pages/Receivables/NewSalesReceipt.razor @@ -0,0 +1,301 @@ +@page "/receivables/sales-receipt/new" +@using System.Text.Json +@using System.Text.Json.Serialization + +New Sales Receipt + +
    +
    +
    +

    New Sales Receipt

    +
    + +
    + + @if (loading) + { +

    Loading...

    + } + else if (errorMessage != null) + { + + } + else + { +
    +
    +
    +
    +
    +
    + + +
    +
    + + +
    +
    + +
    +
    + + +
    +
    + + + + + + + + + +
    +
    + +
    +
    + + +
    +
    +
    +
    +
    + +
    +
    +
    +
    Receipt Summary
    +
    +
    +
    + Customer: + @customerName +
    +
    + Credit Account: + @(creditAccounts.FirstOrDefault(a => a.id == accountToCreditId)?.accountName ?? "None") +
    +
    + Amount: +

    $@amount.ToString("F2")

    +
    + +
    +
    +
    +
    + } +
    + +@code { + private class Customer + { + public int id { get; set; } + public string name { get; set; } = ""; + public int? prepaymentAccountId { get; set; } + } + + private class CashBank + { + public int id { get; set; } + public string name { get; set; } = ""; + public string accountNo { get; set; } = ""; + } + + private class CreditAccount + { + public int id { get; set; } + public string accountName { get; set; } = ""; + } + + [Inject] + private IHttpClientFactory ClientFactory { get; set; } = null!; + + [Inject] + private NavigationManager Navigation { get; set; } = null!; + + private bool loading = true; + private string? errorMessage = null; + + private int customerId = 0; + private string customerName = ""; + private DateTime receiptDate = DateTime.Now; + private int accountToDebitId = 0; + private int accountToCreditId = 0; + private decimal amount = 0; + + private int CustomerId + { + get => customerId; + set + { + customerId = value; + UpdateCustomerInfo(); // Auto-updates credit account when customer changes + } + } + + private List customers = new(); + private List cashBanks = new(); + private List creditAccounts = new(); + + protected override async Task OnInitializedAsync() + { + await LoadCustomers(); + await LoadCashBanks(); + await LoadCreditAccounts(); + loading = false; + } private async Task LoadCustomers() + { + try + { + string apiurl = System.Environment.GetEnvironmentVariable("APIURL") ?? "http://localhost:8001/api/"; + var client = ClientFactory.CreateClient(); + var response = await client.GetAsync($"{apiurl}sales/customers"); + + if (response.IsSuccessStatusCode) + { + var responseString = await response.Content.ReadAsStringAsync(); + customers = JsonSerializer.Deserialize>(responseString) ?? new List(); + } + } + catch (Exception ex) + { + errorMessage = $"Error loading customers: {ex.Message}"; + } + } + + private async Task LoadCashBanks() + { + try + { + string apiurl = System.Environment.GetEnvironmentVariable("APIURL") ?? "http://localhost:8001/api/"; + var client = ClientFactory.CreateClient(); + var response = await client.GetAsync($"{apiurl}financials/cashbanks"); + + if (response.IsSuccessStatusCode) + { + var responseString = await response.Content.ReadAsStringAsync(); + cashBanks = JsonSerializer.Deserialize>(responseString) ?? new List(); + } + } + catch (Exception ex) + { + errorMessage = $"Error loading cash/bank accounts: {ex.Message}"; + } + } + + private async Task LoadCreditAccounts() + { + try + { + string apiurl = System.Environment.GetEnvironmentVariable("APIURL") ?? "http://localhost:8001/api/"; + var client = ClientFactory.CreateClient(); + var response = await client.GetAsync($"{apiurl}common/postingaccounts"); + + if (response.IsSuccessStatusCode) + { + var responseString = await response.Content.ReadAsStringAsync(); + creditAccounts = JsonSerializer.Deserialize>(responseString) ?? new List(); + } + } + catch (Exception ex) + { + errorMessage = $"Error loading credit accounts: {ex.Message}"; + } + } + + private void UpdateCustomerInfo() + { + // CURRENT: Auto-sets credit account to customer's prepayment account + // TO CHANGE BACK: Comment out or remove the accountToCreditId assignment + var customer = customers.FirstOrDefault(c => c.id == CustomerId); + if (customer != null) + { + customerName = customer.name; + accountToCreditId = customer.prepaymentAccountId ?? 0; + } + else + { + customerName = ""; + accountToCreditId = 0; + } + } + + private async Task SaveSalesReceipt() + { + try + { + if (CustomerId == 0) + { + errorMessage = "Please select a customer."; + return; + } + + if (accountToDebitId == 0) + { + errorMessage = "Please select a cash/bank account."; + return; + } + + UpdateCustomerInfo(); + + if (accountToCreditId == 0) + { + errorMessage = "Selected customer does not have a prepayment account configured."; + return; + } + + string apiurl = System.Environment.GetEnvironmentVariable("APIURL") ?? "http://localhost:8001/api/"; + + var payload = new + { + ReceiptDate = receiptDate, + CustomerId = CustomerId, + AccountToDebitId = accountToDebitId, + AccountToCreditId = accountToCreditId, + Amount = amount + }; + + var client = ClientFactory.CreateClient(); + var json = JsonSerializer.Serialize(payload); + var content = new StringContent(json, System.Text.Encoding.UTF8, "application/json"); + + var response = await client.PostAsync($"{apiurl}sales/savereceipt", content); + + if (response.IsSuccessStatusCode) + { + errorMessage = null; + Navigation.NavigateTo("/receivables/sales-receipts"); + } + else + { + var errorContent = await response.Content.ReadAsStringAsync(); + errorMessage = $"Failed to save sales receipt. Status: {response.StatusCode}. Response: {errorContent}"; + } + } + catch (Exception ex) + { + errorMessage = $"Error saving sales receipt: {ex.Message}"; + } + } +} \ No newline at end of file diff --git a/src/BlazorGDB/BlazorGDB/Components/Pages/Receivables/SalesInvoices.razor b/src/BlazorGDB/BlazorGDB/Components/Pages/Receivables/SalesInvoices.razor new file mode 100644 index 000000000..4daf2abc1 --- /dev/null +++ b/src/BlazorGDB/BlazorGDB/Components/Pages/Receivables/SalesInvoices.razor @@ -0,0 +1,9 @@ +@page "/receivables/sales-invoices" + +Sales Invoices + +

    Sales Invoices

    + +@code { + +} diff --git a/src/BlazorGDB/BlazorGDB/Components/Pages/Receivables/SalesOrders.razor b/src/BlazorGDB/BlazorGDB/Components/Pages/Receivables/SalesOrders.razor new file mode 100644 index 000000000..525387a67 --- /dev/null +++ b/src/BlazorGDB/BlazorGDB/Components/Pages/Receivables/SalesOrders.razor @@ -0,0 +1,9 @@ +@page "/receivables/sales-orders" + +Sales Orders + +

    Sales Orders

    + +@code { + +} diff --git a/src/BlazorGDB/BlazorGDB/Components/Pages/Receivables/SalesQuotations.razor b/src/BlazorGDB/BlazorGDB/Components/Pages/Receivables/SalesQuotations.razor new file mode 100644 index 000000000..ecb084eab --- /dev/null +++ b/src/BlazorGDB/BlazorGDB/Components/Pages/Receivables/SalesQuotations.razor @@ -0,0 +1,14 @@ +@page "/receivables/sales-quotations" + +Sales Quotations + +

    Sales Quotations

    + + + Dropdown button + + Action + Another action + Something else here + + \ No newline at end of file diff --git a/src/BlazorGDB/BlazorGDB/Components/Pages/Receivables/SalesReceipts.razor b/src/BlazorGDB/BlazorGDB/Components/Pages/Receivables/SalesReceipts.razor new file mode 100644 index 000000000..9f9491562 --- /dev/null +++ b/src/BlazorGDB/BlazorGDB/Components/Pages/Receivables/SalesReceipts.razor @@ -0,0 +1,182 @@ +@page "/receivables/sales-receipts" +@using System.Text.Json +@using System.Text.Json.Serialization + +Sales Receipts + +
    +
    +
    +

    Sales Receipts

    +
    + +
    + + @if (loading) + { +

    Loading...

    + } + else if (errorMessage != null) + { + + } + else if (salesReceipts == null || !salesReceipts.Any()) + { +

    No sales receipts found.

    + } + else + { +
    + + + + + + + + + + + + + @foreach (var receipt in salesReceipts) + { + + + + + + + + + } + +
    Receipt #CustomerReceipt DateAmountRemaining AmountActions
    @GetValue(receipt, "receiptNo")@GetValue(receipt, "customerName")@GetValue(receipt, "receiptDate")$@GetValue(receipt, "amount")$@GetValue(receipt, "remainingAmountToAllocate") + +
    +
    + } +
    + +@code { + [Inject] + private IHttpClientFactory ClientFactory { get; set; } = null!; + + [Inject] + private NavigationManager Navigation { get; set; } = null!; + + private List salesReceipts = new(); + private bool loading = true; + private string? errorMessage = null; + private bool shouldRefresh = false; + + protected override async Task OnInitializedAsync() + { + await LoadSalesReceipts(); + Navigation.LocationChanged += OnLocationChanged; + } + + private void OnLocationChanged(object? sender, Microsoft.AspNetCore.Components.Routing.LocationChangedEventArgs e) + { + // Refresh data when navigating back to this page + if (e.Location.Contains("/receivables/sales-receipts")) + { + shouldRefresh = true; + InvokeAsync(StateHasChanged); + } + } + + protected override async Task OnAfterRenderAsync(bool firstRender) + { + if (shouldRefresh && !firstRender) + { + shouldRefresh = false; + await LoadSalesReceipts(); + } + } + + public void Dispose() + { + Navigation.LocationChanged -= OnLocationChanged; + } + + private async Task LoadSalesReceipts() + { + try + { + loading = true; + string apiurl = System.Environment.GetEnvironmentVariable("APIURL") ?? "http://localhost:8001/api/"; + + var client = ClientFactory.CreateClient(); + var response = await client.GetAsync($"{apiurl}sales/salesreceipts"); + + if (response.IsSuccessStatusCode) + { + var responseString = await response.Content.ReadAsStringAsync(); + var options = new JsonSerializerOptions + { + PropertyNameCaseInsensitive = true, + DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, + }; + + var data = JsonSerializer.Deserialize>(responseString, options); + salesReceipts = data?.Cast().ToList() ?? new(); + } + else + { + errorMessage = $"Failed to load sales receipts. Status: {response.StatusCode}"; + } + } + catch (Exception ex) + { + errorMessage = $"Error loading sales receipts: {ex.Message}"; + } + finally + { + loading = false; + } + } + + private void SelectReceipt(dynamic receipt) + { + // For now, just show an alert. Could be extended to navigate to a detail view + var receiptNo = GetValue(receipt, "receiptNo"); + // Navigation.NavigateTo($"/receivables/sales-receipt/{receiptNo}"); + } + + private string GetValue(dynamic obj, string propertyName) + { + try + { + if (obj is System.Text.Json.JsonElement je) + { + if (je.TryGetProperty(propertyName, out var prop)) + { + if ((propertyName == "amount" || propertyName == "remainingAmountToAllocate") && prop.ValueKind == System.Text.Json.JsonValueKind.Number) + { + return prop.GetDecimal().ToString("F2"); + } + if (propertyName == "receiptDate" && prop.ValueKind == System.Text.Json.JsonValueKind.String) + { + if (DateTime.TryParse(prop.GetString(), out var date)) + { + return date.ToString("MM/dd/yyyy"); + } + } + return prop.GetString() ?? prop.ToString(); + } + } + return "N/A"; + } + catch + { + return "N/A"; + } + } +} diff --git a/src/BlazorGDB/BlazorGDB/Components/Pages/Reports/BalanceSheet.razor b/src/BlazorGDB/BlazorGDB/Components/Pages/Reports/BalanceSheet.razor new file mode 100644 index 000000000..1f0915221 --- /dev/null +++ b/src/BlazorGDB/BlazorGDB/Components/Pages/Reports/BalanceSheet.razor @@ -0,0 +1,9 @@ +@page "/reports/balance-sheet" + +Balance Sheet + +

    Balance Sheet

    + +@code { + +} diff --git a/src/BlazorGDB/BlazorGDB/Components/Pages/Reports/IncomeStatement.razor b/src/BlazorGDB/BlazorGDB/Components/Pages/Reports/IncomeStatement.razor new file mode 100644 index 000000000..7df1555f3 --- /dev/null +++ b/src/BlazorGDB/BlazorGDB/Components/Pages/Reports/IncomeStatement.razor @@ -0,0 +1,9 @@ +@page "/reports/income-statement" + +Income Statement + +

    Income Statement

    + +@code { + +} diff --git a/src/BlazorGDB/BlazorGDB/Components/Pages/Reports/TrialBalance.razor b/src/BlazorGDB/BlazorGDB/Components/Pages/Reports/TrialBalance.razor new file mode 100644 index 000000000..358cbc301 --- /dev/null +++ b/src/BlazorGDB/BlazorGDB/Components/Pages/Reports/TrialBalance.razor @@ -0,0 +1,9 @@ +@page "/reports/trial-balance" + +Trial Balance + +

    Trial Balance

    + +@code { + +} diff --git a/src/BlazorGDB/BlazorGDB/Components/Pages/SystemAdministration/AuditLogs.razor b/src/BlazorGDB/BlazorGDB/Components/Pages/SystemAdministration/AuditLogs.razor new file mode 100644 index 000000000..ae3396075 --- /dev/null +++ b/src/BlazorGDB/BlazorGDB/Components/Pages/SystemAdministration/AuditLogs.razor @@ -0,0 +1,9 @@ +@page "/system-administration/audit-logs" + +Audit Logs + +

    Audit Logs

    + +@code { + +} diff --git a/src/BlazorGDB/BlazorGDB/Components/Pages/SystemAdministration/SecurityGroups.razor b/src/BlazorGDB/BlazorGDB/Components/Pages/SystemAdministration/SecurityGroups.razor new file mode 100644 index 000000000..1c39e97f3 --- /dev/null +++ b/src/BlazorGDB/BlazorGDB/Components/Pages/SystemAdministration/SecurityGroups.razor @@ -0,0 +1,9 @@ +@page "/system-administration/security-groups" + +Security Groups + +

    Security Groups

    + +@code { + +} diff --git a/src/BlazorGDB/BlazorGDB/Components/Pages/SystemAdministration/SecurityRoles.razor b/src/BlazorGDB/BlazorGDB/Components/Pages/SystemAdministration/SecurityRoles.razor new file mode 100644 index 000000000..f1ebce518 --- /dev/null +++ b/src/BlazorGDB/BlazorGDB/Components/Pages/SystemAdministration/SecurityRoles.razor @@ -0,0 +1,9 @@ +@page "/system-administration/security-roles" + +Security Roles + +

    Security Roles

    + +@code { + +} diff --git a/src/BlazorGDB/BlazorGDB/Components/Pages/SystemAdministration/SecurityUsers.razor b/src/BlazorGDB/BlazorGDB/Components/Pages/SystemAdministration/SecurityUsers.razor new file mode 100644 index 000000000..36a938a5d --- /dev/null +++ b/src/BlazorGDB/BlazorGDB/Components/Pages/SystemAdministration/SecurityUsers.razor @@ -0,0 +1,9 @@ +@page "/system-administration/security-users" + +Security Users + +

    Security Users

    + +@code { + +} diff --git a/src/BlazorGDB/BlazorGDB/Components/Pages/Weather.razor b/src/BlazorGDB/BlazorGDB/Components/Pages/Weather.razor new file mode 100644 index 000000000..43a1ecbe9 --- /dev/null +++ b/src/BlazorGDB/BlazorGDB/Components/Pages/Weather.razor @@ -0,0 +1,64 @@ +@page "/weather" +@attribute [StreamRendering] + +Weather + +

    Weather

    + +

    This component demonstrates showing data.

    + +@if (forecasts == null) +{ +

    Loading...

    +} +else +{ + + + + + + + + + + + @foreach (var forecast in forecasts) + { + + + + + + + } + +
    DateTemp. (C)Temp. (F)Summary
    @forecast.Date.ToShortDateString()@forecast.TemperatureC@forecast.TemperatureF@forecast.Summary
    +} + +@code { + private WeatherForecast[]? forecasts; + + protected override async Task OnInitializedAsync() + { + // Simulate asynchronous loading to demonstrate streaming rendering + await Task.Delay(500); + + var startDate = DateOnly.FromDateTime(DateTime.Now); + var summaries = new[] { "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching" }; + forecasts = Enumerable.Range(1, 5).Select(index => new WeatherForecast + { + Date = startDate.AddDays(index), + TemperatureC = Random.Shared.Next(-20, 55), + Summary = summaries[Random.Shared.Next(summaries.Length)] + }).ToArray(); + } + + private class WeatherForecast + { + public DateOnly Date { get; set; } + public int TemperatureC { get; set; } + public string? Summary { get; set; } + public int TemperatureF => 32 + (int)(TemperatureC / 0.5556); + } +} diff --git a/src/BlazorGDB/BlazorGDB/Components/Routes.razor b/src/BlazorGDB/BlazorGDB/Components/Routes.razor new file mode 100644 index 000000000..c3758f3a9 --- /dev/null +++ b/src/BlazorGDB/BlazorGDB/Components/Routes.razor @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/BlazorGDB/BlazorGDB/Components/_Imports.razor b/src/BlazorGDB/BlazorGDB/Components/_Imports.razor new file mode 100644 index 000000000..9b1b0871d --- /dev/null +++ b/src/BlazorGDB/BlazorGDB/Components/_Imports.razor @@ -0,0 +1,15 @@ +@using System.Net.Http +@using System.Net.Http.Json +@using Microsoft.AspNetCore.Components.Authorization +@using Microsoft.AspNetCore.Components.Forms +@using Microsoft.AspNetCore.Components.Routing +@using Microsoft.AspNetCore.Components.Web +@using static Microsoft.AspNetCore.Components.Web.RenderMode +@using Microsoft.AspNetCore.Components.Web.Virtualization +@using Microsoft.JSInterop +@using BlazorGDB +@using BlazorGDB.Client +@using BlazorGDB.Components +@using LibraryGDB.Models +@using BlazorBootstrap; + diff --git a/src/BlazorGDB/BlazorGDB/Data/ApplicationDbContext.cs b/src/BlazorGDB/BlazorGDB/Data/ApplicationDbContext.cs new file mode 100644 index 000000000..ac7533e71 --- /dev/null +++ b/src/BlazorGDB/BlazorGDB/Data/ApplicationDbContext.cs @@ -0,0 +1,8 @@ +using Microsoft.AspNetCore.Identity.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore; + +namespace BlazorGDB.Data; + +public class ApplicationDbContext(DbContextOptions options) : IdentityDbContext(options) +{ +} diff --git a/src/BlazorGDB/BlazorGDB/Data/ApplicationUser.cs b/src/BlazorGDB/BlazorGDB/Data/ApplicationUser.cs new file mode 100644 index 000000000..d09b151cd --- /dev/null +++ b/src/BlazorGDB/BlazorGDB/Data/ApplicationUser.cs @@ -0,0 +1,9 @@ +using Microsoft.AspNetCore.Identity; + +namespace BlazorGDB.Data; + +// Add profile data for application users by adding properties to the ApplicationUser class +public class ApplicationUser : IdentityUser +{ +} + diff --git a/src/BlazorGDB/BlazorGDB/Data/Migrations/00000000000000_CreateIdentitySchema.Designer.cs b/src/BlazorGDB/BlazorGDB/Data/Migrations/00000000000000_CreateIdentitySchema.Designer.cs new file mode 100644 index 000000000..9bd5930b3 --- /dev/null +++ b/src/BlazorGDB/BlazorGDB/Data/Migrations/00000000000000_CreateIdentitySchema.Designer.cs @@ -0,0 +1,268 @@ +// +using System; +using BlazorGDB.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace BlazorGDB.Migrations +{ + [DbContext(typeof(ApplicationDbContext))] + [Migration("00000000000000_CreateIdentitySchema")] + partial class CreateIdentitySchema + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "8.0.0"); + + modelBuilder.Entity("BlazorGDB.Data.ApplicationUser", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("AccessFailedCount") + .HasColumnType("INTEGER"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("TEXT"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("EmailConfirmed") + .HasColumnType("INTEGER"); + + b.Property("LockoutEnabled") + .HasColumnType("INTEGER"); + + b.Property("LockoutEnd") + .HasColumnType("TEXT"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("PasswordHash") + .HasColumnType("TEXT"); + + b.Property("PhoneNumber") + .HasColumnType("TEXT"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("INTEGER"); + + b.Property("SecurityStamp") + .HasColumnType("TEXT"); + + b.Property("TwoFactorEnabled") + .HasColumnType("INTEGER"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex"); + + b.ToTable("AspNetUsers", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("TEXT"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex"); + + b.ToTable("AspNetRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ClaimType") + .HasColumnType("TEXT"); + + b.Property("ClaimValue") + .HasColumnType("TEXT"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ClaimType") + .HasColumnType("TEXT"); + + b.Property("ClaimValue") + .HasColumnType("TEXT"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("TEXT"); + + b.Property("ProviderKey") + .HasColumnType("TEXT"); + + b.Property("ProviderDisplayName") + .HasColumnType("TEXT"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("RoleId") + .HasColumnType("TEXT"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("LoginProvider") + .HasColumnType("TEXT"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("Value") + .HasColumnType("TEXT"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("BlazorGDB.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("BlazorGDB.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("BlazorGDB.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("BlazorGDB.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/BlazorGDB/BlazorGDB/Data/Migrations/00000000000000_CreateIdentitySchema.cs b/src/BlazorGDB/BlazorGDB/Data/Migrations/00000000000000_CreateIdentitySchema.cs new file mode 100644 index 000000000..34d5f4eb5 --- /dev/null +++ b/src/BlazorGDB/BlazorGDB/Data/Migrations/00000000000000_CreateIdentitySchema.cs @@ -0,0 +1,222 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace BlazorGDB.Migrations +{ + /// + public partial class CreateIdentitySchema : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "AspNetRoles", + columns: table => new + { + Id = table.Column(type: "TEXT", nullable: false), + Name = table.Column(type: "TEXT", maxLength: 256, nullable: true), + NormalizedName = table.Column(type: "TEXT", maxLength: 256, nullable: true), + ConcurrencyStamp = table.Column(type: "TEXT", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetRoles", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "AspNetUsers", + columns: table => new + { + Id = table.Column(type: "TEXT", nullable: false), + UserName = table.Column(type: "TEXT", maxLength: 256, nullable: true), + NormalizedUserName = table.Column(type: "TEXT", maxLength: 256, nullable: true), + Email = table.Column(type: "TEXT", maxLength: 256, nullable: true), + NormalizedEmail = table.Column(type: "TEXT", maxLength: 256, nullable: true), + EmailConfirmed = table.Column(type: "INTEGER", nullable: false), + PasswordHash = table.Column(type: "TEXT", nullable: true), + SecurityStamp = table.Column(type: "TEXT", nullable: true), + ConcurrencyStamp = table.Column(type: "TEXT", nullable: true), + PhoneNumber = table.Column(type: "TEXT", nullable: true), + PhoneNumberConfirmed = table.Column(type: "INTEGER", nullable: false), + TwoFactorEnabled = table.Column(type: "INTEGER", nullable: false), + LockoutEnd = table.Column(type: "TEXT", nullable: true), + LockoutEnabled = table.Column(type: "INTEGER", nullable: false), + AccessFailedCount = table.Column(type: "INTEGER", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUsers", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "AspNetRoleClaims", + columns: table => new + { + Id = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + RoleId = table.Column(type: "TEXT", nullable: false), + ClaimType = table.Column(type: "TEXT", nullable: true), + ClaimValue = table.Column(type: "TEXT", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetRoleClaims", x => x.Id); + table.ForeignKey( + name: "FK_AspNetRoleClaims_AspNetRoles_RoleId", + column: x => x.RoleId, + principalTable: "AspNetRoles", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AspNetUserClaims", + columns: table => new + { + Id = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + UserId = table.Column(type: "TEXT", nullable: false), + ClaimType = table.Column(type: "TEXT", nullable: true), + ClaimValue = table.Column(type: "TEXT", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUserClaims", x => x.Id); + table.ForeignKey( + name: "FK_AspNetUserClaims_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AspNetUserLogins", + columns: table => new + { + LoginProvider = table.Column(type: "TEXT", nullable: false), + ProviderKey = table.Column(type: "TEXT", nullable: false), + ProviderDisplayName = table.Column(type: "TEXT", nullable: true), + UserId = table.Column(type: "TEXT", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUserLogins", x => new { x.LoginProvider, x.ProviderKey }); + table.ForeignKey( + name: "FK_AspNetUserLogins_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AspNetUserRoles", + columns: table => new + { + UserId = table.Column(type: "TEXT", nullable: false), + RoleId = table.Column(type: "TEXT", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUserRoles", x => new { x.UserId, x.RoleId }); + table.ForeignKey( + name: "FK_AspNetUserRoles_AspNetRoles_RoleId", + column: x => x.RoleId, + principalTable: "AspNetRoles", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_AspNetUserRoles_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AspNetUserTokens", + columns: table => new + { + UserId = table.Column(type: "TEXT", nullable: false), + LoginProvider = table.Column(type: "TEXT", nullable: false), + Name = table.Column(type: "TEXT", nullable: false), + Value = table.Column(type: "TEXT", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUserTokens", x => new { x.UserId, x.LoginProvider, x.Name }); + table.ForeignKey( + name: "FK_AspNetUserTokens_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_AspNetRoleClaims_RoleId", + table: "AspNetRoleClaims", + column: "RoleId"); + + migrationBuilder.CreateIndex( + name: "RoleNameIndex", + table: "AspNetRoles", + column: "NormalizedName", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_AspNetUserClaims_UserId", + table: "AspNetUserClaims", + column: "UserId"); + + migrationBuilder.CreateIndex( + name: "IX_AspNetUserLogins_UserId", + table: "AspNetUserLogins", + column: "UserId"); + + migrationBuilder.CreateIndex( + name: "IX_AspNetUserRoles_RoleId", + table: "AspNetUserRoles", + column: "RoleId"); + + migrationBuilder.CreateIndex( + name: "EmailIndex", + table: "AspNetUsers", + column: "NormalizedEmail"); + + migrationBuilder.CreateIndex( + name: "UserNameIndex", + table: "AspNetUsers", + column: "NormalizedUserName", + unique: true); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "AspNetRoleClaims"); + + migrationBuilder.DropTable( + name: "AspNetUserClaims"); + + migrationBuilder.DropTable( + name: "AspNetUserLogins"); + + migrationBuilder.DropTable( + name: "AspNetUserRoles"); + + migrationBuilder.DropTable( + name: "AspNetUserTokens"); + + migrationBuilder.DropTable( + name: "AspNetRoles"); + + migrationBuilder.DropTable( + name: "AspNetUsers"); + } + } +} diff --git a/src/BlazorGDB/BlazorGDB/Data/Migrations/ApplicationDbContextModelSnapshot.cs b/src/BlazorGDB/BlazorGDB/Data/Migrations/ApplicationDbContextModelSnapshot.cs new file mode 100644 index 000000000..296626da8 --- /dev/null +++ b/src/BlazorGDB/BlazorGDB/Data/Migrations/ApplicationDbContextModelSnapshot.cs @@ -0,0 +1,265 @@ +// +using System; +using BlazorGDB.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace BlazorGDB.Migrations +{ + [DbContext(typeof(ApplicationDbContext))] + partial class ApplicationDbContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "8.0.0"); + + modelBuilder.Entity("BlazorGDB.Data.ApplicationUser", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("AccessFailedCount") + .HasColumnType("INTEGER"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("TEXT"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("EmailConfirmed") + .HasColumnType("INTEGER"); + + b.Property("LockoutEnabled") + .HasColumnType("INTEGER"); + + b.Property("LockoutEnd") + .HasColumnType("TEXT"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("PasswordHash") + .HasColumnType("TEXT"); + + b.Property("PhoneNumber") + .HasColumnType("TEXT"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("INTEGER"); + + b.Property("SecurityStamp") + .HasColumnType("TEXT"); + + b.Property("TwoFactorEnabled") + .HasColumnType("INTEGER"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex"); + + b.ToTable("AspNetUsers", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("TEXT"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex"); + + b.ToTable("AspNetRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ClaimType") + .HasColumnType("TEXT"); + + b.Property("ClaimValue") + .HasColumnType("TEXT"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ClaimType") + .HasColumnType("TEXT"); + + b.Property("ClaimValue") + .HasColumnType("TEXT"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("TEXT"); + + b.Property("ProviderKey") + .HasColumnType("TEXT"); + + b.Property("ProviderDisplayName") + .HasColumnType("TEXT"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("RoleId") + .HasColumnType("TEXT"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("LoginProvider") + .HasColumnType("TEXT"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("Value") + .HasColumnType("TEXT"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("BlazorGDB.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("BlazorGDB.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("BlazorGDB.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("BlazorGDB.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/BlazorGDB/BlazorGDB/Data/app.db b/src/BlazorGDB/BlazorGDB/Data/app.db new file mode 100644 index 000000000..769de58a9 Binary files /dev/null and b/src/BlazorGDB/BlazorGDB/Data/app.db differ diff --git a/src/BlazorGDB/BlazorGDB/Program.cs b/src/BlazorGDB/BlazorGDB/Program.cs new file mode 100644 index 000000000..ede7f1204 --- /dev/null +++ b/src/BlazorGDB/BlazorGDB/Program.cs @@ -0,0 +1,89 @@ +using Microsoft.AspNetCore.Components.Authorization; +using Microsoft.AspNetCore.Identity; +using Microsoft.EntityFrameworkCore; +using BlazorGDB.Components; +using BlazorGDB.Components.Account; +using BlazorGDB.Data; + +var builder = WebApplication.CreateBuilder(args); + +builder.AddServiceDefaults(); + +// Add services to the container. +builder.Services.AddRazorComponents() + .AddInteractiveServerComponents() + .AddInteractiveWebAssemblyComponents(); + +builder.Services.AddCascadingAuthenticationState(); +builder.Services.AddScoped(); +builder.Services.AddScoped(); +builder.Services.AddScoped(); + +builder.Services.AddAuthentication(options => + { + options.DefaultScheme = IdentityConstants.ApplicationScheme; + options.DefaultSignInScheme = IdentityConstants.ExternalScheme; + }) + .AddIdentityCookies(); + +var connectionString = builder.Configuration.GetConnectionString("DefaultConnection") ?? throw new InvalidOperationException("Connection string 'DefaultConnection' not found."); +builder.Services.AddDbContext(options => + options.UseSqlite(connectionString)); +builder.Services.AddDatabaseDeveloperPageExceptionFilter(); + +builder.Services.AddIdentityCore(options => options.SignIn.RequireConfirmedAccount = true) + .AddEntityFrameworkStores() + .AddSignInManager() + .AddDefaultTokenProviders(); + +builder.Services.AddSingleton, IdentityNoOpEmailSender>(); + +builder.Services.AddBlazorBootstrap(); + +// Register IHttpClientFactory +builder.Services.AddHttpClient(); + +var app = builder.Build(); + +// Configure the HTTP request pipeline. +if (app.Environment.IsDevelopment()) +{ + app.UseWebAssemblyDebugging(); + app.UseMigrationsEndPoint(); +} +else +{ + app.UseExceptionHandler("/Error", createScopeForErrors: true); + // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts. + app.UseHsts(); + app.UseHttpsRedirection(); +} + +app.MapStaticAssets(); +app.UseHttpsRedirection(); + +// added next line on Nov 27, 2025 by MZE +app.UseBlazorFrameworkFiles(); + +app.UseAntiforgery(); + +app.MapRazorComponents() + .AddInteractiveServerRenderMode() + .AddInteractiveWebAssemblyRenderMode() + .AddAdditionalAssemblies(typeof(BlazorGDB.Client._Imports).Assembly); + +// Add additional endpoints required by the Identity /Account Razor components. +app.MapAdditionalIdentityEndpoints(); + +using (var scope = app.Services.CreateScope()) +{ + var services = scope.ServiceProvider; + + var dbContext = services.GetRequiredService(); + if (!dbContext.Database.GetAppliedMigrations().Any()) + { + dbContext.Database.Migrate(); + } +} + +app.Run(); diff --git a/src/BlazorGDB/BlazorGDB/Properties/launchSettings.json b/src/BlazorGDB/BlazorGDB/Properties/launchSettings.json new file mode 100644 index 000000000..a5c7a5f8a --- /dev/null +++ b/src/BlazorGDB/BlazorGDB/Properties/launchSettings.json @@ -0,0 +1,41 @@ +{ + "$schema": "http://json.schemastore.org/launchsettings.json", + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:63910", + "sslPort": 44371 + } + }, + "profiles": { + "http": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}", + "applicationUrl": "http://localhost:5239", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "https": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}", + "applicationUrl": "https://localhost:7086;http://localhost:5239", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } + } diff --git a/src/BlazorGDB/BlazorGDB/appsettings.Development.json b/src/BlazorGDB/BlazorGDB/appsettings.Development.json new file mode 100644 index 000000000..0c208ae91 --- /dev/null +++ b/src/BlazorGDB/BlazorGDB/appsettings.Development.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/src/BlazorGDB/BlazorGDB/appsettings.json b/src/BlazorGDB/BlazorGDB/appsettings.json new file mode 100644 index 000000000..317446a3c --- /dev/null +++ b/src/BlazorGDB/BlazorGDB/appsettings.json @@ -0,0 +1,13 @@ +{ + "ConnectionStrings": { + "DefaultConnection": "DataSource=good-deed-books.db;Cache=Shared" + }, + "ApiUrl": "http://localhost:8001/api/", + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*" +} \ No newline at end of file diff --git a/src/BlazorGDB/BlazorGDB/good-deed-books.db b/src/BlazorGDB/BlazorGDB/good-deed-books.db new file mode 100644 index 000000000..9ad090262 Binary files /dev/null and b/src/BlazorGDB/BlazorGDB/good-deed-books.db differ diff --git a/src/BlazorGDB/BlazorGDB/wwwroot/app.css b/src/BlazorGDB/BlazorGDB/wwwroot/app.css new file mode 100644 index 000000000..2bd9b7896 --- /dev/null +++ b/src/BlazorGDB/BlazorGDB/wwwroot/app.css @@ -0,0 +1,51 @@ +html, body { + font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; +} + +a, .btn-link { + color: #006bb7; +} + +.btn-primary { + color: #fff; + background-color: #1b6ec2; + border-color: #1861ac; +} + +.btn:focus, .btn:active:focus, .btn-link.nav-link:focus, .form-control:focus, .form-check-input:focus { + box-shadow: 0 0 0 0.1rem white, 0 0 0 0.25rem #258cfb; +} + +.content { + padding-top: 1.1rem; +} + +h1:focus { + outline: none; +} + +.valid.modified:not([type=checkbox]) { + outline: 1px solid #26b050; +} + +.invalid { + outline: 1px solid #e50000; +} + +.validation-message { + color: #e50000; +} + +.blazor-error-boundary { + background: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTYiIGhlaWdodD0iNDkiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIG92ZXJmbG93PSJoaWRkZW4iPjxkZWZzPjxjbGlwUGF0aCBpZD0iY2xpcDAiPjxyZWN0IHg9IjIzNSIgeT0iNTEiIHdpZHRoPSI1NiIgaGVpZ2h0PSI0OSIvPjwvY2xpcFBhdGg+PC9kZWZzPjxnIGNsaXAtcGF0aD0idXJsKCNjbGlwMCkiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0yMzUgLTUxKSI+PHBhdGggZD0iTTI2My41MDYgNTFDMjY0LjcxNyA1MSAyNjUuODEzIDUxLjQ4MzcgMjY2LjYwNiA1Mi4yNjU4TDI2Ny4wNTIgNTIuNzk4NyAyNjcuNTM5IDUzLjYyODMgMjkwLjE4NSA5Mi4xODMxIDI5MC41NDUgOTIuNzk1IDI5MC42NTYgOTIuOTk2QzI5MC44NzcgOTMuNTEzIDI5MSA5NC4wODE1IDI5MSA5NC42NzgyIDI5MSA5Ny4wNjUxIDI4OS4wMzggOTkgMjg2LjYxNyA5OUwyNDAuMzgzIDk5QzIzNy45NjMgOTkgMjM2IDk3LjA2NTEgMjM2IDk0LjY3ODIgMjM2IDk0LjM3OTkgMjM2LjAzMSA5NC4wODg2IDIzNi4wODkgOTMuODA3MkwyMzYuMzM4IDkzLjAxNjIgMjM2Ljg1OCA5Mi4xMzE0IDI1OS40NzMgNTMuNjI5NCAyNTkuOTYxIDUyLjc5ODUgMjYwLjQwNyA1Mi4yNjU4QzI2MS4yIDUxLjQ4MzcgMjYyLjI5NiA1MSAyNjMuNTA2IDUxWk0yNjMuNTg2IDY2LjAxODNDMjYwLjczNyA2Ni4wMTgzIDI1OS4zMTMgNjcuMTI0NSAyNTkuMzEzIDY5LjMzNyAyNTkuMzEzIDY5LjYxMDIgMjU5LjMzMiA2OS44NjA4IDI1OS4zNzEgNzAuMDg4N0wyNjEuNzk1IDg0LjAxNjEgMjY1LjM4IDg0LjAxNjEgMjY3LjgyMSA2OS43NDc1QzI2Ny44NiA2OS43MzA5IDI2Ny44NzkgNjkuNTg3NyAyNjcuODc5IDY5LjMxNzkgMjY3Ljg3OSA2Ny4xMTgyIDI2Ni40NDggNjYuMDE4MyAyNjMuNTg2IDY2LjAxODNaTTI2My41NzYgODYuMDU0N0MyNjEuMDQ5IDg2LjA1NDcgMjU5Ljc4NiA4Ny4zMDA1IDI1OS43ODYgODkuNzkyMSAyNTkuNzg2IDkyLjI4MzcgMjYxLjA0OSA5My41Mjk1IDI2My41NzYgOTMuNTI5NSAyNjYuMTE2IDkzLjUyOTUgMjY3LjM4NyA5Mi4yODM3IDI2Ny4zODcgODkuNzkyMSAyNjcuMzg3IDg3LjMwMDUgMjY2LjExNiA4Ni4wNTQ3IDI2My41NzYgODYuMDU0N1oiIGZpbGw9IiNGRkU1MDAiIGZpbGwtcnVsZT0iZXZlbm9kZCIvPjwvZz48L3N2Zz4=) no-repeat 1rem/1.8rem, #b32121; + padding: 1rem 1rem 1rem 3.7rem; + color: white; +} + + .blazor-error-boundary::after { + content: "An error has occurred." + } + +.darker-border-checkbox.form-check-input { + border-color: #929292; +} diff --git a/src/BlazorGDB/BlazorGDB/wwwroot/favicon.png b/src/BlazorGDB/BlazorGDB/wwwroot/favicon.png new file mode 100644 index 000000000..8422b5969 Binary files /dev/null and b/src/BlazorGDB/BlazorGDB/wwwroot/favicon.png differ diff --git a/src/Core/Core.csproj b/src/Core/Core.csproj index a0aec9e66..a6f54acee 100644 --- a/src/Core/Core.csproj +++ b/src/Core/Core.csproj @@ -1,12 +1,6 @@  - - netstandard2.1 + net10.0 Library - - - - - \ No newline at end of file diff --git a/src/Core/Data/IRepository.cs b/src/Core/Data/IRepository.cs index b3b453a43..00a8eb498 100644 --- a/src/Core/Data/IRepository.cs +++ b/src/Core/Data/IRepository.cs @@ -11,6 +11,8 @@ namespace Core.Data using Domain; using System; using System.Linq; + using System.Threading.Tasks; + /// /// Repository /// @@ -18,8 +20,11 @@ public partial interface IRepository where T : BaseEntity { T GetById(object id); void Insert(T entity); + Task InsertAsync(T entity); void Update(T entity); + Task UpdateAsync(T enitity); void Delete(T entity); + Task DeleteAsync(T entity); IQueryable Table { get; } IQueryable GetAllIncluding(params System.Linq.Expressions.Expression>[] includeProperties); diff --git a/src/Core/Domain/Company.cs b/src/Core/Domain/Company.cs index 4b9b00a37..e543c3d9b 100644 --- a/src/Core/Domain/Company.cs +++ b/src/Core/Domain/Company.cs @@ -17,5 +17,6 @@ public partial class Company : BaseEntity public string Name { get; set; } public string ShortName { get; set; } public string CompanyCode { get; set; } + public string CRA { get; set; } } } diff --git a/src/Core/Domain/Donations/DonationInvoiceHeader.cs b/src/Core/Domain/Donations/DonationInvoiceHeader.cs new file mode 100644 index 000000000..b033a941b --- /dev/null +++ b/src/Core/Domain/Donations/DonationInvoiceHeader.cs @@ -0,0 +1,39 @@ +using Core.Domain.Items; +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations.Schema; +using System.Linq; + +namespace Core.Domain.Donations +{ + [Table("DonationInvoiceHeader")] + public partial class DonationInvoiceHeader : BaseEntity + { + public DonationInvoiceHeader() + { + DonationInvoiceLines = new HashSet(); + } + + public int DonorId { get; set; } + public string No { get; set; } + public DateTime Date { get; set; } + public string ReferenceNo { get; set; } + public string Purpose { get; set; } + public bool IsTaxReceiptIssued { get; set; } + public string TaxReceiptNo { get; set; } + public bool Posted { get; set; } + + public virtual Sales.Customer Donor { get; set; } + public virtual ICollection DonationInvoiceLines { get; set; } + + public decimal ComputeTotalAmount() + { + decimal totalAmount = 0; + foreach (var line in DonationInvoiceLines) + { + totalAmount += line.Quantity * line.Amount; + } + return totalAmount; + } + } +} diff --git a/src/Core/Domain/Donations/DonationInvoiceLine.cs b/src/Core/Domain/Donations/DonationInvoiceLine.cs new file mode 100644 index 000000000..d5568abb5 --- /dev/null +++ b/src/Core/Domain/Donations/DonationInvoiceLine.cs @@ -0,0 +1,20 @@ +using Core.Domain.Items; +using System.ComponentModel.DataAnnotations.Schema; + +namespace Core.Domain.Donations +{ + [Table("DonationInvoiceLine")] + public partial class DonationInvoiceLine : BaseEntity + { + public int DonationInvoiceHeaderId { get; set; } + public int ItemId { get; set; } + public int MeasurementId { get; set; } + public decimal Quantity { get; set; } + public decimal Amount { get; set; } + public string Notes { get; set; } + + public virtual DonationInvoiceHeader DonationInvoiceHeader { get; set; } + public virtual Item Item { get; set; } + public virtual Measurement Measurement { get; set; } + } +} diff --git a/src/Core/Domain/EnumTypes.cs b/src/Core/Domain/EnumTypes.cs index a2a7a2983..7645793e5 100644 --- a/src/Core/Domain/EnumTypes.cs +++ b/src/Core/Domain/EnumTypes.cs @@ -80,7 +80,7 @@ public enum PurchaseOrderStatus Open = 1, PartiallyReceived = 2, FullReceived = 3, - Invoiced = 4 , + Invoiced = 4, Closed = 5 } @@ -106,7 +106,8 @@ public enum SequenceNumberTypes Item, Customer, Vendor, - Contact + Contact, + DonationInvoice } public enum AddressTypes diff --git a/src/Core/Domain/Error/Error.cs b/src/Core/Domain/Error/Error.cs new file mode 100644 index 000000000..74031e204 --- /dev/null +++ b/src/Core/Domain/Error/Error.cs @@ -0,0 +1,12 @@ +namespace Core.Domain.Error +{ + public sealed record Error(string Code, string Message) + { + private static readonly string RecordNotFoundCode = "RecordNotFound"; + private static readonly string ValidationErrorCode = "ValidationError"; + + public static readonly Error None = new(string.Empty, string.Empty); + public static Error RecordNotFound(string message) => new Error(RecordNotFoundCode, message); + public static Error ValidationError(string message) => new Error(ValidationErrorCode, message); + } +} diff --git a/src/Core/Domain/Error/Result.cs b/src/Core/Domain/Error/Result.cs new file mode 100644 index 000000000..0c2a44593 --- /dev/null +++ b/src/Core/Domain/Error/Result.cs @@ -0,0 +1,50 @@ +using System; +using System.Reflection.Metadata.Ecma335; +#nullable enable + +namespace Core.Domain.Error +{ + public class Result where T : class + { + private readonly T? _value; + + private Result(T value) + { + Value = value; + IsSuccess = true; + Error = Error.None; + } + + private Result(Error error) + { + if (error == Error.None) + { + throw new ArgumentException("invalid error", nameof(error)); + } + + IsSuccess = false; + Error = error; + } + + public bool IsSuccess { get; } + public bool IsFailure => !IsSuccess; + + public T Value + { + get + { + if (IsFailure) + { + throw new InvalidOperationException("There is no value for failure result. Use 'Error' property instead."); + } + + return _value!; + } + private init => _value = value; + } + + public Error Error { get; } + public static Result Success(T value) => new Result(value); + public static Result Failure(Error error) => new Result(error); + } +} diff --git a/src/Core/Domain/Financials/Account.cs b/src/Core/Domain/Financials/Account.cs index 7385ec215..5b9a2a6a1 100644 --- a/src/Core/Domain/Financials/Account.cs +++ b/src/Core/Domain/Financials/Account.cs @@ -46,7 +46,7 @@ public Account() public virtual Company Company { get; set; } public virtual ICollection ChildAccounts { get; set; } - [NotMapped] + // [NotMapped] public virtual ICollection ContraAccounts { get; set; } public virtual ICollection GeneralLedgerLines { get; set; } diff --git a/src/Core/Domain/Sales/SalesReceiptLine.cs b/src/Core/Domain/Sales/SalesReceiptLine.cs index 30ff71055..571c93f38 100644 --- a/src/Core/Domain/Sales/SalesReceiptLine.cs +++ b/src/Core/Domain/Sales/SalesReceiptLine.cs @@ -7,6 +7,7 @@ //----------------------------------------------------------------------- using Core.Domain.Financials; +using Core.Domain.Items; using System; using System.Linq; using System.Collections.Generic; @@ -25,11 +26,13 @@ public SalesReceiptLine() public int? SalesInvoiceLineId { get; set; } public int? ItemId { get; set; } public int? AccountToCreditId { get; set; } + public virtual Item Item { get; set; } public int? MeasurementId { get; set; } public decimal? Quantity { get; set; } public decimal? Discount { get; set; } public decimal? Amount { get; set; } public decimal AmountPaid { get; set; } + public virtual Measurement Measurement { get; set; } public virtual SalesReceiptHeader SalesReceiptHeader { get; set; } public virtual SalesInvoiceLine SalesInvoiceLine { get; set; } public virtual Account AccountToCredit { get; set; } diff --git a/src/Core/Domain/TaxSystem/ItemTaxGroup.cs b/src/Core/Domain/TaxSystem/ItemTaxGroup.cs index 3899b6977..b81545890 100644 --- a/src/Core/Domain/TaxSystem/ItemTaxGroup.cs +++ b/src/Core/Domain/TaxSystem/ItemTaxGroup.cs @@ -7,6 +7,7 @@ //----------------------------------------------------------------------- using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; namespace Core.Domain.TaxSystem @@ -20,6 +21,8 @@ public ItemTaxGroup() } public string Name { get; set; } + + [Display(Name = "Fully Exempt")] public bool IsFullyExempt { get; set; } public virtual ICollection ItemTaxGroupTax { get; set; } diff --git a/src/Core/Domain/TaxSystem/TaxGroup.cs b/src/Core/Domain/TaxSystem/TaxGroup.cs index da67483db..9192e920d 100644 --- a/src/Core/Domain/TaxSystem/TaxGroup.cs +++ b/src/Core/Domain/TaxSystem/TaxGroup.cs @@ -7,6 +7,7 @@ //----------------------------------------------------------------------- using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; namespace Core.Domain.TaxSystem @@ -19,6 +20,8 @@ public TaxGroup() TaxGroupTax = new HashSet(); } public string Description { get; set; } + + [Display(Name = "Fully Exempt")] public bool TaxAppliedToShipping { get; set; } public bool IsActive { get; set; } public virtual ICollection TaxGroupTax { get; set; } diff --git a/src/Dto/Administration/AuditLog.cs b/src/Dto/Administration/AuditLog.cs index 383195774..0310beeda 100644 --- a/src/Dto/Administration/AuditLog.cs +++ b/src/Dto/Administration/AuditLog.cs @@ -4,13 +4,13 @@ namespace Dto.Administration { public class AuditLog : BaseDto { - public string UserName { get; set; } + public string? UserName { get; set; } public DateTime AuditEventDateUTC { get; set; } public int AuditEventType { get; set; } - public string TableName { get; set; } - public string RecordId { get; set; } - public string FieldName { get; set; } - public string OriginalValue { get; set; } - public string NewValue { get; set; } + public string? TableName { get; set; } + public string? RecordId { get; set; } + public string? FieldName { get; set; } + public string? OriginalValue { get; set; } + public string? NewValue { get; set; } } } diff --git a/src/Dto/Administration/Company.cs b/src/Dto/Administration/Company.cs index d5f63afbe..a4fb861fc 100644 --- a/src/Dto/Administration/Company.cs +++ b/src/Dto/Administration/Company.cs @@ -6,17 +6,21 @@ public class Company : BaseDto { [Required] [Display(Name = "Company Code")] - public string CompanyCode { get; set; } + public string? CompanyCode { get; set; } [Required] [Display(Name = "Name")] - public string Name { get; set; } + public string? Name { get; set; } [Required] [Display(Name = "Short Name")] - public string ShortName { get; set; } + public string? ShortName { get; set; } + + [Required] + [Display(Name = "CRA")] + public string? CRA { get; set; } [Display(Name = "Logo")] - public byte[] Logo { get; set; } + public byte[]? Logo { get; set; } } } diff --git a/src/Dto/Auditing/AuditableAttribute.cs b/src/Dto/Auditing/AuditableAttribute.cs new file mode 100644 index 000000000..c546b1475 --- /dev/null +++ b/src/Dto/Auditing/AuditableAttribute.cs @@ -0,0 +1,19 @@ +using System.ComponentModel.DataAnnotations; + +namespace Dto.Auditing +{ + // DTO for AuditableAttribute entity + public class AuditableAttribute : BaseDto + { + // Foreign key to the associated AuditableEntity + [Required] + public int AuditableEntityId { get; set; } + + // Name of the auditable attribute + [Required] + public string? AttributeName { get; set; } + + // Indicates whether auditing is enabled for this attribute + public bool EnableAudit { get; set; } + } +} \ No newline at end of file diff --git a/src/Dto/Auditing/AuditableEntity.cs b/src/Dto/Auditing/AuditableEntity.cs new file mode 100644 index 000000000..03d58a335 --- /dev/null +++ b/src/Dto/Auditing/AuditableEntity.cs @@ -0,0 +1,19 @@ +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; + +namespace Dto.Auditing +{ + // DTO for AuditableEntity entity + public class AuditableEntity : BaseDto + { + // Name of the auditable entity + [Required] + public string? EntityName { get; set; } + + // Indicates whether auditing is enabled for this entity + public bool EnableAudit { get; set; } + + // Collection of auditable attributes associated with this entity + public List AuditableAttributes {get; set; } = new List(); + } +} \ No newline at end of file diff --git a/src/Dto/BaseModel.cs b/src/Dto/BaseModel.cs index 7e1baf1fa..b5ae176dd 100644 --- a/src/Dto/BaseModel.cs +++ b/src/Dto/BaseModel.cs @@ -3,6 +3,6 @@ public abstract class BaseDto { public virtual int Id { get; set; } - public string ModifiedBy { get; set; } + public string? ModifiedBy { get; set; } } } diff --git a/src/Dto/Common/Contact.cs b/src/Dto/Common/Contact.cs index a659c19f2..73bf735de 100644 --- a/src/Dto/Common/Contact.cs +++ b/src/Dto/Common/Contact.cs @@ -8,14 +8,14 @@ public class Contact : BaseDto [Display(Name = "First Name")] [Required] [StringLength(100)] - public string FirstName { get; set; } + public string? FirstName { get; set; } [Display(Name = "Last Name")] [Required] [StringLength(100)] - public string LastName { get; set; } + public string? LastName { get; set; } public Party Party { get; set; } - public string MiddleName { get; set; } + public string? MiddleName { get; set; } public int CustomerId { get; set; } public int VendorId { get; set; } diff --git a/src/Dto/Common/Party.cs b/src/Dto/Common/Party.cs index 231be033b..3178b05fe 100644 --- a/src/Dto/Common/Party.cs +++ b/src/Dto/Common/Party.cs @@ -4,10 +4,10 @@ namespace Dto.Common { public class Party : BaseDto { - public string Name { get; set; } - public string Email { get; set; } - public string Website { get; set; } - public string Phone { get; set; } - public string Fax { get; set; } + public string? Name { get; set; } + public string? Email { get; set; } + public string? Website { get; set; } + public string? Phone { get; set; } + public string? Fax { get; set; } } } diff --git a/src/Dto/Common/Status.cs b/src/Dto/Common/Status.cs index 45c57debf..16be2f2b7 100644 --- a/src/Dto/Common/Status.cs +++ b/src/Dto/Common/Status.cs @@ -9,6 +9,6 @@ namespace Dto.Common public class Status { public int Id { get; set; } - public string Description { get; set; } + public string? Description { get; set; } } } diff --git a/src/Dto/Donations/DonationInvoice.cs b/src/Dto/Donations/DonationInvoice.cs new file mode 100644 index 000000000..9a867ff24 --- /dev/null +++ b/src/Dto/Donations/DonationInvoice.cs @@ -0,0 +1,67 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; + +namespace Dto.Donations +{ + public class DonationInvoice : BaseDto + { + public string? No { get; set; } + [Required(ErrorMessage = "Donor is required")] + public int DonorId { get; set; } + public DateTime DonationDate { get; set; } + public string? DonorName { get; set; } + public string? DonorEmail { get; set; } + public decimal Amount { get { return GetTotalAmount(); } } + public string? ReferenceNo { get; set; } + public bool Posted { get; set; } + public bool? ReadyForPosting { get; set; } + public string? CompanyName { get; set; } + public string? CompanyEmail { get; set; } + public bool IsTaxReceiptIssued { get; set; } + public string? TaxReceiptNo { get; set; } + public string? Purpose { get; set; } + public IList? DonationInvoiceLines { get; set; } + + public DonationInvoice() + { + DonationInvoiceLines = new List(); + DonationDate = DateTime.Now; + IsTaxReceiptIssued = false; + } + + private decimal GetTotalAmount() + { + decimal total = 0; + foreach (var line in DonationInvoiceLines!) + { + if (line.Amount is null || line.Quantity is null) + { + continue; + } + + decimal quantityXamount = (line.Amount!.Value * line.Quantity!.Value); + total += quantityXamount; + } + return total; + } + } + + public class DonationInvoiceLine : BaseDto + { + [Required(ErrorMessage = "Item is required")] + public int? ItemId { get; set; } + [Required(ErrorMessage = "Quantity is required")] + [Range(0, 1000000, ErrorMessage = "Quantity must be between 0 and 1000000")] + public decimal? Quantity { get; set; } + [Required(ErrorMessage = "Amount is required")] + [Range(0, 1000000, ErrorMessage = "Amount must be between 0 and 1000000")] + public decimal? Amount { get; set; } + [Required(ErrorMessage = "Measurement is required")] + public int? MeasurementId { get; set; } + public string? MeasurementDescription { get; set; } + public string? ItemNo { get; set; } + public string? ItemDescription { get; set; } + public string? Notes { get; set; } + } +} diff --git a/src/Dto/Dto.csproj b/src/Dto/Dto.csproj index 5b795aa83..2bbecb6e9 100644 --- a/src/Dto/Dto.csproj +++ b/src/Dto/Dto.csproj @@ -1,12 +1,8 @@  - - netstandard2.1 + net10.0 Library + enable + true - - - - - \ No newline at end of file diff --git a/src/Dto/Financial/Account.cs b/src/Dto/Financial/Account.cs index 7a21fc67e..075e6aef9 100644 --- a/src/Dto/Financial/Account.cs +++ b/src/Dto/Financial/Account.cs @@ -7,9 +7,9 @@ public class Account : BaseDto public int AccountClassId { get; set; } public int? ParentAccountId { get; set; } public int CompanyId { get; set; } - public string AccountCode { get; set; } - public string AccountName { get; set; } - public string Description { get; set; } + public string? AccountCode { get; set; } + public string? AccountName { get; set; } + public string? Description { get; set; } public bool IsCash { get; set; } public bool IsContraAccount { get; set; } public decimal Balance { get; set; } diff --git a/src/Dto/Financial/AccountClass.cs b/src/Dto/Financial/AccountClass.cs index ac276df1b..8a9511f86 100644 --- a/src/Dto/Financial/AccountClass.cs +++ b/src/Dto/Financial/AccountClass.cs @@ -2,7 +2,7 @@ { public class AccountClass : BaseDto { - public string Name { get; set; } - public string NormalBalance { get; set; } + public string? Name { get; set; } + public string? NormalBalance { get; set; } } } diff --git a/src/Dto/Financial/Bank.cs b/src/Dto/Financial/Bank.cs index 3ca3d8e2b..41de3af34 100644 --- a/src/Dto/Financial/Bank.cs +++ b/src/Dto/Financial/Bank.cs @@ -2,8 +2,8 @@ { public class Bank : BaseDto { - public string Name { get; set; } - public string AccountNo {get;set;} - public string BankName { get; set; } + public string? Name { get; set; } + public string? AccountNo {get;set;} + public string? BankName { get; set; } } } diff --git a/src/Dto/Financial/JournalEntry.cs b/src/Dto/Financial/JournalEntry.cs index 7d1b67561..620df0577 100644 --- a/src/Dto/Financial/JournalEntry.cs +++ b/src/Dto/Financial/JournalEntry.cs @@ -6,8 +6,8 @@ public class JournalEntry : BaseDto { public DateTime JournalDate { get; set; } public int? VoucherType { get; set; } - public string ReferenceNo { get; set; } - public string Memo { get; set; } + public string? ReferenceNo { get; set; } + public string? Memo { get; set; } public bool? Posted { get; set; } public bool? ReadyForPosting { get; set; } public decimal? debitAmount { get { return GetDebitAmount(); } } @@ -47,6 +47,6 @@ public class JournalEntryLine : BaseDto public int? AccountId { get; set; } public int DrCr { get; set; } public decimal? Amount { get; set; } - public string Memo { get; set; } + public string? Memo { get; set; } } } diff --git a/src/Dto/Financial/MasterGeneralLedger.cs b/src/Dto/Financial/MasterGeneralLedger.cs index 775018f0d..deff0dd33 100644 --- a/src/Dto/Financial/MasterGeneralLedger.cs +++ b/src/Dto/Financial/MasterGeneralLedger.cs @@ -11,10 +11,10 @@ public class MasterGeneralLedger : BaseDto public new int? Id { get; set; } public int AccountId { get; set; } public int CurrencyId { get; set; } - public string DocumentType { get; set; } + public string? DocumentType { get; set; } public int? TransactionNo { get; set; } - public string AccountCode { get; set; } - public string AccountName { get; set; } + public string? AccountCode { get; set; } + public string? AccountName { get; set; } public DateTime? Date { get; set; } public decimal? Debit { get; set; } public decimal? Credit { get; set; } diff --git a/src/Dto/Financial/TreeViewAccounts.cs b/src/Dto/Financial/TreeViewAccounts.cs index 279cc4d5a..fb734b123 100644 --- a/src/Dto/Financial/TreeViewAccounts.cs +++ b/src/Dto/Financial/TreeViewAccounts.cs @@ -4,11 +4,11 @@ namespace Dto.Financial { public class TreeViewAccounts { - public IList Groups { get; set; } + public IList? Groups { get; set; } } public class Group { - public string Name { get; set; } - public IList Groups { get; set; } + public string? Name { get; set; } + public IList? Groups { get; set; } } } diff --git a/src/Dto/Inventory/InventoryControlJournal.cs b/src/Dto/Inventory/InventoryControlJournal.cs index 304fd561c..ea19f1f46 100644 --- a/src/Dto/Inventory/InventoryControlJournal.cs +++ b/src/Dto/Inventory/InventoryControlJournal.cs @@ -6,8 +6,8 @@ public class InventoryControlJournal : BaseDto { public decimal? In { get; set; } public decimal? Out { get; set; } - public string Item { get; set; } - public string Measurement { get; set; } + public string? Item { get; set; } + public string? Measurement { get; set; } public DateTime Date { get; set; } } } diff --git a/src/Dto/Inventory/Item.cs b/src/Dto/Inventory/Item.cs index bdcc4a8be..7292705e3 100644 --- a/src/Dto/Inventory/Item.cs +++ b/src/Dto/Inventory/Item.cs @@ -4,13 +4,13 @@ namespace Dto.Inventory { public class Item : BaseDto { - public string No { get; set; } + public string? No { get; set; } [Required] - public string Code { get; set; } + public string? Code { get; set; } [Required] - public string Description { get; set; } - public string PurchaseDescription { get; set; } - public string SellDescription { get; set; } + public string? Description { get; set; } + public string? PurchaseDescription { get; set; } + public string? SellDescription { get; set; } public decimal? Cost { get; set; } public decimal? Price { get; set; } public decimal? QuantityOnHand { get; set; } @@ -25,7 +25,7 @@ public class Item : BaseDto public int? InventoryAccountId { get; set; } public int? CostOfGoodsSoldAccountId { get; set; } public int? InventoryAdjustmentAccountId { get; set; } - public string ItemTaxGroupName { get; set; } - public string Measurement { get; set; } + public string? ItemTaxGroupName { get; set; } + public string? Measurement { get; set; } } } diff --git a/src/Dto/Inventory/ItemCategory.cs b/src/Dto/Inventory/ItemCategory.cs index 878d6b908..fbc6cf3a4 100644 --- a/src/Dto/Inventory/ItemCategory.cs +++ b/src/Dto/Inventory/ItemCategory.cs @@ -2,6 +2,6 @@ { public class ItemCategory : BaseDto { - public string Name { get; set; } + public string? Name { get; set; } } } diff --git a/src/Dto/Inventory/Measurement.cs b/src/Dto/Inventory/Measurement.cs index 519b0f41f..0a43f4b7f 100644 --- a/src/Dto/Inventory/Measurement.cs +++ b/src/Dto/Inventory/Measurement.cs @@ -2,7 +2,7 @@ { public class Measurement:BaseDto { - public string Code { get; set; } - public string Description { get; set; } + public string? Code { get; set; } + public string? Description { get; set; } } } diff --git a/src/Dto/Purchasing/PurchaseInvoice.cs b/src/Dto/Purchasing/PurchaseInvoice.cs index f4ccb5b25..5c6d85666 100644 --- a/src/Dto/Purchasing/PurchaseInvoice.cs +++ b/src/Dto/Purchasing/PurchaseInvoice.cs @@ -4,19 +4,19 @@ namespace Dto.Purchasing { public class PurchaseInvoice : BaseDto { - public string No { get; set; } + public string? No { get; set; } public int? PurchaseOrderHeaderId { get; set; } - public string VendorInvoiceNo { get; set; } + public string? VendorInvoiceNo { get; set; } public int VendorId { get; set; } - public string VendorName { get; set; } + public string? VendorName { get; set; } public DateTime InvoiceDate { get; set; } public decimal Amount { get { return GetTotalAmount(); } } public decimal AmountPaid { get; set; } - public bool IsPaid { get; set; } + public bool IsPaid { get; set; } = false; public bool Posted { get; set; } public int? FromPurchaseOrderId { get; set; } public int? PaymentTermId { get; set; } - public string ReferenceNo { get; set; } + public string? ReferenceNo { get; set; } public bool? ReadyForPosting { get; set; } public System.Collections.Generic.IList PurchaseInvoiceLines { get; set; } @@ -35,7 +35,7 @@ private decimal GetTotalAmountLessTax() decimal total = 0; foreach (var line in PurchaseInvoiceLines) { - decimal quantityXamount = (line.Amount.Value * line.Quantity.Value); + decimal quantityXamount = (line.Amount!.Value * line.Quantity!.Value); decimal discount = 0; if (line.Discount.HasValue) discount = (line.Discount.Value / 100) > 0 ? (quantityXamount * (line.Discount.Value / 100)) : 0; diff --git a/src/Dto/Purchasing/PurchaseOrder.cs b/src/Dto/Purchasing/PurchaseOrder.cs index 714e82df4..2c32c9cca 100644 --- a/src/Dto/Purchasing/PurchaseOrder.cs +++ b/src/Dto/Purchasing/PurchaseOrder.cs @@ -4,15 +4,15 @@ namespace Dto.Purchasing { public class PurchaseOrder : BaseDto { - public string No { get; set; } + public string? No { get; set; } public int? PaymentTermId { get; set; } public int VendorId { get; set; } - public string VendorName { get; set; } + public string? VendorName { get; set; } public DateTime OrderDate { get; set; } public decimal Amount { get { return GetTotalAmount(); } } public bool Completed { get; set; } - public string ReferenceNo { get; set; } + public string? ReferenceNo { get; set; } public int StatusId { get; set; } public System.Collections.Generic.IList PurchaseOrderLines { get; set; } @@ -31,7 +31,7 @@ private decimal GetTotalAmountLessTax() decimal total = 0; foreach (var line in PurchaseOrderLines) { - decimal quantityXamount = (line.Amount.Value * line.Quantity.Value); + decimal quantityXamount = (line.Amount!.Value * line.Quantity!.Value); decimal discount = 0; if (line.Discount.HasValue) discount = (line.Discount.Value / 100) > 0 ? (quantityXamount * (line.Discount.Value / 100)) : 0; diff --git a/src/Dto/Purchasing/Vendor.cs b/src/Dto/Purchasing/Vendor.cs index 6c94f5761..5a3d3ea95 100644 --- a/src/Dto/Purchasing/Vendor.cs +++ b/src/Dto/Purchasing/Vendor.cs @@ -6,21 +6,21 @@ namespace Dto.Purchasing { public class Vendor : BaseDto { - public string No { get; set; } + public string? No { get; set; } [Required] - public string Name { get; set; } - public string Email { get; set; } - public string Website { get; set; } - public string Phone { get; set; } - public string Fax { get; set; } + public string? Name { get; set; } + public string? Email { get; set; } + public string? Website { get; set; } + public string? Phone { get; set; } + public string? Fax { get; set; } public int? AccountsPayableAccountId { get; set; } public int? PurchaseAccountId { get; set; } public int? PurchaseDiscountAccountId { get; set; } public int? PaymentTermId { get; set; } public int? TaxGroupId { get; set; } public decimal Balance { get; set; } - public string Contact { get; set; } - public string TaxGroup { get; set; } + public string? Contact { get; set; } + public string? TaxGroup { get; set; } public Contact PrimaryContact { get; set; } public Vendor() diff --git a/src/Dto/Sales/CreateCustomer.cs b/src/Dto/Sales/CreateCustomer.cs index 5d99fad9c..aaf4ef974 100644 --- a/src/Dto/Sales/CreateCustomer.cs +++ b/src/Dto/Sales/CreateCustomer.cs @@ -2,7 +2,7 @@ { public class CreateCustomer { - public string Name { get; set; } - public string Phone { get; set; } + public string? Name { get; set; } + public string? Phone { get; set; } } } diff --git a/src/Dto/Sales/Customer.cs b/src/Dto/Sales/Customer.cs index b4321b445..d070d5346 100644 --- a/src/Dto/Sales/Customer.cs +++ b/src/Dto/Sales/Customer.cs @@ -6,13 +6,13 @@ namespace Dto.Sales { public class Customer : BaseDto { - public string No { get; set; } + public string? No { get; set; } [Required] - public string Name { get; set; } - public string Email { get; set; } - public string Website { get; set; } - public string Phone { get; set; } - public string Fax { get; set; } + public string? Name { get; set; } + public string? Email { get; set; } + public string? Website { get; set; } + public string? Phone { get; set; } + public string? Fax { get; set; } public int? AccountsReceivableId { get; set; } public int? SalesAccountId { get; set; } @@ -21,10 +21,10 @@ public class Customer : BaseDto public int? TaxGroupId { get; set; } public int? PaymentTermId { get; set; } public decimal Balance { get; set; } - public string Contact { get; set; } - public string TaxGroup { get; set; } - public Contact PrimaryContact { get; set; } - public IEnumerable Invoices { get; set; } + public string? Contact { get; set; } + public string? TaxGroup { get; set; } + public Contact? PrimaryContact { get; set; } + public IEnumerable? Invoices { get; set; } public Customer() { PrimaryContact = new Contact(); diff --git a/src/Dto/Sales/MonthlySales.cs b/src/Dto/Sales/MonthlySales.cs index a25e746f4..64927459c 100644 --- a/src/Dto/Sales/MonthlySales.cs +++ b/src/Dto/Sales/MonthlySales.cs @@ -2,7 +2,7 @@ { public class MonthlySales { - public string Month { get; set; } + public string? Month { get; set; } public decimal Amount { get; set; } } diff --git a/src/Dto/Sales/SalesInvoice.cs b/src/Dto/Sales/SalesInvoice.cs index a49c836e0..f7a3b1898 100644 --- a/src/Dto/Sales/SalesInvoice.cs +++ b/src/Dto/Sales/SalesInvoice.cs @@ -1,32 +1,36 @@ using System; using System.Collections; using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; namespace Dto.Sales { public class SalesInvoice : BaseDto { - public string No { get; set; } - public int? CustomerId { get; set; } + public string? No { get; set; } + [Required(ErrorMessage = "Customer is required")] + public int CustomerId { get; set; } public DateTime InvoiceDate { get; set; } + [Required(ErrorMessage = "Payment Term is required")] public int? PaymentTermId { get; set; } public int? FromSalesOrderId { get; set; } public int? FromSalesDeliveryId { get; set; } - public string CustomerName { get; set; } - public string CustomerEmail { get; set; } + public string? CustomerName { get; set; } + public string? CustomerEmail { get; set; } public decimal Amount { get { return GetTotalAmount(); } } public decimal TotalAllocatedAmount { get; set; } - public string ReferenceNo { get; set; } + public string? ReferenceNo { get; set; } public bool Posted { get; set; } public bool? ReadyForPosting { get; set; } - public string CompanyName { get; set; } - public string CompanyEmail { get; set; } + public string? CompanyName { get; set; } + public string? CompanyEmail { get; set; } public decimal? TotalAmountAfterTax { get; set; } - public IList SalesInvoiceLines { get; set; } + public IList? SalesInvoiceLines { get; set; } public decimal? TotalTax { get; set; } public SalesInvoice() { SalesInvoiceLines = new List(); + InvoiceDate = DateTime.Now; } private decimal GetTotalAmount() @@ -37,9 +41,14 @@ private decimal GetTotalAmount() private decimal GetTotalAmountWithoutTax() { decimal total = 0; - foreach (var line in SalesInvoiceLines) + foreach (var line in SalesInvoiceLines!) { - decimal quantityXamount = (line.Amount.Value * line.Quantity.Value); + if(line.Amount is null || line.Quantity is null) + { + continue; + } + + decimal quantityXamount = (line.Amount!.Value * line.Quantity!.Value); decimal discount = 0; if (line.Discount.HasValue) discount = (line.Discount.Value / 100) > 0 ? (quantityXamount * (line.Discount.Value / 100)) : 0; @@ -51,13 +60,21 @@ private decimal GetTotalAmountWithoutTax() public class SalesInvoiceLine : BaseDto { + [Required(ErrorMessage = "Item is required")] public int? ItemId { get; set; } - public int? MeasurementId { get; set; } + [Required(ErrorMessage = "Quantity is required")] + [Range(0, 1000000, ErrorMessage = "Quantity must be between 0 and 1000000")] public decimal? Quantity { get; set; } - public decimal? Discount { get; set; } + [Required(ErrorMessage = "Amount is required")] + [Range(0, 1000000, ErrorMessage = "Amount must be between 0 and 1000000")] public decimal? Amount { get; set; } - public string MeasurementDescription { get; set; } - public string ItemNo { get; set; } - public string ItemDescription { get; set; } + [Required(ErrorMessage = "Discount is required")] + [Range(0, 100, ErrorMessage = "Discount must be between 0 and 100")] + public decimal? Discount { get; set; } + [Required(ErrorMessage = "Measurement is required")] + public int? MeasurementId { get; set; } + public string? MeasurementDescription { get; set; } + public string? ItemNo { get; set; } + public string? ItemDescription { get; set; } } -} +} \ No newline at end of file diff --git a/src/Dto/Sales/SalesOrder.cs b/src/Dto/Sales/SalesOrder.cs index df42de9b5..04402aa51 100644 --- a/src/Dto/Sales/SalesOrder.cs +++ b/src/Dto/Sales/SalesOrder.cs @@ -5,22 +5,22 @@ namespace Dto.Sales { public class SalesOrder : BaseDto { - public string No { get; set; } - public int? CustomerId { get; set; } + public string No { get; set; } = "1"; + public int CustomerId { get; set; } = 1; public DateTime OrderDate { get; set; } - public int? PaymentTermId { get; set; } - public string ReferenceNo { get; set; } + public int? PaymentTermId { get; set; } = 1; + public string ReferenceNo { get; set; } = "1"; public decimal? Amount { get { return GetTotalAmount(); } } - public string CustomerNo { get; set; } - public string CustomerName { get; set; } - public int StatusId { get; set; } - - public int? QuotationId { get; set; } + public string CustomerNo { get; set; } = "1"; + public string CustomerName { get; set; } = "1"; + public int StatusId { get; set; } = 1; + public int? QuotationId { get; set; } = 1; public IList SalesOrderLines { get; set; } public SalesOrder() { SalesOrderLines = new List(); + OrderDate = DateTime.Now; // TODO: Can be set by user } private decimal GetTotalAmount() @@ -33,7 +33,7 @@ private decimal GetTotalAmountLessTax() decimal total = 0; foreach (var line in SalesOrderLines) { - decimal quantityXamount = (line.Amount.Value * line.Quantity.Value); + decimal quantityXamount = (line.Amount!.Value * line.Quantity!.Value); decimal discount = 0; if (line.Discount.HasValue) discount = (line.Discount.Value / 100) > 0 ? (quantityXamount * (line.Discount.Value / 100)) : 0; @@ -50,9 +50,9 @@ public class SalesOrderLine : BaseDto public decimal? Quantity { get; set; } public decimal? Discount { get; set; } public decimal? Amount { get; set; } - public string MeasurementDescription { get; set; } - public string ItemNo { get; set; } - public string ItemDescription { get; set; } + public string? MeasurementDescription { get; set; } + public string? ItemNo { get; set; } + public string? ItemDescription { get; set; } public decimal? RemainingQtyToInvoice { get; set; } } } diff --git a/src/Dto/Sales/SalesQuotation.cs b/src/Dto/Sales/SalesQuotation.cs index e6fdd7470..82720ba1a 100644 --- a/src/Dto/Sales/SalesQuotation.cs +++ b/src/Dto/Sales/SalesQuotation.cs @@ -5,22 +5,20 @@ namespace Dto.Sales { public class SalesQuotation : BaseDto { - public string No { get; set; } + public string? No { get; set; } public int? CustomerId { get; set; } - public string CustomerName { get; set; } + public string? CustomerName { get; set; } public DateTime QuotationDate { get; set; } public int? PaymentTermId { get; set; } - public string ReferenceNo { get; set; } + public string? ReferenceNo { get; set; } public decimal Amount { get { return GetTotalAmount(); } } public int StatusId { get; set; } - - public string SalesQuoteStatus { get; set; } - + public string? SalesQuoteStatus { get; set; } public virtual List SalesQuotationLines { get; set; } - public SalesQuotation() { SalesQuotationLines = new List(); + QuotationDate = DateTime.Now; } private decimal GetTotalAmount() @@ -33,11 +31,12 @@ private decimal GetTotalAmountLessTax() decimal total = 0; foreach (var line in SalesQuotationLines) { - decimal quantityXamount = (line.Amount.Value * line.Quantity.Value); + decimal quantityXamount = (line.Amount!.Value * line.Quantity!.Value); decimal discount = 0; - if(line.Discount.HasValue) + if (line.Discount.HasValue) discount = (line.Discount.Value / 100) > 0 ? (quantityXamount * (line.Discount.Value / 100)) : 0; - total += ((line.Amount.Value * line.Quantity.Value) - discount); + decimal lineTotal = quantityXamount - discount; + total += Math.Max(0, lineTotal); } return total; } @@ -50,5 +49,7 @@ public class SalesQuotationLine : BaseDto public decimal? Quantity { get; set; } public decimal? Amount { get; set; } public decimal? Discount { get; set; } + public string? ItemDescription { get; set; } + public string? MeasurementDescription { get; set; } } } diff --git a/src/Dto/Sales/SalesReceipt.cs b/src/Dto/Sales/SalesReceipt.cs index 5e5c9cf85..e5dfd64f0 100644 --- a/src/Dto/Sales/SalesReceipt.cs +++ b/src/Dto/Sales/SalesReceipt.cs @@ -4,22 +4,23 @@ namespace Dto.Sales { public class SalesReceipt : BaseDto { - public string ReceiptNo { get; set; } + public string? ReceiptNo { get; set; } public int CustomerId { get; set; } - public string CustomerName { get; set; } + public string? CustomerName { get; set; } public decimal Amount { get; set; } public decimal RemainingAmountToAllocate { get; set; } public int AccountToDebitId { get; set; } - public string AccountToDebit { get; set; } + public string? AccountToDebit { get; set; } + public int AccountToCreditId { get; set; } public int GeneralLedgerHederId { get; set; } public DateTime ReceiptDate { get; set; } - public string ReferenceNo { get; set; } + public string? ReferenceNo { get; set; } } public class SalesReceiptLine : BaseDto { public int AccountToCreditId { get; set; } - public string AccountToCredit { get; set; } + public string? AccountToCredit { get; set; } public decimal Amount { get; set; } } } diff --git a/src/Dto/Security/Group.cs b/src/Dto/Security/Group.cs index b7869ccbc..bf00d857a 100644 --- a/src/Dto/Security/Group.cs +++ b/src/Dto/Security/Group.cs @@ -4,8 +4,8 @@ namespace Dto.Security { public class Group : BaseDto { - public string Name { get; set; } - public string DisplayName { get; set; } + public string? Name { get; set; } + public string? DisplayName { get; set; } public IList Permissions { get; set; } public Group() diff --git a/src/Dto/Security/Permission.cs b/src/Dto/Security/Permission.cs index 286707aab..a0e8f5dc7 100644 --- a/src/Dto/Security/Permission.cs +++ b/src/Dto/Security/Permission.cs @@ -2,8 +2,8 @@ { public class Permission : BaseDto { - public string Name { get; set; } - public string DisplayName { get; set; } - public Group Group { get; set; } + public string? Name { get; set; } + public string? DisplayName { get; set; } + public Group? Group { get; set; } } } diff --git a/src/Dto/Security/Role.cs b/src/Dto/Security/Role.cs index aaa9e0e6b..4f4df1f0b 100644 --- a/src/Dto/Security/Role.cs +++ b/src/Dto/Security/Role.cs @@ -4,8 +4,8 @@ namespace Dto.Security { public class Role : BaseDto { - public string Name { get; set; } - public string DisplayName { get; set; } + public string? Name { get; set; } + public string? DisplayName { get; set; } public bool SysAdmin { get; set; } public IList Users { get; set; } public IList Permissions { get; set; } diff --git a/src/Dto/Security/Token.cs b/src/Dto/Security/Token.cs new file mode 100644 index 000000000..ad2299338 --- /dev/null +++ b/src/Dto/Security/Token.cs @@ -0,0 +1,4 @@ +namespace Dto.Security +{ + public record Token(string AccessToken, string RefreshToken); +} diff --git a/src/Dto/Security/User.cs b/src/Dto/Security/User.cs index 651758585..098b9748a 100644 --- a/src/Dto/Security/User.cs +++ b/src/Dto/Security/User.cs @@ -4,10 +4,10 @@ namespace Dto.Security { public class User : BaseDto { - public string FirstName { get; set; } - public string LastName { get; set; } - public string UserName { get; set; } - public string Email { get; set; } + public string? FirstName { get; set; } + public string? LastName { get; set; } + public string? UserName { get; set; } + public string? Email { get; set; } public IList Roles { get; set; } public bool SysAdmin { get { return IsSystemAdministrators(); } } diff --git a/src/Dto/TaxSystem/ItemTaxGroup.cs b/src/Dto/TaxSystem/ItemTaxGroup.cs index 2d2227033..19ac0c171 100644 --- a/src/Dto/TaxSystem/ItemTaxGroup.cs +++ b/src/Dto/TaxSystem/ItemTaxGroup.cs @@ -1,10 +1,13 @@ using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; namespace Dto.TaxSystem { public class ItemTaxGroup : BaseDto { - public string Name { get; set; } + public string? Name { get; set; } + + [Display(Name = "Fully Exempt")] public bool IsFullyExempt { get; set; } public IList Taxes { get; set; } diff --git a/src/Dto/TaxSystem/Tax.cs b/src/Dto/TaxSystem/Tax.cs index 6a8d9bbe4..818ac732d 100644 --- a/src/Dto/TaxSystem/Tax.cs +++ b/src/Dto/TaxSystem/Tax.cs @@ -2,15 +2,15 @@ { public class Tax : BaseDto { - public string TaxName { get; set; } - public string TaxCode { get; set; } + public string? TaxName { get; set; } + public string? TaxCode { get; set; } public decimal Rate { get; set; } public bool IsActive { get; set; } } public class TaxSystemDto { - public System.Collections.Generic.IEnumerable Taxes { get; set; } - public System.Collections.Generic.IEnumerable TaxGroups { get; set; } - public System.Collections.Generic.IEnumerable ItemTaxGroups { get; set; } + public System.Collections.Generic.IEnumerable? Taxes { get; set; } + public System.Collections.Generic.IEnumerable? TaxGroups { get; set; } + public System.Collections.Generic.IEnumerable? ItemTaxGroups { get; set; } } } diff --git a/src/Dto/TaxSystem/TaxForCreation.cs b/src/Dto/TaxSystem/TaxForCreation.cs new file mode 100644 index 000000000..f5f82c776 --- /dev/null +++ b/src/Dto/TaxSystem/TaxForCreation.cs @@ -0,0 +1,26 @@ +using System.ComponentModel.DataAnnotations; + +namespace Dto.TaxSystem +{ + public class TaxForCreation + { + public int SalesAccountId { get; set; } + public int PurchaseAccountId { get; set; } + + [Required(ErrorMessage = "The Tax Name is a required field.")] + [MaxLength(50, ErrorMessage = "Tax Name must be less than 20 characters.")] + public string? TaxName { get; set; } + + [Required(ErrorMessage = "The Tax Code is a required field.")] + [MaxLength(16, ErrorMessage = "Tax Code must be less than 20 characters.")] + public string? TaxCode { get; set; } + + [Range(0, 100, ErrorMessage = "Rate must be between 0 and 100.")] + public decimal Rate { get; set; } + + public bool IsActive { get; set; } + + public TaxGroup? TaxGroup { get; set; } + public ItemTaxGroup? ItemTaxGroup { get; set; } + } +} diff --git a/src/Dto/TaxSystem/TaxForUpdate.cs b/src/Dto/TaxSystem/TaxForUpdate.cs new file mode 100644 index 000000000..6196dad42 --- /dev/null +++ b/src/Dto/TaxSystem/TaxForUpdate.cs @@ -0,0 +1,16 @@ +using System.ComponentModel.DataAnnotations; + +namespace Dto.TaxSystem +{ + public class TaxForUpdate + { + public int SalesAccountId { get; set; } + public int PurchaseAccountId { get; set; } + + public Tax? Tax { get; set; } + + + public TaxGroup? TaxGroup { get; set; } + public ItemTaxGroup? ItemTaxGroup { get; set; } + } +} diff --git a/src/Dto/TaxSystem/TaxGroup.cs b/src/Dto/TaxSystem/TaxGroup.cs index 747391cfc..b2c72f603 100644 --- a/src/Dto/TaxSystem/TaxGroup.cs +++ b/src/Dto/TaxSystem/TaxGroup.cs @@ -1,10 +1,13 @@ using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; namespace Dto.TaxSystem { public class TaxGroup : BaseDto { - public string Description { get; set; } + public string? Description { get; set; } + + [Display (Name="Apply to shipping")] public bool TaxAppliedToShipping { get; set; } public bool IsActive { get; set; } public IList Taxes { get; set; } diff --git a/src/GoodBooksReact/.babelec.json b/src/GoodBooksReact/.babelec.json new file mode 100644 index 000000000..99ffbfd27 --- /dev/null +++ b/src/GoodBooksReact/.babelec.json @@ -0,0 +1,19 @@ +{ + "presets": [ + "@babel/preset-env", + "@babel/preset-react", + "@babel/preset-env", + "@babel/preset-react" + ], + "plugins": [ + "@babel/plugin-transform-runtime", + "@babel/plugin-transform-object-assign", + [ + "@babel/plugin-proposal-decorators", + { + "legacy": true + } + ], + ["@babel/plugin-proposal-class-properties", { "loose": true }] + ] + } \ No newline at end of file diff --git a/src/GoodBooksReact/.eslintrc.cjs b/src/GoodBooksReact/.eslintrc.cjs new file mode 100644 index 000000000..89a6c8f68 --- /dev/null +++ b/src/GoodBooksReact/.eslintrc.cjs @@ -0,0 +1,19 @@ +module.exports = { + root: true, + env: { browser: true, es2020: true }, + extends: [ + 'eslint:recommended', + 'plugin:@typescript-eslint/recommended', + 'plugin:react-hooks/recommended', + ], + ignorePatterns: ['dist', '.eslintrc.cjs'], + parser: '@typescript-eslint/parser', + plugins: ['react-refresh'], + rules: { + 'react-refresh/only-export-components': [ + 'warn', + { allowConstantExport: true }, + ], + "@typescript-eslint/no-explicit-any": "error" + } +} diff --git a/src/GoodBooksReact/.gitignore b/src/GoodBooksReact/.gitignore new file mode 100644 index 000000000..a547bf36d --- /dev/null +++ b/src/GoodBooksReact/.gitignore @@ -0,0 +1,24 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/src/GoodBooksReact/README.md b/src/GoodBooksReact/README.md new file mode 100644 index 000000000..0d6babedd --- /dev/null +++ b/src/GoodBooksReact/README.md @@ -0,0 +1,30 @@ +# React + TypeScript + Vite + +This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules. + +Currently, two official plugins are available: + +- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh +- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh + +## Expanding the ESLint configuration + +If you are developing a production application, we recommend updating the configuration to enable type aware lint rules: + +- Configure the top-level `parserOptions` property like this: + +```js +export default { + // other rules... + parserOptions: { + ecmaVersion: 'latest', + sourceType: 'module', + project: ['./tsconfig.json', './tsconfig.node.json'], + tsconfigRootDir: __dirname, + }, +} +``` + +- Replace `plugin:@typescript-eslint/recommended` to `plugin:@typescript-eslint/recommended-type-checked` or `plugin:@typescript-eslint/strict-type-checked` +- Optionally add `plugin:@typescript-eslint/stylistic-type-checked` +- Install [eslint-plugin-react](https://github.com/jsx-eslint/eslint-plugin-react) and add `plugin:react/recommended` & `plugin:react/jsx-runtime` to the `extends` list diff --git a/src/GoodBooksReact/errors.txt b/src/GoodBooksReact/errors.txt new file mode 100644 index 000000000..62c4291eb --- /dev/null +++ b/src/GoodBooksReact/errors.txt @@ -0,0 +1,472 @@ + +> goodbooksreact@0.0.0 tsc +> tsc + +src/components/Shared/Components/SelectPaymentTerm.tsx(6,25): error TS7006: Parameter 'e' implicitly has an 'any' type. +src/components/Shared/Components/SelectQuotationStatus.tsx(6,25): error TS7006: Parameter 'e' implicitly has an 'any' type. +src/components/Shared/Components/SelectVendor.tsx(6,20): error TS7006: Parameter 'e' implicitly has an 'any' type. +src/components/Shared/Components/SelectVendor.tsx(24,47): error TS2339: Property 'id' does not exist on type 'AsyncDisposable'. +src/components/Shared/Components/SelectVendor.tsx(24,67): error TS2339: Property 'id' does not exist on type 'AsyncDisposable'. +src/components/Shared/Components/SelectVendor.tsx(24,82): error TS2339: Property 'name' does not exist on type 'AsyncDisposable'. +src/components/Shared/Components/SelectVoucherType.tsx(6,25): error TS7006: Parameter 'e' implicitly has an 'any' type. +src/components/Shared/Stores/AppState.tsx(1,1): error TS6133: 'observable' is declared but its value is never read. +src/components/Shared/Stores/Common/CommonStore.tsx(1,21): error TS6133: 'extendObservable' is declared but its value is never read. +src/components/Shared/Stores/Common/CommonStore.tsx(1,39): error TS6133: 'action' is declared but its value is never read. +src/components/Shared/Stores/Common/CommonStore.tsx(31,36): error TS2345: Argument of type 'any' is not assignable to parameter of type 'never'. +src/components/Shared/Stores/Common/CommonStore.tsx(42,39): error TS2345: Argument of type 'any' is not assignable to parameter of type 'never'. +src/components/Shared/Stores/Common/CommonStore.tsx(50,29): error TS7006: Parameter 'result' implicitly has an 'any' type. +src/components/Shared/Stores/Common/CommonStore.tsx(53,34): error TS2345: Argument of type 'any' is not assignable to parameter of type 'never'. +src/components/Shared/Stores/Common/CommonStore.tsx(64,32): error TS2345: Argument of type 'any' is not assignable to parameter of type 'never'. +src/components/Shared/Stores/Common/CommonStore.tsx(75,39): error TS2345: Argument of type 'any' is not assignable to parameter of type 'never'. +src/components/Shared/Stores/Common/CommonStore.tsx(90,42): error TS2345: Argument of type 'any' is not assignable to parameter of type 'never'. +src/components/Shared/Stores/Common/CommonStore.tsx(101,35): error TS2345: Argument of type 'any' is not assignable to parameter of type 'never'. +src/components/Shared/Stores/Common/CommonStore.tsx(116,29): error TS7006: Parameter 'tax' implicitly has an 'any' type. +src/components/Shared/Stores/Common/CommonStore.tsx(125,29): error TS7006: Parameter 'tax' implicitly has an 'any' type. +src/components/Shared/Stores/Financials/JournalEntry.tsx(5,5): error TS2564: Property 'voucherType' has no initializer and is not definitely assigned in the constructor. +src/components/Shared/Stores/Financials/JournalEntry.tsx(6,5): error TS2564: Property 'journalDate' has no initializer and is not definitely assigned in the constructor. +src/components/Shared/Stores/Financials/JournalEntry.tsx(7,5): error TS2564: Property 'referenceNo' has no initializer and is not definitely assigned in the constructor. +src/components/Shared/Stores/Financials/JournalEntry.tsx(8,5): error TS2564: Property 'memo' has no initializer and is not definitely assigned in the constructor. +src/components/Shared/Stores/Financials/JournalEntryLine.tsx(8,17): error TS7006: Parameter 'id' implicitly has an 'any' type. +src/components/Shared/Stores/Financials/JournalEntryLine.tsx(8,21): error TS7006: Parameter 'accountId' implicitly has an 'any' type. +src/components/Shared/Stores/Financials/JournalEntryLine.tsx(8,32): error TS7006: Parameter 'drcr' implicitly has an 'any' type. +src/components/Shared/Stores/Financials/JournalEntryLine.tsx(8,38): error TS7006: Parameter 'amount' implicitly has an 'any' type. +src/components/Shared/Stores/Financials/JournalEntryLine.tsx(8,46): error TS7006: Parameter 'memo' implicitly has an 'any' type. +src/components/Shared/Stores/Financials/JournalEntryStore.tsx(1,39): error TS6133: 'action' is declared but its value is never read. +src/components/Shared/Stores/Financials/JournalEntryStore.tsx(1,47): error TS6133: 'intercept' is declared but its value is never read. +src/components/Shared/Stores/Financials/JournalEntryStore.tsx(1,58): error TS6133: 'reaction' is declared but its value is never read. +src/components/Shared/Stores/Financials/JournalEntryStore.tsx(3,1): error TS6133: 'd3' is declared but its value is never read. +src/components/Shared/Stores/Financials/JournalEntryStore.tsx(3,21): error TS7016: Could not find a declaration file for module 'd3'. 'D:/_playground/react/GoodBooksReact/node_modules/d3/src/index.js' implicitly has an 'any' type. + Try `npm i --save-dev @types/d3` if it exists or add a new declaration (.d.ts) file containing `declare module 'd3';` +src/components/Shared/Stores/Financials/JournalEntryStore.tsx(17,5): error TS7008: Member 'originalJournalEntry' implicitly has an 'any' type. +src/components/Shared/Stores/Financials/JournalEntryStore.tsx(20,17): error TS7008: Member 'validationErrors' implicitly has an 'any' type. +src/components/Shared/Stores/Financials/JournalEntryStore.tsx(45,19): error TS7006: Parameter 'dirty' implicitly has an 'any' type. +src/components/Shared/Stores/Financials/JournalEntryStore.tsx(49,23): error TS7006: Parameter 'initialized' implicitly has an 'any' type. +src/components/Shared/Stores/Financials/JournalEntryStore.tsx(55,29): error TS7006: Parameter 'result' implicitly has an 'any' type. +src/components/Shared/Stores/Financials/JournalEntryStore.tsx(58,21): error TS2683: 'this' implicitly has type 'any' because it does not have a type annotation. +src/components/Shared/Stores/Financials/JournalEntryStore.tsx(60,17): error TS2683: 'this' implicitly has type 'any' because it does not have a type annotation. +src/components/Shared/Stores/Financials/JournalEntryStore.tsx(61,17): error TS2683: 'this' implicitly has type 'any' because it does not have a type annotation. +src/components/Shared/Stores/Financials/JournalEntryStore.tsx(62,17): error TS2683: 'this' implicitly has type 'any' because it does not have a type annotation. +src/components/Shared/Stores/Financials/JournalEntryStore.tsx(63,17): error TS2683: 'this' implicitly has type 'any' because it does not have a type annotation. +src/components/Shared/Stores/Financials/JournalEntryStore.tsx(64,17): error TS2683: 'this' implicitly has type 'any' because it does not have a type annotation. +src/components/Shared/Stores/Financials/JournalEntryStore.tsx(65,17): error TS2683: 'this' implicitly has type 'any' because it does not have a type annotation. +src/components/Shared/Stores/Financials/JournalEntryStore.tsx(66,17): error TS2683: 'this' implicitly has type 'any' because it does not have a type annotation. +src/components/Shared/Stores/Financials/JournalEntryStore.tsx(68,29): error TS2531: Object is possibly 'null'. +src/components/Shared/Stores/Financials/JournalEntryStore.tsx(83,33): error TS6133: 'response' is declared but its value is never read. +src/components/Shared/Stores/Financials/JournalEntryStore.tsx(86,34): error TS7006: Parameter 'error' implicitly has an 'any' type. +src/components/Shared/Stores/Financials/JournalEntryStore.tsx(87,46): error TS7006: Parameter 'err' implicitly has an 'any' type. +src/components/Shared/Stores/Financials/JournalEntryStore.tsx(88,25): error TS2683: 'this' implicitly has type 'any' because it does not have a type annotation. +src/components/Shared/Stores/Financials/JournalEntryStore.tsx(89,28): error TS2683: 'this' implicitly has type 'any' because it does not have a type annotation. +src/components/Shared/Stores/Financials/JournalEntryStore.tsx(130,33): error TS6133: 'response' is declared but its value is never read. +src/components/Shared/Stores/Financials/JournalEntryStore.tsx(133,34): error TS7006: Parameter 'error' implicitly has an 'any' type. +src/components/Shared/Stores/Financials/JournalEntryStore.tsx(134,46): error TS7006: Parameter 'err' implicitly has an 'any' type. +src/components/Shared/Stores/Financials/JournalEntryStore.tsx(135,25): error TS2683: 'this' implicitly has type 'any' because it does not have a type annotation. +src/components/Shared/Stores/Financials/JournalEntryStore.tsx(136,28): error TS2683: 'this' implicitly has type 'any' because it does not have a type annotation. +src/components/Shared/Stores/Financials/JournalEntryStore.tsx(141,17): error TS7006: Parameter 'id' implicitly has an 'any' type. +src/components/Shared/Stores/Financials/JournalEntryStore.tsx(141,21): error TS7006: Parameter 'accountId' implicitly has an 'any' type. +src/components/Shared/Stores/Financials/JournalEntryStore.tsx(141,32): error TS7006: Parameter 'drcr' implicitly has an 'any' type. +src/components/Shared/Stores/Financials/JournalEntryStore.tsx(141,38): error TS7006: Parameter 'amount' implicitly has an 'any' type. +src/components/Shared/Stores/Financials/JournalEntryStore.tsx(141,46): error TS7006: Parameter 'memo' implicitly has an 'any' type. +src/components/Shared/Stores/Financials/JournalEntryStore.tsx(146,20): error TS7006: Parameter 'row' implicitly has an 'any' type. +src/components/Shared/Stores/Financials/JournalEntryStore.tsx(146,25): error TS7006: Parameter 'targetProperty' implicitly has an 'any' type. +src/components/Shared/Stores/Financials/JournalEntryStore.tsx(146,41): error TS7006: Parameter 'value' implicitly has an 'any' type. +src/components/Shared/Stores/Financials/JournalEntryStore.tsx(148,13): error TS7053: Element implicitly has an 'any' type because expression of type 'any' can't be used to index type 'JournalEntryLine'. +src/components/Shared/Stores/Financials/JournalEntryStore.tsx(151,20): error TS7006: Parameter 'row' implicitly has an 'any' type. +src/components/Shared/Stores/Financials/JournalEntryStore.tsx(155,24): error TS7006: Parameter 'date' implicitly has an 'any' type. +src/components/Shared/Stores/Financials/JournalEntryStore.tsx(159,24): error TS7006: Parameter 'refNo' implicitly has an 'any' type. +src/components/Shared/Stores/Financials/JournalEntryStore.tsx(163,17): error TS7006: Parameter 'memo' implicitly has an 'any' type. +src/components/Shared/Stores/Financials/JournalEntryStore.tsx(167,24): error TS7006: Parameter 'type' implicitly has an 'any' type. +src/components/Shared/Stores/Financials/JournalEntryStore.tsx(171,21): error TS7006: Parameter 'editMode' implicitly has an 'any' type. +src/components/Shared/Stores/Financials/JournalEntryUIStore.tsx(1,1): error TS6192: All imports in import declaration are unused. +src/components/Shared/Stores/Financials/JournalEntryUIStore.tsx(2,1): error TS6133: 'mobx' is declared but its value is never read. +src/components/Shared/Stores/Purchasing/PurchaseInvoice.tsx(5,5): error TS2564: Property 'fromPurchaseOrderId' has no initializer and is not definitely assigned in the constructor. +src/components/Shared/Stores/Purchasing/PurchaseInvoice.tsx(6,5): error TS2564: Property 'vendorId' has no initializer and is not definitely assigned in the constructor. +src/components/Shared/Stores/Purchasing/PurchaseInvoice.tsx(7,5): error TS2564: Property 'invoiceDate' has no initializer and is not definitely assigned in the constructor. +src/components/Shared/Stores/Purchasing/PurchaseInvoice.tsx(8,5): error TS2564: Property 'paymentTermId' has no initializer and is not definitely assigned in the constructor. +src/components/Shared/Stores/Purchasing/PurchaseInvoice.tsx(9,5): error TS2564: Property 'referenceNo' has no initializer and is not definitely assigned in the constructor. +src/components/Shared/Stores/Purchasing/PurchaseInvoice.tsx(13,5): error TS2564: Property 'statusId' has no initializer and is not definitely assigned in the constructor. +src/components/Shared/Stores/Purchasing/PurchaseInvoiceLine.tsx(10,17): error TS7006: Parameter 'id' implicitly has an 'any' type. +src/components/Shared/Stores/Purchasing/PurchaseInvoiceLine.tsx(10,21): error TS7006: Parameter 'itemId' implicitly has an 'any' type. +src/components/Shared/Stores/Purchasing/PurchaseInvoiceLine.tsx(10,29): error TS7006: Parameter 'measurementId' implicitly has an 'any' type. +src/components/Shared/Stores/Purchasing/PurchaseInvoiceLine.tsx(10,44): error TS7006: Parameter 'quantity' implicitly has an 'any' type. +src/components/Shared/Stores/Purchasing/PurchaseInvoiceLine.tsx(10,54): error TS7006: Parameter 'amount' implicitly has an 'any' type. +src/components/Shared/Stores/Purchasing/PurchaseInvoiceLine.tsx(10,62): error TS7006: Parameter 'discount' implicitly has an 'any' type. +src/components/Shared/Stores/Purchasing/PurchaseInvoiceStore.tsx(1,39): error TS6133: 'action' is declared but its value is never read. +src/components/Shared/Stores/Purchasing/PurchaseInvoiceStore.tsx(1,56): error TS6133: 'computed' is declared but its value is never read. +src/components/Shared/Stores/Purchasing/PurchaseInvoiceStore.tsx(18,17): error TS7008: Member 'validationErrors' implicitly has an 'any' type. +src/components/Shared/Stores/Purchasing/PurchaseInvoiceStore.tsx(20,17): error TS7008: Member 'purchaseInvoiceStatus' implicitly has an 'any' type. +src/components/Shared/Stores/Purchasing/PurchaseInvoiceStore.tsx(39,33): error TS7006: Parameter 'result' implicitly has an 'any' type. +src/components/Shared/Stores/Purchasing/PurchaseInvoiceStore.tsx(44,25): error TS2683: 'this' implicitly has type 'any' because it does not have a type annotation. +src/components/Shared/Stores/Purchasing/PurchaseInvoiceStore.tsx(55,21): error TS2683: 'this' implicitly has type 'any' because it does not have a type annotation. +src/components/Shared/Stores/Purchasing/PurchaseInvoiceStore.tsx(56,21): error TS2683: 'this' implicitly has type 'any' because it does not have a type annotation. +src/components/Shared/Stores/Purchasing/PurchaseInvoiceStore.tsx(57,21): error TS2683: 'this' implicitly has type 'any' because it does not have a type annotation. +src/components/Shared/Stores/Purchasing/PurchaseInvoiceStore.tsx(58,21): error TS2683: 'this' implicitly has type 'any' because it does not have a type annotation. +src/components/Shared/Stores/Purchasing/PurchaseInvoiceStore.tsx(59,21): error TS2683: 'this' implicitly has type 'any' because it does not have a type annotation. +src/components/Shared/Stores/Purchasing/PurchaseInvoiceStore.tsx(60,21): error TS2683: 'this' implicitly has type 'any' because it does not have a type annotation. +src/components/Shared/Stores/Purchasing/PurchaseInvoiceStore.tsx(61,21): error TS2683: 'this' implicitly has type 'any' because it does not have a type annotation. +src/components/Shared/Stores/Purchasing/PurchaseInvoiceStore.tsx(63,34): error TS6133: 'error' is declared but its value is never read. +src/components/Shared/Stores/Purchasing/PurchaseInvoiceStore.tsx(63,34): error TS7006: Parameter 'error' implicitly has an 'any' type. +src/components/Shared/Stores/Purchasing/PurchaseInvoiceStore.tsx(68,33): error TS7006: Parameter 'result' implicitly has an 'any' type. +src/components/Shared/Stores/Purchasing/PurchaseInvoiceStore.tsx(71,25): error TS2683: 'this' implicitly has type 'any' because it does not have a type annotation. +src/components/Shared/Stores/Purchasing/PurchaseInvoiceStore.tsx(79,25): error TS2683: 'this' implicitly has type 'any' because it does not have a type annotation. +src/components/Shared/Stores/Purchasing/PurchaseInvoiceStore.tsx(79,56): error TS2683: 'this' implicitly has type 'any' because it does not have a type annotation. +src/components/Shared/Stores/Purchasing/PurchaseInvoiceStore.tsx(82,21): error TS2683: 'this' implicitly has type 'any' because it does not have a type annotation. +src/components/Shared/Stores/Purchasing/PurchaseInvoiceStore.tsx(83,21): error TS2683: 'this' implicitly has type 'any' because it does not have a type annotation. +src/components/Shared/Stores/Purchasing/PurchaseInvoiceStore.tsx(84,21): error TS2683: 'this' implicitly has type 'any' because it does not have a type annotation. +src/components/Shared/Stores/Purchasing/PurchaseInvoiceStore.tsx(85,21): error TS2683: 'this' implicitly has type 'any' because it does not have a type annotation. +src/components/Shared/Stores/Purchasing/PurchaseInvoiceStore.tsx(86,21): error TS2683: 'this' implicitly has type 'any' because it does not have a type annotation. +src/components/Shared/Stores/Purchasing/PurchaseInvoiceStore.tsx(87,21): error TS2683: 'this' implicitly has type 'any' because it does not have a type annotation. +src/components/Shared/Stores/Purchasing/PurchaseInvoiceStore.tsx(88,21): error TS2683: 'this' implicitly has type 'any' because it does not have a type annotation. +src/components/Shared/Stores/Purchasing/PurchaseInvoiceStore.tsx(89,21): error TS2683: 'this' implicitly has type 'any' because it does not have a type annotation. +src/components/Shared/Stores/Purchasing/PurchaseInvoiceStore.tsx(92,21): error TS2683: 'this' implicitly has type 'any' because it does not have a type annotation. +src/components/Shared/Stores/Purchasing/PurchaseInvoiceStore.tsx(94,33): error TS2531: Object is possibly 'null'. +src/components/Shared/Stores/Purchasing/PurchaseInvoiceStore.tsx(99,34): error TS6133: 'error' is declared but its value is never read. +src/components/Shared/Stores/Purchasing/PurchaseInvoiceStore.tsx(99,34): error TS7006: Parameter 'error' implicitly has an 'any' type. +src/components/Shared/Stores/Purchasing/PurchaseInvoiceStore.tsx(115,13): error TS6133: 'gtotal' is declared but its value is never read. +src/components/Shared/Stores/Purchasing/PurchaseInvoiceStore.tsx(121,33): error TS7006: Parameter 'result' implicitly has an 'any' type. +src/components/Shared/Stores/Purchasing/PurchaseInvoiceStore.tsx(123,43): error TS2683: 'this' implicitly has type 'any' because it does not have a type annotation. +src/components/Shared/Stores/Purchasing/PurchaseInvoiceStore.tsx(125,21): error TS2683: 'this' implicitly has type 'any' because it does not have a type annotation. +src/components/Shared/Stores/Purchasing/PurchaseInvoiceStore.tsx(126,21): error TS2683: 'this' implicitly has type 'any' because it does not have a type annotation. +src/components/Shared/Stores/Purchasing/PurchaseInvoiceStore.tsx(140,33): error TS6133: 'response' is declared but its value is never read. +src/components/Shared/Stores/Purchasing/PurchaseInvoiceStore.tsx(143,34): error TS7006: Parameter 'error' implicitly has an 'any' type. +src/components/Shared/Stores/Purchasing/PurchaseInvoiceStore.tsx(144,46): error TS7006: Parameter 'err' implicitly has an 'any' type. +src/components/Shared/Stores/Purchasing/PurchaseInvoiceStore.tsx(145,25): error TS2683: 'this' implicitly has type 'any' because it does not have a type annotation. +src/components/Shared/Stores/Purchasing/PurchaseInvoiceStore.tsx(146,28): error TS2683: 'this' implicitly has type 'any' because it does not have a type annotation. +src/components/Shared/Stores/Purchasing/PurchaseInvoiceStore.tsx(159,33): error TS6133: 'response' is declared but its value is never read. +src/components/Shared/Stores/Purchasing/PurchaseInvoiceStore.tsx(162,34): error TS7006: Parameter 'error' implicitly has an 'any' type. +src/components/Shared/Stores/Purchasing/PurchaseInvoiceStore.tsx(163,46): error TS7006: Parameter 'err' implicitly has an 'any' type. +src/components/Shared/Stores/Purchasing/PurchaseInvoiceStore.tsx(164,25): error TS2683: 'this' implicitly has type 'any' because it does not have a type annotation. +src/components/Shared/Stores/Purchasing/PurchaseInvoiceStore.tsx(165,28): error TS2683: 'this' implicitly has type 'any' because it does not have a type annotation. +src/components/Shared/Stores/Purchasing/PurchaseInvoiceStore.tsx(224,58): error TS6133: 'discount' is declared but its value is never read. +src/components/Shared/Stores/Purchasing/PurchaseInvoiceStore.tsx(242,58): error TS6133: 'discount' is declared but its value is never read. +src/components/Shared/Stores/Purchasing/PurchaseInvoiceStore.tsx(264,24): error TS7006: Parameter 'refNo' implicitly has an 'any' type. +src/components/Shared/Stores/Purchasing/PurchaseInvoiceStore.tsx(267,19): error TS7006: Parameter 'vendorId' implicitly has an 'any' type. +src/components/Shared/Stores/Purchasing/PurchaseInvoiceStore.tsx(271,24): error TS7006: Parameter 'paymentTermId' implicitly has an 'any' type. +src/components/Shared/Stores/Purchasing/PurchaseInvoiceStore.tsx(274,24): error TS7006: Parameter 'date' implicitly has an 'any' type. +src/components/Shared/Stores/Purchasing/PurchaseInvoiceStore.tsx(278,17): error TS7006: Parameter 'id' implicitly has an 'any' type. +src/components/Shared/Stores/Purchasing/PurchaseInvoiceStore.tsx(278,21): error TS7006: Parameter 'itemId' implicitly has an 'any' type. +src/components/Shared/Stores/Purchasing/PurchaseInvoiceStore.tsx(278,29): error TS7006: Parameter 'measurementId' implicitly has an 'any' type. +src/components/Shared/Stores/Purchasing/PurchaseInvoiceStore.tsx(278,44): error TS7006: Parameter 'quantity' implicitly has an 'any' type. +src/components/Shared/Stores/Purchasing/PurchaseInvoiceStore.tsx(278,54): error TS7006: Parameter 'amount' implicitly has an 'any' type. +src/components/Shared/Stores/Purchasing/PurchaseInvoiceStore.tsx(278,62): error TS7006: Parameter 'discount' implicitly has an 'any' type. +src/components/Shared/Stores/Purchasing/PurchaseInvoiceStore.tsx(283,20): error TS7006: Parameter 'row' implicitly has an 'any' type. +src/components/Shared/Stores/Purchasing/PurchaseInvoiceStore.tsx(287,20): error TS7006: Parameter 'row' implicitly has an 'any' type. +src/components/Shared/Stores/Purchasing/PurchaseInvoiceStore.tsx(287,25): error TS7006: Parameter 'targetProperty' implicitly has an 'any' type. +src/components/Shared/Stores/Purchasing/PurchaseInvoiceStore.tsx(287,41): error TS7006: Parameter 'value' implicitly has an 'any' type. +src/components/Shared/Stores/Purchasing/PurchaseInvoiceStore.tsx(289,13): error TS7053: Element implicitly has an 'any' type because expression of type 'any' can't be used to index type 'PurchaseInvoiceLine'. +src/components/Shared/Stores/Purchasing/PurchaseInvoiceStore.tsx(294,18): error TS7006: Parameter 'row' implicitly has an 'any' type. +src/components/Shared/Stores/Purchasing/PurchaseInvoiceStore.tsx(301,21): error TS7006: Parameter 'editMode' implicitly has an 'any' type. +src/components/Shared/Stores/Purchasing/PurchaseInvoiceStore.tsx(305,30): error TS7006: Parameter 'statusId' implicitly has an 'any' type. +src/components/Shared/Stores/Purchasing/PurchaseInvoiceStore.tsx(316,20): error TS7006: Parameter 'itemId' implicitly has an 'any' type. +src/components/Shared/Stores/Purchasing/PurchaseInvoiceStore.tsx(319,43): error TS2339: Property 'id' does not exist on type 'never'. +src/components/Shared/Stores/Purchasing/PurchaseInvoiceStore.tsx(320,50): error TS2339: Property 'code' does not exist on type 'never'. +src/components/Shared/Stores/Purchasing/PurchaseOrder.tsx(4,5): error TS2564: Property 'id' has no initializer and is not definitely assigned in the constructor. +src/components/Shared/Stores/Purchasing/PurchaseOrder.tsx(5,5): error TS2564: Property 'vendorId' has no initializer and is not definitely assigned in the constructor. +src/components/Shared/Stores/Purchasing/PurchaseOrder.tsx(6,5): error TS2564: Property 'orderDate' has no initializer and is not definitely assigned in the constructor. +src/components/Shared/Stores/Purchasing/PurchaseOrder.tsx(7,5): error TS2564: Property 'paymentTermId' has no initializer and is not definitely assigned in the constructor. +src/components/Shared/Stores/Purchasing/PurchaseOrder.tsx(8,5): error TS2564: Property 'referenceNo' has no initializer and is not definitely assigned in the constructor. +src/components/Shared/Stores/Purchasing/PurchaseOrder.tsx(9,5): error TS2564: Property 'statusId' has no initializer and is not definitely assigned in the constructor. +src/components/Shared/Stores/Purchasing/PurchaseOrderLine.tsx(10,17): error TS7006: Parameter 'id' implicitly has an 'any' type. +src/components/Shared/Stores/Purchasing/PurchaseOrderLine.tsx(10,21): error TS7006: Parameter 'itemId' implicitly has an 'any' type. +src/components/Shared/Stores/Purchasing/PurchaseOrderLine.tsx(10,29): error TS7006: Parameter 'measurementId' implicitly has an 'any' type. +src/components/Shared/Stores/Purchasing/PurchaseOrderLine.tsx(10,44): error TS7006: Parameter 'quantity' implicitly has an 'any' type. +src/components/Shared/Stores/Purchasing/PurchaseOrderLine.tsx(10,54): error TS7006: Parameter 'amount' implicitly has an 'any' type. +src/components/Shared/Stores/Purchasing/PurchaseOrderLine.tsx(10,62): error TS7006: Parameter 'discount' implicitly has an 'any' type. +src/components/Shared/Stores/Purchasing/PurchaseOrderLine.tsx(10,72): error TS7006: Parameter 'code' implicitly has an 'any' type. +src/components/Shared/Stores/Purchasing/PurchaseOrderStore.tsx(1,39): error TS6133: 'action' is declared but its value is never read. +src/components/Shared/Stores/Purchasing/PurchaseOrderStore.tsx(1,56): error TS6133: 'computed' is declared but its value is never read. +src/components/Shared/Stores/Purchasing/PurchaseOrderStore.tsx(18,17): error TS7008: Member 'validationErrors' implicitly has an 'any' type. +src/components/Shared/Stores/Purchasing/PurchaseOrderStore.tsx(20,17): error TS7008: Member 'purchaseOrderStatus' implicitly has an 'any' type. +src/components/Shared/Stores/Purchasing/PurchaseOrderStore.tsx(38,33): error TS7006: Parameter 'result' implicitly has an 'any' type. +src/components/Shared/Stores/Purchasing/PurchaseOrderStore.tsx(39,21): error TS2683: 'this' implicitly has type 'any' because it does not have a type annotation. +src/components/Shared/Stores/Purchasing/PurchaseOrderStore.tsx(40,21): error TS2683: 'this' implicitly has type 'any' because it does not have a type annotation. +src/components/Shared/Stores/Purchasing/PurchaseOrderStore.tsx(41,21): error TS2683: 'this' implicitly has type 'any' because it does not have a type annotation. +src/components/Shared/Stores/Purchasing/PurchaseOrderStore.tsx(42,21): error TS2683: 'this' implicitly has type 'any' because it does not have a type annotation. +src/components/Shared/Stores/Purchasing/PurchaseOrderStore.tsx(43,21): error TS2683: 'this' implicitly has type 'any' because it does not have a type annotation. +src/components/Shared/Stores/Purchasing/PurchaseOrderStore.tsx(44,21): error TS2683: 'this' implicitly has type 'any' because it does not have a type annotation. +src/components/Shared/Stores/Purchasing/PurchaseOrderStore.tsx(45,21): error TS2683: 'this' implicitly has type 'any' because it does not have a type annotation. +src/components/Shared/Stores/Purchasing/PurchaseOrderStore.tsx(47,25): error TS2683: 'this' implicitly has type 'any' because it does not have a type annotation. +src/components/Shared/Stores/Purchasing/PurchaseOrderStore.tsx(55,25): error TS2683: 'this' implicitly has type 'any' because it does not have a type annotation. +src/components/Shared/Stores/Purchasing/PurchaseOrderStore.tsx(55,56): error TS2683: 'this' implicitly has type 'any' because it does not have a type annotation. +src/components/Shared/Stores/Purchasing/PurchaseOrderStore.tsx(57,21): error TS2683: 'this' implicitly has type 'any' because it does not have a type annotation. +src/components/Shared/Stores/Purchasing/PurchaseOrderStore.tsx(58,33): error TS2531: Object is possibly 'null'. +src/components/Shared/Stores/Purchasing/PurchaseOrderStore.tsx(63,34): error TS6133: 'error' is declared but its value is never read. +src/components/Shared/Stores/Purchasing/PurchaseOrderStore.tsx(63,34): error TS7006: Parameter 'error' implicitly has an 'any' type. +src/components/Shared/Stores/Purchasing/PurchaseOrderStore.tsx(78,13): error TS6133: 'gtotal' is declared but its value is never read. +src/components/Shared/Stores/Purchasing/PurchaseOrderStore.tsx(84,33): error TS7006: Parameter 'result' implicitly has an 'any' type. +src/components/Shared/Stores/Purchasing/PurchaseOrderStore.tsx(86,43): error TS2683: 'this' implicitly has type 'any' because it does not have a type annotation. +src/components/Shared/Stores/Purchasing/PurchaseOrderStore.tsx(88,21): error TS2683: 'this' implicitly has type 'any' because it does not have a type annotation. +src/components/Shared/Stores/Purchasing/PurchaseOrderStore.tsx(89,21): error TS2683: 'this' implicitly has type 'any' because it does not have a type annotation. +src/components/Shared/Stores/Purchasing/PurchaseOrderStore.tsx(98,13): error TS2322: Type 'string' is not assignable to type 'Date'. +src/components/Shared/Stores/Purchasing/PurchaseOrderStore.tsx(107,33): error TS6133: 'response' is declared but its value is never read. +src/components/Shared/Stores/Purchasing/PurchaseOrderStore.tsx(110,34): error TS7006: Parameter 'error' implicitly has an 'any' type. +src/components/Shared/Stores/Purchasing/PurchaseOrderStore.tsx(111,46): error TS7006: Parameter 'err' implicitly has an 'any' type. +src/components/Shared/Stores/Purchasing/PurchaseOrderStore.tsx(112,25): error TS2683: 'this' implicitly has type 'any' because it does not have a type annotation. +src/components/Shared/Stores/Purchasing/PurchaseOrderStore.tsx(113,28): error TS2683: 'this' implicitly has type 'any' because it does not have a type annotation. +src/components/Shared/Stores/Purchasing/PurchaseOrderStore.tsx(120,58): error TS2367: This comparison appears to be unintentional because the types 'number' and 'string' have no overlap. +src/components/Shared/Stores/Purchasing/PurchaseOrderStore.tsx(122,63): error TS2367: This comparison appears to be unintentional because the types 'number' and 'string' have no overlap. +src/components/Shared/Stores/Purchasing/PurchaseOrderStore.tsx(124,59): error TS2367: This comparison appears to be unintentional because the types 'Date' and 'string' have no overlap. +src/components/Shared/Stores/Purchasing/PurchaseOrderStore.tsx(172,58): error TS6133: 'discount' is declared but its value is never read. +src/components/Shared/Stores/Purchasing/PurchaseOrderStore.tsx(190,58): error TS6133: 'discount' is declared but its value is never read. +src/components/Shared/Stores/Purchasing/PurchaseOrderStore.tsx(211,24): error TS7006: Parameter 'refNo' implicitly has an 'any' type. +src/components/Shared/Stores/Purchasing/PurchaseOrderStore.tsx(214,19): error TS7006: Parameter 'vendorId' implicitly has an 'any' type. +src/components/Shared/Stores/Purchasing/PurchaseOrderStore.tsx(218,24): error TS7006: Parameter 'paymentTermId' implicitly has an 'any' type. +src/components/Shared/Stores/Purchasing/PurchaseOrderStore.tsx(222,22): error TS7006: Parameter 'date' implicitly has an 'any' type. +src/components/Shared/Stores/Purchasing/PurchaseOrderStore.tsx(226,17): error TS7006: Parameter 'id' implicitly has an 'any' type. +src/components/Shared/Stores/Purchasing/PurchaseOrderStore.tsx(226,21): error TS7006: Parameter 'itemId' implicitly has an 'any' type. +src/components/Shared/Stores/Purchasing/PurchaseOrderStore.tsx(226,29): error TS7006: Parameter 'measurementId' implicitly has an 'any' type. +src/components/Shared/Stores/Purchasing/PurchaseOrderStore.tsx(226,44): error TS7006: Parameter 'quantity' implicitly has an 'any' type. +src/components/Shared/Stores/Purchasing/PurchaseOrderStore.tsx(226,54): error TS7006: Parameter 'amount' implicitly has an 'any' type. +src/components/Shared/Stores/Purchasing/PurchaseOrderStore.tsx(226,62): error TS7006: Parameter 'discount' implicitly has an 'any' type. +src/components/Shared/Stores/Purchasing/PurchaseOrderStore.tsx(226,72): error TS7006: Parameter 'code' implicitly has an 'any' type. +src/components/Shared/Stores/Purchasing/PurchaseOrderStore.tsx(231,20): error TS7006: Parameter 'row' implicitly has an 'any' type. +src/components/Shared/Stores/Purchasing/PurchaseOrderStore.tsx(235,20): error TS7006: Parameter 'row' implicitly has an 'any' type. +src/components/Shared/Stores/Purchasing/PurchaseOrderStore.tsx(235,25): error TS7006: Parameter 'targetProperty' implicitly has an 'any' type. +src/components/Shared/Stores/Purchasing/PurchaseOrderStore.tsx(235,41): error TS7006: Parameter 'value' implicitly has an 'any' type. +src/components/Shared/Stores/Purchasing/PurchaseOrderStore.tsx(237,13): error TS7053: Element implicitly has an 'any' type because expression of type 'any' can't be used to index type 'PurchaseOrderLine'. +src/components/Shared/Stores/Purchasing/PurchaseOrderStore.tsx(242,18): error TS7006: Parameter 'row' implicitly has an 'any' type. +src/components/Shared/Stores/Purchasing/PurchaseOrderStore.tsx(249,21): error TS7006: Parameter 'editMode' implicitly has an 'any' type. +src/components/Shared/Stores/Purchasing/PurchaseOrderStore.tsx(253,28): error TS7006: Parameter 'statusId' implicitly has an 'any' type. +src/components/Shared/Stores/Purchasing/PurchaseOrderStore.tsx(270,20): error TS7006: Parameter 'itemId' implicitly has an 'any' type. +src/components/Shared/Stores/Purchasing/PurchaseOrderStore.tsx(273,43): error TS2339: Property 'id' does not exist on type 'never'. +src/components/Shared/Stores/Purchasing/PurchaseOrderStore.tsx(274,50): error TS2339: Property 'code' does not exist on type 'never'. +src/components/Shared/Stores/Quotations/SalesQuotation.tsx(4,5): error TS2564: Property 'id' has no initializer and is not definitely assigned in the constructor. +src/components/Shared/Stores/Quotations/SalesQuotation.tsx(5,5): error TS2564: Property 'customerId' has no initializer and is not definitely assigned in the constructor. +src/components/Shared/Stores/Quotations/SalesQuotation.tsx(6,5): error TS2564: Property 'quotationDate' has no initializer and is not definitely assigned in the constructor. +src/components/Shared/Stores/Quotations/SalesQuotation.tsx(7,5): error TS2564: Property 'paymentTermId' has no initializer and is not definitely assigned in the constructor. +src/components/Shared/Stores/Quotations/SalesQuotation.tsx(8,5): error TS2564: Property 'referenceNo' has no initializer and is not definitely assigned in the constructor. +src/components/Shared/Stores/Quotations/SalesQuotation.tsx(9,5): error TS2564: Property 'statusId' has no initializer and is not definitely assigned in the constructor. +src/components/Shared/Stores/Quotations/SalesQuotationLine.tsx(10,17): error TS7006: Parameter 'id' implicitly has an 'any' type. +src/components/Shared/Stores/Quotations/SalesQuotationLine.tsx(10,21): error TS7006: Parameter 'itemId' implicitly has an 'any' type. +src/components/Shared/Stores/Quotations/SalesQuotationLine.tsx(10,29): error TS7006: Parameter 'measurementId' implicitly has an 'any' type. +src/components/Shared/Stores/Quotations/SalesQuotationLine.tsx(10,44): error TS7006: Parameter 'quantity' implicitly has an 'any' type. +src/components/Shared/Stores/Quotations/SalesQuotationLine.tsx(10,54): error TS7006: Parameter 'amount' implicitly has an 'any' type. +src/components/Shared/Stores/Quotations/SalesQuotationLine.tsx(10,62): error TS7006: Parameter 'discount' implicitly has an 'any' type. +src/components/Shared/Stores/Quotations/SalesQuotationLine.tsx(10,72): error TS7006: Parameter 'code' implicitly has an 'any' type. +src/components/Shared/Stores/Quotations/SalesQuotationStore.tsx(1,39): error TS6133: 'action' is declared but its value is never read. +src/components/Shared/Stores/Quotations/SalesQuotationStore.tsx(1,56): error TS6133: 'computed' is declared but its value is never read. +src/components/Shared/Stores/Quotations/SalesQuotationStore.tsx(3,1): error TS6133: 'd3' is declared but its value is never read. +src/components/Shared/Stores/Quotations/SalesQuotationStore.tsx(3,21): error TS7016: Could not find a declaration file for module 'd3'. 'D:/_playground/react/GoodBooksReact/node_modules/d3/src/index.js' implicitly has an 'any' type. + Try `npm i --save-dev @types/d3` if it exists or add a new declaration (.d.ts) file containing `declare module 'd3';` +src/components/Shared/Stores/Quotations/SalesQuotationStore.tsx(19,17): error TS7008: Member 'salesQuotationStatus' implicitly has an 'any' type. +src/components/Shared/Stores/Quotations/SalesQuotationStore.tsx(20,17): error TS7008: Member 'validationErrors' implicitly has an 'any' type. +src/components/Shared/Stores/Quotations/SalesQuotationStore.tsx(23,17): error TS7006: Parameter 'quotationId' implicitly has an 'any' type. +src/components/Shared/Stores/Quotations/SalesQuotationStore.tsx(41,35): error TS7006: Parameter 'result' implicitly has an 'any' type. +src/components/Shared/Stores/Quotations/SalesQuotationStore.tsx(42,17): error TS2683: 'this' implicitly has type 'any' because it does not have a type annotation. +src/components/Shared/Stores/Quotations/SalesQuotationStore.tsx(43,17): error TS2683: 'this' implicitly has type 'any' because it does not have a type annotation. +src/components/Shared/Stores/Quotations/SalesQuotationStore.tsx(44,17): error TS2683: 'this' implicitly has type 'any' because it does not have a type annotation. +src/components/Shared/Stores/Quotations/SalesQuotationStore.tsx(45,17): error TS2683: 'this' implicitly has type 'any' because it does not have a type annotation. +src/components/Shared/Stores/Quotations/SalesQuotationStore.tsx(46,17): error TS2683: 'this' implicitly has type 'any' because it does not have a type annotation. +src/components/Shared/Stores/Quotations/SalesQuotationStore.tsx(47,17): error TS2683: 'this' implicitly has type 'any' because it does not have a type annotation. +src/components/Shared/Stores/Quotations/SalesQuotationStore.tsx(48,17): error TS2683: 'this' implicitly has type 'any' because it does not have a type annotation. +src/components/Shared/Stores/Quotations/SalesQuotationStore.tsx(50,21): error TS2683: 'this' implicitly has type 'any' because it does not have a type annotation. +src/components/Shared/Stores/Quotations/SalesQuotationStore.tsx(59,21): error TS2683: 'this' implicitly has type 'any' because it does not have a type annotation. +src/components/Shared/Stores/Quotations/SalesQuotationStore.tsx(59,52): error TS2683: 'this' implicitly has type 'any' because it does not have a type annotation. +src/components/Shared/Stores/Quotations/SalesQuotationStore.tsx(61,17): error TS2683: 'this' implicitly has type 'any' because it does not have a type annotation. +src/components/Shared/Stores/Quotations/SalesQuotationStore.tsx(62,29): error TS2531: Object is possibly 'null'. +src/components/Shared/Stores/Quotations/SalesQuotationStore.tsx(84,13): error TS6133: 'gtotal' is declared but its value is never read. +src/components/Shared/Stores/Quotations/SalesQuotationStore.tsx(90,33): error TS7006: Parameter 'result' implicitly has an 'any' type. +src/components/Shared/Stores/Quotations/SalesQuotationStore.tsx(92,43): error TS2683: 'this' implicitly has type 'any' because it does not have a type annotation. +src/components/Shared/Stores/Quotations/SalesQuotationStore.tsx(94,21): error TS2683: 'this' implicitly has type 'any' because it does not have a type annotation. +src/components/Shared/Stores/Quotations/SalesQuotationStore.tsx(95,21): error TS2683: 'this' implicitly has type 'any' because it does not have a type annotation. +src/components/Shared/Stores/Quotations/SalesQuotationStore.tsx(104,13): error TS2322: Type 'string' is not assignable to type 'Date'. +src/components/Shared/Stores/Quotations/SalesQuotationStore.tsx(116,37): error TS6133: 'response' is declared but its value is never read. +src/components/Shared/Stores/Quotations/SalesQuotationStore.tsx(119,38): error TS7006: Parameter 'error' implicitly has an 'any' type. +src/components/Shared/Stores/Quotations/SalesQuotationStore.tsx(120,50): error TS7006: Parameter 'err' implicitly has an 'any' type. +src/components/Shared/Stores/Quotations/SalesQuotationStore.tsx(121,29): error TS2683: 'this' implicitly has type 'any' because it does not have a type annotation. +src/components/Shared/Stores/Quotations/SalesQuotationStore.tsx(122,32): error TS2683: 'this' implicitly has type 'any' because it does not have a type annotation. +src/components/Shared/Stores/Quotations/SalesQuotationStore.tsx(132,82): error TS2345: Argument of type 'number' is not assignable to parameter of type 'string'. +src/components/Shared/Stores/Quotations/SalesQuotationStore.tsx(140,37): error TS6133: 'response' is declared but its value is never read. +src/components/Shared/Stores/Quotations/SalesQuotationStore.tsx(143,38): error TS7006: Parameter 'error' implicitly has an 'any' type. +src/components/Shared/Stores/Quotations/SalesQuotationStore.tsx(144,50): error TS7006: Parameter 'err' implicitly has an 'any' type. +src/components/Shared/Stores/Quotations/SalesQuotationStore.tsx(145,29): error TS2683: 'this' implicitly has type 'any' because it does not have a type annotation. +src/components/Shared/Stores/Quotations/SalesQuotationStore.tsx(146,32): error TS2683: 'this' implicitly has type 'any' because it does not have a type annotation. +src/components/Shared/Stores/Quotations/SalesQuotationStore.tsx(164,88): error TS2367: This comparison appears to be unintentional because the types 'number' and 'string' have no overlap. +src/components/Shared/Stores/Quotations/SalesQuotationStore.tsx(166,95): error TS2367: This comparison appears to be unintentional because the types 'number' and 'string' have no overlap. +src/components/Shared/Stores/Quotations/SalesQuotationStore.tsx(168,90): error TS2367: This comparison appears to be unintentional because the types 'number' and 'string' have no overlap. +src/components/Shared/Stores/Quotations/SalesQuotationStore.tsx(170,88): error TS2367: This comparison appears to be unintentional because the types 'number' and 'string' have no overlap. +src/components/Shared/Stores/Quotations/SalesQuotationStore.tsx(182,24): error TS7006: Parameter 'statusId' implicitly has an 'any' type. +src/components/Shared/Stores/Quotations/SalesQuotationStore.tsx(221,58): error TS6133: 'discount' is declared but its value is never read. +src/components/Shared/Stores/Quotations/SalesQuotationStore.tsx(239,58): error TS6133: 'discount' is declared but its value is never read. +src/components/Shared/Stores/Quotations/SalesQuotationStore.tsx(260,21): error TS7006: Parameter 'custId' implicitly has an 'any' type. +src/components/Shared/Stores/Quotations/SalesQuotationStore.tsx(264,24): error TS7006: Parameter 'termId' implicitly has an 'any' type. +src/components/Shared/Stores/Quotations/SalesQuotationStore.tsx(268,26): error TS7006: Parameter 'date' implicitly has an 'any' type. +src/components/Shared/Stores/Quotations/SalesQuotationStore.tsx(271,24): error TS7006: Parameter 'refNo' implicitly has an 'any' type. +src/components/Shared/Stores/Quotations/SalesQuotationStore.tsx(275,24): error TS7006: Parameter 'statusId' implicitly has an 'any' type. +src/components/Shared/Stores/Quotations/SalesQuotationStore.tsx(279,20): error TS7006: Parameter 'itemId' implicitly has an 'any' type. +src/components/Shared/Stores/Quotations/SalesQuotationStore.tsx(282,43): error TS2339: Property 'id' does not exist on type 'never'. +src/components/Shared/Stores/Quotations/SalesQuotationStore.tsx(283,50): error TS2339: Property 'code' does not exist on type 'never'. +src/components/Shared/Stores/Quotations/SalesQuotationStore.tsx(288,17): error TS7006: Parameter 'id' implicitly has an 'any' type. +src/components/Shared/Stores/Quotations/SalesQuotationStore.tsx(288,21): error TS7006: Parameter 'itemId' implicitly has an 'any' type. +src/components/Shared/Stores/Quotations/SalesQuotationStore.tsx(288,29): error TS7006: Parameter 'measurementId' implicitly has an 'any' type. +src/components/Shared/Stores/Quotations/SalesQuotationStore.tsx(288,44): error TS7006: Parameter 'quantity' implicitly has an 'any' type. +src/components/Shared/Stores/Quotations/SalesQuotationStore.tsx(288,54): error TS7006: Parameter 'amount' implicitly has an 'any' type. +src/components/Shared/Stores/Quotations/SalesQuotationStore.tsx(288,62): error TS7006: Parameter 'discount' implicitly has an 'any' type. +src/components/Shared/Stores/Quotations/SalesQuotationStore.tsx(288,72): error TS7006: Parameter 'code' implicitly has an 'any' type. +src/components/Shared/Stores/Quotations/SalesQuotationStore.tsx(293,20): error TS7006: Parameter 'row' implicitly has an 'any' type. +src/components/Shared/Stores/Quotations/SalesQuotationStore.tsx(297,20): error TS7006: Parameter 'row' implicitly has an 'any' type. +src/components/Shared/Stores/Quotations/SalesQuotationStore.tsx(297,25): error TS7006: Parameter 'targetProperty' implicitly has an 'any' type. +src/components/Shared/Stores/Quotations/SalesQuotationStore.tsx(297,41): error TS7006: Parameter 'value' implicitly has an 'any' type. +src/components/Shared/Stores/Quotations/SalesQuotationStore.tsx(299,9): error TS7053: Element implicitly has an 'any' type because expression of type 'any' can't be used to index type 'SalesQuotationLine'. +src/components/Shared/Stores/Quotations/SalesQuotationStore.tsx(303,18): error TS7006: Parameter 'row' implicitly has an 'any' type. +src/components/Shared/Stores/Quotations/SalesQuotationStore.tsx(310,21): error TS7006: Parameter 'editMode' implicitly has an 'any' type. +src/components/Shared/Stores/Sales/SalesInvoice.tsx(6,5): error TS2564: Property 'customerId' has no initializer and is not definitely assigned in the constructor. +src/components/Shared/Stores/Sales/SalesInvoice.tsx(7,5): error TS2564: Property 'invoiceDate' has no initializer and is not definitely assigned in the constructor. +src/components/Shared/Stores/Sales/SalesInvoice.tsx(8,5): error TS2564: Property 'paymentTermId' has no initializer and is not definitely assigned in the constructor. +src/components/Shared/Stores/Sales/SalesInvoice.tsx(9,5): error TS2564: Property 'referenceNo' has no initializer and is not definitely assigned in the constructor. +src/components/Shared/Stores/Sales/SalesInvoiceLine.tsx(11,17): error TS7006: Parameter 'id' implicitly has an 'any' type. +src/components/Shared/Stores/Sales/SalesInvoiceLine.tsx(11,21): error TS7006: Parameter 'itemId' implicitly has an 'any' type. +src/components/Shared/Stores/Sales/SalesInvoiceLine.tsx(11,29): error TS7006: Parameter 'measurementId' implicitly has an 'any' type. +src/components/Shared/Stores/Sales/SalesInvoiceLine.tsx(11,44): error TS7006: Parameter 'quantity' implicitly has an 'any' type. +src/components/Shared/Stores/Sales/SalesInvoiceLine.tsx(11,54): error TS7006: Parameter 'amount' implicitly has an 'any' type. +src/components/Shared/Stores/Sales/SalesInvoiceLine.tsx(11,62): error TS7006: Parameter 'discount' implicitly has an 'any' type. +src/components/Shared/Stores/Sales/SalesInvoiceLine.tsx(11,72): error TS7006: Parameter 'code' implicitly has an 'any' type. +src/components/Shared/Stores/Sales/SalesInvoiceStore.tsx(1,39): error TS6133: 'action' is declared but its value is never read. +src/components/Shared/Stores/Sales/SalesInvoiceStore.tsx(1,56): error TS6133: 'computed' is declared but its value is never read. +src/components/Shared/Stores/Sales/SalesInvoiceStore.tsx(18,17): error TS7008: Member 'validationErrors' implicitly has an 'any' type. +src/components/Shared/Stores/Sales/SalesInvoiceStore.tsx(22,17): error TS7006: Parameter 'orderId' implicitly has an 'any' type. +src/components/Shared/Stores/Sales/SalesInvoiceStore.tsx(22,26): error TS7006: Parameter 'invoiceId' implicitly has an 'any' type. +src/components/Shared/Stores/Sales/SalesInvoiceStore.tsx(40,34): error TS7006: Parameter 'result' implicitly has an 'any' type. +src/components/Shared/Stores/Sales/SalesInvoiceStore.tsx(45,21): error TS2683: 'this' implicitly has type 'any' because it does not have a type annotation. +src/components/Shared/Stores/Sales/SalesInvoiceStore.tsx(54,17): error TS2683: 'this' implicitly has type 'any' because it does not have a type annotation. +src/components/Shared/Stores/Sales/SalesInvoiceStore.tsx(55,17): error TS2683: 'this' implicitly has type 'any' because it does not have a type annotation. +src/components/Shared/Stores/Sales/SalesInvoiceStore.tsx(56,17): error TS2683: 'this' implicitly has type 'any' because it does not have a type annotation. +src/components/Shared/Stores/Sales/SalesInvoiceStore.tsx(57,17): error TS2683: 'this' implicitly has type 'any' because it does not have a type annotation. +src/components/Shared/Stores/Sales/SalesInvoiceStore.tsx(58,17): error TS2683: 'this' implicitly has type 'any' because it does not have a type annotation. +src/components/Shared/Stores/Sales/SalesInvoiceStore.tsx(59,17): error TS2683: 'this' implicitly has type 'any' because it does not have a type annotation. +src/components/Shared/Stores/Sales/SalesInvoiceStore.tsx(60,17): error TS2683: 'this' implicitly has type 'any' because it does not have a type annotation. +src/components/Shared/Stores/Sales/SalesInvoiceStore.tsx(65,34): error TS7006: Parameter 'result' implicitly has an 'any' type. +src/components/Shared/Stores/Sales/SalesInvoiceStore.tsx(67,21): error TS2683: 'this' implicitly has type 'any' because it does not have a type annotation. +src/components/Shared/Stores/Sales/SalesInvoiceStore.tsx(75,21): error TS2683: 'this' implicitly has type 'any' because it does not have a type annotation. +src/components/Shared/Stores/Sales/SalesInvoiceStore.tsx(75,52): error TS2683: 'this' implicitly has type 'any' because it does not have a type annotation. +src/components/Shared/Stores/Sales/SalesInvoiceStore.tsx(78,17): error TS2683: 'this' implicitly has type 'any' because it does not have a type annotation. +src/components/Shared/Stores/Sales/SalesInvoiceStore.tsx(79,17): error TS2683: 'this' implicitly has type 'any' because it does not have a type annotation. +src/components/Shared/Stores/Sales/SalesInvoiceStore.tsx(80,17): error TS2683: 'this' implicitly has type 'any' because it does not have a type annotation. +src/components/Shared/Stores/Sales/SalesInvoiceStore.tsx(81,17): error TS2683: 'this' implicitly has type 'any' because it does not have a type annotation. +src/components/Shared/Stores/Sales/SalesInvoiceStore.tsx(82,17): error TS2683: 'this' implicitly has type 'any' because it does not have a type annotation. +src/components/Shared/Stores/Sales/SalesInvoiceStore.tsx(83,17): error TS2683: 'this' implicitly has type 'any' because it does not have a type annotation. +src/components/Shared/Stores/Sales/SalesInvoiceStore.tsx(84,17): error TS2683: 'this' implicitly has type 'any' because it does not have a type annotation. +src/components/Shared/Stores/Sales/SalesInvoiceStore.tsx(85,17): error TS2683: 'this' implicitly has type 'any' because it does not have a type annotation. +src/components/Shared/Stores/Sales/SalesInvoiceStore.tsx(87,29): error TS2531: Object is possibly 'null'. +src/components/Shared/Stores/Sales/SalesInvoiceStore.tsx(105,13): error TS6133: 'gtotal' is declared but its value is never read. +src/components/Shared/Stores/Sales/SalesInvoiceStore.tsx(111,33): error TS7006: Parameter 'result' implicitly has an 'any' type. +src/components/Shared/Stores/Sales/SalesInvoiceStore.tsx(113,43): error TS2683: 'this' implicitly has type 'any' because it does not have a type annotation. +src/components/Shared/Stores/Sales/SalesInvoiceStore.tsx(115,21): error TS2683: 'this' implicitly has type 'any' because it does not have a type annotation. +src/components/Shared/Stores/Sales/SalesInvoiceStore.tsx(116,21): error TS2683: 'this' implicitly has type 'any' because it does not have a type annotation. +src/components/Shared/Stores/Sales/SalesInvoiceStore.tsx(125,13): error TS2322: Type 'string' is not assignable to type 'Date'. +src/components/Shared/Stores/Sales/SalesInvoiceStore.tsx(134,33): error TS6133: 'response' is declared but its value is never read. +src/components/Shared/Stores/Sales/SalesInvoiceStore.tsx(137,34): error TS7006: Parameter 'error' implicitly has an 'any' type. +src/components/Shared/Stores/Sales/SalesInvoiceStore.tsx(138,46): error TS7006: Parameter 'err' implicitly has an 'any' type. +src/components/Shared/Stores/Sales/SalesInvoiceStore.tsx(139,25): error TS2683: 'this' implicitly has type 'any' because it does not have a type annotation. +src/components/Shared/Stores/Sales/SalesInvoiceStore.tsx(140,28): error TS2683: 'this' implicitly has type 'any' because it does not have a type annotation. +src/components/Shared/Stores/Sales/SalesInvoiceStore.tsx(164,33): error TS6133: 'response' is declared but its value is never read. +src/components/Shared/Stores/Sales/SalesInvoiceStore.tsx(167,34): error TS7006: Parameter 'error' implicitly has an 'any' type. +src/components/Shared/Stores/Sales/SalesInvoiceStore.tsx(168,46): error TS7006: Parameter 'err' implicitly has an 'any' type. +src/components/Shared/Stores/Sales/SalesInvoiceStore.tsx(169,25): error TS2683: 'this' implicitly has type 'any' because it does not have a type annotation. +src/components/Shared/Stores/Sales/SalesInvoiceStore.tsx(170,28): error TS2683: 'this' implicitly has type 'any' because it does not have a type annotation. +src/components/Shared/Stores/Sales/SalesInvoiceStore.tsx(225,58): error TS6133: 'discount' is declared but its value is never read. +src/components/Shared/Stores/Sales/SalesInvoiceStore.tsx(243,58): error TS6133: 'discount' is declared but its value is never read. +src/components/Shared/Stores/Sales/SalesInvoiceStore.tsx(265,24): error TS7006: Parameter 'refNo' implicitly has an 'any' type. +src/components/Shared/Stores/Sales/SalesInvoiceStore.tsx(268,21): error TS7006: Parameter 'custId' implicitly has an 'any' type. +src/components/Shared/Stores/Sales/SalesInvoiceStore.tsx(272,24): error TS7006: Parameter 'date' implicitly has an 'any' type. +src/components/Shared/Stores/Sales/SalesInvoiceStore.tsx(276,24): error TS7006: Parameter 'termId' implicitly has an 'any' type. +src/components/Shared/Stores/Sales/SalesInvoiceStore.tsx(280,17): error TS7006: Parameter 'id' implicitly has an 'any' type. +src/components/Shared/Stores/Sales/SalesInvoiceStore.tsx(280,21): error TS7006: Parameter 'itemId' implicitly has an 'any' type. +src/components/Shared/Stores/Sales/SalesInvoiceStore.tsx(280,29): error TS7006: Parameter 'measurementId' implicitly has an 'any' type. +src/components/Shared/Stores/Sales/SalesInvoiceStore.tsx(280,44): error TS7006: Parameter 'quantity' implicitly has an 'any' type. +src/components/Shared/Stores/Sales/SalesInvoiceStore.tsx(280,54): error TS7006: Parameter 'amount' implicitly has an 'any' type. +src/components/Shared/Stores/Sales/SalesInvoiceStore.tsx(280,62): error TS7006: Parameter 'discount' implicitly has an 'any' type. +src/components/Shared/Stores/Sales/SalesInvoiceStore.tsx(280,72): error TS7006: Parameter 'code' implicitly has an 'any' type. +src/components/Shared/Stores/Sales/SalesInvoiceStore.tsx(285,20): error TS7006: Parameter 'row' implicitly has an 'any' type. +src/components/Shared/Stores/Sales/SalesInvoiceStore.tsx(289,20): error TS7006: Parameter 'row' implicitly has an 'any' type. +src/components/Shared/Stores/Sales/SalesInvoiceStore.tsx(289,25): error TS7006: Parameter 'targetProperty' implicitly has an 'any' type. +src/components/Shared/Stores/Sales/SalesInvoiceStore.tsx(289,41): error TS7006: Parameter 'value' implicitly has an 'any' type. +src/components/Shared/Stores/Sales/SalesInvoiceStore.tsx(291,13): error TS7053: Element implicitly has an 'any' type because expression of type 'any' can't be used to index type 'SalesInvoiceLine'. +src/components/Shared/Stores/Sales/SalesInvoiceStore.tsx(296,18): error TS7006: Parameter 'row' implicitly has an 'any' type. +src/components/Shared/Stores/Sales/SalesInvoiceStore.tsx(303,21): error TS7006: Parameter 'editMode' implicitly has an 'any' type. +src/components/Shared/Stores/Sales/SalesInvoiceStore.tsx(307,20): error TS7006: Parameter 'itemId' implicitly has an 'any' type. +src/components/Shared/Stores/Sales/SalesInvoiceStore.tsx(310,43): error TS2339: Property 'id' does not exist on type 'never'. +src/components/Shared/Stores/Sales/SalesInvoiceStore.tsx(311,50): error TS2339: Property 'code' does not exist on type 'never'. +src/components/Shared/Stores/Sales/SalesOrder.tsx(13,5): error TS2564: Property 'id' has no initializer and is not definitely assigned in the constructor. +src/components/Shared/Stores/Sales/SalesOrder.tsx(14,5): error TS2564: Property 'customerId' has no initializer and is not definitely assigned in the constructor. +src/components/Shared/Stores/Sales/SalesOrder.tsx(15,5): error TS2564: Property 'orderDate' has no initializer and is not definitely assigned in the constructor. +src/components/Shared/Stores/Sales/SalesOrder.tsx(16,5): error TS2564: Property 'paymentTermId' has no initializer and is not definitely assigned in the constructor. +src/components/Shared/Stores/Sales/SalesOrder.tsx(17,5): error TS2564: Property 'referenceNo' has no initializer and is not definitely assigned in the constructor. +src/components/Shared/Stores/Sales/SalesOrder.tsx(18,5): error TS2564: Property 'statusId' has no initializer and is not definitely assigned in the constructor. +src/components/Shared/Stores/Sales/SalesOrder.tsx(19,5): error TS2564: Property 'quotationId' has no initializer and is not definitely assigned in the constructor. +src/components/Shared/Stores/Sales/SalesOrderLine.tsx(9,17): error TS7006: Parameter 'id' implicitly has an 'any' type. +src/components/Shared/Stores/Sales/SalesOrderLine.tsx(9,21): error TS7006: Parameter 'itemId' implicitly has an 'any' type. +src/components/Shared/Stores/Sales/SalesOrderLine.tsx(9,29): error TS7006: Parameter 'measurementId' implicitly has an 'any' type. +src/components/Shared/Stores/Sales/SalesOrderLine.tsx(9,44): error TS7006: Parameter 'quantity' implicitly has an 'any' type. +src/components/Shared/Stores/Sales/SalesOrderLine.tsx(9,54): error TS7006: Parameter 'amount' implicitly has an 'any' type. +src/components/Shared/Stores/Sales/SalesOrderLine.tsx(9,62): error TS7006: Parameter 'discount' implicitly has an 'any' type. +src/components/Shared/Stores/Sales/SalesOrderLine.tsx(9,72): error TS7006: Parameter 'code' implicitly has an 'any' type. +src/components/Shared/Stores/Sales/SalesOrderStore.tsx(1,39): error TS6133: 'action' is declared but its value is never read. +src/components/Shared/Stores/Sales/SalesOrderStore.tsx(1,56): error TS6133: 'computed' is declared but its value is never read. +src/components/Shared/Stores/Sales/SalesOrderStore.tsx(18,17): error TS7008: Member 'validationErrors' implicitly has an 'any' type. +src/components/Shared/Stores/Sales/SalesOrderStore.tsx(19,17): error TS7008: Member 'salesOrderStatus' implicitly has an 'any' type. +src/components/Shared/Stores/Sales/SalesOrderStore.tsx(20,17): error TS7008: Member 'salesQuotationStatus' implicitly has an 'any' type. +src/components/Shared/Stores/Sales/SalesOrderStore.tsx(23,17): error TS7006: Parameter 'quotationId' implicitly has an 'any' type. +src/components/Shared/Stores/Sales/SalesOrderStore.tsx(23,30): error TS7006: Parameter 'orderId' implicitly has an 'any' type. +src/components/Shared/Stores/Sales/SalesOrderStore.tsx(39,35): error TS7006: Parameter 'result' implicitly has an 'any' type. +src/components/Shared/Stores/Sales/SalesOrderStore.tsx(40,17): error TS2683: 'this' implicitly has type 'any' because it does not have a type annotation. +src/components/Shared/Stores/Sales/SalesOrderStore.tsx(41,17): error TS2683: 'this' implicitly has type 'any' because it does not have a type annotation. +src/components/Shared/Stores/Sales/SalesOrderStore.tsx(42,17): error TS2683: 'this' implicitly has type 'any' because it does not have a type annotation. +src/components/Shared/Stores/Sales/SalesOrderStore.tsx(43,17): error TS2683: 'this' implicitly has type 'any' because it does not have a type annotation. +src/components/Shared/Stores/Sales/SalesOrderStore.tsx(44,17): error TS2683: 'this' implicitly has type 'any' because it does not have a type annotation. +src/components/Shared/Stores/Sales/SalesOrderStore.tsx(45,17): error TS2683: 'this' implicitly has type 'any' because it does not have a type annotation. +src/components/Shared/Stores/Sales/SalesOrderStore.tsx(47,17): error TS2683: 'this' implicitly has type 'any' because it does not have a type annotation. +src/components/Shared/Stores/Sales/SalesOrderStore.tsx(49,21): error TS2683: 'this' implicitly has type 'any' because it does not have a type annotation. +src/components/Shared/Stores/Sales/SalesOrderStore.tsx(58,17): error TS2683: 'this' implicitly has type 'any' because it does not have a type annotation. +src/components/Shared/Stores/Sales/SalesOrderStore.tsx(59,29): error TS2531: Object is possibly 'null'. +src/components/Shared/Stores/Sales/SalesOrderStore.tsx(67,35): error TS7006: Parameter 'result' implicitly has an 'any' type. +src/components/Shared/Stores/Sales/SalesOrderStore.tsx(68,17): error TS2683: 'this' implicitly has type 'any' because it does not have a type annotation. +src/components/Shared/Stores/Sales/SalesOrderStore.tsx(69,17): error TS2683: 'this' implicitly has type 'any' because it does not have a type annotation. +src/components/Shared/Stores/Sales/SalesOrderStore.tsx(70,17): error TS2683: 'this' implicitly has type 'any' because it does not have a type annotation. +src/components/Shared/Stores/Sales/SalesOrderStore.tsx(71,17): error TS2683: 'this' implicitly has type 'any' because it does not have a type annotation. +src/components/Shared/Stores/Sales/SalesOrderStore.tsx(72,17): error TS2683: 'this' implicitly has type 'any' because it does not have a type annotation. +src/components/Shared/Stores/Sales/SalesOrderStore.tsx(73,17): error TS2683: 'this' implicitly has type 'any' because it does not have a type annotation. +src/components/Shared/Stores/Sales/SalesOrderStore.tsx(75,21): error TS2683: 'this' implicitly has type 'any' because it does not have a type annotation. +src/components/Shared/Stores/Sales/SalesOrderStore.tsx(83,21): error TS2683: 'this' implicitly has type 'any' because it does not have a type annotation. +src/components/Shared/Stores/Sales/SalesOrderStore.tsx(83,52): error TS2683: 'this' implicitly has type 'any' because it does not have a type annotation. +src/components/Shared/Stores/Sales/SalesOrderStore.tsx(85,17): error TS2683: 'this' implicitly has type 'any' because it does not have a type annotation. +src/components/Shared/Stores/Sales/SalesOrderStore.tsx(86,29): error TS2531: Object is possibly 'null'. +src/components/Shared/Stores/Sales/SalesOrderStore.tsx(105,13): error TS6133: 'gtotal' is declared but its value is never read. +src/components/Shared/Stores/Sales/SalesOrderStore.tsx(111,33): error TS7006: Parameter 'result' implicitly has an 'any' type. +src/components/Shared/Stores/Sales/SalesOrderStore.tsx(113,43): error TS2683: 'this' implicitly has type 'any' because it does not have a type annotation. +src/components/Shared/Stores/Sales/SalesOrderStore.tsx(115,21): error TS2683: 'this' implicitly has type 'any' because it does not have a type annotation. +src/components/Shared/Stores/Sales/SalesOrderStore.tsx(116,21): error TS2683: 'this' implicitly has type 'any' because it does not have a type annotation. +src/components/Shared/Stores/Sales/SalesOrderStore.tsx(126,13): error TS2322: Type 'string' is not assignable to type 'Date'. +src/components/Shared/Stores/Sales/SalesOrderStore.tsx(136,33): error TS6133: 'response' is declared but its value is never read. +src/components/Shared/Stores/Sales/SalesOrderStore.tsx(139,34): error TS7006: Parameter 'error' implicitly has an 'any' type. +src/components/Shared/Stores/Sales/SalesOrderStore.tsx(140,46): error TS7006: Parameter 'err' implicitly has an 'any' type. +src/components/Shared/Stores/Sales/SalesOrderStore.tsx(141,25): error TS2683: 'this' implicitly has type 'any' because it does not have a type annotation. +src/components/Shared/Stores/Sales/SalesOrderStore.tsx(142,28): error TS2683: 'this' implicitly has type 'any' because it does not have a type annotation. +src/components/Shared/Stores/Sales/SalesOrderStore.tsx(197,58): error TS6133: 'discount' is declared but its value is never read. +src/components/Shared/Stores/Sales/SalesOrderStore.tsx(215,58): error TS6133: 'discount' is declared but its value is never read. +src/components/Shared/Stores/Sales/SalesOrderStore.tsx(236,24): error TS7006: Parameter 'statusId' implicitly has an 'any' type. +src/components/Shared/Stores/Sales/SalesOrderStore.tsx(252,20): error TS7006: Parameter 'statusId' implicitly has an 'any' type. +src/components/Shared/Stores/Sales/SalesOrderStore.tsx(271,20): error TS7006: Parameter 'itemId' implicitly has an 'any' type. +src/components/Shared/Stores/Sales/SalesOrderStore.tsx(274,43): error TS2339: Property 'id' does not exist on type 'never'. +src/components/Shared/Stores/Sales/SalesOrderStore.tsx(275,50): error TS2339: Property 'code' does not exist on type 'never'. +src/components/Shared/Stores/Sales/SalesOrderStore.tsx(280,24): error TS7006: Parameter 'refNo' implicitly has an 'any' type. +src/components/Shared/Stores/Sales/SalesOrderStore.tsx(283,21): error TS7006: Parameter 'custId' implicitly has an 'any' type. +src/components/Shared/Stores/Sales/SalesOrderStore.tsx(287,22): error TS7006: Parameter 'date' implicitly has an 'any' type. +src/components/Shared/Stores/Sales/SalesOrderStore.tsx(291,24): error TS7006: Parameter 'termId' implicitly has an 'any' type. +src/components/Shared/Stores/Sales/SalesOrderStore.tsx(295,17): error TS7006: Parameter 'id' implicitly has an 'any' type. +src/components/Shared/Stores/Sales/SalesOrderStore.tsx(295,21): error TS7006: Parameter 'itemId' implicitly has an 'any' type. +src/components/Shared/Stores/Sales/SalesOrderStore.tsx(295,29): error TS7006: Parameter 'measurementId' implicitly has an 'any' type. +src/components/Shared/Stores/Sales/SalesOrderStore.tsx(295,44): error TS7006: Parameter 'quantity' implicitly has an 'any' type. +src/components/Shared/Stores/Sales/SalesOrderStore.tsx(295,54): error TS7006: Parameter 'amount' implicitly has an 'any' type. +src/components/Shared/Stores/Sales/SalesOrderStore.tsx(295,62): error TS7006: Parameter 'discount' implicitly has an 'any' type. +src/components/Shared/Stores/Sales/SalesOrderStore.tsx(295,72): error TS7006: Parameter 'code' implicitly has an 'any' type. +src/components/Shared/Stores/Sales/SalesOrderStore.tsx(300,20): error TS7006: Parameter 'row' implicitly has an 'any' type. +src/components/Shared/Stores/Sales/SalesOrderStore.tsx(304,20): error TS7006: Parameter 'row' implicitly has an 'any' type. +src/components/Shared/Stores/Sales/SalesOrderStore.tsx(304,25): error TS7006: Parameter 'targetProperty' implicitly has an 'any' type. +src/components/Shared/Stores/Sales/SalesOrderStore.tsx(304,41): error TS7006: Parameter 'value' implicitly has an 'any' type. +src/components/Shared/Stores/Sales/SalesOrderStore.tsx(306,13): error TS7053: Element implicitly has an 'any' type because expression of type 'any' can't be used to index type 'SalesOrderLine'. +src/components/Shared/Stores/Sales/SalesOrderStore.tsx(311,18): error TS7006: Parameter 'row' implicitly has an 'any' type. +src/components/Shared/Stores/Sales/SalesOrderStore.tsx(318,21): error TS7006: Parameter 'editMode' implicitly has an 'any' type. +src/components/Shared/Stores/TaxSystem/Tax.tsx(2,5): error TS2564: Property 'id' has no initializer and is not definitely assigned in the constructor. +src/components/Shared/Stores/TaxSystem/Tax.tsx(3,5): error TS2564: Property 'code' has no initializer and is not definitely assigned in the constructor. +src/components/Shared/Stores/TaxSystem/Tax.tsx(4,5): error TS2564: Property 'name' has no initializer and is not definitely assigned in the constructor. +src/components/Shared/Stores/TaxSystem/Tax.tsx(5,5): error TS2564: Property 'rate' has no initializer and is not definitely assigned in the constructor. diff --git a/src/GoodBooksReact/index.html b/src/GoodBooksReact/index.html new file mode 100644 index 000000000..048d1d378 --- /dev/null +++ b/src/GoodBooksReact/index.html @@ -0,0 +1,13 @@ + + + + + + + Vite + React + TS + + +
    + + + diff --git a/src/GoodBooksReact/package-lock.json b/src/GoodBooksReact/package-lock.json new file mode 100644 index 000000000..1dcc65fba --- /dev/null +++ b/src/GoodBooksReact/package-lock.json @@ -0,0 +1,8113 @@ +{ + "name": "goodbooksreact", + "version": "0.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "goodbooksreact", + "version": "0.0.0", + "dependencies": { + "accounting": "^0.4.1", + "ag-grid": "^18.1.2", + "d3": "^7.8.5", + "font-awesome": "^4.7.0", + "jspdf": "^2.5.1", + "pace-progress": "^1.0.2", + "react": "^18.2.0", + "react-dom": "^18.2.0", + "react-dom-factories": "^1.0.2", + "react-router": "^6.21.1", + "react-router-dom": "^6.21.1" + }, + "devDependencies": { + "@babel/core": "^7.23.6", + "@babel/plugin-proposal-decorators": "^7.23.6", + "@babel/plugin-syntax-decorators": "^7.23.3", + "@babel/preset-env": "^7.23.6", + "@coreui/coreui": "^4.3.2", + "@coreui/icons": "^3.0.1", + "@types/accounting": "^0.4.5", + "@types/react": "^18.2.43", + "@types/react-dom": "^18.2.17", + "@types/react-router": "^5.1.20", + "@types/react-router-dom": "^5.3.3", + "@typescript-eslint/eslint-plugin": "^6.14.0", + "@typescript-eslint/parser": "^6.14.0", + "@vitejs/plugin-react": "^4.2.1", + "ag-grid-community": "^31.0.1", + "ajv": "^8.12.0", + "axios": "^1.6.2", + "bootstrap": "^5.3.2", + "eslint": "^8.55.0", + "eslint-plugin-react-hooks": "^4.6.0", + "eslint-plugin-react-refresh": "^0.4.5", + "html2canvas": "^1.4.1", + "ionicons": "^7.2.2", + "jquery": "^3.7.1", + "mobx": "^6.12.0", + "mobx-react": "^9.1.0", + "perfect-scrollbar": "^1.5.5", + "popper.js": "^1.16.1", + "saas": "^1.0.0", + "simple-line-icons": "^2.5.5", + "ts-loader": "^9.5.1", + "tsd": "^0.30.0", + "typescript": "^5.2.2", + "vite": "^5.0.8", + "webpack": "^5.89.0", + "webpack-cli": "^5.1.4" + } + }, + "node_modules/@aashutoshrathi/word-wrap": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz", + "integrity": "sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@ampproject/remapping": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.1.tgz", + "integrity": "sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg==", + "dev": true, + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.0", + "@jridgewell/trace-mapping": "^0.3.9" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.23.5", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.23.5.tgz", + "integrity": "sha512-CgH3s1a96LipHCmSUmYFPwY7MNx8C3avkq7i4Wl3cfa662ldtUe4VM1TPXX70pfmrlWTb6jLqTYrZyT2ZTJBgA==", + "dev": true, + "dependencies": { + "@babel/highlight": "^7.23.4", + "chalk": "^2.4.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.23.5", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.23.5.tgz", + "integrity": "sha512-uU27kfDRlhfKl+w1U6vp16IuvSLtjAxdArVXPa9BvLkrr7CYIsxH5adpHObeAGY/41+syctUWOZ140a2Rvkgjw==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.23.6", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.23.6.tgz", + "integrity": "sha512-FxpRyGjrMJXh7X3wGLGhNDCRiwpWEF74sKjTLDJSG5Kyvow3QZaG0Adbqzi9ZrVjTWpsX+2cxWXD71NMg93kdw==", + "dev": true, + "dependencies": { + "@ampproject/remapping": "^2.2.0", + "@babel/code-frame": "^7.23.5", + "@babel/generator": "^7.23.6", + "@babel/helper-compilation-targets": "^7.23.6", + "@babel/helper-module-transforms": "^7.23.3", + "@babel/helpers": "^7.23.6", + "@babel/parser": "^7.23.6", + "@babel/template": "^7.22.15", + "@babel/traverse": "^7.23.6", + "@babel/types": "^7.23.6", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/core/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/generator": { + "version": "7.23.6", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.6.tgz", + "integrity": "sha512-qrSfCYxYQB5owCmGLbl8XRpX1ytXlpueOb0N0UmQwA073KZxejgQTzAmJezxvpwQD9uGtK2shHdi55QT+MbjIw==", + "dev": true, + "dependencies": { + "@babel/types": "^7.23.6", + "@jridgewell/gen-mapping": "^0.3.2", + "@jridgewell/trace-mapping": "^0.3.17", + "jsesc": "^2.5.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-annotate-as-pure": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.22.5.tgz", + "integrity": "sha512-LvBTxu8bQSQkcyKOU+a1btnNFQ1dMAd0R6PyW3arXes06F6QLWLIrd681bxRPIXlrMGR3XYnW9JyML7dP3qgxg==", + "dev": true, + "dependencies": { + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-builder-binary-assignment-operator-visitor": { + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.22.15.tgz", + "integrity": "sha512-QkBXwGgaoC2GtGZRoma6kv7Szfv06khvhFav67ZExau2RaXzy8MpHSMO2PNoP2XtmQphJQRHFfg77Bq731Yizw==", + "dev": true, + "dependencies": { + "@babel/types": "^7.22.15" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.23.6", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.23.6.tgz", + "integrity": "sha512-9JB548GZoQVmzrFgp8o7KxdgkTGm6xs9DW0o/Pim72UDjzr5ObUQ6ZzYPqA+g9OTS2bBQoctLJrky0RDCAWRgQ==", + "dev": true, + "dependencies": { + "@babel/compat-data": "^7.23.5", + "@babel/helper-validator-option": "^7.23.5", + "browserslist": "^4.22.2", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-create-class-features-plugin": { + "version": "7.23.6", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.23.6.tgz", + "integrity": "sha512-cBXU1vZni/CpGF29iTu4YRbOZt3Wat6zCoMDxRF1MayiEc4URxOj31tT65HUM0CRpMowA3HCJaAOVOUnMf96cw==", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.22.5", + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-function-name": "^7.23.0", + "@babel/helper-member-expression-to-functions": "^7.23.0", + "@babel/helper-optimise-call-expression": "^7.22.5", + "@babel/helper-replace-supers": "^7.22.20", + "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.6", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-create-class-features-plugin/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-create-regexp-features-plugin": { + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.22.15.tgz", + "integrity": "sha512-29FkPLFjn4TPEa3RE7GpW+qbE8tlsu3jntNYNfcGsc49LphF1PQIiD+vMZ1z1xVOKt+93khA9tc2JBs3kBjA7w==", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.22.5", + "regexpu-core": "^5.3.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-create-regexp-features-plugin/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-define-polyfill-provider": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.4.4.tgz", + "integrity": "sha512-QcJMILQCu2jm5TFPGA3lCpJJTeEP+mqeXooG/NZbg/h5FTFi6V0+99ahlRsW8/kRLyb24LZVCCiclDedhLKcBA==", + "dev": true, + "dependencies": { + "@babel/helper-compilation-targets": "^7.22.6", + "@babel/helper-plugin-utils": "^7.22.5", + "debug": "^4.1.1", + "lodash.debounce": "^4.0.8", + "resolve": "^1.14.2" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/@babel/helper-environment-visitor": { + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz", + "integrity": "sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-function-name": { + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz", + "integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==", + "dev": true, + "dependencies": { + "@babel/template": "^7.22.15", + "@babel/types": "^7.23.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-hoist-variables": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz", + "integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==", + "dev": true, + "dependencies": { + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-member-expression-to-functions": { + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.23.0.tgz", + "integrity": "sha512-6gfrPwh7OuT6gZyJZvd6WbTfrqAo7vm4xCzAXOusKqq/vWdKXphTpj5klHKNmRUU6/QRGlBsyU9mAIPaWHlqJA==", + "dev": true, + "dependencies": { + "@babel/types": "^7.23.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.22.15.tgz", + "integrity": "sha512-0pYVBnDKZO2fnSPCrgM/6WMc7eS20Fbok+0r88fp+YtWVLZrp4CkafFGIp+W0VKw4a22sgebPT99y+FDNMdP4w==", + "dev": true, + "dependencies": { + "@babel/types": "^7.22.15" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.23.3.tgz", + "integrity": "sha512-7bBs4ED9OmswdfDzpz4MpWgSrV7FXlc3zIagvLFjS5H+Mk7Snr21vQ6QwrsoCGMfNC4e4LQPdoULEt4ykz0SRQ==", + "dev": true, + "dependencies": { + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-module-imports": "^7.22.15", + "@babel/helper-simple-access": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.6", + "@babel/helper-validator-identifier": "^7.22.20" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-optimise-call-expression": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.22.5.tgz", + "integrity": "sha512-HBwaojN0xFRx4yIvpwGqxiV2tUfl7401jlok564NgB9EHS1y6QT17FmKWm4ztqjeVdXLuC4fSvHc5ePpQjoTbw==", + "dev": true, + "dependencies": { + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.22.5.tgz", + "integrity": "sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-remap-async-to-generator": { + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.22.20.tgz", + "integrity": "sha512-pBGyV4uBqOns+0UvhsTO8qgl8hO89PmiDYv+/COyp1aeMcmfrfruz+/nCMFiYyFF/Knn0yfrC85ZzNFjembFTw==", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.22.5", + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-wrap-function": "^7.22.20" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-replace-supers": { + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.22.20.tgz", + "integrity": "sha512-qsW0In3dbwQUbK8kejJ4R7IHVGwHJlV6lpG6UA7a9hSa2YEiAib+N1T2kr6PEeUT+Fl7najmSOS6SmAwCHK6Tw==", + "dev": true, + "dependencies": { + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-member-expression-to-functions": "^7.22.15", + "@babel/helper-optimise-call-expression": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-simple-access": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.22.5.tgz", + "integrity": "sha512-n0H99E/K+Bika3++WNL17POvo4rKWZ7lZEp1Q+fStVbUi8nxPQEBOlTmCOxW/0JsS56SKKQ+ojAe2pHKJHN35w==", + "dev": true, + "dependencies": { + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-skip-transparent-expression-wrappers": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.22.5.tgz", + "integrity": "sha512-tK14r66JZKiC43p8Ki33yLBVJKlQDFoA8GYN67lWCDCqoL6EMMSuM9b+Iff2jHaM/RRFYl7K+iiru7hbRqNx8Q==", + "dev": true, + "dependencies": { + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-split-export-declaration": { + "version": "7.22.6", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz", + "integrity": "sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==", + "dev": true, + "dependencies": { + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.23.4.tgz", + "integrity": "sha512-803gmbQdqwdf4olxrX4AJyFBV/RTr3rSmOj0rKwesmzlfhYNDEs+/iOcznzpNWlJlIlTJC2QfPFcHB6DlzdVLQ==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz", + "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.23.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.23.5.tgz", + "integrity": "sha512-85ttAOMLsr53VgXkTbkx8oA6YTfT4q7/HzXSLEYmjcSTJPMPQtvq1BD79Byep5xMUYbGRzEpDsjUf3dyp54IKw==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-wrap-function": { + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.22.20.tgz", + "integrity": "sha512-pms/UwkOpnQe/PDAEdV/d7dVCoBbB+R4FvYoHGZz+4VPcg7RtYy2KP7S2lbuWM6FCSgob5wshfGESbC/hzNXZw==", + "dev": true, + "dependencies": { + "@babel/helper-function-name": "^7.22.5", + "@babel/template": "^7.22.15", + "@babel/types": "^7.22.19" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.23.6", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.23.6.tgz", + "integrity": "sha512-wCfsbN4nBidDRhpDhvcKlzHWCTlgJYUUdSJfzXb2NuBssDSIjc3xcb+znA7l+zYsFljAcGM0aFkN40cR3lXiGA==", + "dev": true, + "dependencies": { + "@babel/template": "^7.22.15", + "@babel/traverse": "^7.23.6", + "@babel/types": "^7.23.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight": { + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.23.4.tgz", + "integrity": "sha512-acGdbYSfp2WheJoJm/EBBBLh/ID8KDc64ISZ9DYtBmC8/Q204PZJLHyzeB5qMzJ5trcOkybd78M4x2KWsUq++A==", + "dev": true, + "dependencies": { + "@babel/helper-validator-identifier": "^7.22.20", + "chalk": "^2.4.2", + "js-tokens": "^4.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.23.6", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.6.tgz", + "integrity": "sha512-Z2uID7YJ7oNvAI20O9X0bblw7Qqs8Q2hFy0R9tAfnfLkp5MW0UH9eUvnDSnFwKZ0AvgS1ucqR4KzvVHgnke1VQ==", + "dev": true, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.23.3.tgz", + "integrity": "sha512-iRkKcCqb7iGnq9+3G6rZ+Ciz5VywC4XNRHe57lKM+jOeYAoR0lVqdeeDRfh0tQcTfw/+vBhHn926FmQhLtlFLQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.23.3.tgz", + "integrity": "sha512-WwlxbfMNdVEpQjZmK5mhm7oSwD3dS6eU+Iwsi4Knl9wAletWem7kaRsGOG+8UEbRyqxY4SS5zvtfXwX+jMxUwQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5", + "@babel/plugin-transform-optional-chaining": "^7.23.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.13.0" + } + }, + "node_modules/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.23.3.tgz", + "integrity": "sha512-XaJak1qcityzrX0/IU5nKHb34VaibwP3saKqG6a/tppelgllOH13LUann4ZCIBcVOeE6H18K4Vx9QKkVww3z/w==", + "dev": true, + "dependencies": { + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-proposal-decorators": { + "version": "7.23.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-decorators/-/plugin-proposal-decorators-7.23.6.tgz", + "integrity": "sha512-D7Ccq9LfkBFnow3azZGJvZYgcfeqAw3I1e5LoTpj6UKIFQilh8yqXsIGcRIqbBdsPWIz+Ze7ZZfggSj62Qp+Fg==", + "dev": true, + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.23.6", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-replace-supers": "^7.22.20", + "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.6", + "@babel/plugin-syntax-decorators": "^7.23.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-private-property-in-object": { + "version": "7.21.0-placeholder-for-preset-env.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz", + "integrity": "sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==", + "dev": true, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-async-generators": { + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", + "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-class-properties": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", + "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.12.13" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-class-static-block": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz", + "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-decorators": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-decorators/-/plugin-syntax-decorators-7.23.3.tgz", + "integrity": "sha512-cf7Niq4/+/juY67E0PbgH0TDhLQ5J7zS8C/Q5FFx+DWyrRa9sUQdTXkjqKu8zGvuqr7vw1muKiukseihU+PJDA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-dynamic-import": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz", + "integrity": "sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-export-namespace-from": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-export-namespace-from/-/plugin-syntax-export-namespace-from-7.8.3.tgz", + "integrity": "sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.3" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-assertions": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.23.3.tgz", + "integrity": "sha512-lPgDSU+SJLK3xmFDTV2ZRQAiM7UuUjGidwBywFavObCiZc1BeAAcMtHJKUya92hPHO+at63JJPLygilZard8jw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-attributes": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.23.3.tgz", + "integrity": "sha512-pawnE0P9g10xgoP7yKr6CK63K2FMsTE+FZidZO/1PwRdzmAPVs+HS1mAURUsgaoxammTJvULUdIkEK0gOcU2tA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-meta": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", + "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-json-strings": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", + "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-logical-assignment-operators": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", + "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", + "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-numeric-separator": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", + "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-object-rest-spread": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", + "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-catch-binding": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", + "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-chaining": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", + "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-private-property-in-object": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz", + "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-top-level-await": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", + "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-unicode-sets-regex": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-unicode-sets-regex/-/plugin-syntax-unicode-sets-regex-7.18.6.tgz", + "integrity": "sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg==", + "dev": true, + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-transform-arrow-functions": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.23.3.tgz", + "integrity": "sha512-NzQcQrzaQPkaEwoTm4Mhyl8jI1huEL/WWIEvudjTCMJ9aBZNpsJbMASx7EQECtQQPS/DcnFpo0FIh3LvEO9cxQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-async-generator-functions": { + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.23.4.tgz", + "integrity": "sha512-efdkfPhHYTtn0G6n2ddrESE91fgXxjlqLsnUtPWnJs4a4mZIbUaK7ffqKIIUKXSHwcDvaCVX6GXkaJJFqtX7jw==", + "dev": true, + "dependencies": { + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-remap-async-to-generator": "^7.22.20", + "@babel/plugin-syntax-async-generators": "^7.8.4" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-async-to-generator": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.23.3.tgz", + "integrity": "sha512-A7LFsKi4U4fomjqXJlZg/u0ft/n8/7n7lpffUP/ZULx/DtV9SGlNKZolHH6PE8Xl1ngCc0M11OaeZptXVkfKSw==", + "dev": true, + "dependencies": { + "@babel/helper-module-imports": "^7.22.15", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-remap-async-to-generator": "^7.22.20" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-block-scoped-functions": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.23.3.tgz", + "integrity": "sha512-vI+0sIaPIO6CNuM9Kk5VmXcMVRiOpDh7w2zZt9GXzmE/9KD70CUEVhvPR/etAeNK/FAEkhxQtXOzVF3EuRL41A==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-block-scoping": { + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.23.4.tgz", + "integrity": "sha512-0QqbP6B6HOh7/8iNR4CQU2Th/bbRtBp4KS9vcaZd1fZ0wSh5Fyssg0UCIHwxh+ka+pNDREbVLQnHCMHKZfPwfw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-class-properties": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.23.3.tgz", + "integrity": "sha512-uM+AN8yCIjDPccsKGlw271xjJtGii+xQIF/uMPS8H15L12jZTsLfF4o5vNO7d/oUguOyfdikHGc/yi9ge4SGIg==", + "dev": true, + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.22.15", + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-class-static-block": { + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.23.4.tgz", + "integrity": "sha512-nsWu/1M+ggti1SOALj3hfx5FXzAY06fwPJsUZD4/A5e1bWi46VUIWtD+kOX6/IdhXGsXBWllLFDSnqSCdUNydQ==", + "dev": true, + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.22.15", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-class-static-block": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.12.0" + } + }, + "node_modules/@babel/plugin-transform-classes": { + "version": "7.23.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.23.5.tgz", + "integrity": "sha512-jvOTR4nicqYC9yzOHIhXG5emiFEOpappSJAl73SDSEDcybD+Puuze8Tnpb9p9qEyYup24tq891gkaygIFvWDqg==", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.22.5", + "@babel/helper-compilation-targets": "^7.22.15", + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-function-name": "^7.23.0", + "@babel/helper-optimise-call-expression": "^7.22.5", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-replace-supers": "^7.22.20", + "@babel/helper-split-export-declaration": "^7.22.6", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-computed-properties": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.23.3.tgz", + "integrity": "sha512-dTj83UVTLw/+nbiHqQSFdwO9CbTtwq1DsDqm3CUEtDrZNET5rT5E6bIdTlOftDTDLMYxvxHNEYO4B9SLl8SLZw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/template": "^7.22.15" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-destructuring": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.23.3.tgz", + "integrity": "sha512-n225npDqjDIr967cMScVKHXJs7rout1q+tt50inyBCPkyZ8KxeI6d+GIbSBTT/w/9WdlWDOej3V9HE5Lgk57gw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-dotall-regex": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.23.3.tgz", + "integrity": "sha512-vgnFYDHAKzFaTVp+mneDsIEbnJ2Np/9ng9iviHw3P/KVcgONxpNULEW/51Z/BaFojG2GI2GwwXck5uV1+1NOYQ==", + "dev": true, + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.22.15", + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-duplicate-keys": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.23.3.tgz", + "integrity": "sha512-RrqQ+BQmU3Oyav3J+7/myfvRCq7Tbz+kKLLshUmMwNlDHExbGL7ARhajvoBJEvc+fCguPPu887N+3RRXBVKZUA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-dynamic-import": { + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.23.4.tgz", + "integrity": "sha512-V6jIbLhdJK86MaLh4Jpghi8ho5fGzt3imHOBu/x0jlBaPYqDoWz4RDXjmMOfnh+JWNaQleEAByZLV0QzBT4YQQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-dynamic-import": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-exponentiation-operator": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.23.3.tgz", + "integrity": "sha512-5fhCsl1odX96u7ILKHBj4/Y8vipoqwsJMh4csSA8qFfxrZDEA4Ssku2DyNvMJSmZNOEBT750LfFPbtrnTP90BQ==", + "dev": true, + "dependencies": { + "@babel/helper-builder-binary-assignment-operator-visitor": "^7.22.15", + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-export-namespace-from": { + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.23.4.tgz", + "integrity": "sha512-GzuSBcKkx62dGzZI1WVgTWvkkz84FZO5TC5T8dl/Tht/rAla6Dg/Mz9Yhypg+ezVACf/rgDuQt3kbWEv7LdUDQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-export-namespace-from": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-for-of": { + "version": "7.23.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.23.6.tgz", + "integrity": "sha512-aYH4ytZ0qSuBbpfhuofbg/e96oQ7U2w1Aw/UQmKT+1l39uEhUPoFS3fHevDc1G0OvewyDudfMKY1OulczHzWIw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-function-name": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.23.3.tgz", + "integrity": "sha512-I1QXp1LxIvt8yLaib49dRW5Okt7Q4oaxao6tFVKS/anCdEOMtYwWVKoiOA1p34GOWIZjUK0E+zCp7+l1pfQyiw==", + "dev": true, + "dependencies": { + "@babel/helper-compilation-targets": "^7.22.15", + "@babel/helper-function-name": "^7.23.0", + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-json-strings": { + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.23.4.tgz", + "integrity": "sha512-81nTOqM1dMwZ/aRXQ59zVubN9wHGqk6UtqRK+/q+ciXmRy8fSolhGVvG09HHRGo4l6fr/c4ZhXUQH0uFW7PZbg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-json-strings": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-literals": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.23.3.tgz", + "integrity": "sha512-wZ0PIXRxnwZvl9AYpqNUxpZ5BiTGrYt7kueGQ+N5FiQ7RCOD4cm8iShd6S6ggfVIWaJf2EMk8eRzAh52RfP4rQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-logical-assignment-operators": { + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.23.4.tgz", + "integrity": "sha512-Mc/ALf1rmZTP4JKKEhUwiORU+vcfarFVLfcFiolKUo6sewoxSEgl36ak5t+4WamRsNr6nzjZXQjM35WsU+9vbg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-member-expression-literals": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.23.3.tgz", + "integrity": "sha512-sC3LdDBDi5x96LA+Ytekz2ZPk8i/Ck+DEuDbRAll5rknJ5XRTSaPKEYwomLcs1AA8wg9b3KjIQRsnApj+q51Ag==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-amd": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.23.3.tgz", + "integrity": "sha512-vJYQGxeKM4t8hYCKVBlZX/gtIY2I7mRGFNcm85sgXGMTBcoV3QdVtdpbcWEbzbfUIUZKwvgFT82mRvaQIebZzw==", + "dev": true, + "dependencies": { + "@babel/helper-module-transforms": "^7.23.3", + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-commonjs": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.23.3.tgz", + "integrity": "sha512-aVS0F65LKsdNOtcz6FRCpE4OgsP2OFnW46qNxNIX9h3wuzaNcSQsJysuMwqSibC98HPrf2vCgtxKNwS0DAlgcA==", + "dev": true, + "dependencies": { + "@babel/helper-module-transforms": "^7.23.3", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-simple-access": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-systemjs": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.23.3.tgz", + "integrity": "sha512-ZxyKGTkF9xT9YJuKQRo19ewf3pXpopuYQd8cDXqNzc3mUNbOME0RKMoZxviQk74hwzfQsEe66dE92MaZbdHKNQ==", + "dev": true, + "dependencies": { + "@babel/helper-hoist-variables": "^7.22.5", + "@babel/helper-module-transforms": "^7.23.3", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-validator-identifier": "^7.22.20" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-umd": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.23.3.tgz", + "integrity": "sha512-zHsy9iXX2nIsCBFPud3jKn1IRPWg3Ing1qOZgeKV39m1ZgIdpJqvlWVeiHBZC6ITRG0MfskhYe9cLgntfSFPIg==", + "dev": true, + "dependencies": { + "@babel/helper-module-transforms": "^7.23.3", + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-named-capturing-groups-regex": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.22.5.tgz", + "integrity": "sha512-YgLLKmS3aUBhHaxp5hi1WJTgOUb/NCuDHzGT9z9WTt3YG+CPRhJs6nprbStx6DnWM4dh6gt7SU3sZodbZ08adQ==", + "dev": true, + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.22.5", + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-transform-new-target": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.23.3.tgz", + "integrity": "sha512-YJ3xKqtJMAT5/TIZnpAR3I+K+WaDowYbN3xyxI8zxx/Gsypwf9B9h0VB+1Nh6ACAAPRS5NSRje0uVv5i79HYGQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-nullish-coalescing-operator": { + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.23.4.tgz", + "integrity": "sha512-jHE9EVVqHKAQx+VePv5LLGHjmHSJR76vawFPTdlxR/LVJPfOEGxREQwQfjuZEOPTwG92X3LINSh3M40Rv4zpVA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-numeric-separator": { + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.23.4.tgz", + "integrity": "sha512-mps6auzgwjRrwKEZA05cOwuDc9FAzoyFS4ZsG/8F43bTLf/TgkJg7QXOrPO1JO599iA3qgK9MXdMGOEC8O1h6Q==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-numeric-separator": "^7.10.4" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-object-rest-spread": { + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.23.4.tgz", + "integrity": "sha512-9x9K1YyeQVw0iOXJlIzwm8ltobIIv7j2iLyP2jIhEbqPRQ7ScNgwQufU2I0Gq11VjyG4gI4yMXt2VFags+1N3g==", + "dev": true, + "dependencies": { + "@babel/compat-data": "^7.23.3", + "@babel/helper-compilation-targets": "^7.22.15", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-transform-parameters": "^7.23.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-object-super": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.23.3.tgz", + "integrity": "sha512-BwQ8q0x2JG+3lxCVFohg+KbQM7plfpBwThdW9A6TMtWwLsbDA01Ek2Zb/AgDN39BiZsExm4qrXxjk+P1/fzGrA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-replace-supers": "^7.22.20" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-optional-catch-binding": { + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.23.4.tgz", + "integrity": "sha512-XIq8t0rJPHf6Wvmbn9nFxU6ao4c7WhghTR5WyV8SrJfUFzyxhCm4nhC+iAp3HFhbAKLfYpgzhJ6t4XCtVwqO5A==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-optional-chaining": { + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.23.4.tgz", + "integrity": "sha512-ZU8y5zWOfjM5vZ+asjgAPwDaBjJzgufjES89Rs4Lpq63O300R/kOz30WCLo6BxxX6QVEilwSlpClnG5cZaikTA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5", + "@babel/plugin-syntax-optional-chaining": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-parameters": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.23.3.tgz", + "integrity": "sha512-09lMt6UsUb3/34BbECKVbVwrT9bO6lILWln237z7sLaWnMsTi7Yc9fhX5DLpkJzAGfaReXI22wP41SZmnAA3Vw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-private-methods": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.23.3.tgz", + "integrity": "sha512-UzqRcRtWsDMTLrRWFvUBDwmw06tCQH9Rl1uAjfh6ijMSmGYQ+fpdB+cnqRC8EMh5tuuxSv0/TejGL+7vyj+50g==", + "dev": true, + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.22.15", + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-private-property-in-object": { + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.23.4.tgz", + "integrity": "sha512-9G3K1YqTq3F4Vt88Djx1UZ79PDyj+yKRnUy7cZGSMe+a7jkwD259uKKuUzQlPkGam7R+8RJwh5z4xO27fA1o2A==", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.22.5", + "@babel/helper-create-class-features-plugin": "^7.22.15", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-private-property-in-object": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-property-literals": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.23.3.tgz", + "integrity": "sha512-jR3Jn3y7cZp4oEWPFAlRsSWjxKe4PZILGBSd4nis1TsC5qeSpb+nrtihJuDhNI7QHiVbUaiXa0X2RZY3/TI6Nw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-self": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.23.3.tgz", + "integrity": "sha512-qXRvbeKDSfwnlJnanVRp0SfuWE5DQhwQr5xtLBzp56Wabyo+4CMosF6Kfp+eOD/4FYpql64XVJ2W0pVLlJZxOQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-source": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.23.3.tgz", + "integrity": "sha512-91RS0MDnAWDNvGC6Wio5XYkyWI39FMFO+JK9+4AlgaTH+yWwVTsw7/sn6LK0lH7c5F+TFkpv/3LfCJ1Ydwof/g==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-regenerator": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.23.3.tgz", + "integrity": "sha512-KP+75h0KghBMcVpuKisx3XTu9Ncut8Q8TuvGO4IhY+9D5DFEckQefOuIsB/gQ2tG71lCke4NMrtIPS8pOj18BQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5", + "regenerator-transform": "^0.15.2" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-reserved-words": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.23.3.tgz", + "integrity": "sha512-QnNTazY54YqgGxwIexMZva9gqbPa15t/x9VS+0fsEFWplwVpXYZivtgl43Z1vMpc1bdPP2PP8siFeVcnFvA3Cg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-shorthand-properties": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.23.3.tgz", + "integrity": "sha512-ED2fgqZLmexWiN+YNFX26fx4gh5qHDhn1O2gvEhreLW2iI63Sqm4llRLCXALKrCnbN4Jy0VcMQZl/SAzqug/jg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-spread": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.23.3.tgz", + "integrity": "sha512-VvfVYlrlBVu+77xVTOAoxQ6mZbnIq5FM0aGBSFEcIh03qHf+zNqA4DC/3XMUozTg7bZV3e3mZQ0i13VB6v5yUg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-sticky-regex": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.23.3.tgz", + "integrity": "sha512-HZOyN9g+rtvnOU3Yh7kSxXrKbzgrm5X4GncPY1QOquu7epga5MxKHVpYu2hvQnry/H+JjckSYRb93iNfsioAGg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-template-literals": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.23.3.tgz", + "integrity": "sha512-Flok06AYNp7GV2oJPZZcP9vZdszev6vPBkHLwxwSpaIqx75wn6mUd3UFWsSsA0l8nXAKkyCmL/sR02m8RYGeHg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-typeof-symbol": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.23.3.tgz", + "integrity": "sha512-4t15ViVnaFdrPC74be1gXBSMzXk3B4Us9lP7uLRQHTFpV5Dvt33pn+2MyyNxmN3VTTm3oTrZVMUmuw3oBnQ2oQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-escapes": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.23.3.tgz", + "integrity": "sha512-OMCUx/bU6ChE3r4+ZdylEqAjaQgHAgipgW8nsCfu5pGqDcFytVd91AwRvUJSBZDz0exPGgnjoqhgRYLRjFZc9Q==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-property-regex": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.23.3.tgz", + "integrity": "sha512-KcLIm+pDZkWZQAFJ9pdfmh89EwVfmNovFBcXko8szpBeF8z68kWIPeKlmSOkT9BXJxs2C0uk+5LxoxIv62MROA==", + "dev": true, + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.22.15", + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-regex": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.23.3.tgz", + "integrity": "sha512-wMHpNA4x2cIA32b/ci3AfwNgheiva2W0WUKWTK7vBHBhDKfPsc5cFGNWm69WBqpwd86u1qwZ9PWevKqm1A3yAw==", + "dev": true, + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.22.15", + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-sets-regex": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.23.3.tgz", + "integrity": "sha512-W7lliA/v9bNR83Qc3q1ip9CQMZ09CcHDbHfbLRDNuAhn1Mvkr1ZNF7hPmztMQvtTGVLJ9m8IZqWsTkXOml8dbw==", + "dev": true, + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.22.15", + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/preset-env": { + "version": "7.23.6", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.23.6.tgz", + "integrity": "sha512-2XPn/BqKkZCpzYhUUNZ1ssXw7DcXfKQEjv/uXZUXgaebCMYmkEsfZ2yY+vv+xtXv50WmL5SGhyB6/xsWxIvvOQ==", + "dev": true, + "dependencies": { + "@babel/compat-data": "^7.23.5", + "@babel/helper-compilation-targets": "^7.23.6", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-validator-option": "^7.23.5", + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.23.3", + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.23.3", + "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.23.3", + "@babel/plugin-proposal-private-property-in-object": "7.21.0-placeholder-for-preset-env.2", + "@babel/plugin-syntax-async-generators": "^7.8.4", + "@babel/plugin-syntax-class-properties": "^7.12.13", + "@babel/plugin-syntax-class-static-block": "^7.14.5", + "@babel/plugin-syntax-dynamic-import": "^7.8.3", + "@babel/plugin-syntax-export-namespace-from": "^7.8.3", + "@babel/plugin-syntax-import-assertions": "^7.23.3", + "@babel/plugin-syntax-import-attributes": "^7.23.3", + "@babel/plugin-syntax-import-meta": "^7.10.4", + "@babel/plugin-syntax-json-strings": "^7.8.3", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", + "@babel/plugin-syntax-numeric-separator": "^7.10.4", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", + "@babel/plugin-syntax-optional-chaining": "^7.8.3", + "@babel/plugin-syntax-private-property-in-object": "^7.14.5", + "@babel/plugin-syntax-top-level-await": "^7.14.5", + "@babel/plugin-syntax-unicode-sets-regex": "^7.18.6", + "@babel/plugin-transform-arrow-functions": "^7.23.3", + "@babel/plugin-transform-async-generator-functions": "^7.23.4", + "@babel/plugin-transform-async-to-generator": "^7.23.3", + "@babel/plugin-transform-block-scoped-functions": "^7.23.3", + "@babel/plugin-transform-block-scoping": "^7.23.4", + "@babel/plugin-transform-class-properties": "^7.23.3", + "@babel/plugin-transform-class-static-block": "^7.23.4", + "@babel/plugin-transform-classes": "^7.23.5", + "@babel/plugin-transform-computed-properties": "^7.23.3", + "@babel/plugin-transform-destructuring": "^7.23.3", + "@babel/plugin-transform-dotall-regex": "^7.23.3", + "@babel/plugin-transform-duplicate-keys": "^7.23.3", + "@babel/plugin-transform-dynamic-import": "^7.23.4", + "@babel/plugin-transform-exponentiation-operator": "^7.23.3", + "@babel/plugin-transform-export-namespace-from": "^7.23.4", + "@babel/plugin-transform-for-of": "^7.23.6", + "@babel/plugin-transform-function-name": "^7.23.3", + "@babel/plugin-transform-json-strings": "^7.23.4", + "@babel/plugin-transform-literals": "^7.23.3", + "@babel/plugin-transform-logical-assignment-operators": "^7.23.4", + "@babel/plugin-transform-member-expression-literals": "^7.23.3", + "@babel/plugin-transform-modules-amd": "^7.23.3", + "@babel/plugin-transform-modules-commonjs": "^7.23.3", + "@babel/plugin-transform-modules-systemjs": "^7.23.3", + "@babel/plugin-transform-modules-umd": "^7.23.3", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.22.5", + "@babel/plugin-transform-new-target": "^7.23.3", + "@babel/plugin-transform-nullish-coalescing-operator": "^7.23.4", + "@babel/plugin-transform-numeric-separator": "^7.23.4", + "@babel/plugin-transform-object-rest-spread": "^7.23.4", + "@babel/plugin-transform-object-super": "^7.23.3", + "@babel/plugin-transform-optional-catch-binding": "^7.23.4", + "@babel/plugin-transform-optional-chaining": "^7.23.4", + "@babel/plugin-transform-parameters": "^7.23.3", + "@babel/plugin-transform-private-methods": "^7.23.3", + "@babel/plugin-transform-private-property-in-object": "^7.23.4", + "@babel/plugin-transform-property-literals": "^7.23.3", + "@babel/plugin-transform-regenerator": "^7.23.3", + "@babel/plugin-transform-reserved-words": "^7.23.3", + "@babel/plugin-transform-shorthand-properties": "^7.23.3", + "@babel/plugin-transform-spread": "^7.23.3", + "@babel/plugin-transform-sticky-regex": "^7.23.3", + "@babel/plugin-transform-template-literals": "^7.23.3", + "@babel/plugin-transform-typeof-symbol": "^7.23.3", + "@babel/plugin-transform-unicode-escapes": "^7.23.3", + "@babel/plugin-transform-unicode-property-regex": "^7.23.3", + "@babel/plugin-transform-unicode-regex": "^7.23.3", + "@babel/plugin-transform-unicode-sets-regex": "^7.23.3", + "@babel/preset-modules": "0.1.6-no-external-plugins", + "babel-plugin-polyfill-corejs2": "^0.4.6", + "babel-plugin-polyfill-corejs3": "^0.8.5", + "babel-plugin-polyfill-regenerator": "^0.5.3", + "core-js-compat": "^3.31.0", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/preset-env/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/preset-modules": { + "version": "0.1.6-no-external-plugins", + "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.6-no-external-plugins.tgz", + "integrity": "sha512-HrcgcIESLm9aIR842yhJ5RWan/gebQUJ6E/E5+rf0y9o6oj7w0Br+sWuL6kEQ/o/AdfvR1Je9jG18/gnpwjEyA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/types": "^7.4.4", + "esutils": "^2.0.2" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/@babel/regjsgen": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@babel/regjsgen/-/regjsgen-0.8.0.tgz", + "integrity": "sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA==", + "dev": true + }, + "node_modules/@babel/runtime": { + "version": "7.23.6", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.23.6.tgz", + "integrity": "sha512-zHd0eUrf5GZoOWVCXp6koAKQTfZV07eit6bGPmJgnZdnSAvvZee6zniW2XMF7Cmc4ISOOnPy3QaSiIJGJkVEDQ==", + "dependencies": { + "regenerator-runtime": "^0.14.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/template": { + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.15.tgz", + "integrity": "sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.22.13", + "@babel/parser": "^7.22.15", + "@babel/types": "^7.22.15" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.23.6", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.6.tgz", + "integrity": "sha512-czastdK1e8YByZqezMPFiZ8ahwVMh/ESl9vPgvgdB9AmFMGP5jfpFax74AQgl5zj4XHzqeYAg2l8PuUeRS1MgQ==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.23.5", + "@babel/generator": "^7.23.6", + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-function-name": "^7.23.0", + "@babel/helper-hoist-variables": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.6", + "@babel/parser": "^7.23.6", + "@babel/types": "^7.23.6", + "debug": "^4.3.1", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.23.6", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.6.tgz", + "integrity": "sha512-+uarb83brBzPKN38NX1MkB6vb6+mwvR6amUulqAE7ccQw1pEl+bCia9TbdG1lsnFP7lZySvUn37CHyXQdfTwzg==", + "dev": true, + "dependencies": { + "@babel/helper-string-parser": "^7.23.4", + "@babel/helper-validator-identifier": "^7.22.20", + "to-fast-properties": "^2.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@coreui/coreui": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/@coreui/coreui/-/coreui-4.3.2.tgz", + "integrity": "sha512-SKGY6E6v7QGq0P3YTnZQRSrU8t0euLQ3UV/FH5j0JmHYBBu7Mv0Hd9g8AESnj8xrCelKZ5bdZKZhmKaIdG5clw==", + "dev": true, + "dependencies": { + "postcss-combine-duplicated-selectors": "^10.0.3" + }, + "peerDependencies": { + "@popperjs/core": "^2.11.6" + } + }, + "node_modules/@coreui/icons": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@coreui/icons/-/icons-3.0.1.tgz", + "integrity": "sha512-u9UKEcRMyY9pa4jUoLij8pAR03g5g6TLWV33/Mx2ix8sffyi0eO4fLV8DSTQljDCw938zt7KYog5cVKEAJUxxg==", + "dev": true + }, + "node_modules/@discoveryjs/json-ext": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz", + "integrity": "sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==", + "dev": true, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.19.10", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.19.10.tgz", + "integrity": "sha512-Q+mk96KJ+FZ30h9fsJl+67IjNJm3x2eX+GBWGmocAKgzp27cowCOOqSdscX80s0SpdFXZnIv/+1xD1EctFx96Q==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.19.10", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.19.10.tgz", + "integrity": "sha512-7W0bK7qfkw1fc2viBfrtAEkDKHatYfHzr/jKAHNr9BvkYDXPcC6bodtm8AyLJNNuqClLNaeTLuwURt4PRT9d7w==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.19.10", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.19.10.tgz", + "integrity": "sha512-1X4CClKhDgC3by7k8aOWZeBXQX8dHT5QAMCAQDArCLaYfkppoARvh0fit3X2Qs+MXDngKcHv6XXyQCpY0hkK1Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.19.10", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.19.10.tgz", + "integrity": "sha512-O/nO/g+/7NlitUxETkUv/IvADKuZXyH4BHf/g/7laqKC4i/7whLpB0gvpPc2zpF0q9Q6FXS3TS75QHac9MvVWw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.19.10", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.19.10.tgz", + "integrity": "sha512-YSRRs2zOpwypck+6GL3wGXx2gNP7DXzetmo5pHXLrY/VIMsS59yKfjPizQ4lLt5vEI80M41gjm2BxrGZ5U+VMA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.19.10", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.19.10.tgz", + "integrity": "sha512-alfGtT+IEICKtNE54hbvPg13xGBe4GkVxyGWtzr+yHO7HIiRJppPDhOKq3zstTcVf8msXb/t4eavW3jCDpMSmA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.19.10", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.19.10.tgz", + "integrity": "sha512-dMtk1wc7FSH8CCkE854GyGuNKCewlh+7heYP/sclpOG6Cectzk14qdUIY5CrKDbkA/OczXq9WesqnPl09mj5dg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.19.10", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.19.10.tgz", + "integrity": "sha512-G5UPPspryHu1T3uX8WiOEUa6q6OlQh6gNl4CO4Iw5PS+Kg5bVggVFehzXBJY6X6RSOMS8iXDv2330VzaObm4Ag==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.19.10", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.19.10.tgz", + "integrity": "sha512-j6gUW5aAaPgD416Hk9FHxn27On28H4eVI9rJ4az7oCGTFW48+LcgNDBN+9f8rKZz7EEowo889CPKyeaD0iw9Kg==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.19.10", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.19.10.tgz", + "integrity": "sha512-QxaouHWZ+2KWEj7cGJmvTIHVALfhpGxo3WLmlYfJ+dA5fJB6lDEIg+oe/0//FuyVHuS3l79/wyBxbHr0NgtxJQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.19.10", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.19.10.tgz", + "integrity": "sha512-4ub1YwXxYjj9h1UIZs2hYbnTZBtenPw5NfXCRgEkGb0b6OJ2gpkMvDqRDYIDRjRdWSe/TBiZltm3Y3Q8SN1xNg==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.19.10", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.19.10.tgz", + "integrity": "sha512-lo3I9k+mbEKoxtoIbM0yC/MZ1i2wM0cIeOejlVdZ3D86LAcFXFRdeuZmh91QJvUTW51bOK5W2BznGNIl4+mDaA==", + "cpu": [ + "loong64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.19.10", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.19.10.tgz", + "integrity": "sha512-J4gH3zhHNbdZN0Bcr1QUGVNkHTdpijgx5VMxeetSk6ntdt+vR1DqGmHxQYHRmNb77tP6GVvD+K0NyO4xjd7y4A==", + "cpu": [ + "mips64el" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.19.10", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.19.10.tgz", + "integrity": "sha512-tgT/7u+QhV6ge8wFMzaklOY7KqiyitgT1AUHMApau32ZlvTB/+efeCtMk4eXS+uEymYK249JsoiklZN64xt6oQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.19.10", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.19.10.tgz", + "integrity": "sha512-0f/spw0PfBMZBNqtKe5FLzBDGo0SKZKvMl5PHYQr3+eiSscfJ96XEknCe+JoOayybWUFQbcJTrk946i3j9uYZA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.19.10", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.19.10.tgz", + "integrity": "sha512-pZFe0OeskMHzHa9U38g+z8Yx5FNCLFtUnJtQMpwhS+r4S566aK2ci3t4NCP4tjt6d5j5uo4h7tExZMjeKoehAA==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.19.10", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.19.10.tgz", + "integrity": "sha512-SpYNEqg/6pZYoc+1zLCjVOYvxfZVZj6w0KROZ3Fje/QrM3nfvT2llI+wmKSrWuX6wmZeTapbarvuNNK/qepSgA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.19.10", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.19.10.tgz", + "integrity": "sha512-ACbZ0vXy9zksNArWlk2c38NdKg25+L9pr/mVaj9SUq6lHZu/35nx2xnQVRGLrC1KKQqJKRIB0q8GspiHI3J80Q==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.19.10", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.19.10.tgz", + "integrity": "sha512-PxcgvjdSjtgPMiPQrM3pwSaG4kGphP+bLSb+cihuP0LYdZv1epbAIecHVl5sD3npkfYBZ0ZnOjR878I7MdJDFg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.19.10", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.19.10.tgz", + "integrity": "sha512-ZkIOtrRL8SEJjr+VHjmW0znkPs+oJXhlJbNwfI37rvgeMtk3sxOQevXPXjmAPZPigVTncvFqLMd+uV0IBSEzqA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.19.10", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.19.10.tgz", + "integrity": "sha512-+Sa4oTDbpBfGpl3Hn3XiUe4f8TU2JF7aX8cOfqFYMMjXp6ma6NJDztl5FDG8Ezx0OjwGikIHw+iA54YLDNNVfw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.19.10", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.19.10.tgz", + "integrity": "sha512-EOGVLK1oWMBXgfttJdPHDTiivYSjX6jDNaATeNOaCOFEVcfMjtbx7WVQwPSE1eIfCp/CaSF2nSrDtzc4I9f8TQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.19.10", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.19.10.tgz", + "integrity": "sha512-whqLG6Sc70AbU73fFYvuYzaE4MNMBIlR1Y/IrUeOXFrWHxBEjjbZaQ3IXIQS8wJdAzue2GwYZCjOrgrU1oUHoA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", + "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", + "dev": true, + "dependencies": { + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.10.0", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.10.0.tgz", + "integrity": "sha512-Cu96Sd2By9mCNTx2iyKOmq10v22jUVQv0lQnlGNy16oE9589yE+QADPbrMGCkA51cKZSg3Pu/aTJVTGfL/qjUA==", + "dev": true, + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", + "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", + "dev": true, + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.6.0", + "globals": "^13.19.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/@eslint/eslintrc/node_modules/globals": { + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", + "dev": true, + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@eslint/eslintrc/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "node_modules/@eslint/js": { + "version": "8.56.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.56.0.tgz", + "integrity": "sha512-gMsVel9D7f2HLkBma9VbtzZRehRogVRfbr++f06nL2vnCGCNlzOD+/MUov/F4p8myyAHspEhVobgjpX64q5m6A==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/@humanwhocodes/config-array": { + "version": "0.11.13", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.13.tgz", + "integrity": "sha512-JSBDMiDKSzQVngfRjOdFXgFfklaXI4K9nLF49Auh21lmBWRLIK3+xTErTWD4KU54pb6coM6ESE7Awz/FNU3zgQ==", + "dev": true, + "dependencies": { + "@humanwhocodes/object-schema": "^2.0.1", + "debug": "^4.1.1", + "minimatch": "^3.0.5" + }, + "engines": { + "node": ">=10.10.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/object-schema": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.1.tgz", + "integrity": "sha512-dvuCeX5fC9dXgJn9t+X5atfmgQAzUOWqS1254Gh0m6i8wKd10ebXkfNKiRK+1GWi/yTvvLDHpoxLr0xxxeslWw==", + "dev": true + }, + "node_modules/@jest/schemas": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", + "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", + "dev": true, + "dependencies": { + "@sinclair/typebox": "^0.27.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", + "integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==", + "dev": true, + "dependencies": { + "@jridgewell/set-array": "^1.0.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.9" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz", + "integrity": "sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/set-array": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", + "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/source-map": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.5.tgz", + "integrity": "sha512-UTYAUj/wviwdsMfzoSJspJxbkH5o1snzwX0//0ENX1u/55kkZZkcTZP6u9bwKGkv+dkk9at4m1Cpt0uY80kcpQ==", + "dev": true, + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.0", + "@jridgewell/trace-mapping": "^0.3.9" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.4.15", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", + "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", + "dev": true + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.20", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.20.tgz", + "integrity": "sha512-R8LcPeWZol2zR8mmH3JeKQ6QRCFb7XgUhV9ZlGhHLGyg4wpPiPZNQOOWhFZhxKw8u//yTbNGI42Bx/3paXEQ+Q==", + "dev": true, + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@popperjs/core": { + "version": "2.11.8", + "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz", + "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==", + "dev": true, + "peer": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/popperjs" + } + }, + "node_modules/@remix-run/router": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.14.1.tgz", + "integrity": "sha512-Qg4DMQsfPNAs88rb2xkdk03N3bjK4jgX5fR24eHCTR9q6PrhZQZ4UJBPzCHJkIpTRN1UKxx2DzjZmnC+7Lj0Ow==", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.9.1.tgz", + "integrity": "sha512-6vMdBZqtq1dVQ4CWdhFwhKZL6E4L1dV6jUjuBvsavvNJSppzi6dLBbuV+3+IyUREaj9ZFvQefnQm28v4OCXlig==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.9.1.tgz", + "integrity": "sha512-Jto9Fl3YQ9OLsTDWtLFPtaIMSL2kwGyGoVCmPC8Gxvym9TCZm4Sie+cVeblPO66YZsYH8MhBKDMGZ2NDxuk/XQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.9.1.tgz", + "integrity": "sha512-LtYcLNM+bhsaKAIGwVkh5IOWhaZhjTfNOkGzGqdHvhiCUVuJDalvDxEdSnhFzAn+g23wgsycmZk1vbnaibZwwA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.9.1.tgz", + "integrity": "sha512-KyP/byeXu9V+etKO6Lw3E4tW4QdcnzDG/ake031mg42lob5tN+5qfr+lkcT/SGZaH2PdW4Z1NX9GHEkZ8xV7og==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.9.1.tgz", + "integrity": "sha512-Yqz/Doumf3QTKplwGNrCHe/B2p9xqDghBZSlAY0/hU6ikuDVQuOUIpDP/YcmoT+447tsZTmirmjgG3znvSCR0Q==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.9.1.tgz", + "integrity": "sha512-u3XkZVvxcvlAOlQJ3UsD1rFvLWqu4Ef/Ggl40WAVCuogf4S1nJPHh5RTgqYFpCOvuGJ7H5yGHabjFKEZGExk5Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.9.1.tgz", + "integrity": "sha512-0XSYN/rfWShW+i+qjZ0phc6vZ7UWI8XWNz4E/l+6edFt+FxoEghrJHjX1EY/kcUGCnZzYYRCl31SNdfOi450Aw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.9.1.tgz", + "integrity": "sha512-LmYIO65oZVfFt9t6cpYkbC4d5lKHLYv5B4CSHRpnANq0VZUQXGcCPXHzbCXCz4RQnx7jvlYB1ISVNCE/omz5cw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.9.1.tgz", + "integrity": "sha512-kr8rEPQ6ns/Lmr/hiw8sEVj9aa07gh1/tQF2Y5HrNCCEPiCBGnBUt9tVusrcBBiJfIt1yNaXN6r1CCmpbFEDpg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.9.1.tgz", + "integrity": "sha512-t4QSR7gN+OEZLG0MiCgPqMWZGwmeHhsM4AkegJ0Kiy6TnJ9vZ8dEIwHw1LcZKhbHxTY32hp9eVCMdR3/I8MGRw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.9.1.tgz", + "integrity": "sha512-7XI4ZCBN34cb+BH557FJPmh0kmNz2c25SCQeT9OiFWEgf8+dL6ZwJ8f9RnUIit+j01u07Yvrsuu1rZGxJCc51g==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.9.1.tgz", + "integrity": "sha512-yE5c2j1lSWOH5jp+Q0qNL3Mdhr8WuqCNVjc6BxbVfS5cAS6zRmdiw7ktb8GNpDCEUJphILY6KACoFoRtKoqNQg==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.9.1.tgz", + "integrity": "sha512-PyJsSsafjmIhVgaI1Zdj7m8BB8mMckFah/xbpplObyHfiXzKcI5UOUXRyOdHW7nz4DpMCuzLnF7v5IWHenCwYA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@sinclair/typebox": { + "version": "0.27.8", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", + "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", + "dev": true + }, + "node_modules/@stencil/core": { + "version": "4.9.0", + "resolved": "https://registry.npmjs.org/@stencil/core/-/core-4.9.0.tgz", + "integrity": "sha512-aWSkhBmk3yPwRAkUwBbzRwmdhb8hKiQ/JMr9m5jthpBZLjtppYbzz6PN2MhSMDfRp6K93eQw5WogSEH4HHuB6w==", + "dev": true, + "bin": { + "stencil": "bin/stencil" + }, + "engines": { + "node": ">=16.0.0", + "npm": ">=7.10.0" + } + }, + "node_modules/@tsd/typescript": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/@tsd/typescript/-/typescript-5.3.3.tgz", + "integrity": "sha512-CQlfzol0ldaU+ftWuG52vH29uRoKboLinLy84wS8TQOu+m+tWoaUfk4svL4ij2V8M5284KymJBlHUusKj6k34w==", + "dev": true, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/@types/accounting": { + "version": "0.4.5", + "resolved": "https://registry.npmjs.org/@types/accounting/-/accounting-0.4.5.tgz", + "integrity": "sha512-rf3MKoFZ3REGY2aQTqGwo79aJKom1xgHiJVBy0RguTqpxny1nDT4iYeLx2EgDEkXAhgbwnuLUoxlg4RKHIB7fQ==", + "dev": true + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.6.8", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.8.tgz", + "integrity": "sha512-ASsj+tpEDsEiFr1arWrlN6V3mdfjRMZt6LtK/Vp/kreFLnr5QH5+DhvD5nINYZXzwJvXeGq+05iUXcAzVrqWtw==", + "dev": true, + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dev": true, + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.20.4", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.4.tgz", + "integrity": "sha512-mSM/iKUk5fDDrEV/e83qY+Cr3I1+Q3qqTuEn++HAWYjEa1+NxZr6CNrcJGf2ZTnq4HoFGC3zaTPZTobCzCFukA==", + "dev": true, + "dependencies": { + "@babel/types": "^7.20.7" + } + }, + "node_modules/@types/eslint": { + "version": "8.56.0", + "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.56.0.tgz", + "integrity": "sha512-FlsN0p4FhuYRjIxpbdXovvHQhtlG05O1GG/RNWvdAxTboR438IOTwmrY/vLA+Xfgg06BTkP045M3vpFwTMv1dg==", + "dev": true, + "dependencies": { + "@types/estree": "*", + "@types/json-schema": "*" + } + }, + "node_modules/@types/eslint-scope": { + "version": "3.7.7", + "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.7.tgz", + "integrity": "sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==", + "dev": true, + "dependencies": { + "@types/eslint": "*", + "@types/estree": "*" + } + }, + "node_modules/@types/estree": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", + "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==", + "dev": true + }, + "node_modules/@types/history": { + "version": "4.7.11", + "resolved": "https://registry.npmjs.org/@types/history/-/history-4.7.11.tgz", + "integrity": "sha512-qjDJRrmvBMiTx+jyLxvLfJU7UznFuokDv4f3WRuriHKERccVpFU+8XMQUAbDzoiJCsmexxRExQeMwwCdamSKDA==", + "dev": true + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true + }, + "node_modules/@types/minimist": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/@types/minimist/-/minimist-1.2.5.tgz", + "integrity": "sha512-hov8bUuiLiyFPGyFPE1lwWhmzYbirOXQNNo40+y3zow8aFVTeyn3VWL0VFFfdNddA8S4Vf0Tc062rzyNr7Paag==", + "dev": true + }, + "node_modules/@types/node": { + "version": "20.10.5", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.10.5.tgz", + "integrity": "sha512-nNPsNE65wjMxEKI93yOP+NPGGBJz/PoN3kZsVLee0XMiJolxSekEVD8wRwBUBqkwc7UWop0edW50yrCQW4CyRw==", + "dev": true, + "dependencies": { + "undici-types": "~5.26.4" + } + }, + "node_modules/@types/normalize-package-data": { + "version": "2.4.4", + "resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.4.tgz", + "integrity": "sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==", + "dev": true + }, + "node_modules/@types/prop-types": { + "version": "15.7.11", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.11.tgz", + "integrity": "sha512-ga8y9v9uyeiLdpKddhxYQkxNDrfvuPrlFb0N1qnZZByvcElJaXthF1UhvCh9TLWJBEHeNtdnbysW7Y6Uq8CVng==", + "dev": true + }, + "node_modules/@types/raf": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/@types/raf/-/raf-3.4.3.tgz", + "integrity": "sha512-c4YAvMedbPZ5tEyxzQdMoOhhJ4RD3rngZIdwC2/qDN3d7JpEhB6fiBRKVY1lg5B7Wk+uPBjn5f39j1/2MY1oOw==", + "optional": true + }, + "node_modules/@types/react": { + "version": "18.2.45", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.45.tgz", + "integrity": "sha512-TtAxCNrlrBp8GoeEp1npd5g+d/OejJHFxS3OWmrPBMFaVQMSN0OFySozJio5BHxTuTeug00AVXVAjfDSfk+lUg==", + "dev": true, + "dependencies": { + "@types/prop-types": "*", + "@types/scheduler": "*", + "csstype": "^3.0.2" + } + }, + "node_modules/@types/react-dom": { + "version": "18.2.18", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.2.18.tgz", + "integrity": "sha512-TJxDm6OfAX2KJWJdMEVTwWke5Sc/E/RlnPGvGfS0W7+6ocy2xhDVQVh/KvC2Uf7kACs+gDytdusDSdWfWkaNzw==", + "dev": true, + "dependencies": { + "@types/react": "*" + } + }, + "node_modules/@types/react-router": { + "version": "5.1.20", + "resolved": "https://registry.npmjs.org/@types/react-router/-/react-router-5.1.20.tgz", + "integrity": "sha512-jGjmu/ZqS7FjSH6owMcD5qpq19+1RS9DeVRqfl1FeBMxTDQAGwlMWOcs52NDoXaNKyG3d1cYQFMs9rCrb88o9Q==", + "dev": true, + "dependencies": { + "@types/history": "^4.7.11", + "@types/react": "*" + } + }, + "node_modules/@types/react-router-dom": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/@types/react-router-dom/-/react-router-dom-5.3.3.tgz", + "integrity": "sha512-kpqnYK4wcdm5UaWI3fLcELopqLrHgLqNsdpHauzlQktfkHL3npOSwtj1Uz9oKBAzs7lFtVkV8j83voAz2D8fhw==", + "dev": true, + "dependencies": { + "@types/history": "^4.7.11", + "@types/react": "*", + "@types/react-router": "*" + } + }, + "node_modules/@types/scheduler": { + "version": "0.16.8", + "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.8.tgz", + "integrity": "sha512-WZLiwShhwLRmeV6zH+GkbOFT6Z6VklCItrDioxUnv+u4Ll+8vKeFySoFyK/0ctcRpOmwAicELfmys1sDc/Rw+A==", + "dev": true + }, + "node_modules/@types/semver": { + "version": "7.5.6", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.6.tgz", + "integrity": "sha512-dn1l8LaMea/IjDoHNd9J52uBbInB796CDffS6VdIxvqYCPSG0V0DzHp76GpaWnlhg88uYyPbXCDIowa86ybd5A==", + "dev": true + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.15.0.tgz", + "integrity": "sha512-j5qoikQqPccq9QoBAupOP+CBu8BaJ8BLjaXSioDISeTZkVO3ig7oSIKh3H+rEpee7xCXtWwSB4KIL5l6hWZzpg==", + "dev": true, + "dependencies": { + "@eslint-community/regexpp": "^4.5.1", + "@typescript-eslint/scope-manager": "6.15.0", + "@typescript-eslint/type-utils": "6.15.0", + "@typescript-eslint/utils": "6.15.0", + "@typescript-eslint/visitor-keys": "6.15.0", + "debug": "^4.3.4", + "graphemer": "^1.4.0", + "ignore": "^5.2.4", + "natural-compare": "^1.4.0", + "semver": "^7.5.4", + "ts-api-utils": "^1.0.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^6.0.0 || ^6.0.0-alpha", + "eslint": "^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.15.0.tgz", + "integrity": "sha512-MkgKNnsjC6QwcMdlNAel24jjkEO/0hQaMDLqP4S9zq5HBAUJNQB6y+3DwLjX7b3l2b37eNAxMPLwb3/kh8VKdA==", + "dev": true, + "dependencies": { + "@typescript-eslint/scope-manager": "6.15.0", + "@typescript-eslint/types": "6.15.0", + "@typescript-eslint/typescript-estree": "6.15.0", + "@typescript-eslint/visitor-keys": "6.15.0", + "debug": "^4.3.4" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.15.0.tgz", + "integrity": "sha512-+BdvxYBltqrmgCNu4Li+fGDIkW9n//NrruzG9X1vBzaNK+ExVXPoGB71kneaVw/Jp+4rH/vaMAGC6JfMbHstVg==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "6.15.0", + "@typescript-eslint/visitor-keys": "6.15.0" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.15.0.tgz", + "integrity": "sha512-CnmHKTfX6450Bo49hPg2OkIm/D/TVYV7jO1MCfPYGwf6x3GO0VU8YMO5AYMn+u3X05lRRxA4fWCz87GFQV6yVQ==", + "dev": true, + "dependencies": { + "@typescript-eslint/typescript-estree": "6.15.0", + "@typescript-eslint/utils": "6.15.0", + "debug": "^4.3.4", + "ts-api-utils": "^1.0.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/types": { + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.15.0.tgz", + "integrity": "sha512-yXjbt//E4T/ee8Ia1b5mGlbNj9fB9lJP4jqLbZualwpP2BCQ5is6BcWwxpIsY4XKAhmdv3hrW92GdtJbatC6dQ==", + "dev": true, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.15.0.tgz", + "integrity": "sha512-7mVZJN7Hd15OmGuWrp2T9UvqR2Ecg+1j/Bp1jXUEY2GZKV6FXlOIoqVDmLpBiEiq3katvj/2n2mR0SDwtloCew==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "6.15.0", + "@typescript-eslint/visitor-keys": "6.15.0", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "semver": "^7.5.4", + "ts-api-utils": "^1.0.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.15.0.tgz", + "integrity": "sha512-eF82p0Wrrlt8fQSRL0bGXzK5nWPRV2dYQZdajcfzOD9+cQz9O7ugifrJxclB+xVOvWvagXfqS4Es7vpLP4augw==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.4.0", + "@types/json-schema": "^7.0.12", + "@types/semver": "^7.5.0", + "@typescript-eslint/scope-manager": "6.15.0", + "@typescript-eslint/types": "6.15.0", + "@typescript-eslint/typescript-estree": "6.15.0", + "semver": "^7.5.4" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^7.0.0 || ^8.0.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.15.0.tgz", + "integrity": "sha512-1zvtdC1a9h5Tb5jU9x3ADNXO9yjP8rXlaoChu0DQX40vf5ACVpYIVIZhIMZ6d5sDXH7vq4dsZBT1fEGj8D2n2w==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "6.15.0", + "eslint-visitor-keys": "^3.4.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@ungap/structured-clone": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz", + "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==", + "dev": true + }, + "node_modules/@vitejs/plugin-react": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.2.1.tgz", + "integrity": "sha512-oojO9IDc4nCUUi8qIR11KoQm0XFFLIwsRBwHRR4d/88IWghn1y6ckz/bJ8GHDCsYEJee8mDzqtJxh15/cisJNQ==", + "dev": true, + "dependencies": { + "@babel/core": "^7.23.5", + "@babel/plugin-transform-react-jsx-self": "^7.23.3", + "@babel/plugin-transform-react-jsx-source": "^7.23.3", + "@types/babel__core": "^7.20.5", + "react-refresh": "^0.14.0" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "peerDependencies": { + "vite": "^4.2.0 || ^5.0.0" + } + }, + "node_modules/@webassemblyjs/ast": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.6.tgz", + "integrity": "sha512-IN1xI7PwOvLPgjcf180gC1bqn3q/QaOCwYUahIOhbYUu8KA/3tw2RT/T0Gidi1l7Hhj5D/INhJxiICObqpMu4Q==", + "dev": true, + "dependencies": { + "@webassemblyjs/helper-numbers": "1.11.6", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6" + } + }, + "node_modules/@webassemblyjs/floating-point-hex-parser": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.6.tgz", + "integrity": "sha512-ejAj9hfRJ2XMsNHk/v6Fu2dGS+i4UaXBXGemOfQ/JfQ6mdQg/WXtwleQRLLS4OvfDhv8rYnVwH27YJLMyYsxhw==", + "dev": true + }, + "node_modules/@webassemblyjs/helper-api-error": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.6.tgz", + "integrity": "sha512-o0YkoP4pVu4rN8aTJgAyj9hC2Sv5UlkzCHhxqWj8butaLvnpdc2jOwh4ewE6CX0txSfLn/UYaV/pheS2Txg//Q==", + "dev": true + }, + "node_modules/@webassemblyjs/helper-buffer": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.6.tgz", + "integrity": "sha512-z3nFzdcp1mb8nEOFFk8DrYLpHvhKC3grJD2ardfKOzmbmJvEf/tPIqCY+sNcwZIY8ZD7IkB2l7/pqhUhqm7hLA==", + "dev": true + }, + "node_modules/@webassemblyjs/helper-numbers": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.6.tgz", + "integrity": "sha512-vUIhZ8LZoIWHBohiEObxVm6hwP034jwmc9kuq5GdHZH0wiLVLIPcMCdpJzG4C11cHoQ25TFIQj9kaVADVX7N3g==", + "dev": true, + "dependencies": { + "@webassemblyjs/floating-point-hex-parser": "1.11.6", + "@webassemblyjs/helper-api-error": "1.11.6", + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webassemblyjs/helper-wasm-bytecode": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.6.tgz", + "integrity": "sha512-sFFHKwcmBprO9e7Icf0+gddyWYDViL8bpPjJJl0WHxCdETktXdmtWLGVzoHbqUcY4Be1LkNfwTmXOJUFZYSJdA==", + "dev": true + }, + "node_modules/@webassemblyjs/helper-wasm-section": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.6.tgz", + "integrity": "sha512-LPpZbSOwTpEC2cgn4hTydySy1Ke+XEu+ETXuoyvuyezHO3Kjdu90KK95Sh9xTbmjrCsUwvWwCOQQNta37VrS9g==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "1.11.6", + "@webassemblyjs/helper-buffer": "1.11.6", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6", + "@webassemblyjs/wasm-gen": "1.11.6" + } + }, + "node_modules/@webassemblyjs/ieee754": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.11.6.tgz", + "integrity": "sha512-LM4p2csPNvbij6U1f19v6WR56QZ8JcHg3QIJTlSwzFcmx6WSORicYj6I63f9yU1kEUtrpG+kjkiIAkevHpDXrg==", + "dev": true, + "dependencies": { + "@xtuc/ieee754": "^1.2.0" + } + }, + "node_modules/@webassemblyjs/leb128": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.11.6.tgz", + "integrity": "sha512-m7a0FhE67DQXgouf1tbN5XQcdWoNgaAuoULHIfGFIEVKA6tu/edls6XnIlkmS6FrXAquJRPni3ZZKjw6FSPjPQ==", + "dev": true, + "dependencies": { + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webassemblyjs/utf8": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.11.6.tgz", + "integrity": "sha512-vtXf2wTQ3+up9Zsg8sa2yWiQpzSsMyXj0qViVP6xKGCUT8p8YJ6HqI7l5eCnWx1T/FYdsv07HQs2wTFbbof/RA==", + "dev": true + }, + "node_modules/@webassemblyjs/wasm-edit": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.6.tgz", + "integrity": "sha512-Ybn2I6fnfIGuCR+Faaz7YcvtBKxvoLV3Lebn1tM4o/IAJzmi9AWYIPWpyBfU8cC+JxAO57bk4+zdsTjJR+VTOw==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "1.11.6", + "@webassemblyjs/helper-buffer": "1.11.6", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6", + "@webassemblyjs/helper-wasm-section": "1.11.6", + "@webassemblyjs/wasm-gen": "1.11.6", + "@webassemblyjs/wasm-opt": "1.11.6", + "@webassemblyjs/wasm-parser": "1.11.6", + "@webassemblyjs/wast-printer": "1.11.6" + } + }, + "node_modules/@webassemblyjs/wasm-gen": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.6.tgz", + "integrity": "sha512-3XOqkZP/y6B4F0PBAXvI1/bky7GryoogUtfwExeP/v7Nzwo1QLcq5oQmpKlftZLbT+ERUOAZVQjuNVak6UXjPA==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "1.11.6", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6", + "@webassemblyjs/ieee754": "1.11.6", + "@webassemblyjs/leb128": "1.11.6", + "@webassemblyjs/utf8": "1.11.6" + } + }, + "node_modules/@webassemblyjs/wasm-opt": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.6.tgz", + "integrity": "sha512-cOrKuLRE7PCe6AsOVl7WasYf3wbSo4CeOk6PkrjS7g57MFfVUF9u6ysQBBODX0LdgSvQqRiGz3CXvIDKcPNy4g==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "1.11.6", + "@webassemblyjs/helper-buffer": "1.11.6", + "@webassemblyjs/wasm-gen": "1.11.6", + "@webassemblyjs/wasm-parser": "1.11.6" + } + }, + "node_modules/@webassemblyjs/wasm-parser": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.6.tgz", + "integrity": "sha512-6ZwPeGzMJM3Dqp3hCsLgESxBGtT/OeCvCZ4TA1JUPYgmhAx38tTPR9JaKy0S5H3evQpO/h2uWs2j6Yc/fjkpTQ==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "1.11.6", + "@webassemblyjs/helper-api-error": "1.11.6", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6", + "@webassemblyjs/ieee754": "1.11.6", + "@webassemblyjs/leb128": "1.11.6", + "@webassemblyjs/utf8": "1.11.6" + } + }, + "node_modules/@webassemblyjs/wast-printer": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.11.6.tgz", + "integrity": "sha512-JM7AhRcE+yW2GWYaKeHL5vt4xqee5N2WcezptmgyhNS+ScggqcT1OtXykhAb13Sn5Yas0j2uv9tHgrjwvzAP4A==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "1.11.6", + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webpack-cli/configtest": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@webpack-cli/configtest/-/configtest-2.1.1.tgz", + "integrity": "sha512-wy0mglZpDSiSS0XHrVR+BAdId2+yxPSoJW8fsna3ZpYSlufjvxnP4YbKTCBZnNIcGN4r6ZPXV55X4mYExOfLmw==", + "dev": true, + "engines": { + "node": ">=14.15.0" + }, + "peerDependencies": { + "webpack": "5.x.x", + "webpack-cli": "5.x.x" + } + }, + "node_modules/@webpack-cli/info": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@webpack-cli/info/-/info-2.0.2.tgz", + "integrity": "sha512-zLHQdI/Qs1UyT5UBdWNqsARasIA+AaF8t+4u2aS2nEpBQh2mWIVb8qAklq0eUENnC5mOItrIB4LiS9xMtph18A==", + "dev": true, + "engines": { + "node": ">=14.15.0" + }, + "peerDependencies": { + "webpack": "5.x.x", + "webpack-cli": "5.x.x" + } + }, + "node_modules/@webpack-cli/serve": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@webpack-cli/serve/-/serve-2.0.5.tgz", + "integrity": "sha512-lqaoKnRYBdo1UgDX8uF24AfGMifWK19TxPmM5FHc2vAGxrJ/qtyUyFBWoY1tISZdelsQ5fBcOusifo5o5wSJxQ==", + "dev": true, + "engines": { + "node": ">=14.15.0" + }, + "peerDependencies": { + "webpack": "5.x.x", + "webpack-cli": "5.x.x" + }, + "peerDependenciesMeta": { + "webpack-dev-server": { + "optional": true + } + } + }, + "node_modules/@xtuc/ieee754": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", + "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", + "dev": true + }, + "node_modules/@xtuc/long": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", + "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", + "dev": true + }, + "node_modules/accounting": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/accounting/-/accounting-0.4.1.tgz", + "integrity": "sha512-RU6KY9Y5wllyaCNBo1W11ZOTnTHMMgOZkIwdOOs6W5ibMTp72i4xIbEA48djxVGqMNTUNbvrP/1nWg5Af5m2gQ==" + }, + "node_modules/acorn": { + "version": "8.11.2", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.2.tgz", + "integrity": "sha512-nc0Axzp/0FILLEVsm4fNwLCwMttvhEI263QtVPQcbpfZZ3ts0hLsZGOpE6czNlid7CJ9MlyH8reXkpsf3YUY4w==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-import-assertions": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.9.0.tgz", + "integrity": "sha512-cmMwop9x+8KFhxvKrKfPYmN6/pKTYYHBqLa0DfvVZcKMJWNyWLnaqND7dx/qn66R7ewM1UX5XMaDVP5wlVTaVA==", + "dev": true, + "peerDependencies": { + "acorn": "^8" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/ag-grid": { + "version": "18.1.2", + "resolved": "https://registry.npmjs.org/ag-grid/-/ag-grid-18.1.2.tgz", + "integrity": "sha512-HtJt8iFcRKCBj5UHBDmwSLLr72F3XDACeBNarH4nJWFHIqcnu7u0Ifrd2nftPmfEBj6YjFHawDqcZL2yo3YfmQ==", + "deprecated": "ag-grid is now deprecated - please use @ag-grid-community/all-modules. See www.ag-grid.com/javascript-grid-modules/ for more information." + }, + "node_modules/ag-grid-community": { + "version": "31.0.1", + "resolved": "https://registry.npmjs.org/ag-grid-community/-/ag-grid-community-31.0.1.tgz", + "integrity": "sha512-RZQlW1DTOJHsUR/tnbnTJQKgAnDlHi05YYyTe5AgNor/1TlX1hoYdcqrGsJjvcHQgTjeEgzWOL0yf+KcqXZzxg==", + "dev": true + }, + "node_modules/ajv": { + "version": "8.12.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", + "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-escapes": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", + "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "dev": true, + "dependencies": { + "type-fest": "^0.21.3" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-escapes/node_modules/type-fest": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "node_modules/array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/arrify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", + "integrity": "sha512-3CYzex9M9FGQjCGMGyi6/31c8GJbgb0qGyrx5HWxPd0aCwh4cB2YjMb2Xf9UuoogrMrlO9cTqnB5rI5GHZTcUA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "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 + }, + "node_modules/atob": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", + "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==", + "bin": { + "atob": "bin/atob.js" + }, + "engines": { + "node": ">= 4.5.0" + } + }, + "node_modules/axios": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.2.tgz", + "integrity": "sha512-7i24Ri4pmDRfJTR7LDBhsOTtcm+9kjX5WiY1X3wIisx6G9So3pfMkEiU7emUBe46oceVImccTEM3k6C5dbVW8A==", + "dev": true, + "dependencies": { + "follow-redirects": "^1.15.0", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, + "node_modules/babel-plugin-polyfill-corejs2": { + "version": "0.4.7", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.7.tgz", + "integrity": "sha512-LidDk/tEGDfuHW2DWh/Hgo4rmnw3cduK6ZkOI1NPFceSK3n/yAGeOsNT7FLnSGHkXj3RHGSEVkN3FsCTY6w2CQ==", + "dev": true, + "dependencies": { + "@babel/compat-data": "^7.22.6", + "@babel/helper-define-polyfill-provider": "^0.4.4", + "semver": "^6.3.1" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/babel-plugin-polyfill-corejs2/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/babel-plugin-polyfill-corejs3": { + "version": "0.8.7", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.8.7.tgz", + "integrity": "sha512-KyDvZYxAzkC0Aj2dAPyDzi2Ym15e5JKZSK+maI7NAwSqofvuFglbSsxE7wUOvTg9oFVnHMzVzBKcqEb4PJgtOA==", + "dev": true, + "dependencies": { + "@babel/helper-define-polyfill-provider": "^0.4.4", + "core-js-compat": "^3.33.1" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/babel-plugin-polyfill-regenerator": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.5.4.tgz", + "integrity": "sha512-S/x2iOCvDaCASLYsOOgWOq4bCfKYVqvO/uxjkaYyZ3rVsVE3CeAI/c84NpyuBBymEgNvHgjEot3a9/Z/kXvqsg==", + "dev": true, + "dependencies": { + "@babel/helper-define-polyfill-provider": "^0.4.4" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "node_modules/base64-arraybuffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz", + "integrity": "sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ==", + "devOptional": true, + "engines": { + "node": ">= 0.6.0" + } + }, + "node_modules/bootstrap": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.3.2.tgz", + "integrity": "sha512-D32nmNWiQHo94BKHLmOrdjlL05q1c8oxbtBphQFb9Z5to6eGRDCm0QgeaZ4zFBHzfg2++rqa2JkqCcxDy0sH0g==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/twbs" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/bootstrap" + } + ], + "peerDependencies": { + "@popperjs/core": "^2.11.8" + } + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "dependencies": { + "fill-range": "^7.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.22.2", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.22.2.tgz", + "integrity": "sha512-0UgcrvQmBDvZHFGdYUehrCNIazki7/lUP3kkoi/r3YB2amZbFM9J43ZRkJTXBUZK4gmx56+Sqk9+Vs9mwZx9+A==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "caniuse-lite": "^1.0.30001565", + "electron-to-chromium": "^1.4.601", + "node-releases": "^2.0.14", + "update-browserslist-db": "^1.0.13" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/btoa": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/btoa/-/btoa-1.2.1.tgz", + "integrity": "sha512-SB4/MIGlsiVkMcHmT+pSmIPoNDoHg+7cMzmt3Uxt628MTz2487DKSqK/fuhFBrkuqrYv5UCEnACpF4dTFNKc/g==", + "bin": { + "btoa": "bin/btoa.js" + }, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/camelcase-keys": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-6.2.2.tgz", + "integrity": "sha512-YrwaA0vEKazPBkn0ipTiMpSajYDSe+KjQfrjhcBMxJt/znbvlHd8Pw/Vamaz5EB4Wfhs3SUR3Z9mwRu/P3s3Yg==", + "dev": true, + "dependencies": { + "camelcase": "^5.3.1", + "map-obj": "^4.0.0", + "quick-lru": "^4.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001571", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001571.tgz", + "integrity": "sha512-tYq/6MoXhdezDLFZuCO/TKboTzuQ/xR5cFdgXPfDtM7/kchBO3b4VWghE/OAi/DV7tTdhmLjZiZBZi1fA/GheQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ] + }, + "node_modules/canvg": { + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/canvg/-/canvg-3.0.10.tgz", + "integrity": "sha512-qwR2FRNO9NlzTeKIPIKpnTY6fqwuYSequ8Ru8c0YkYU7U0oW+hLUvWadLvAu1Rl72OMNiFhoLu4f8eUjQ7l/+Q==", + "optional": true, + "dependencies": { + "@babel/runtime": "^7.12.5", + "@types/raf": "^3.4.0", + "core-js": "^3.8.3", + "raf": "^3.4.1", + "regenerator-runtime": "^0.13.7", + "rgbcolor": "^1.0.1", + "stackblur-canvas": "^2.0.0", + "svg-pathdata": "^6.0.3" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/canvg/node_modules/regenerator-runtime": { + "version": "0.13.11", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", + "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==", + "optional": true + }, + "node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/chrome-trace-event": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz", + "integrity": "sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg==", + "dev": true, + "engines": { + "node": ">=6.0" + } + }, + "node_modules/clone-deep": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz", + "integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==", + "dev": true, + "dependencies": { + "is-plain-object": "^2.0.4", + "kind-of": "^6.0.2", + "shallow-clone": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true + }, + "node_modules/colorette": { + "version": "2.0.20", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", + "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", + "dev": true + }, + "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, + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true + }, + "node_modules/copy-anything": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/copy-anything/-/copy-anything-2.0.6.tgz", + "integrity": "sha512-1j20GZTsvKNkc4BY3NpMOM8tt///wY3FpIzozTOFO2ffuZcV61nojHXVKIy3WM+7ADCy5FVhdZYHYDdgTU0yJw==", + "dev": true, + "dependencies": { + "is-what": "^3.14.1" + }, + "funding": { + "url": "https://github.com/sponsors/mesqueeb" + } + }, + "node_modules/core-js": { + "version": "3.34.0", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.34.0.tgz", + "integrity": "sha512-aDdvlDder8QmY91H88GzNi9EtQi2TjvQhpCX6B1v/dAZHU1AuLgHvRh54RiOerpEhEW46Tkf+vgAViB/CWC0ag==", + "hasInstallScript": true, + "optional": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } + }, + "node_modules/core-js-compat": { + "version": "3.34.0", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.34.0.tgz", + "integrity": "sha512-4ZIyeNbW/Cn1wkMMDy+mvrRUxrwFNjKwbhCfQpDd+eLgYipDqp8oGFGtLmhh18EDPKA0g3VUBYOxQGGwvWLVpA==", + "dev": true, + "dependencies": { + "browserslist": "^4.22.2" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/css-line-break": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/css-line-break/-/css-line-break-2.1.0.tgz", + "integrity": "sha512-FHcKFCZcAha3LwfVBhCQbW2nCNbkZXn7KVUJcsT5/P8YmfsVja0FMPJr0B903j/E69HUphKiV9iQArX8SDYA4w==", + "devOptional": true, + "dependencies": { + "utrie": "^1.0.2" + } + }, + "node_modules/cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "dev": true, + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/csstype": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", + "dev": true + }, + "node_modules/d3": { + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/d3/-/d3-7.8.5.tgz", + "integrity": "sha512-JgoahDG51ncUfJu6wX/1vWQEqOflgXyl4MaHqlcSruTez7yhaRKR9i8VjjcQGeS2en/jnFivXuaIMnseMMt0XA==", + "dependencies": { + "d3-array": "3", + "d3-axis": "3", + "d3-brush": "3", + "d3-chord": "3", + "d3-color": "3", + "d3-contour": "4", + "d3-delaunay": "6", + "d3-dispatch": "3", + "d3-drag": "3", + "d3-dsv": "3", + "d3-ease": "3", + "d3-fetch": "3", + "d3-force": "3", + "d3-format": "3", + "d3-geo": "3", + "d3-hierarchy": "3", + "d3-interpolate": "3", + "d3-path": "3", + "d3-polygon": "3", + "d3-quadtree": "3", + "d3-random": "3", + "d3-scale": "4", + "d3-scale-chromatic": "3", + "d3-selection": "3", + "d3-shape": "3", + "d3-time": "3", + "d3-time-format": "4", + "d3-timer": "3", + "d3-transition": "3", + "d3-zoom": "3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-array": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz", + "integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==", + "dependencies": { + "internmap": "1 - 2" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-axis": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-axis/-/d3-axis-3.0.0.tgz", + "integrity": "sha512-IH5tgjV4jE/GhHkRV0HiVYPDtvfjHQlQfJHs0usq7M30XcSBvOotpmH1IgkcXsO/5gEQZD43B//fc7SRT5S+xw==", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-brush": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-brush/-/d3-brush-3.0.0.tgz", + "integrity": "sha512-ALnjWlVYkXsVIGlOsuWH1+3udkYFI48Ljihfnh8FZPF2QS9o+PzGLBslO0PjzVoHLZ2KCVgAM8NVkXPJB2aNnQ==", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-drag": "2 - 3", + "d3-interpolate": "1 - 3", + "d3-selection": "3", + "d3-transition": "3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-chord": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-chord/-/d3-chord-3.0.1.tgz", + "integrity": "sha512-VE5S6TNa+j8msksl7HwjxMHDM2yNK3XCkusIlpX5kwauBfXuyLAtNg9jCp/iHH61tgI4sb6R/EIMWCqEIdjT/g==", + "dependencies": { + "d3-path": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-color": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz", + "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-contour": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/d3-contour/-/d3-contour-4.0.2.tgz", + "integrity": "sha512-4EzFTRIikzs47RGmdxbeUvLWtGedDUNkTcmzoeyg4sP/dvCexO47AaQL7VKy/gul85TOxw+IBgA8US2xwbToNA==", + "dependencies": { + "d3-array": "^3.2.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-delaunay": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/d3-delaunay/-/d3-delaunay-6.0.4.tgz", + "integrity": "sha512-mdjtIZ1XLAM8bm/hx3WwjfHt6Sggek7qH043O8KEjDXN40xi3vx/6pYSVTwLjEgiXQTbvaouWKynLBiUZ6SK6A==", + "dependencies": { + "delaunator": "5" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-dispatch": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-3.0.1.tgz", + "integrity": "sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-drag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-drag/-/d3-drag-3.0.0.tgz", + "integrity": "sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-selection": "3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-dsv": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-dsv/-/d3-dsv-3.0.1.tgz", + "integrity": "sha512-UG6OvdI5afDIFP9w4G0mNq50dSOsXHJaRE8arAS5o9ApWnIElp8GZw1Dun8vP8OyHOZ/QJUKUJwxiiCCnUwm+Q==", + "dependencies": { + "commander": "7", + "iconv-lite": "0.6", + "rw": "1" + }, + "bin": { + "csv2json": "bin/dsv2json.js", + "csv2tsv": "bin/dsv2dsv.js", + "dsv2dsv": "bin/dsv2dsv.js", + "dsv2json": "bin/dsv2json.js", + "json2csv": "bin/json2dsv.js", + "json2dsv": "bin/json2dsv.js", + "json2tsv": "bin/json2dsv.js", + "tsv2csv": "bin/dsv2dsv.js", + "tsv2json": "bin/dsv2json.js" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-dsv/node_modules/commander": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", + "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", + "engines": { + "node": ">= 10" + } + }, + "node_modules/d3-ease": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz", + "integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-fetch": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-fetch/-/d3-fetch-3.0.1.tgz", + "integrity": "sha512-kpkQIM20n3oLVBKGg6oHrUchHM3xODkTzjMoj7aWQFq5QEM+R6E4WkzT5+tojDY7yjez8KgCBRoj4aEr99Fdqw==", + "dependencies": { + "d3-dsv": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-force": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-force/-/d3-force-3.0.0.tgz", + "integrity": "sha512-zxV/SsA+U4yte8051P4ECydjD/S+qeYtnaIyAs9tgHCqfguma/aAQDjo85A9Z6EKhBirHRJHXIgJUlffT4wdLg==", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-quadtree": "1 - 3", + "d3-timer": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-format": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.0.tgz", + "integrity": "sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-geo": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-geo/-/d3-geo-3.1.0.tgz", + "integrity": "sha512-JEo5HxXDdDYXCaWdwLRt79y7giK8SbhZJbFWXqbRTolCHFI5jRqteLzCsq51NKbUoX0PjBVSohxrx+NoOUujYA==", + "dependencies": { + "d3-array": "2.5.0 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-hierarchy": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/d3-hierarchy/-/d3-hierarchy-3.1.2.tgz", + "integrity": "sha512-FX/9frcub54beBdugHjDCdikxThEqjnR93Qt7PvQTOHxyiNCAlvMrHhclk3cD5VeAaq9fxmfRp+CnWw9rEMBuA==", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-interpolate": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz", + "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==", + "dependencies": { + "d3-color": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-path": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz", + "integrity": "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-polygon": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-polygon/-/d3-polygon-3.0.1.tgz", + "integrity": "sha512-3vbA7vXYwfe1SYhED++fPUQlWSYTTGmFmQiany/gdbiWgU/iEyQzyymwL9SkJjFFuCS4902BSzewVGsHHmHtXg==", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-quadtree": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-quadtree/-/d3-quadtree-3.0.1.tgz", + "integrity": "sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw==", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-random": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-random/-/d3-random-3.0.1.tgz", + "integrity": "sha512-FXMe9GfxTxqd5D6jFsQ+DJ8BJS4E/fT5mqqdjovykEB2oFbTMDVdg1MGFxfQW+FBOGoB++k8swBrgwSHT1cUXQ==", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-scale": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz", + "integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==", + "dependencies": { + "d3-array": "2.10.0 - 3", + "d3-format": "1 - 3", + "d3-interpolate": "1.2.0 - 3", + "d3-time": "2.1.1 - 3", + "d3-time-format": "2 - 4" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-scale-chromatic": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-scale-chromatic/-/d3-scale-chromatic-3.0.0.tgz", + "integrity": "sha512-Lx9thtxAKrO2Pq6OO2Ua474opeziKr279P/TKZsMAhYyNDD3EnCffdbgeSYN5O7m2ByQsxtuP2CSDczNUIZ22g==", + "dependencies": { + "d3-color": "1 - 3", + "d3-interpolate": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-selection": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz", + "integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-shape": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.0.tgz", + "integrity": "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==", + "dependencies": { + "d3-path": "^3.1.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz", + "integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==", + "dependencies": { + "d3-array": "2 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time-format": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz", + "integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==", + "dependencies": { + "d3-time": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-timer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz", + "integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-transition": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-transition/-/d3-transition-3.0.1.tgz", + "integrity": "sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==", + "dependencies": { + "d3-color": "1 - 3", + "d3-dispatch": "1 - 3", + "d3-ease": "1 - 3", + "d3-interpolate": "1 - 3", + "d3-timer": "1 - 3" + }, + "engines": { + "node": ">=12" + }, + "peerDependencies": { + "d3-selection": "2 - 3" + } + }, + "node_modules/d3-zoom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-zoom/-/d3-zoom-3.0.0.tgz", + "integrity": "sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-drag": "2 - 3", + "d3-interpolate": "1 - 3", + "d3-selection": "2 - 3", + "d3-transition": "2 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/decamelize-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/decamelize-keys/-/decamelize-keys-1.1.1.tgz", + "integrity": "sha512-WiPxgEirIV0/eIOMcnFBA3/IJZAZqKnwAwWyvvdi4lsr1WCN22nhdf/3db3DoZcUjTV2SqfzIwNyp6y2xs3nmg==", + "dev": true, + "dependencies": { + "decamelize": "^1.1.0", + "map-obj": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/decamelize-keys/node_modules/map-obj": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz", + "integrity": "sha512-7N/q3lyZ+LVCp7PzuxrJr4KMbBE2hW7BT7YNia330OFxIf4d3r5zVpicP2650l7CPN6RM9zOJRl3NGpqSiw3Eg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true + }, + "node_modules/delaunator": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/delaunator/-/delaunator-5.0.0.tgz", + "integrity": "sha512-AyLvtyJdbv/U1GkiS6gUUzclRoAY4Gs75qkMygJJhU75LW4DNuSF2RMzpxs9jw9Oz1BobHjTdkG3zdP55VxAqw==", + "dependencies": { + "robust-predicates": "^3.0.0" + } + }, + "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, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/diff-sequences": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", + "integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==", + "dev": true, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, + "dependencies": { + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/dompurify": { + "version": "2.4.7", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-2.4.7.tgz", + "integrity": "sha512-kxxKlPEDa6Nc5WJi+qRgPbOAbgTpSULL+vI3NUXsZMlkJxTqYI9wg5ZTay2sFrdZRWHPWNi+EdAhcJf81WtoMQ==", + "optional": true + }, + "node_modules/electron-to-chromium": { + "version": "1.4.616", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.616.tgz", + "integrity": "sha512-1n7zWYh8eS0L9Uy+GskE0lkBUNK83cXTVJI0pU3mGprFsbfSdAc15VTFbo+A+Bq4pwstmL30AVcEU3Fo463lNg==", + "dev": true + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "node_modules/enhanced-resolve": { + "version": "5.15.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.15.0.tgz", + "integrity": "sha512-LXYT42KJ7lpIKECr2mAXIaMldcNCh/7E0KBKOu4KSfkHmP+mZmSs+8V5gBAqisWBy0OO4W5Oyys0GO1Y8KtdKg==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.2.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/envinfo": { + "version": "7.11.0", + "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.11.0.tgz", + "integrity": "sha512-G9/6xF1FPbIw0TtalAMaVPpiq2aDEuKLXM314jPVAO9r2fo2a4BLqMNkmRS7O/xPPZ+COAhGIz3ETvHEV3eUcg==", + "dev": true, + "bin": { + "envinfo": "dist/cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/errno": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.8.tgz", + "integrity": "sha512-dJ6oBr5SQ1VSd9qkk7ByRgb/1SH4JZjCHSW/mr63/QcXO9zLVxvJ6Oy13nio03rxpSnVDDjFor75SjVeZWPW/A==", + "dev": true, + "optional": true, + "dependencies": { + "prr": "~1.0.1" + }, + "bin": { + "errno": "cli.js" + } + }, + "node_modules/error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dev": true, + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/es-module-lexer": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.4.1.tgz", + "integrity": "sha512-cXLGjP0c4T3flZJKQSuziYoq7MlT+rnvfZjfp7h+I7K9BNX54kP9nyWvdbwjQ4u1iWbOL4u96fgeZLToQlZC7w==", + "dev": true + }, + "node_modules/esbuild": { + "version": "0.19.10", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.19.10.tgz", + "integrity": "sha512-S1Y27QGt/snkNYrRcswgRFqZjaTG5a5xM3EQo97uNBnH505pdzSNe/HLBq1v0RO7iK/ngdbhJB6mDAp0OK+iUA==", + "dev": true, + "hasInstallScript": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.19.10", + "@esbuild/android-arm": "0.19.10", + "@esbuild/android-arm64": "0.19.10", + "@esbuild/android-x64": "0.19.10", + "@esbuild/darwin-arm64": "0.19.10", + "@esbuild/darwin-x64": "0.19.10", + "@esbuild/freebsd-arm64": "0.19.10", + "@esbuild/freebsd-x64": "0.19.10", + "@esbuild/linux-arm": "0.19.10", + "@esbuild/linux-arm64": "0.19.10", + "@esbuild/linux-ia32": "0.19.10", + "@esbuild/linux-loong64": "0.19.10", + "@esbuild/linux-mips64el": "0.19.10", + "@esbuild/linux-ppc64": "0.19.10", + "@esbuild/linux-riscv64": "0.19.10", + "@esbuild/linux-s390x": "0.19.10", + "@esbuild/linux-x64": "0.19.10", + "@esbuild/netbsd-x64": "0.19.10", + "@esbuild/openbsd-x64": "0.19.10", + "@esbuild/sunos-x64": "0.19.10", + "@esbuild/win32-arm64": "0.19.10", + "@esbuild/win32-ia32": "0.19.10", + "@esbuild/win32-x64": "0.19.10" + } + }, + "node_modules/escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/eslint": { + "version": "8.56.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.56.0.tgz", + "integrity": "sha512-Go19xM6T9puCOWntie1/P997aXxFsOi37JIHRWI514Hc6ZnaHGKY9xFhrU65RT6CcBEzZoGG1e6Nq+DT04ZtZQ==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.6.1", + "@eslint/eslintrc": "^2.1.4", + "@eslint/js": "8.56.0", + "@humanwhocodes/config-array": "^0.11.13", + "@humanwhocodes/module-importer": "^1.0.1", + "@nodelib/fs.walk": "^1.2.8", + "@ungap/structured-clone": "^1.2.0", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "doctrine": "^3.0.0", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^7.2.2", + "eslint-visitor-keys": "^3.4.3", + "espree": "^9.6.1", + "esquery": "^1.4.2", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "globals": "^13.19.0", + "graphemer": "^1.4.0", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "is-path-inside": "^3.0.3", + "js-yaml": "^4.1.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3", + "strip-ansi": "^6.0.1", + "text-table": "^0.2.0" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-formatter-pretty": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/eslint-formatter-pretty/-/eslint-formatter-pretty-4.1.0.tgz", + "integrity": "sha512-IsUTtGxF1hrH6lMWiSl1WbGaiP01eT6kzywdY1U+zLc0MP+nwEnUiS9UI8IaOTUhTeQJLlCEWIbXINBH4YJbBQ==", + "dev": true, + "dependencies": { + "@types/eslint": "^7.2.13", + "ansi-escapes": "^4.2.1", + "chalk": "^4.1.0", + "eslint-rule-docs": "^1.1.5", + "log-symbols": "^4.0.0", + "plur": "^4.0.0", + "string-width": "^4.2.0", + "supports-hyperlinks": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint-formatter-pretty/node_modules/@types/eslint": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-7.29.0.tgz", + "integrity": "sha512-VNcvioYDH8/FxaeTKkM4/TiTwt6pBV9E3OfGmvaw8tPl0rrHCJ4Ll15HRT+pMiFAf/MLQvAzC+6RzUMEL9Ceng==", + "dev": true, + "dependencies": { + "@types/estree": "*", + "@types/json-schema": "*" + } + }, + "node_modules/eslint-formatter-pretty/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/eslint-formatter-pretty/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/eslint-formatter-pretty/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/eslint-formatter-pretty/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/eslint-formatter-pretty/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/eslint-formatter-pretty/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/eslint-plugin-react-hooks": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.6.0.tgz", + "integrity": "sha512-oFc7Itz9Qxh2x4gNHStv3BqJq54ExXmfC+a1NjAta66IAN87Wu0R/QArgIS9qKzX3dXKPI9H5crl9QchNMY9+g==", + "dev": true, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0" + } + }, + "node_modules/eslint-plugin-react-refresh": { + "version": "0.4.5", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.4.5.tgz", + "integrity": "sha512-D53FYKJa+fDmZMtriODxvhwrO+IOqrxoEo21gMA0sjHdU6dPVH4OhyFip9ypl8HOF5RV5KdTo+rBQLvnY2cO8w==", + "dev": true, + "peerDependencies": { + "eslint": ">=7" + } + }, + "node_modules/eslint-rule-docs": { + "version": "1.1.235", + "resolved": "https://registry.npmjs.org/eslint-rule-docs/-/eslint-rule-docs-1.1.235.tgz", + "integrity": "sha512-+TQ+x4JdTnDoFEXXb3fDvfGOwnyNV7duH8fXWTPD1ieaBmB8omj7Gw/pMBBu4uI2uJCCU8APDaQJzWuXnTsH4A==", + "dev": true + }, + "node_modules/eslint-scope": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", + "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", + "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/eslint/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/eslint/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/eslint/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/eslint/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/eslint/node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/globals": { + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", + "dev": true, + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/eslint/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "node_modules/eslint/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/espree": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", + "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", + "dev": true, + "dependencies": { + "acorn": "^8.9.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.4.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz", + "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==", + "dev": true, + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "dev": true, + "engines": { + "node": ">=0.8.x" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true + }, + "node_modules/fast-glob": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", + "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true + }, + "node_modules/fastest-levenshtein": { + "version": "1.0.16", + "resolved": "https://registry.npmjs.org/fastest-levenshtein/-/fastest-levenshtein-1.0.16.tgz", + "integrity": "sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg==", + "dev": true, + "engines": { + "node": ">= 4.9.1" + } + }, + "node_modules/fastq": { + "version": "1.16.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.16.0.tgz", + "integrity": "sha512-ifCoaXsDrsdkWTtiNJX5uzHDsrck5TzfKKDcuFFTIrrc/BS076qgEIfoIy1VeZqViznfKiysPYTh/QeHtnIsYA==", + "dev": true, + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/fflate": { + "version": "0.4.8", + "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.4.8.tgz", + "integrity": "sha512-FJqqoDBR00Mdj9ppamLa/Y7vxm+PRmNWA67N846RvsoYVMKB4q3y/de5PA7gUmRMYK/8CMz2GDZQmCRN1wBcWA==" + }, + "node_modules/file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "dev": true, + "dependencies": { + "flat-cache": "^3.0.4" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", + "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", + "dev": true, + "bin": { + "flat": "cli.js" + } + }, + "node_modules/flat-cache": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", + "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==", + "dev": true, + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.3", + "rimraf": "^3.0.2" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/flatted": { + "version": "3.2.9", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.9.tgz", + "integrity": "sha512-36yxDn5H7OFZQla0/jFJmbIKTdZAQHngCedGxiMmpNfEZM0sdEeT+WczLQrjK6D7o2aiyLYDnkw0R3JK0Qv1RQ==", + "dev": true + }, + "node_modules/follow-redirects": { + "version": "1.15.3", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.3.tgz", + "integrity": "sha512-1VzOtuEM8pC9SFU1E+8KfTjZyMztRsgEfwQl44z8A25uy13jSzTj6dyK2Df52iV0vgHCfBwLhDWevLn95w5v6Q==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/font-awesome": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/font-awesome/-/font-awesome-4.7.0.tgz", + "integrity": "sha512-U6kGnykA/6bFmg1M/oT9EkFeIYv7JlX3bozwQJWiiLz6L0w3F5vBVPxHlwyX/vtNq1ckcpRKOB9f2Qal/VtFpg==", + "engines": { + "node": ">=0.10.3" + } + }, + "node_modules/form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "dev": true, + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true + }, + "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, + "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, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/glob-to-regexp": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", + "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", + "dev": true + }, + "node_modules/globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dev": true, + "dependencies": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true + }, + "node_modules/graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "dev": true + }, + "node_modules/hard-rejection": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/hard-rejection/-/hard-rejection-2.1.0.tgz", + "integrity": "sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/hasown": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.0.tgz", + "integrity": "sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/hosted-git-info": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-4.1.0.tgz", + "integrity": "sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/hosted-git-info/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/hosted-git-info/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "node_modules/html2canvas": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/html2canvas/-/html2canvas-1.4.1.tgz", + "integrity": "sha512-fPU6BHNpsyIhr8yyMpTLLxAbkaK8ArIBcmZIRiBLiDhjeqvXolaEmDGmELFuX9I4xDcaKKcJl+TKZLqruBbmWA==", + "devOptional": true, + "dependencies": { + "css-line-break": "^2.1.0", + "text-segmentation": "^1.0.3" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ignore": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.0.tgz", + "integrity": "sha512-g7dmpshy+gD7mh88OC9NwSGTKoc3kyLAZQRU1mt53Aw/vnvfXnbC+F/7F7QoYVKbV+KNvJx8wArewKy1vXMtlg==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/image-size": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/image-size/-/image-size-0.5.5.tgz", + "integrity": "sha512-6TDAlDPZxUFCv+fuOkIoXT/V/f3Qbq8e37p+YOiYrUv3v9cc3/6x78VdfPgFVaB9dZYeLUfKgHRebpkm/oP2VQ==", + "dev": true, + "optional": true, + "bin": { + "image-size": "bin/image-size.js" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dev": true, + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/import-local": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.1.0.tgz", + "integrity": "sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg==", + "dev": true, + "dependencies": { + "pkg-dir": "^4.2.0", + "resolve-cwd": "^3.0.0" + }, + "bin": { + "import-local-fixture": "fixtures/cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "dev": true, + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "node_modules/internmap": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz", + "integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==", + "engines": { + "node": ">=12" + } + }, + "node_modules/interpret": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-3.1.1.tgz", + "integrity": "sha512-6xwYfHbajpoF0xLW+iwLkhwgvLoZDfjYfoFNu8ftMoXINzwuymNLd9u/KmwtdT2GbR+/Cz66otEGEVVUHX9QLQ==", + "dev": true, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/ionicons": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/ionicons/-/ionicons-7.2.2.tgz", + "integrity": "sha512-I3iYIfc9Q9FRifWyFSwTAvbEABWlWY32i0sAVDDPGYnaIZVugkLCZFbEcrphW6ixVPg8tt1oLwalo/JJwbEqnA==", + "dev": true, + "dependencies": { + "@stencil/core": "^4.0.3" + } + }, + "node_modules/irregular-plurals": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/irregular-plurals/-/irregular-plurals-3.5.0.tgz", + "integrity": "sha512-1ANGLZ+Nkv1ptFb2pa8oG8Lem4krflKuX/gINiHJHjJUKaJHk/SXk5x6K3J+39/p0h1RQ2saROclJJ+QLvETCQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "dev": true + }, + "node_modules/is-core-module": { + "version": "2.13.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.1.tgz", + "integrity": "sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==", + "dev": true, + "dependencies": { + "hasown": "^2.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-plain-obj": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", + "integrity": "sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "dev": true, + "dependencies": { + "isobject": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-unicode-supported": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", + "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-what": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/is-what/-/is-what-3.14.1.tgz", + "integrity": "sha512-sNxgpk9793nzSs7bA6JQJGeIuRBQhAaNGG77kzYQgMkrID+lS6SlK07K5LaptscDlSaIgH+GPFzf+d75FVxozA==", + "dev": true + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, + "node_modules/isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/jest-diff": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.7.0.tgz", + "integrity": "sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==", + "dev": true, + "dependencies": { + "chalk": "^4.0.0", + "diff-sequences": "^29.6.3", + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-diff/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-diff/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest-diff/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/jest-diff/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/jest-diff/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-diff/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-get-type": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.6.3.tgz", + "integrity": "sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==", + "dev": true, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-worker": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", + "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", + "dev": true, + "dependencies": { + "@types/node": "*", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/jest-worker/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-worker/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/jquery": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.7.1.tgz", + "integrity": "sha512-m4avr8yL8kmFN8psrbFFFmB/If14iN5o9nw/NgnnM+kybDJpRsAynV2BsfpTYrTRysYUdADVD7CkUUizgkpLfg==", + "dev": true + }, + "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==" + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsesc": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", + "dev": true, + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true + }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true + }, + "node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/jspdf": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/jspdf/-/jspdf-2.5.1.tgz", + "integrity": "sha512-hXObxz7ZqoyhxET78+XR34Xu2qFGrJJ2I2bE5w4SM8eFaFEkW2xcGRVUss360fYelwRSid/jT078kbNvmoW0QA==", + "dependencies": { + "@babel/runtime": "^7.14.0", + "atob": "^2.1.2", + "btoa": "^1.2.1", + "fflate": "^0.4.8" + }, + "optionalDependencies": { + "canvg": "^3.0.6", + "core-js": "^3.6.0", + "dompurify": "^2.2.0", + "html2canvas": "^1.0.0-rc.5" + } + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/less": { + "version": "3.13.1", + "resolved": "https://registry.npmjs.org/less/-/less-3.13.1.tgz", + "integrity": "sha512-SwA1aQXGUvp+P5XdZslUOhhLnClSLIjWvJhmd+Vgib5BFIr9lMNlQwmwUNOjXThF/A0x+MCYYPeWEfeWiLRnTw==", + "dev": true, + "dependencies": { + "copy-anything": "^2.0.1", + "tslib": "^1.10.0" + }, + "bin": { + "lessc": "bin/lessc" + }, + "engines": { + "node": ">=6" + }, + "optionalDependencies": { + "errno": "^0.1.1", + "graceful-fs": "^4.1.2", + "image-size": "~0.5.0", + "make-dir": "^2.1.0", + "mime": "^1.4.1", + "native-request": "^1.0.5", + "source-map": "~0.6.0" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "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 + }, + "node_modules/loader-runner": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.0.tgz", + "integrity": "sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==", + "dev": true, + "engines": { + "node": ">=6.11.5" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash.debounce": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", + "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==", + "dev": true + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true + }, + "node_modules/log-symbols": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", + "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", + "dev": true, + "dependencies": { + "chalk": "^4.1.0", + "is-unicode-supported": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-symbols/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/log-symbols/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/log-symbols/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/log-symbols/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/log-symbols/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/log-symbols/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "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==", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/make-dir": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", + "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", + "dev": true, + "optional": true, + "dependencies": { + "pify": "^4.0.1", + "semver": "^5.6.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/make-dir/node_modules/semver": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "dev": true, + "optional": true, + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/map-obj": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-4.3.0.tgz", + "integrity": "sha512-hdN1wVrZbb29eBGiGjJbeP8JbKjq1urkHJ/LIP/NY48MZ1QVXUsQBV1G1zvYFHn1XE06cwjBsOI2K3Ulnj1YXQ==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/meow": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/meow/-/meow-9.0.0.tgz", + "integrity": "sha512-+obSblOQmRhcyBt62furQqRAQpNyWXo8BuQ5bN7dG8wmwQ+vwHKp/rCFD4CrTP8CsDQD1sjoZ94K417XEUk8IQ==", + "dev": true, + "dependencies": { + "@types/minimist": "^1.2.0", + "camelcase-keys": "^6.2.2", + "decamelize": "^1.2.0", + "decamelize-keys": "^1.1.0", + "hard-rejection": "^2.1.0", + "minimist-options": "4.1.0", + "normalize-package-data": "^3.0.0", + "read-pkg-up": "^7.0.1", + "redent": "^3.0.0", + "trim-newlines": "^3.0.0", + "type-fest": "^0.18.0", + "yargs-parser": "^20.2.3" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/meow/node_modules/type-fest": { + "version": "0.18.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.18.1.tgz", + "integrity": "sha512-OIAYXk8+ISY+qTOwkHtKqzAuxchoMiD9Udx+FSGQDuiRR+PJKJHc2NJAXlbhkGwTt/4/nKZxELY1w3ReWOL8mw==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", + "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "dev": true, + "dependencies": { + "braces": "^3.0.2", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "dev": true, + "optional": true, + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=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, + "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, + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/min-indent": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", + "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minimist-options": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/minimist-options/-/minimist-options-4.1.0.tgz", + "integrity": "sha512-Q4r8ghd80yhO/0j1O3B2BjweX3fiHg9cdOwjJd2J76Q135c+NDxGCqdYKQ1SKBuFfgWbAUzBfvYjPUEeNgqN1A==", + "dev": true, + "dependencies": { + "arrify": "^1.0.1", + "is-plain-obj": "^1.1.0", + "kind-of": "^6.0.3" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/mobx": { + "version": "6.12.0", + "resolved": "https://registry.npmjs.org/mobx/-/mobx-6.12.0.tgz", + "integrity": "sha512-Mn6CN6meXEnMa0a5u6a5+RKrqRedHBhZGd15AWLk9O6uFY4KYHzImdt8JI8WODo1bjTSRnwXhJox+FCUZhCKCQ==", + "dev": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mobx" + } + }, + "node_modules/mobx-react": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/mobx-react/-/mobx-react-9.1.0.tgz", + "integrity": "sha512-DeDRTYw4AlgHw8xEXtiZdKKEnp+c5/jeUgTbTQXEqnAzfkrgYRWP3p3Nv3Whc2CEcM/mDycbDWGjxKokQdlffg==", + "dev": true, + "dependencies": { + "mobx-react-lite": "^4.0.4" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mobx" + }, + "peerDependencies": { + "mobx": "^6.9.0", + "react": "^16.8.0 || ^17 || ^18" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + }, + "react-native": { + "optional": true + } + } + }, + "node_modules/mobx-react-lite": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/mobx-react-lite/-/mobx-react-lite-4.0.5.tgz", + "integrity": "sha512-StfB2wxE8imKj1f6T8WWPf4lVMx3cYH9Iy60bbKXEs21+HQ4tvvfIBZfSmMXgQAefi8xYEwQIz4GN9s0d2h7dg==", + "dev": true, + "dependencies": { + "use-sync-external-store": "^1.2.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mobx" + }, + "peerDependencies": { + "mobx": "^6.9.0", + "react": "^16.8.0 || ^17 || ^18" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + }, + "react-native": { + "optional": true + } + } + }, + "node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/nanoid": { + "version": "3.3.7", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", + "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/native-request": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/native-request/-/native-request-1.1.0.tgz", + "integrity": "sha512-uZ5rQaeRn15XmpgE0xoPL8YWqcX90VtCFglYwAgkvKM5e8fog+vePLAhHxuuv/gRkrQxIeh5U3q9sMNUrENqWw==", + "dev": true, + "optional": true + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true + }, + "node_modules/neo-async": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", + "dev": true + }, + "node_modules/node-releases": { + "version": "2.0.14", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz", + "integrity": "sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==", + "dev": true + }, + "node_modules/normalize-package-data": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-3.0.3.tgz", + "integrity": "sha512-p2W1sgqij3zMMyRC067Dg16bfzVH+w7hyegmpIvZ4JNjqtGOVAIvLmjBx3yP7YTe9vKJgkoNOPjwQGogDoMXFA==", + "dev": true, + "dependencies": { + "hosted-git-info": "^4.0.1", + "is-core-module": "^2.5.0", + "semver": "^7.3.4", + "validate-npm-package-license": "^3.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/optionator": { + "version": "0.9.3", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz", + "integrity": "sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==", + "dev": true, + "dependencies": { + "@aashutoshrathi/word-wrap": "^1.2.3", + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/pace-progress": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/pace-progress/-/pace-progress-1.0.2.tgz", + "integrity": "sha512-uhOU/vb2N6TM8gkoEGOLusLVghxJ+P1OtundjeoVzUhspa58JZ64YLsLJZVK8JU9utDeuHr6Xz/cXxdX3rWmtg==", + "deprecated": "Disclaimer, We no longer use this library internally and are focusing our efforts on open sourcing and maintaining projects that we do use and can meaningfully contribute to. Sorry for any frustrations with this project (we're happy to link to any fork that has an excited, commited maintainer)." + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true + }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/perfect-scrollbar": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/perfect-scrollbar/-/perfect-scrollbar-1.5.5.tgz", + "integrity": "sha512-dzalfutyP3e/FOpdlhVryN4AJ5XDVauVWxybSkLZmakFE2sS3y3pc4JnSprw8tGmHvkaG5Edr5T7LBTZ+WWU2g==", + "dev": true + }, + "node_modules/performance-now": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", + "integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==", + "optional": true + }, + "node_modules/picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", + "dev": true + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pify": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", + "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", + "dev": true, + "optional": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, + "dependencies": { + "find-up": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-dir/node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-dir/node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-dir/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pkg-dir/node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/plur": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/plur/-/plur-4.0.0.tgz", + "integrity": "sha512-4UGewrYgqDFw9vV6zNV+ADmPAUAfJPKtGvb/VdpQAx25X5f3xXdGdyOEVFwkl8Hl/tl7+xbeHqSEM+D5/TirUg==", + "dev": true, + "dependencies": { + "irregular-plurals": "^3.2.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/popper.js": { + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/popper.js/-/popper.js-1.16.1.tgz", + "integrity": "sha512-Wb4p1J4zyFTbM+u6WuO4XstYx4Ky9Cewe4DWrel7B0w6VVICvPwdOpotjzcf6eD8TsckVnIMNONQyPIUFOUbCQ==", + "deprecated": "You can find the new Popper v2 at @popperjs/core, this package is dedicated to the legacy v1", + "dev": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/popperjs" + } + }, + "node_modules/postcss": { + "version": "8.4.32", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.32.tgz", + "integrity": "sha512-D/kj5JNu6oo2EIy+XL/26JEDTlIbB8hw85G8StOE6L74RQAVVP5rej6wxCNqyMbR4RkPfqvezVbPw81Ngd6Kcw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "nanoid": "^3.3.7", + "picocolors": "^1.0.0", + "source-map-js": "^1.0.2" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-combine-duplicated-selectors": { + "version": "10.0.3", + "resolved": "https://registry.npmjs.org/postcss-combine-duplicated-selectors/-/postcss-combine-duplicated-selectors-10.0.3.tgz", + "integrity": "sha512-IP0BmwFloCskv7DV7xqvzDXqMHpwdczJa6ZvIW8abgHdcIHs9mCJX2ltFhu3EwA51ozp13DByng30+Ke+eIExA==", + "dev": true, + "dependencies": { + "postcss-selector-parser": "^6.0.4" + }, + "engines": { + "node": "^10.0.0 || ^12.0.0 || >=14.0.0" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-selector-parser": { + "version": "6.0.13", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.13.tgz", + "integrity": "sha512-EaV1Gl4mUEV4ddhDnv/xtj7sxwrwxdetHdWUGnT4VJQf+4d05v6lHYZr8N573k5Z0BViss7BDhfWtKS3+sfAqQ==", + "dev": true, + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/pretty-format": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", + "dev": true, + "dependencies": { + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/pretty-format/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "dev": true + }, + "node_modules/prr": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz", + "integrity": "sha512-yPw4Sng1gWghHQWj0B3ZggWUm4qVbPwPFcRG8KyxiU7J2OHFSoEHKS+EZ3fv5l1t9CyCiop6l/ZYeWbrgoQejw==", + "dev": true, + "optional": true + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/quick-lru": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-4.0.1.tgz", + "integrity": "sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/raf": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/raf/-/raf-3.4.1.tgz", + "integrity": "sha512-Sq4CW4QhwOHE8ucn6J34MqtZCeWFP2aQSmrlroYgqAV1PjStIhJXxYuTgUIfkEk7zTLjmIjLmU5q+fbD1NnOJA==", + "optional": true, + "dependencies": { + "performance-now": "^2.1.0" + } + }, + "node_modules/randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "dev": true, + "dependencies": { + "safe-buffer": "^5.1.0" + } + }, + "node_modules/react": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz", + "integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==", + "dependencies": { + "loose-envify": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz", + "integrity": "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==", + "dependencies": { + "loose-envify": "^1.1.0", + "scheduler": "^0.23.0" + }, + "peerDependencies": { + "react": "^18.2.0" + } + }, + "node_modules/react-dom-factories": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/react-dom-factories/-/react-dom-factories-1.0.2.tgz", + "integrity": "sha512-Bmic2N3oKji7vw9qjDr2dmwHvOATbFSnKy7EH0uT/qjvzIUsiXp6Yquk72LJ3WfMtRnq3ujXMMo7GsJeLPfFWw==" + }, + "node_modules/react-is": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", + "dev": true + }, + "node_modules/react-refresh": { + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.0.tgz", + "integrity": "sha512-wViHqhAd8OHeLS/IRMJjTSDHF3U9eWi62F/MledQGPdJGDhodXJ9PBLNGr6WWL7qlH12Mt3TyTpbS+hGXMjCzQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-router": { + "version": "6.21.1", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.21.1.tgz", + "integrity": "sha512-W0l13YlMTm1YrpVIOpjCADJqEUpz1vm+CMo47RuFX4Ftegwm6KOYsL5G3eiE52jnJpKvzm6uB/vTKTPKM8dmkA==", + "dependencies": { + "@remix-run/router": "1.14.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "react": ">=16.8" + } + }, + "node_modules/react-router-dom": { + "version": "6.21.1", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.21.1.tgz", + "integrity": "sha512-QCNrtjtDPwHDO+AO21MJd7yIcr41UetYt5jzaB9Y1UYaPTCnVuJq6S748g1dE11OQlCFIQg+RtAA1SEZIyiBeA==", + "dependencies": { + "@remix-run/router": "1.14.1", + "react-router": "6.21.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8" + } + }, + "node_modules/read-pkg": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz", + "integrity": "sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==", + "dev": true, + "dependencies": { + "@types/normalize-package-data": "^2.4.0", + "normalize-package-data": "^2.5.0", + "parse-json": "^5.0.0", + "type-fest": "^0.6.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/read-pkg-up": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-7.0.1.tgz", + "integrity": "sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==", + "dev": true, + "dependencies": { + "find-up": "^4.1.0", + "read-pkg": "^5.2.0", + "type-fest": "^0.8.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/read-pkg-up/node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/read-pkg-up/node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/read-pkg-up/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/read-pkg-up/node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/read-pkg-up/node_modules/type-fest": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", + "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/read-pkg/node_modules/hosted-git-info": { + "version": "2.8.9", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", + "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==", + "dev": true + }, + "node_modules/read-pkg/node_modules/normalize-package-data": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", + "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", + "dev": true, + "dependencies": { + "hosted-git-info": "^2.1.4", + "resolve": "^1.10.0", + "semver": "2 || 3 || 4 || 5", + "validate-npm-package-license": "^3.0.1" + } + }, + "node_modules/read-pkg/node_modules/semver": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "dev": true, + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/read-pkg/node_modules/type-fest": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.6.0.tgz", + "integrity": "sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/rechoir": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.8.0.tgz", + "integrity": "sha512-/vxpCXddiX8NGfGO/mTafwjq4aFa/71pvamip0++IQk3zG8cbCj0fifNPrjjF1XMXUne91jL9OoxmdykoEtifQ==", + "dev": true, + "dependencies": { + "resolve": "^1.20.0" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/redent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", + "integrity": "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==", + "dev": true, + "dependencies": { + "indent-string": "^4.0.0", + "strip-indent": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/regenerate": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", + "integrity": "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==", + "dev": true + }, + "node_modules/regenerate-unicode-properties": { + "version": "10.1.1", + "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.1.1.tgz", + "integrity": "sha512-X007RyZLsCJVVrjgEFVpLUTZwyOZk3oiL75ZcuYjlIWd6rNJtOjkBwQc5AsRrpbKVkxN6sklw/k/9m2jJYOf8Q==", + "dev": true, + "dependencies": { + "regenerate": "^1.4.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/regenerator-runtime": { + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", + "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==" + }, + "node_modules/regenerator-transform": { + "version": "0.15.2", + "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.15.2.tgz", + "integrity": "sha512-hfMp2BoF0qOk3uc5V20ALGDS2ddjQaLrdl7xrGXvAIow7qeWRM2VA2HuCHkUKk9slq3VwEwLNK3DFBqDfPGYtg==", + "dev": true, + "dependencies": { + "@babel/runtime": "^7.8.4" + } + }, + "node_modules/regexpu-core": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-5.3.2.tgz", + "integrity": "sha512-RAM5FlZz+Lhmo7db9L298p2vHP5ZywrVXmVXpmAD9GuL5MPH6t9ROw1iA/wfHkQ76Qe7AaPF0nGuim96/IrQMQ==", + "dev": true, + "dependencies": { + "@babel/regjsgen": "^0.8.0", + "regenerate": "^1.4.2", + "regenerate-unicode-properties": "^10.1.0", + "regjsparser": "^0.9.1", + "unicode-match-property-ecmascript": "^2.0.0", + "unicode-match-property-value-ecmascript": "^2.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/regjsparser": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.9.1.tgz", + "integrity": "sha512-dQUtn90WanSNl+7mQKcXAgZxvUe7Z0SqXlgzv0za4LwiUhyzBC58yQO3liFoUgu8GiJVInAhJjkj1N0EtQ5nkQ==", + "dev": true, + "dependencies": { + "jsesc": "~0.5.0" + }, + "bin": { + "regjsparser": "bin/parser" + } + }, + "node_modules/regjsparser/node_modules/jsesc": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", + "integrity": "sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA==", + "dev": true, + "bin": { + "jsesc": "bin/jsesc" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve": { + "version": "1.22.8", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", + "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", + "dev": true, + "dependencies": { + "is-core-module": "^2.13.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-cwd": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", + "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", + "dev": true, + "dependencies": { + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve-cwd/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, + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true, + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rgbcolor": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/rgbcolor/-/rgbcolor-1.0.1.tgz", + "integrity": "sha512-9aZLIrhRaD97sgVhtJOW6ckOEh6/GnvQtdVNfdZ6s67+3/XwLS9lBcQYzEEhYVeUowN7pRzMLsyGhK2i/xvWbw==", + "optional": true, + "engines": { + "node": ">= 0.8.15" + } + }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/robust-predicates": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/robust-predicates/-/robust-predicates-3.0.2.tgz", + "integrity": "sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg==" + }, + "node_modules/rollup": { + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.9.1.tgz", + "integrity": "sha512-pgPO9DWzLoW/vIhlSoDByCzcpX92bKEorbgXuZrqxByte3JFk2xSW2JEeAcyLc9Ru9pqcNNW+Ob7ntsk2oT/Xw==", + "dev": true, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.9.1", + "@rollup/rollup-android-arm64": "4.9.1", + "@rollup/rollup-darwin-arm64": "4.9.1", + "@rollup/rollup-darwin-x64": "4.9.1", + "@rollup/rollup-linux-arm-gnueabihf": "4.9.1", + "@rollup/rollup-linux-arm64-gnu": "4.9.1", + "@rollup/rollup-linux-arm64-musl": "4.9.1", + "@rollup/rollup-linux-riscv64-gnu": "4.9.1", + "@rollup/rollup-linux-x64-gnu": "4.9.1", + "@rollup/rollup-linux-x64-musl": "4.9.1", + "@rollup/rollup-win32-arm64-msvc": "4.9.1", + "@rollup/rollup-win32-ia32-msvc": "4.9.1", + "@rollup/rollup-win32-x64-msvc": "4.9.1", + "fsevents": "~2.3.2" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/rw": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/rw/-/rw-1.3.3.tgz", + "integrity": "sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==" + }, + "node_modules/saas": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/saas/-/saas-1.0.0.tgz", + "integrity": "sha512-FgayhFS18BlPfcyMcOqxD7PIyNyUjqyv8R+rsr3X2KRK2icEUL4uvWBF+lZ0IPqJIO2kUO0d20OXY+R+pdriqg==", + "dev": true + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "node_modules/scheduler": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.0.tgz", + "integrity": "sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==", + "dependencies": { + "loose-envify": "^1.1.0" + } + }, + "node_modules/schema-utils": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", + "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", + "dev": true, + "dependencies": { + "@types/json-schema": "^7.0.8", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/schema-utils/node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/schema-utils/node_modules/ajv-keywords": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", + "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", + "dev": true, + "peerDependencies": { + "ajv": "^6.9.1" + } + }, + "node_modules/schema-utils/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "node_modules/semver": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/semver/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/semver/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "node_modules/serialize-javascript": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.1.tgz", + "integrity": "sha512-owoXEFjWRllis8/M1Q+Cw5k8ZH40e3zhp/ovX+Xr/vi1qj6QesbyXXViFbpNvWvPNAD62SutwEXavefrLJWj7w==", + "dev": true, + "dependencies": { + "randombytes": "^2.1.0" + } + }, + "node_modules/shallow-clone": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", + "integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==", + "dev": true, + "dependencies": { + "kind-of": "^6.0.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/simple-line-icons": { + "version": "2.5.5", + "resolved": "https://registry.npmjs.org/simple-line-icons/-/simple-line-icons-2.5.5.tgz", + "integrity": "sha512-v52iGG/qFZTSD/70yOfA1lYoN6zmjEfDjzFT6U6jNSCsh/aeVjy+8sYyTXWz1w7tLIkN2XeMmG+cLJp/0zYK4Q==", + "dev": true, + "dependencies": { + "less": "^3.12.2" + } + }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-js": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", + "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dev": true, + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/spdx-correct": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.2.0.tgz", + "integrity": "sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==", + "dev": true, + "dependencies": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-exceptions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz", + "integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==", + "dev": true + }, + "node_modules/spdx-expression-parse": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", + "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", + "dev": true, + "dependencies": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-license-ids": { + "version": "3.0.16", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.16.tgz", + "integrity": "sha512-eWN+LnM3GR6gPu35WxNgbGl8rmY1AEmoMDvL/QD6zYmPWgywxWqJWNdLGT+ke8dKNWrcYgYjPpG5gbTfghP8rw==", + "dev": true + }, + "node_modules/stackblur-canvas": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/stackblur-canvas/-/stackblur-canvas-2.6.0.tgz", + "integrity": "sha512-8S1aIA+UoF6erJYnglGPug6MaHYGo1Ot7h5fuXx4fUPvcvQfcdw2o/ppCse63+eZf8PPidSu4v1JnmEVtEDnpg==", + "optional": true, + "engines": { + "node": ">=0.1.14" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-indent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz", + "integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==", + "dev": true, + "dependencies": { + "min-indent": "^1.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/supports-hyperlinks": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-2.3.0.tgz", + "integrity": "sha512-RpsAZlpWcDwOPQA22aCH4J0t7L8JmAvsCxfOSEwm7cQs3LshN36QaTkwd70DnBOXDWGssw2eUoc8CaRWT0XunA==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0", + "supports-color": "^7.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-hyperlinks/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-hyperlinks/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/svg-pathdata": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/svg-pathdata/-/svg-pathdata-6.0.3.tgz", + "integrity": "sha512-qsjeeq5YjBZ5eMdFuUa4ZosMLxgr5RZ+F+Y1OrDhuOCEInRMA3x74XdBtggJcj9kOeInz0WE+LgCPDkZFlBYJw==", + "optional": true, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/tapable": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", + "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/terser": { + "version": "5.26.0", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.26.0.tgz", + "integrity": "sha512-dytTGoE2oHgbNV9nTzgBEPaqAWvcJNl66VZ0BkJqlvp71IjO8CxdBx/ykCNb47cLnCmCvRZ6ZR0tLkqvZCdVBQ==", + "dev": true, + "dependencies": { + "@jridgewell/source-map": "^0.3.3", + "acorn": "^8.8.2", + "commander": "^2.20.0", + "source-map-support": "~0.5.20" + }, + "bin": { + "terser": "bin/terser" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/terser-webpack-plugin": { + "version": "5.3.9", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.9.tgz", + "integrity": "sha512-ZuXsqE07EcggTWQjXUj+Aot/OMcD0bMKGgF63f7UxYcu5/AJF53aIpK1YoP5xR9l6s/Hy2b+t1AM0bLNPRuhwA==", + "dev": true, + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.17", + "jest-worker": "^27.4.5", + "schema-utils": "^3.1.1", + "serialize-javascript": "^6.0.1", + "terser": "^5.16.8" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.1.0" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "uglify-js": { + "optional": true + } + } + }, + "node_modules/text-segmentation": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/text-segmentation/-/text-segmentation-1.0.3.tgz", + "integrity": "sha512-iOiPUo/BGnZ6+54OsWxZidGCsdU8YbE4PSpdPinp7DeMtUJNJBoJ/ouUSTJjHkh1KntHaltHl/gDs2FC4i5+Nw==", + "devOptional": true, + "dependencies": { + "utrie": "^1.0.2" + } + }, + "node_modules/text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "dev": true + }, + "node_modules/to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/trim-newlines": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-3.0.1.tgz", + "integrity": "sha512-c1PTsA3tYrIsLGkJkzHF+w9F2EyxfXGo4UyJc4pFL++FMjnq0HJS69T3M7d//gKrFKwy429bouPescbjecU+Zw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/ts-api-utils": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.0.3.tgz", + "integrity": "sha512-wNMeqtMz5NtwpT/UZGY5alT+VoKdSsOOP/kqHFcUW1P/VRhH2wJ48+DN2WwUliNbQ976ETwDL0Ifd2VVvgonvg==", + "dev": true, + "engines": { + "node": ">=16.13.0" + }, + "peerDependencies": { + "typescript": ">=4.2.0" + } + }, + "node_modules/ts-loader": { + "version": "9.5.1", + "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-9.5.1.tgz", + "integrity": "sha512-rNH3sK9kGZcH9dYzC7CewQm4NtxJTjSEVRJ2DyBZR7f8/wcta+iV44UPCXc5+nzDzivKtlzV6c9P4e+oFhDLYg==", + "dev": true, + "dependencies": { + "chalk": "^4.1.0", + "enhanced-resolve": "^5.0.0", + "micromatch": "^4.0.0", + "semver": "^7.3.4", + "source-map": "^0.7.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "typescript": "*", + "webpack": "^5.0.0" + } + }, + "node_modules/ts-loader/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/ts-loader/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/ts-loader/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/ts-loader/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/ts-loader/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/ts-loader/node_modules/source-map": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", + "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/ts-loader/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/tsd": { + "version": "0.30.0", + "resolved": "https://registry.npmjs.org/tsd/-/tsd-0.30.0.tgz", + "integrity": "sha512-aHL4rEuf3wwRzKCH8yqsE1oMAJYn7SAQ2JfWSgjr1e5/fqr+ggohQazECMpSoRAqSQeM/iIFugoyL/0eFwdTcA==", + "dev": true, + "dependencies": { + "@tsd/typescript": "~5.3.3", + "eslint-formatter-pretty": "^4.1.0", + "globby": "^11.0.1", + "jest-diff": "^29.0.3", + "meow": "^9.0.0", + "path-exists": "^4.0.0", + "read-pkg-up": "^7.0.0" + }, + "bin": { + "tsd": "dist/cli.js" + }, + "engines": { + "node": ">=14.16" + } + }, + "node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/typescript": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.3.tgz", + "integrity": "sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", + "dev": true + }, + "node_modules/unicode-canonical-property-names-ecmascript": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz", + "integrity": "sha512-yY5PpDlfVIU5+y/BSCxAJRBIS1Zc2dDG3Ujq+sR0U+JjUevW2JhocOF+soROYDSaAezOzOKuyyixhD6mBknSmQ==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-match-property-ecmascript": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz", + "integrity": "sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==", + "dev": true, + "dependencies": { + "unicode-canonical-property-names-ecmascript": "^2.0.0", + "unicode-property-aliases-ecmascript": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-match-property-value-ecmascript": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.1.0.tgz", + "integrity": "sha512-qxkjQt6qjg/mYscYMC0XKRn3Rh0wFPlfxB0xkt9CfyTvpX1Ra0+rAmdX2QyAobptSEvuy4RtpPRui6XkV+8wjA==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-property-aliases-ecmascript": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.1.0.tgz", + "integrity": "sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.0.13", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz", + "integrity": "sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "escalade": "^3.1.1", + "picocolors": "^1.0.0" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/use-sync-external-store": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz", + "integrity": "sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==", + "dev": true, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true + }, + "node_modules/utrie": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/utrie/-/utrie-1.0.2.tgz", + "integrity": "sha512-1MLa5ouZiOmQzUbjbu9VmjLzn1QLXBhwpUa7kdLUQK+KQ5KA9I1vk5U4YHe/X2Ch7PYnJfWuWT+VbuxbGwljhw==", + "devOptional": true, + "dependencies": { + "base64-arraybuffer": "^1.0.2" + } + }, + "node_modules/validate-npm-package-license": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", + "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", + "dev": true, + "dependencies": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, + "node_modules/vite": { + "version": "5.0.10", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.0.10.tgz", + "integrity": "sha512-2P8J7WWgmc355HUMlFrwofacvr98DAjoE52BfdbwQtyLH06XKwaL/FMnmKM2crF0iX4MpmMKoDlNCB1ok7zHCw==", + "dev": true, + "dependencies": { + "esbuild": "^0.19.3", + "postcss": "^8.4.32", + "rollup": "^4.2.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || >=20.0.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + }, + "node_modules/watchpack": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz", + "integrity": "sha512-Lcvm7MGST/4fup+ifyKi2hjyIAwcdI4HRgtvTpIUxBRhB+RFtUh8XtDOxUfctVCnhVi+QQj49i91OyvzkJl6cg==", + "dev": true, + "dependencies": { + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.1.2" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/webpack": { + "version": "5.89.0", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.89.0.tgz", + "integrity": "sha512-qyfIC10pOr70V+jkmud8tMfajraGCZMBWJtrmuBymQKCrLTRejBI8STDp1MCyZu/QTdZSeacCQYpYNQVOzX5kw==", + "dev": true, + "dependencies": { + "@types/eslint-scope": "^3.7.3", + "@types/estree": "^1.0.0", + "@webassemblyjs/ast": "^1.11.5", + "@webassemblyjs/wasm-edit": "^1.11.5", + "@webassemblyjs/wasm-parser": "^1.11.5", + "acorn": "^8.7.1", + "acorn-import-assertions": "^1.9.0", + "browserslist": "^4.14.5", + "chrome-trace-event": "^1.0.2", + "enhanced-resolve": "^5.15.0", + "es-module-lexer": "^1.2.1", + "eslint-scope": "5.1.1", + "events": "^3.2.0", + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.2.9", + "json-parse-even-better-errors": "^2.3.1", + "loader-runner": "^4.2.0", + "mime-types": "^2.1.27", + "neo-async": "^2.6.2", + "schema-utils": "^3.2.0", + "tapable": "^2.1.1", + "terser-webpack-plugin": "^5.3.7", + "watchpack": "^2.4.0", + "webpack-sources": "^3.2.3" + }, + "bin": { + "webpack": "bin/webpack.js" + }, + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependenciesMeta": { + "webpack-cli": { + "optional": true + } + } + }, + "node_modules/webpack-cli": { + "version": "5.1.4", + "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-5.1.4.tgz", + "integrity": "sha512-pIDJHIEI9LR0yxHXQ+Qh95k2EvXpWzZ5l+d+jIo+RdSm9MiHfzazIxwwni/p7+x4eJZuvG1AJwgC4TNQ7NRgsg==", + "dev": true, + "dependencies": { + "@discoveryjs/json-ext": "^0.5.0", + "@webpack-cli/configtest": "^2.1.1", + "@webpack-cli/info": "^2.0.2", + "@webpack-cli/serve": "^2.0.5", + "colorette": "^2.0.14", + "commander": "^10.0.1", + "cross-spawn": "^7.0.3", + "envinfo": "^7.7.3", + "fastest-levenshtein": "^1.0.12", + "import-local": "^3.0.2", + "interpret": "^3.1.1", + "rechoir": "^0.8.0", + "webpack-merge": "^5.7.3" + }, + "bin": { + "webpack-cli": "bin/cli.js" + }, + "engines": { + "node": ">=14.15.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "5.x.x" + }, + "peerDependenciesMeta": { + "@webpack-cli/generators": { + "optional": true + }, + "webpack-bundle-analyzer": { + "optional": true + }, + "webpack-dev-server": { + "optional": true + } + } + }, + "node_modules/webpack-cli/node_modules/commander": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz", + "integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==", + "dev": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/webpack-merge": { + "version": "5.10.0", + "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-5.10.0.tgz", + "integrity": "sha512-+4zXKdx7UnO+1jaN4l2lHVD+mFvnlZQP/6ljaJVb4SZiwIKeUnrT5l0gkT8z+n4hKpC+jpOv6O9R+gLtag7pSA==", + "dev": true, + "dependencies": { + "clone-deep": "^4.0.1", + "flat": "^5.0.2", + "wildcard": "^2.0.0" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/webpack-sources": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.3.tgz", + "integrity": "sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==", + "dev": true, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/webpack/node_modules/eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/webpack/node_modules/estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/wildcard": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/wildcard/-/wildcard-2.0.1.tgz", + "integrity": "sha512-CC1bOL87PIWSBhDcTrdeLo6eGT7mCFtrg0uIJtqJUFyK+eJnzl8A1niH56uu7KMa5XFrtiV+AQuHO3n7DsHnLQ==", + "dev": true + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true + }, + "node_modules/yargs-parser": { + "version": "20.2.9", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", + "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } +} diff --git a/src/GoodBooksReact/package.json b/src/GoodBooksReact/package.json new file mode 100644 index 000000000..d6dbe41c0 --- /dev/null +++ b/src/GoodBooksReact/package.json @@ -0,0 +1,66 @@ +{ + "name": "goodbooksreact", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "webpack": "webpack", + "gulp": "gulp", + "tsc": "tsc", + "dev": "vite", + "build": "tsc && vite build", + "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0", + "preview": "vite preview" + }, + "dependencies": { + "accounting": "^0.4.1", + "ag-grid": "^18.1.2", + "d3": "^7.8.5", + "font-awesome": "^4.7.0", + "jspdf": "^2.5.1", + "pace-progress": "^1.0.2", + "react": "^18.2.0", + "react-dom": "^18.2.0", + "react-dom-factories": "^1.0.2", + "react-router": "^6.21.1", + "react-router-dom": "^6.21.1" + }, + "devDependencies": { + "@babel/core": "^7.23.6", + "@babel/plugin-proposal-decorators": "^7.23.6", + "@babel/plugin-syntax-decorators": "^7.23.3", + "@babel/preset-env": "^7.23.6", + "@coreui/coreui": "^4.3.2", + "@coreui/icons": "^3.0.1", + "@types/accounting": "^0.4.5", + "@types/react": "^18.2.43", + "@types/react-dom": "^18.2.17", + "@types/react-router": "^5.1.20", + "@types/react-router-dom": "^5.3.3", + "@typescript-eslint/eslint-plugin": "^6.14.0", + "@typescript-eslint/parser": "^6.14.0", + "@vitejs/plugin-react": "^4.2.1", + "ag-grid-community": "^31.0.1", + "ajv": "^8.12.0", + "axios": "^1.6.2", + "bootstrap": "^5.3.2", + "eslint": "^8.55.0", + "eslint-plugin-react-hooks": "^4.6.0", + "eslint-plugin-react-refresh": "^0.4.5", + "html2canvas": "^1.4.1", + "ionicons": "^7.2.2", + "jquery": "^3.7.1", + "mobx": "^6.12.0", + "mobx-react": "^9.1.0", + "perfect-scrollbar": "^1.5.5", + "popper.js": "^1.16.1", + "saas": "^1.0.0", + "simple-line-icons": "^2.5.5", + "ts-loader": "^9.5.1", + "tsd": "^0.30.0", + "typescript": "^5.2.2", + "vite": "^5.0.8", + "webpack": "^5.89.0", + "webpack-cli": "^5.1.4" + } +} diff --git a/src/GoodBooksReact/public/staticwebapp.config.json b/src/GoodBooksReact/public/staticwebapp.config.json new file mode 100644 index 000000000..0bbb59398 --- /dev/null +++ b/src/GoodBooksReact/public/staticwebapp.config.json @@ -0,0 +1,9 @@ +{ + "navigationFallback": { + "rewrite": "index.html", + "exclude": ["/static/media/*.{png,jpg,jpeg,gif,bmp}", "/static/css/*"] + }, + "mimeTypes": { + ".json": "text/json" + } +} \ No newline at end of file diff --git a/src/GoodBooksReact/public/vite.svg b/src/GoodBooksReact/public/vite.svg new file mode 100644 index 000000000..e7b8dfb1b --- /dev/null +++ b/src/GoodBooksReact/public/vite.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/GoodBooksReact/src/App.tsx b/src/GoodBooksReact/src/App.tsx new file mode 100644 index 000000000..c7b3e0f8f --- /dev/null +++ b/src/GoodBooksReact/src/App.tsx @@ -0,0 +1,21 @@ +// import Heading from "./components/Heading" +// import Section from "./components/Section" +// import Counter from "./components/Counter" +// import List from "./components/List" +// import Home from "./components/Home/Index" +import { Outlet } from "react-router" +import Home from "./components/Home/Index" + +// https://www.youtube.com/watch?v=G7UzhrNX60o + +function App() { + + return ( + <> + + + + ) +} + +export default App diff --git a/src/GoodBooksReact/src/assets/react.svg b/src/GoodBooksReact/src/assets/react.svg new file mode 100644 index 000000000..6c87de9bb --- /dev/null +++ b/src/GoodBooksReact/src/assets/react.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/GoodBooksReact/src/components/Counter.tsx b/src/GoodBooksReact/src/components/Counter.tsx new file mode 100644 index 000000000..ba835722f --- /dev/null +++ b/src/GoodBooksReact/src/components/Counter.tsx @@ -0,0 +1,15 @@ +import { useState } from "react" + +const Counter = () => { + const [count, setCount] = useState(1) + return( + <> +

    Count is {count}

    + +      + + + ) +} + +export default Counter diff --git a/src/AccountGoWeb/Scripts/Financials/JournalEntry.tsx b/src/GoodBooksReact/src/components/Financials/JournalEntry.tsx similarity index 73% rename from src/AccountGoWeb/Scripts/Financials/JournalEntry.tsx rename to src/GoodBooksReact/src/components/Financials/JournalEntry.tsx index 50118e226..ff9a6248c 100644 --- a/src/AccountGoWeb/Scripts/Financials/JournalEntry.tsx +++ b/src/GoodBooksReact/src/components/Financials/JournalEntry.tsx @@ -1,25 +1,21 @@ import * as React from "react"; -import * as ReactDOM from "react-dom"; +//import * as ReactDOM from "react-dom"; import {observer} from "mobx-react"; -import {autorun, reaction, toJS, intercept} from 'mobx'; -import * as d3 from "d3"; import SelectVoucherType from "../Shared/Components/SelectVoucherType"; import SelectAccount from "../Shared/Components/SelectAccount"; import SelectDebitCredit from "../Shared/Components/SelectDebitCredit"; import JournalEntryStore from "../Shared/Stores/Financials/JournalEntryStore"; -import JournalEntryUIStore from "../Shared/Stores/Financials/JournalEntryUIStore"; -let store = new JournalEntryStore(); -let uiStore = new JournalEntryUIStore(store); +const store = new JournalEntryStore(); -@observer -class ValidationErrors extends React.Component{ +class ValidationErrors extends React.Component { render() { if (store.validationErrors !== undefined && store.validationErrors.length > 0) { - var errors = []; - store.validationErrors.map(function (item, index) { + const errors: string[] = []; + store.validationErrors.map(function (item: any, index: number) { + const errors: React.ReactNode[] = []; errors.push(
  • {item}
  • ); }); return ( @@ -33,15 +29,15 @@ class ValidationErrors extends React.Component{ return null; } } +const ObservedValidationErrors = observer(ValidationErrors); -@observer -class EditButton extends React.Component{ +class EditButton extends React.Component { onClickEditButton() { // Remove " disabledControl" from current className - var nodes = document.getElementById("divJournalEntryForm").getElementsByTagName('*'); - for (var i = 0; i < nodes.length; i++) { - var subStringLength = nodes[i].className.length - " disabledControl".length; - nodes[i].className = nodes[i].className.substring(0, subStringLength); + const nodes = document.getElementById("divJournalEntryForm")?.getElementsByTagName('*'); + for (let i = 0; i < nodes!.length; i++) { + const subStringLength = nodes![i].className.length - " disabledControl".length; + nodes![i].className = nodes![i].className.substring(0, subStringLength); } store.changedEditMode(true) } @@ -57,10 +53,10 @@ class EditButton extends React.Component{ ); } } +const ObservedEditButton = observer(EditButton); -@observer -class SaveJournalEntryButton extends React.Component{ - onClickSaveNewJournalEntry(e) { +class SaveJournalEntryButton extends React.Component { + onClickSaveNewJournalEntry() { store.saveNewJournalEntry(); } render() { @@ -73,10 +69,11 @@ class SaveJournalEntryButton extends React.Component{ ); } } +const ObservedSaveJournalEntryButton = observer(SaveJournalEntryButton); -class CancelJournalEntryButton extends React.Component{ +class CancelJournalEntryButton extends React.Component { cancelOnClick() { - let baseUrl = location.protocol + const baseUrl = location.protocol + "//" + location.hostname + (location.port && ":" + location.port) + "/"; @@ -92,9 +89,8 @@ class CancelJournalEntryButton extends React.Component{ } } -@observer -class PostJournalEntryButton extends React.Component{ - postOnClick(e) { +class PostJournalEntryButton extends React.Component { + postOnClick() { store.postJournal(); } @@ -107,33 +103,34 @@ class PostJournalEntryButton extends React.Component{ ); } } +const ObservedPostJournalEntryButton = observer(PostJournalEntryButton); -@observer -class JournalEntryHeader extends React.Component{ - onChangeJournalDate(e) { - store.changedJournalDate(e.target.value); +class JournalEntryHeader extends React.Component { + onChangeJournalDate(e: React.ChangeEvent) { + const dateValue = new Date(e.target.value); + store.changedJournalDate(dateValue); } - onChangeReferenceNo(e) { + onChangeReferenceNo(e: React.ChangeEvent) { store.changedReferenceNo(e.target.value); } - onChangeMemo(e) { + onChangeMemo(e: React.ChangeEvent) { store.changedMemo(e.target.value); } render() { return (
    -
    - General +
    + General
    Date
    + value={store.journalEntry.journalDate !== undefined ? store.journalEntry.journalDate.toISOString().substring(0, 10) : new Date(Date.now()).toISOString().substring(0, 10) } />
    Voucher
    @@ -159,33 +156,36 @@ class JournalEntryHeader extends React.Component{ ); } } +const ObservedJournalEntryHeader = observer(JournalEntryHeader); + +class JournalEntryLines extends React.Component { -@observer -class JournalEntryLines extends React.Component{ - onChangeAmount(e) { + onChangeAmount(e: any) { store.updateLineItem(e.target.name, "amount", e.target.value); } - onChangeMemo(e) { + + onChangeMemo(e: any) { store.updateLineItem(e.target.name, "memo", e.target.value); } - onClickRemoveLineItem(i, e) { + + onClickRemoveLineItem(i: number) { store.removeLineItem(i); } + addLineItem() { - var accountId, drcr, amount, memo; - accountId = (document.getElementById("optNewAccountId") as HTMLInputElement).value;; - drcr = (document.getElementById("optNewDebitCredit") as HTMLInputElement).value; - amount = (document.getElementById("txtNewAmount") as HTMLInputElement).value; - memo = (document.getElementById("txtNewMemo") as HTMLInputElement).value; + const accountId: string = (document.getElementById("optNewAccountId") as HTMLInputElement).value; + const drcr: string = (document.getElementById("optNewDebitCredit") as HTMLInputElement).value; + const amount: string = (document.getElementById("txtNewAmount") as HTMLInputElement).value; + const memo: string = (document.getElementById("txtNewMemo") as HTMLInputElement).value; - store.addLineItem(0, accountId, drcr, amount, memo); + store.addLineItem(0, Number(accountId), Number(drcr), Number(amount), memo); (document.getElementById("txtNewAmount") as HTMLInputElement).value = "0"; (document.getElementById("txtNewMemo") as HTMLInputElement).value = ""; } render() { - var lineItems = []; - for (var i = 0; i < store.journalEntry.journalEntryLines.length; i++) { + const lineItems: JSX.Element[] = []; + for (let i = 0; i < store.journalEntry.journalEntryLines.length; i++) { lineItems.push( @@ -236,28 +236,30 @@ class JournalEntryLines extends React.Component{ ); } } +const ObservedJournalEntryLines = observer(JournalEntryLines); -@observer -export default class JournalEntry extends React.Component { +class JournalEntry extends React.Component { render() { return (
    - +
    - - - + + +
    - + - +
    ); } } +const ObservedJournalEntry = observer(JournalEntry); + +export default ObservedJournalEntry; -ReactDOM.render(, document.getElementById("divJournalEntry")); \ No newline at end of file diff --git a/src/GoodBooksReact/src/components/Heading.tsx b/src/GoodBooksReact/src/components/Heading.tsx new file mode 100644 index 000000000..b3e1acadd --- /dev/null +++ b/src/GoodBooksReact/src/components/Heading.tsx @@ -0,0 +1,13 @@ +import { ReactElement } from "react" + +type HeadingProps = {title: string} + +const Heading = ({title}: HeadingProps): ReactElement => { + return( +
    +

    {title}

    +
    + ) +} + +export default Heading diff --git a/src/GoodBooksReact/src/components/Home/Index.tsx b/src/GoodBooksReact/src/components/Home/Index.tsx new file mode 100644 index 000000000..d763afbb2 --- /dev/null +++ b/src/GoodBooksReact/src/components/Home/Index.tsx @@ -0,0 +1,96 @@ +import * as React from "react"; + +type HomeProps = { + // specify the props type here +}; + +type HomeState = { + // specify the state type here +}; + +class Home extends React.Component { + render() { + return ( +
    + +
    + + ); + } +} + +export default Home; \ No newline at end of file diff --git a/src/GoodBooksReact/src/components/List.tsx b/src/GoodBooksReact/src/components/List.tsx new file mode 100644 index 000000000..af52353a1 --- /dev/null +++ b/src/GoodBooksReact/src/components/List.tsx @@ -0,0 +1,22 @@ +import { ReactNode } from "react" + +interface ListProps { + items: T[], + render: (item: T) => ReactNode +} + +const List = ({items, render}: ListProps) => { + return( +
    +
      + {items.map((item, index) => ( +
    • + {render(item)} +
    • + ))} +
    +
    + ) +} + +export default List \ No newline at end of file diff --git a/src/AccountGoWeb/Scripts/Purchasing/PurchaseInvoice.tsx b/src/GoodBooksReact/src/components/Purchasing/PurchaseInvoice.tsx similarity index 72% rename from src/AccountGoWeb/Scripts/Purchasing/PurchaseInvoice.tsx rename to src/GoodBooksReact/src/components/Purchasing/PurchaseInvoice.tsx index a32f93a01..5d7d2a8f1 100644 --- a/src/AccountGoWeb/Scripts/Purchasing/PurchaseInvoice.tsx +++ b/src/GoodBooksReact/src/components/Purchasing/PurchaseInvoice.tsx @@ -1,5 +1,4 @@ import * as React from "react"; -import * as ReactDOM from "react-dom"; import {observer} from "mobx-react"; import * as accounting from "accounting"; @@ -9,23 +8,24 @@ import SelectLineItem from "../Shared/Components/SelectLineItem"; import SelectLineMeasurement from "../Shared/Components/SelectLineMeasurement"; import PurchaseInvoiceStore from "../Shared/Stores/Purchasing/PurchaseInvoiceStore"; +import PurchaseInvoiceLine from "../Shared/Stores/Purchasing/PurchaseInvoiceLine"; -let purchId = window.location.search.split("?purchId=")[1]; -let invoiceId = window.location.search.split("?invoiceId=")[1]; +const purchId = window.location.search.split("?purchId=")[1]; +const invoiceId = window.location.search.split("?invoiceId=")[1]; -let store = new PurchaseInvoiceStore(purchId, invoiceId); +const store = new PurchaseInvoiceStore(Number(purchId), Number(invoiceId)); -let baseUrl = location.protocol - + "//" + location.hostname - + (location.port && ":" + location.port) - + "/"; +// let baseUrl: string = location.protocol +// + "//" + location.hostname +// + (location.port && ":" + location.port) +// + "/"; -@observer -class ValidationErrors extends React.Component{ +class ValidationErrors extends React.Component { render() { if (store.validationErrors !== undefined && store.validationErrors.length > 0) { - var errors = []; + const errors: string[] = []; store.validationErrors.map(function (item, index) { + const errors: React.ReactNode[] = []; errors.push(
  • {item}
  • ); }); return ( @@ -40,15 +40,15 @@ class ValidationErrors extends React.Component{ return null; } } +const ObservedValidationErrors = observer(ValidationErrors); -@observer -class EditButton extends React.Component{ +class EditButton extends React.Component { onClickEditButton() { // Remove " disabledControl" from current className - var nodes = document.getElementById("divPurchaseInvoiceForm").getElementsByTagName('*'); - for (var i = 0; i < nodes.length; i++) { - var subStringLength = nodes[i].className.length - " disabledControl".length; - nodes[i].className = nodes[i].className.substring(0, subStringLength); + const nodes = document.getElementById("divPurchaseInvoiceForm")?.getElementsByTagName('*'); + for (let i = 0; i < nodes!.length; i++) { + const subStringLength = nodes![i].className.length - " disabledControl".length; + nodes![i].className = nodes![i].className.substring(0, subStringLength); } store.changedEditMode(true); } @@ -64,10 +64,10 @@ class EditButton extends React.Component{ ); } } +const ObservedEditButton = observer(EditButton); -@observer -class SavePurchaseInvoiceButton extends React.Component{ - saveNewPurchaseInvoice(e) { +class SavePurchaseInvoiceButton extends React.Component{ + saveNewPurchaseInvoice() { store.savePurchaseInvoice(); } @@ -81,10 +81,11 @@ class SavePurchaseInvoiceButton extends React.Component{ ); } } +const ObservedSavePurchaseInvoiceButton = observer(SavePurchaseInvoiceButton); -class CancelPurchaseInvoiceButton extends React.Component{ +class CancelPurchaseInvoiceButton extends React.Component { cancelOnClick() { - let baseUrl = location.protocol + const baseUrl = location.protocol + "//" + location.hostname + (location.port && ":" + location.port) + "/"; @@ -101,9 +102,8 @@ class CancelPurchaseInvoiceButton extends React.Component{ } } -@observer -class PostButton extends React.Component{ - postOnClick(e) { +class PostButton extends React.Component { + postOnClick() { store.postInvoice(); } @@ -116,23 +116,26 @@ class PostButton extends React.Component{ ); } } +const ObservedPostButton = observer(PostButton); -@observer -class PurchaseInvoiceHeader extends React.Component{ - onChangeInvoiceDate(e) { - store.changedInvoiceDate(e.target.value); +class PurchaseInvoiceHeader extends React.Component { + onChangeInvoiceDate(e: React.ChangeEvent) { + store.changedInvoiceDate(new Date(e.target.value)); } - onChangeVendor(e) { - store.changedVendor(e.target.value); + + onChangeVendor(e: React.ChangeEvent) { + store.changedVendor(Number(e.target.value)); } - onChangeReferenceNo(e) { + + onChangeReferenceNo(e: React.ChangeEvent) { store.changedReferenceNo(e.target.value); } + render() { return (
    -
    - Vendor Information +
    + Vendor Information
    @@ -149,7 +152,7 @@ class PurchaseInvoiceHeader extends React.Component{
    Date
    + value={store.purchaseInvoice.invoiceDate !== undefined ? store.purchaseInvoice.invoiceDate.toISOString().substring(0, 10) : new Date(Date.now()).toISOString().substring(0, 10) } />
    Reference no.
    @@ -166,22 +169,21 @@ class PurchaseInvoiceHeader extends React.Component{ ); } } +const ObservedPurchaseInvoiceHeader = observer(PurchaseInvoiceHeader); -@observer -class PurchaseInvoiceLines extends React.Component{ +class PurchaseInvoiceLines extends React.Component { addLineItem() { if (store.validationLine()) { + const itemId: string = (document.getElementById("optNewItemId") as HTMLInputElement).value; + const measurementId: string = (document.getElementById("optNewMeasurementId") as HTMLInputElement).value; + const quantity: string = (document.getElementById("txtNewQuantity") as HTMLInputElement).value; + const amount: string = (document.getElementById("txtNewAmount") as HTMLInputElement).value; + const discount: string = (document.getElementById("txtNewDiscount") as HTMLInputElement).value; + const code = (document.getElementById("txtNewCode") as HTMLInputElement).value; - var itemId, measurementId, quantity, amount, discount; - itemId = (document.getElementById("optNewItemId") as HTMLInputElement).value; - measurementId = (document.getElementById("optNewMeasurementId") as HTMLInputElement).value; - quantity = (document.getElementById("txtNewQuantity") as HTMLInputElement).value; - amount = (document.getElementById("txtNewAmount") as HTMLInputElement).value; - discount = (document.getElementById("txtNewDiscount") as HTMLInputElement).value; - - //console.log(`itemId: ${itemId} | measurementId: ${measurementId} | quantity: ${quantity} | amount: ${amount} | discount: ${discount}`); - store.addLineItem(0, itemId, measurementId, quantity, amount, discount); + store.addLineItem(0, Number(itemId), Number(measurementId), + Number(quantity), Number(amount), Number(discount), code); (document.getElementById("optNewItemId") as HTMLInputElement).value = ""; (document.getElementById("txtNewCode") as HTMLInputElement).value = ""; @@ -192,48 +194,48 @@ class PurchaseInvoiceLines extends React.Component{ } } - onClickRemoveLineItem(i, e) { + onClickRemoveLineItem(i: number) { store.removeLineItem(i); } - onChangeQuantity(e) { + onChangeQuantity(e: any) { store.updateLineItem(e.target.name, "quantity", e.target.value); } - onChangeAmount(e) { + onChangeAmount(e: any) { store.updateLineItem(e.target.name, "amount", e.target.value); } - onChangeDiscount(e) { + onChangeDiscount(e: any) { store.updateLineItem(e.target.name, "discount", e.target.value); } - onChangeItem(e) { + onChangeItem(e: any) { store.updateLineItem(e.target.name, "itemId", e.target.value); } - onChangeCode(e) { + onChangeCode(e: any) { store.updateLineItem(e.target.name, "code", e.target.value); } - onFocusOutItem(e, isNew, i) { - - var isExisting = false; - for (var x = 0; x < store.commonStore.items.length; x++) { - if (store.commonStore.items[x].code == i.target.value) { + onFocusOutItem(e: any, isNew: boolean, i: any) { + let isExisting = false; + for (let x = 0; x < store.commonStore.items.length; x++) { + const lineItem = store.commonStore.items[x] as PurchaseInvoiceLine; + if (lineItem.code == i.target.value) { isExisting = true; if (isNew) { - (document.getElementById("optNewItemId") as HTMLInputElement).value = store.commonStore.items[x].id; - (document.getElementById("optNewMeasurementId") as HTMLInputElement).value = store.commonStore.items[x].sellMeasurementId; - (document.getElementById("txtNewAmount") as HTMLInputElement).value = store.commonStore.items[x].price; + (document.getElementById("optNewItemId") as HTMLInputElement).value = lineItem.id.toString(); + (document.getElementById("optNewMeasurementId") as HTMLInputElement).value = lineItem.measurementId.toString(); + (document.getElementById("txtNewAmount") as HTMLInputElement).value = lineItem.amount.toString(); (document.getElementById("txtNewQuantity") as HTMLInputElement).value = "1"; - document.getElementById("txtNewCode").style.borderColor = ""; + document.getElementById("txtNewCode")!.style.borderColor = ""; } else { - store.updateLineItem(e, "itemId", store.commonStore.items[x].id); - store.updateLineItem(e, "measurementId", store.commonStore.items[x].sellMeasurementId); - store.updateLineItem(e, "amount", store.commonStore.items[x].price); + store.updateLineItem(e, "itemId", lineItem.id); + store.updateLineItem(e, "measurementId", lineItem.measurementId); + store.updateLineItem(e, "amount", lineItem.amount); store.updateLineItem(e, "quantity", 1); i.target.style.borderColor = ""; } @@ -247,7 +249,7 @@ class PurchaseInvoiceLines extends React.Component{ (document.getElementById("optNewMeasurementId") as HTMLInputElement).value = ""; (document.getElementById("txtNewAmount") as HTMLInputElement).value = ""; (document.getElementById("txtNewQuantity") as HTMLInputElement).value = ""; - document.getElementById("txtNewCode").style.borderColor = '#FF0000'; + document.getElementById("txtNewCode")!.style.borderColor = '#FF0000'; //document.getElementById("txtNewCode").appendChild(span); // document.getElementById("txtNewCode").style.border = 'solid'; } @@ -265,9 +267,10 @@ class PurchaseInvoiceLines extends React.Component{ } render() { - var newLine = 0; - var lineItems = []; - for (var i = 0; i < store.purchaseInvoice.purchaseInvoiceLines.length; i++) { + let newLine = 0; + const lineItems = []; + let lastIndex = 0; + for (let i = 0; i < store.purchaseInvoice.purchaseInvoiceLines.length; i++) { newLine = newLine + 10; lineItems.push( @@ -286,6 +289,7 @@ class PurchaseInvoiceLines extends React.Component{ ); + lastIndex = i; } return (
    @@ -312,7 +316,7 @@ class PurchaseInvoiceLines extends React.Component{ - + @@ -331,9 +335,9 @@ class PurchaseInvoiceLines extends React.Component{ ); } } +const ObservedPurchaseInvoiceLines = observer(PurchaseInvoiceLines); -@observer -class PurchaseInvoiceTotals extends React.Component{ +class PurchaseInvoiceTotals extends React.Component { render() { return (
    @@ -352,28 +356,28 @@ class PurchaseInvoiceTotals extends React.Component{ } } -@observer -export default class PurchaseInvoice extends React.Component { +class PurchaseInvoice extends React.Component { render() { return (
    - +
    - - - + + +
    - + - +
    ); } } +const ObservedPurchaseInvoice = observer(PurchaseInvoice); -ReactDOM.render(, document.getElementById("divPurchaseInvoice")); \ No newline at end of file +export default ObservedPurchaseInvoice; diff --git a/src/AccountGoWeb/Scripts/Purchasing/PurchaseOrder.tsx b/src/GoodBooksReact/src/components/Purchasing/PurchaseOrder.tsx similarity index 70% rename from src/AccountGoWeb/Scripts/Purchasing/PurchaseOrder.tsx rename to src/GoodBooksReact/src/components/Purchasing/PurchaseOrder.tsx index d15f59f06..12dbc3456 100644 --- a/src/AccountGoWeb/Scripts/Purchasing/PurchaseOrder.tsx +++ b/src/GoodBooksReact/src/components/Purchasing/PurchaseOrder.tsx @@ -1,5 +1,4 @@ import * as React from "react"; -import * as ReactDOM from "react-dom"; import {observer} from "mobx-react"; import * as accounting from "accounting"; @@ -9,22 +8,23 @@ import SelectLineItem from "../Shared/Components/SelectLineItem"; import SelectLineMeasurement from "../Shared/Components/SelectLineMeasurement"; import PurchaseOrderStore from "../Shared/Stores/Purchasing/PurchaseOrderStore"; +import PurchaseOrderLine from "../Shared/Stores/Purchasing/PurchaseOrderLine"; -let purchId = window.location.search.split("?purchId=")[1]; +const purchId = window.location.search.split("?purchId=")[1]; -let store = new PurchaseOrderStore(purchId); +const store = new PurchaseOrderStore(Number(purchId)); -let baseUrl = location.protocol - + "//" + location.hostname - + (location.port && ":" + location.port) - + "/"; +// let baseUrl: string = location.protocol +// + "//" + location.hostname +// + (location.port && ":" + location.port) +// + "/"; -@observer -class ValidationErrors extends React.Component{ +class ValidationErrors extends React.Component { render() { if (store.validationErrors !== undefined && store.validationErrors.length > 0) { - var errors = []; + const errors: string[] = []; store.validationErrors.map(function (item, index) { + const errors: React.ReactNode[] = []; errors.push(
  • {item}
  • ); }); return ( @@ -39,22 +39,22 @@ class ValidationErrors extends React.Component{ return null; } } +const ObservedValidationErrors = observer(ValidationErrors); - - -@observer -class PurchaseOrderHeader extends React.Component{ - onChangeOrderDate(e) { - store.changedOrderDate(e.target.value); +class PurchaseOrderHeader extends React.Component { + onChangeOrderDate(e: React.ChangeEvent) { + store.changedOrderDate(new Date(e.target.value)); } - onChangeReferenceNo(e) { + + onChangeReferenceNo(e: React.ChangeEvent) { store.changedReferenceNo(e.target.value); } + render() { return (
    -
    - Vendor Information +
    + Vendor Information
    @@ -72,7 +72,7 @@ class PurchaseOrderHeader extends React.Component{
    Date
    + value={store.purchaseOrder.orderDate !== undefined ? store.purchaseOrder.orderDate.toString().substring(0, 10) : new Date(Date.now()).toISOString().substring(0, 10) } />
    Reference no.
    @@ -89,70 +89,67 @@ class PurchaseOrderHeader extends React.Component{ ); } } +const ObservedPurchaseOrderHeader = observer(PurchaseOrderHeader); -@observer -class PurchaseOrderLines extends React.Component{ +class PurchaseOrderLines extends React.Component { addLineItem() { if (store.validationLine()) { - - var itemId, measurementId, quantity, amount, discount, code; - itemId = (document.getElementById("optNewItemId") as HTMLInputElement).value; - measurementId = (document.getElementById("optNewMeasurementId") as HTMLInputElement).value; - quantity = (document.getElementById("txtNewQuantity") as HTMLInputElement).value; - amount = (document.getElementById("txtNewAmount") as HTMLInputElement).value; - discount = (document.getElementById("txtNewDiscount") as HTMLInputElement).value; - code = (document.getElementById("txtNewCode") as HTMLInputElement).value; - - //console.log(`itemId: ${itemId} | measurementId: ${measurementId} | quantity: ${quantity} | amount: ${amount} | discount: ${discount}`); - store.addLineItem(0, itemId, measurementId, quantity, amount, discount, code); - - (document.getElementById("optNewItemId") as HTMLInputElement).value = ""; - (document.getElementById("txtNewCode") as HTMLInputElement).value = ""; - (document.getElementById("optNewMeasurementId") as HTMLInputElement).value = ""; - (document.getElementById("txtNewQuantity") as HTMLInputElement).value = "1"; - (document.getElementById("txtNewAmount") as HTMLInputElement).value = ""; - (document.getElementById("txtNewDiscount") as HTMLInputElement).value = ""; - } + const itemId: string = (document.getElementById("optNewItemId") as HTMLInputElement).value; + const measurementId: string = (document.getElementById("optNewMeasurementId") as HTMLInputElement).value; + const quantity: string = (document.getElementById("txtNewQuantity") as HTMLInputElement).value; + const amount: string = (document.getElementById("txtNewAmount") as HTMLInputElement).value; + const discount: string = (document.getElementById("txtNewDiscount") as HTMLInputElement).value; + const code: string = (document.getElementById("txtNewCode") as HTMLInputElement).value; + + store.addLineItem(0, Number(itemId), Number(measurementId), Number(quantity), Number(amount), Number(discount), code); + + (document.getElementById("optNewItemId") as HTMLInputElement).value = ""; + (document.getElementById("txtNewCode") as HTMLInputElement).value = ""; + (document.getElementById("optNewMeasurementId") as HTMLInputElement).value = ""; + (document.getElementById("txtNewQuantity") as HTMLInputElement).value = "1"; + (document.getElementById("txtNewAmount") as HTMLInputElement).value = ""; + (document.getElementById("txtNewDiscount") as HTMLInputElement).value = ""; + } } - onClickRemoveLineItem(i, e) { + onClickRemoveLineItem(i: number) { store.removeLineItem(i); } - onChangeQuantity(e) { + onChangeQuantity(e: any) { store.updateLineItem(e.target.name, "quantity", e.target.value); } - onChangeAmount(e) { + onChangeAmount(e: any) { store.updateLineItem(e.target.name, "amount", e.target.value); } - onChangeDiscount(e) { + onChangeDiscount(e: any) { store.updateLineItem(e.target.name, "discount", e.target.value); } - onChangeCode(e) { + onChangeCode(e: any) { store.updateLineItem(e.target.name, "code", e.target.value); } - onFocusOutItem(e, isNew, i) { - - var isExisting = false; - for (var x = 0; x < store.commonStore.items.length; x++) { - if (store.commonStore.items[x].code == i.target.value) { + onFocusOutItem(e: any, isNew: boolean, i: any) { + let isExisting = false; + for (let x = 0; x < store.commonStore.items.length; x++) { + const lineItem = store.commonStore.items[x] as PurchaseOrderLine; + if (lineItem.code == i.target.value) { isExisting = true; if (isNew) { - (document.getElementById("optNewItemId") as HTMLInputElement).value = store.commonStore.items[x].id; - (document.getElementById("optNewMeasurementId") as HTMLInputElement).value = store.commonStore.items[x].sellMeasurementId; - (document.getElementById("txtNewAmount") as HTMLInputElement).value = store.commonStore.items[x].price; + (document.getElementById("optNewItemId") as HTMLInputElement).value = lineItem.id.toString(); + (document.getElementById("optNewMeasurementId") as HTMLInputElement).value = lineItem.measurementId.toString(); + (document.getElementById("txtNewAmount") as HTMLInputElement).value = lineItem.amount.toString(); (document.getElementById("txtNewQuantity") as HTMLInputElement).value = "1"; - document.getElementById("txtNewCode").style.borderColor = ""; + document.getElementById("txtNewCode")!.style.borderColor = ""; } else { - store.updateLineItem(e, "itemId", store.commonStore.items[x].id); - store.updateLineItem(e, "measurementId", store.commonStore.items[x].sellMeasurementId); - store.updateLineItem(e, "amount", store.commonStore.items[x].price); + store.updateLineItem(e, "itemId", lineItem.id); + store.updateLineItem(e, "measurementId", lineItem.measurementId); + store.updateLineItem(e, "amount", lineItem.amount); store.updateLineItem(e, "quantity", 1); i.target.style.borderColor = ""; } @@ -166,7 +163,7 @@ class PurchaseOrderLines extends React.Component{ (document.getElementById("optNewMeasurementId") as HTMLInputElement).value = ""; (document.getElementById("txtNewAmount") as HTMLInputElement).value = ""; (document.getElementById("txtNewQuantity") as HTMLInputElement).value = ""; - document.getElementById("txtNewCode").style.borderColor = '#FF0000'; + document.getElementById("txtNewCode")!.style.borderColor = '#FF0000'; //document.getElementById("txtNewCode").appendChild(span); // document.getElementById("txtNewCode").style.border = 'solid'; } @@ -184,9 +181,10 @@ class PurchaseOrderLines extends React.Component{ } render() { - var newLine = 0; - var lineItems = []; - for (var i = 0; i < store.purchaseOrder.purchaseOrderLines.length; i++) { + let newLine = 0; + const lineItems: JSX.Element[] = []; + let lastIndex: number = 0; + for (let i = 0; i < store.purchaseOrder.purchaseOrderLines.length; i++) { newLine = newLine + 10; lineItems.push( @@ -205,6 +203,7 @@ class PurchaseOrderLines extends React.Component{ ); + lastIndex = i; } return (
    @@ -231,7 +230,7 @@ class PurchaseOrderLines extends React.Component{ - + @@ -250,9 +249,9 @@ class PurchaseOrderLines extends React.Component{ ); } } +const ObservedPurchaseOrderLines = observer(PurchaseOrderLines); -@observer -class PurchaseOrderTotals extends React.Component{ +class PurchaseOrderTotals extends React.Component { render() { return (
    @@ -270,15 +269,15 @@ class PurchaseOrderTotals extends React.Component{ ); } } +const ObservedPurchaseOrderTotals = observer(PurchaseOrderTotals); -@observer -class EditButton extends React.Component { +class EditButton extends React.Component { onClickEditButton() { // Remove " disabledControl" from current className - var nodes = document.getElementById("divPurchaseOrderForm").getElementsByTagName('*'); - for (var i = 0; i < nodes.length; i++) { - var subStringLength = nodes[i].className.length - " disabledControl".length; - nodes[i].className = nodes[i].className.substring(0, subStringLength); + const nodes = document.getElementById("divPurchaseOrderForm")?.getElementsByTagName('*'); + for (let i = 0; i < nodes!.length; i++) { + const subStringLength = nodes![i].className.length - " disabledControl".length; + nodes![i].className = nodes![i].className.substring(0, subStringLength); } store.changedEditMode(true); @@ -296,9 +295,10 @@ class EditButton extends React.Component { } } +const ObservedEditButton = observer(EditButton); -class SavePurchaseOrderButton extends React.Component{ - saveNewPurchaseOrder(e) { +class SavePurchaseOrderButton extends React.Component { + saveNewPurchaseOrder() { store.savePurchaseOrder(); } @@ -309,9 +309,9 @@ class SavePurchaseOrderButton extends React.Component{ } } -class CancelPurchaseOrderButton extends React.Component{ +class CancelPurchaseOrderButton extends React.Component { cancelOnClick() { - let baseUrl = location.protocol + const baseUrl = location.protocol + "//" + location.hostname + (location.port && ":" + location.port) + "/"; @@ -328,18 +328,18 @@ class CancelPurchaseOrderButton extends React.Component{ } } -export default class AddPurchaseOrder extends React.Component { +class AddPurchaseOrder extends React.Component { render() { return (
    - +
    - - - - + + + +
    @@ -349,5 +349,6 @@ export default class AddPurchaseOrder extends React.Component { ); } } +const ObservedAddPurchaseOrder = observer(AddPurchaseOrder); -ReactDOM.render(, document.getElementById("divPurchaseOrder")); \ No newline at end of file +export default ObservedAddPurchaseOrder; \ No newline at end of file diff --git a/src/AccountGoWeb/Scripts/Sales/SalesInvoice.tsx b/src/GoodBooksReact/src/components/Sales/SalesInvoice.tsx similarity index 70% rename from src/AccountGoWeb/Scripts/Sales/SalesInvoice.tsx rename to src/GoodBooksReact/src/components/Sales/SalesInvoice.tsx index 036b28e23..97560e2eb 100644 --- a/src/AccountGoWeb/Scripts/Sales/SalesInvoice.tsx +++ b/src/GoodBooksReact/src/components/Sales/SalesInvoice.tsx @@ -1,31 +1,25 @@ import * as React from "react"; -import * as ReactDOM from "react-dom"; import {observer} from "mobx-react"; +// import from "accounting" npm package import * as accounting from "accounting"; import SelectCustomer from "../Shared/Components/SelectCustomer"; import SelectPaymentTerm from "../Shared/Components/SelectPaymentTerm"; import SelectLineItem from "../Shared/Components/SelectLineItem"; import SelectLineMeasurement from "../Shared/Components/SelectLineMeasurement"; - +import SalesInvoiceLine from "../Shared/Stores/Sales/SalesInvoiceLine"; import SalesInvoiceStore from "../Shared/Stores/Sales/SalesInvoiceStore"; -let orderId = window.location.search.split("?orderId=")[1]; -let invoiceId = window.location.search.split("?invoiceId=")[1]; - -let store = new SalesInvoiceStore(orderId, invoiceId); - -let baseUrl = location.protocol - + "//" + location.hostname - + (location.port && ":" + location.port) - + "/"; +const orderId = window.location.search.split("?orderId=")[1]; +const invoiceId = window.location.search.split("?invoiceId=")[1]; +const store = new SalesInvoiceStore(Number(orderId), Number(invoiceId)); -@observer -class ValidationErrors extends React.Component{ +class ValidationErrors extends React.Component { render() { if (store.validationErrors !== undefined && store.validationErrors.length > 0) { - var errors = []; - store.validationErrors.map(function (item, index) { + const errors: string[] = []; + store.validationErrors.map(function (item: string, index: number) { + const errors: React.ReactNode[] = []; errors.push(
  • {item}
  • ); }); return ( @@ -40,13 +34,14 @@ class ValidationErrors extends React.Component{ return null; } } -@observer -class EditButton extends React.Component{ +const ObservedValidationErrors = observer(ValidationErrors); + +class EditButton extends React.Component { onClickEditButton() { // Remove " disabledControl" from current className - var nodes = document.getElementById("divSalesInvoiceForm").getElementsByTagName('*'); - for (var i = 0; i < nodes.length; i++) { - var subStringLength = nodes[i].className.length - " disabledControl".length; + const nodes = document.getElementById("divSalesInvoiceForm")!.getElementsByTagName('*'); + for (let i = 0; i < nodes.length; i++) { + const subStringLength = nodes[i].className.length - " disabledControl".length; nodes[i].className = nodes[i].className.substring(0, subStringLength); } store.changedEditMode(true) @@ -63,13 +58,13 @@ class EditButton extends React.Component{ ); } } +const ObservedEditButton = observer(EditButton); -@observer -class SaveInvoiceButton extends React.Component{ - saveNewSalesInvoice(e) { +class SaveInvoiceButton extends React.Component { + saveNewSalesInvoice() { store.saveNewSalesInvoice(); } - onChangeReferenceNo(e) { + onChangeReferenceNo(e: React.ChangeEvent) { store.changedReferenceNo(e.target.value); } render() { @@ -82,10 +77,11 @@ class SaveInvoiceButton extends React.Component{ ); } } +const ObservedSaveInvoiceButton = observer(SaveInvoiceButton); -class CancelInvoiceButton extends React.Component{ +class CancelInvoiceButton extends React.Component { cancelOnClick() { - let baseUrl = location.protocol + const baseUrl = location.protocol + "//" + location.hostname + (location.port && ":" + location.port) + "/"; @@ -101,10 +97,10 @@ class CancelInvoiceButton extends React.Component{ ); } } +const ObservedCancelInvoiceButton = observer(CancelInvoiceButton); -@observer -class PrintButton extends React.Component{ - printOnClick(e) { +class PrintButton extends React.Component { + printOnClick() { store.printInvoice(); } @@ -118,10 +114,10 @@ class PrintButton extends React.Component{ ); } } +const ObservedPrintButton = observer(PrintButton); -@observer -class PostButton extends React.Component{ - postOnClick(e) { +class PostButton extends React.Component { + postOnClick() { store.postInvoice(); } @@ -135,20 +131,20 @@ class PostButton extends React.Component{ ); } } +const ObservedPostButton = observer(PostButton); -@observer -class SalesInvoiceHeader extends React.Component{ - onChangeInvoiceDate(e) { - store.changedInvoiceDate(e.target.value); +class SalesInvoiceHeader extends React.Component { + onChangeInvoiceDate(e: React.ChangeEvent) { + store.changedInvoiceDate(new Date(e.target.value)); } - onChangeReferenceNo(e) { + onChangeReferenceNo(e: React.ChangeEvent) { store.changedReferenceNo(e.target.value); } render() { return (
    - Customer Information + Customer Information
    @@ -165,7 +161,7 @@ class SalesInvoiceHeader extends React.Component{
    Date
    + value={store.salesInvoice.invoiceDate !== undefined ? store.salesInvoice.invoiceDate.toISOString().substring(0, 10) : new Date(Date.now()).toISOString().substring(0, 10) } />
    Reference no.
    @@ -177,23 +173,21 @@ class SalesInvoiceHeader extends React.Component{ ); } } +const ObservedSalesInvoiceHeader = observer(SalesInvoiceHeader); -@observer -class SalesInvoiceLines extends React.Component{ +class SalesInvoiceLines extends React.Component { addLineItem() { if (store.validationLine()) { - - var itemId, measurementId, quantity, amount, discount, code; - itemId = (document.getElementById("optNewItemId") as HTMLInputElement).value; - measurementId = (document.getElementById("optNewMeasurementId") as HTMLInputElement).value; - quantity = (document.getElementById("txtNewQuantity") as HTMLInputElement).value; - amount = (document.getElementById("txtNewAmount") as HTMLInputElement).value; - discount = (document.getElementById("txtNewDiscount") as HTMLInputElement).value; - code = (document.getElementById("txtNewCode") as HTMLInputElement).value; + const itemId: string = (document.getElementById("optNewItemId") as HTMLInputElement).value; + const measurementId: string = (document.getElementById("optNewMeasurementId") as HTMLInputElement).value; + const quantity: string = (document.getElementById("txtNewQuantity") as HTMLInputElement).value; + const amount: string = (document.getElementById("txtNewAmount") as HTMLInputElement).value; + const discount: string = (document.getElementById("txtNewDiscount") as HTMLInputElement).value; + const code: string = (document.getElementById("txtNewCode") as HTMLInputElement).value; //console.log(`itemId: ${itemId} | measurementId: ${measurementId} | quantity: ${quantity} | amount: ${amount} | discount: ${discount}`); - store.addLineItem(0, itemId, measurementId, quantity, amount, discount, code); + store.addLineItem(0, Number(itemId), Number(measurementId), Number(quantity), Number(amount), Number(discount), code); (document.getElementById("optNewItemId") as HTMLInputElement).value = ""; (document.getElementById("txtNewCode") as HTMLInputElement).value = ""; @@ -204,71 +198,68 @@ class SalesInvoiceLines extends React.Component{ } } - onClickRemoveLineItem(i, e) { + onClickRemoveLineItem(i: number) { store.removeLineItem(i); } - onChangeQuantity(e) { + onChangeQuantity(e: any) { store.updateLineItem(e.target.name, "quantity", e.target.value); } - onChangeAmount(e) { + onChangeAmount(e: any) { store.updateLineItem(e.target.name, "amount", e.target.value); } - onChangeDiscount(e) { + onChangeDiscount(e: any) { store.updateLineItem(e.target.name, "discount", e.target.value); } - onChangeCode(e) { + onChangeCode(e: any) { store.updateLineItem(e.target.name, "code", e.target.value); } - - onFocusOutItem(e, isNew, i) { - - var isExisting = false; - for (var x = 0; x < store.commonStore.items.length; x++) { - if (store.commonStore.items[x].code == i.target.value) { + onFocusOutItem(e: any, isNew: boolean, i: any) { + let isExisting: boolean = false; + for (let x = 0; x < store.commonStore.items.length; x++) { + const lineitem: SalesInvoiceLine = store.commonStore.items[x] as SalesInvoiceLine + if (lineitem.code === i.target.value) { isExisting = true; if (isNew) { - (document.getElementById("optNewItemId") as HTMLInputElement).value = store.commonStore.items[x].id; - (document.getElementById("optNewMeasurementId") as HTMLInputElement).value = store.commonStore.items[x].sellMeasurementId; - (document.getElementById("txtNewAmount") as HTMLInputElement).value = store.commonStore.items[x].price; + (document.getElementById("optNewItemId") as HTMLInputElement).value = lineitem.id.toString(); + (document.getElementById("optNewMeasurementId") as HTMLInputElement).value = lineitem.measurementId.toString(); + (document.getElementById("txtNewAmount") as HTMLInputElement).value = lineitem.amount.toString(); (document.getElementById("txtNewQuantity") as HTMLInputElement).value = "1"; - document.getElementById("txtNewCode").style.borderColor = ""; + document.getElementById("txtNewCode")!.style.borderColor = ""; } else { - store.updateLineItem(e, "itemId", store.commonStore.items[x].id); - store.updateLineItem(e, "measurementId", store.commonStore.items[x].sellMeasurementId); - store.updateLineItem(e, "amount", store.commonStore.items[x].price); - store.updateLineItem(e, "quantity", 1); + store.updateLineItem(e, "itemId", lineitem.id.toString()); + store.updateLineItem(e, "measurementId", lineitem.measurementId.toString()); + store.updateLineItem(e, "amount", lineitem.amount.toString()); + store.updateLineItem(e, "quantity", "1"); i.target.style.borderColor = ""; } } } - if (!isExisting) - + if (!isExisting) { if (isNew) { (document.getElementById("optNewItemId") as HTMLInputElement).value = ""; (document.getElementById("optNewMeasurementId") as HTMLInputElement).value = ""; (document.getElementById("txtNewAmount") as HTMLInputElement).value = ""; (document.getElementById("txtNewQuantity") as HTMLInputElement).value = ""; - document.getElementById("txtNewCode").style.borderColor = '#FF0000'; + document.getElementById("txtNewCode")!.style.borderColor = '#FF0000'; } else { - i.target.style.borderColor = "red"; - } - + } } render() { - var newLine = 0; - var lineItems = []; - for (var i = 0; i < store.salesInvoice.salesInvoiceLines.length; i++) { + let newLine = 0; + const lineItems: JSX.Element[] = []; + let lastIndex: number = 0; + for (let i = 0; i < store.salesInvoice.salesInvoiceLines.length; i++) { newLine = newLine + 10; lineItems.push( @@ -287,6 +278,7 @@ class SalesInvoiceLines extends React.Component{ ); + lastIndex = i; } return (
    @@ -313,7 +305,7 @@ class SalesInvoiceLines extends React.Component{ - + @@ -332,9 +324,9 @@ class SalesInvoiceLines extends React.Component{ ); } } +const ObservedSalesInvoiceLines = observer(SalesInvoiceLines); -@observer -class SalesInvoiceTotals extends React.Component{ +class SalesInvoiceTotals extends React.Component { render() { return (
    @@ -352,30 +344,31 @@ class SalesInvoiceTotals extends React.Component{ ); } } +const ObservedSalesInvoiceTotals = observer(SalesInvoiceTotals); -@observer -export default class SalesInvoice extends React.Component { +class SalesInvoice extends React.Component { render() { return (
    - +
    - - - - + + + +
    - - - - + + + +
    ); } } +const ObservedSalesInvoice = observer(SalesInvoice); -ReactDOM.render(, document.getElementById("divSalesInvoice")); \ No newline at end of file +export default ObservedSalesInvoice; diff --git a/src/AccountGoWeb/Scripts/Sales/SalesOrder.tsx b/src/GoodBooksReact/src/components/Sales/SalesOrder.tsx similarity index 72% rename from src/AccountGoWeb/Scripts/Sales/SalesOrder.tsx rename to src/GoodBooksReact/src/components/Sales/SalesOrder.tsx index 076104c5d..ee0015eb4 100644 --- a/src/AccountGoWeb/Scripts/Sales/SalesOrder.tsx +++ b/src/GoodBooksReact/src/components/Sales/SalesOrder.tsx @@ -1,5 +1,4 @@ import * as React from "react"; -import * as ReactDOM from "react-dom"; import { observer } from "mobx-react"; import * as accounting from "accounting"; @@ -9,22 +8,22 @@ import SelectLineItem from "../Shared/Components/SelectLineItem"; import SelectLineMeasurement from "../Shared/Components/SelectLineMeasurement"; import SalesOrderStore from "../Shared/Stores/Sales/SalesOrderStore"; +import SalesOrderLine from "../Shared/Stores/Sales/SalesOrderLine"; -let quotationId = window.location.search.split("?quotationId=")[1]; -let orderId = window.location.search.split("?orderId=")[1]; +const quotationId = window.location.search.split("?quotationId=")[1]; +const orderId = window.location.search.split("?orderId=")[1]; +const store = new SalesOrderStore(Number(quotationId), Number(orderId)); -let store = new SalesOrderStore(quotationId, orderId); - -let baseUrl = location.protocol + "//" + location.hostname + (location.port && ":" + location.port) + "/"; - -@observer -class ValidationErrors extends React.Component { +class ValidationErrors extends React.Component { render() { if (store.validationErrors !== undefined && store.validationErrors.length > 0) { - var errors = []; - store.validationErrors.map(function(item, index) { + const errors: string[] = []; + + store.validationErrors.map(function(item: string, index: number) { + const errors: React.ReactNode[] = []; errors.push(
  • {item}
  • ); }); + return (
      {errors}
    @@ -34,9 +33,10 @@ class ValidationErrors extends React.Component { return null; } } +const ObservedValidationErrors = observer(ValidationErrors); -class SaveOrderButton extends React.Component { - saveNewSalesOrder(e) { +class SaveOrderButton extends React.Component { + saveNewSalesOrder() { store.saveNewSalesOrder(); } @@ -52,9 +52,9 @@ class SaveOrderButton extends React.Component { } } -class CancelOrderButton extends React.Component { +class CancelOrderButton extends React.Component { cancelOnClick() { - let baseUrl = location.protocol + "//" + location.hostname + (location.port && ":" + location.port) + "/"; + const baseUrl = location.protocol + "//" + location.hostname + (location.port && ":" + location.port) + "/"; window.location.href = baseUrl + "sales/salesorders"; } @@ -73,21 +73,20 @@ class CancelOrderButton extends React.Component { } } -@observer -class SalesOrderHeader extends React.Component { - onChangeOrderDate(e) { - store.changedOrderDate(e.target.value); +class SalesOrderHeader extends React.Component { + onChangeOrderDate(e: React.ChangeEvent) { + store.changedOrderDate(new Date(e.target.value)); } - onChangeReferenceNo(e) { + onChangeReferenceNo(e: React.ChangeEvent) { store.changedReferenceNo(e.target.value); } render() { return ( -
    -
    - Customer Information +
    +
    + Customer Information
    -
    +
    Customer
    @@ -102,7 +101,7 @@ class SalesOrderHeader extends React.Component {
    Date
    + value={store.salesOrder.orderDate !== undefined ? store.salesOrder.orderDate.toString().substring(0, 10) : new Date(Date.now()).toISOString().substring(0, 10)} />
    Reference no.
    @@ -118,20 +117,19 @@ class SalesOrderHeader extends React.Component { ); } } +const ObservedSalesOrderHeader = observer(SalesOrderHeader); -@observer -class SalesOrderLines extends React.Component { +class SalesOrderLines extends React.Component { addLineItem() { if (store.validationLine()) { - var itemId, measurementId, quantity, amount, discount, code; - itemId = (document.getElementById("optNewItemId") as HTMLInputElement).value; - measurementId = (document.getElementById("optNewMeasurementId") as HTMLInputElement).value; - quantity = (document.getElementById("txtNewQuantity") as HTMLInputElement).value; - amount = (document.getElementById("txtNewAmount") as HTMLInputElement).value; - discount = (document.getElementById("txtNewDiscount") as HTMLInputElement).value; - code = (document.getElementById("txtNewCode") as HTMLInputElement).value; - //console.log(`itemId: ${itemId} | measurementId: ${measurementId} | quantity: ${quantity} | amount: ${amount} | discount: ${discount}`); - store.addLineItem(0, itemId, measurementId, quantity, amount, discount, code); + const itemId: string = (document.getElementById("optNewItemId") as HTMLInputElement).value; + const measurementId: string = (document.getElementById("optNewMeasurementId") as HTMLInputElement).value; + const quantity: string = (document.getElementById("txtNewQuantity") as HTMLInputElement).value; + const amount: string = (document.getElementById("txtNewAmount") as HTMLInputElement).value; + const discount: string = (document.getElementById("txtNewDiscount") as HTMLInputElement).value; + const code: string = (document.getElementById("txtNewCode") as HTMLInputElement).value; + + store.addLineItem(0, Number(itemId), Number(measurementId), Number(quantity), Number(amount), Number(discount), code); (document.getElementById("optNewItemId") as HTMLInputElement).value = ""; (document.getElementById("txtNewCode") as HTMLInputElement).value = ""; @@ -142,42 +140,43 @@ class SalesOrderLines extends React.Component { } } - onClickRemoveLineItem(i, e) { + onClickRemoveLineItem(i: number) { store.removeLineItem(i); } - onChangeQuantity(e) { + onChangeQuantity(e: any) { store.updateLineItem(e.target.name, "quantity", e.target.value); } - onChangeAmount(e) { + onChangeAmount(e: any) { store.updateLineItem(e.target.name, "amount", e.target.value); } - onChangeDiscount(e) { + onChangeDiscount(e: any) { store.updateLineItem(e.target.name, "discount", e.target.value); } - onChangeCode(e) { + onChangeCode(e: any) { store.updateLineItem(e.target.name, "code", e.target.value); } - onFocusOutItem(e, isNew, i) { - var isExisting = false; - for (var x = 0; x < store.commonStore.items.length; x++) { - if (store.commonStore.items[x].code == i.target.value) { + onFocusOutItem(e: any, isNew: boolean, i: any) { + let isExisting = false; + for (let x = 0; x < store.commonStore.items.length; x++) { + const lineItem = store.commonStore.items[x] as SalesOrderLine; + if (lineItem.code == i.target.value) { isExisting = true; if (isNew) { - (document.getElementById("optNewItemId") as HTMLInputElement).value = store.commonStore.items[x].id; + (document.getElementById("optNewItemId") as HTMLInputElement).value = lineItem.id.toString(); (document.getElementById("optNewMeasurementId") as HTMLInputElement).value = - store.commonStore.items[x].sellMeasurementId; - (document.getElementById("txtNewAmount") as HTMLInputElement).value = store.commonStore.items[x].price; + lineItem.measurementId.toString(); + (document.getElementById("txtNewAmount") as HTMLInputElement).value = lineItem.amount.toString(); (document.getElementById("txtNewQuantity") as HTMLInputElement).value = "1"; - document.getElementById("txtNewCode").style.borderColor = ""; + document.getElementById("txtNewCode")!.style.borderColor = ""; } else { - store.updateLineItem(e, "itemId", store.commonStore.items[x].id); - store.updateLineItem(e, "measurementId", store.commonStore.items[x].sellMeasurementId); - store.updateLineItem(e, "amount", store.commonStore.items[x].price); + store.updateLineItem(e, "itemId", lineItem.id); + store.updateLineItem(e, "measurementId", lineItem.measurementId); + store.updateLineItem(e, "amount", lineItem.amount); store.updateLineItem(e, "quantity", 1); i.target.style.borderColor = ""; } @@ -190,7 +189,7 @@ class SalesOrderLines extends React.Component { (document.getElementById("optNewMeasurementId") as HTMLInputElement).value = ""; (document.getElementById("txtNewAmount") as HTMLInputElement).value = ""; (document.getElementById("txtNewQuantity") as HTMLInputElement).value = ""; - document.getElementById("txtNewCode").style.borderColor = "#FF0000"; + document.getElementById("txtNewCode")!.style.borderColor = "#FF0000"; //document.getElementById("txtNewCode").appendChild(span); // document.getElementById("txtNewCode").style.border = 'solid'; } else { @@ -206,9 +205,10 @@ class SalesOrderLines extends React.Component { //{store.salesOrder.salesOrderLines[i].itemId} render() { - var newLine = 0; - var lineItems = []; - for (var i = 0; i < store.salesOrder.salesOrderLines.length; i++) { + let newLine = 0; + const lineItems: JSX.Element[] = []; + let lastIndex: number = 0; + for (let i = 0; i < store.salesOrder.salesOrderLines.length; i++) { newLine = newLine + 10; lineItems.push( @@ -266,6 +266,7 @@ class SalesOrderLines extends React.Component { ); + lastIndex = i; } return (
    @@ -299,7 +300,7 @@ class SalesOrderLines extends React.Component { className="form-control" type="text" id="txtNewCode" - onBlur={this.onFocusOutItem.bind(this, i, true)} + onBlur={this.onFocusOutItem.bind(this, lastIndex, true)} /> @@ -328,9 +329,9 @@ class SalesOrderLines extends React.Component { ); } } +const ObservedSalesOrderLines = observer(SalesOrderLines); -@observer -class SalesOrderTotals extends React.Component { +class SalesOrderTotals extends React.Component { render() { return (
    @@ -354,14 +355,14 @@ class SalesOrderTotals extends React.Component { ); } } +const ObservedSalesOrderTotals = observer(SalesOrderTotals); -@observer class EditButton extends React.Component { onClickEditButton() { // Remove " disabledControl" from current className - var nodes = document.getElementById("divSalesOrderForm").getElementsByTagName("*"); - for (var i = 0; i < nodes.length; i++) { - var subStringLength = nodes[i].className.length - " disabledControl".length; + const nodes = document.getElementById("divSalesOrderForm")!.getElementsByTagName("*"); + for (let i = 0; i < nodes.length; i++) { + const subStringLength = nodes[i].className.length - " disabledControl".length; nodes[i].className = nodes[i].className.substring(0, subStringLength); } @@ -386,19 +387,20 @@ class EditButton extends React.Component { ); } } +const ObservedEditButton = observer(EditButton); -export default class SalesOrder extends React.Component { +class SalesOrder extends React.Component { render() { return (
    - +
    - - - - + + + +
    @@ -408,5 +410,7 @@ export default class SalesOrder extends React.Component { ); } } +const ObservedSalesOrder = observer(SalesOrder); + +export default ObservedSalesOrder; -ReactDOM.render(, document.getElementById("divSalesOrder")); diff --git a/src/GoodBooksReact/src/components/Section.tsx b/src/GoodBooksReact/src/components/Section.tsx new file mode 100644 index 000000000..0f8e52bdd --- /dev/null +++ b/src/GoodBooksReact/src/components/Section.tsx @@ -0,0 +1,17 @@ +import { ReactNode } from "react" + +type SectionProps = { + title?: string, + children: ReactNode +} + +const Section = ({title = "My subheading", children}: SectionProps) => { + return( +
    +

    {title}

    +

    {children}

    +
    + ) +} + +export default Section diff --git a/src/AccountGoWeb/Scripts/Shared/Components/SelectAccount.tsx b/src/GoodBooksReact/src/components/Shared/Components/SelectAccount.tsx similarity index 75% rename from src/AccountGoWeb/Scripts/Shared/Components/SelectAccount.tsx rename to src/GoodBooksReact/src/components/Shared/Components/SelectAccount.tsx index 6674f29de..21d8e0c3d 100644 --- a/src/AccountGoWeb/Scripts/Shared/Components/SelectAccount.tsx +++ b/src/GoodBooksReact/src/components/Shared/Components/SelectAccount.tsx @@ -1,15 +1,14 @@ import * as React from "react"; import {observer} from "mobx-react"; -@observer -export default class SelectVoucherType extends React.Component{ - onChangeAccount(e) { +class SelectVoucherType extends React.Component{ + onChangeAccount(e: any) { if (this.props.row !== undefined) this.props.store.updateLineItem(this.props.row, "accountId", e.target.value); } render() { - var options = []; - this.props.store.commonStore.accounts.map(function (account) { + const options: JSX.Element[] = []; + this.props.store.commonStore.accounts.map(function (account: any) { return ( options.push() ); @@ -22,4 +21,7 @@ export default class SelectVoucherType extends React.Component{ ); } -} \ No newline at end of file +} +const ObservedSelectVoucherType = observer(SelectVoucherType); + +export default ObservedSelectVoucherType; diff --git a/src/AccountGoWeb/Scripts/Shared/Components/SelectCustomer.tsx b/src/GoodBooksReact/src/components/Shared/Components/SelectCustomer.tsx similarity index 76% rename from src/AccountGoWeb/Scripts/Shared/Components/SelectCustomer.tsx rename to src/GoodBooksReact/src/components/Shared/Components/SelectCustomer.tsx index a0840148d..5f51f8837 100644 --- a/src/AccountGoWeb/Scripts/Shared/Components/SelectCustomer.tsx +++ b/src/GoodBooksReact/src/components/Shared/Components/SelectCustomer.tsx @@ -1,12 +1,11 @@ import * as React from "react"; import {observer} from "mobx-react"; -@observer -export default class SelectCustomer extends React.Component{ - onChangeCustomer(e) { +class SelectCustomer extends React.Component{ + onChangeCustomer(e: any) { this.props.store.changedCustomer(e.target.value); - for (var i = 0; i < this.props.store.commonStore.customers.length; i++) { + for (let i = 0; i < this.props.store.commonStore.customers.length; i++) { if (this.props.store.commonStore.customers[i].id === parseInt(e.target.value)) { this.props.store.changedPaymentTerm(this.props.store.commonStore.customers[i].paymentTermId); } @@ -18,8 +17,8 @@ export default class SelectCustomer extends React.Component{ } render() { - var options = []; - this.props.store.commonStore.customers.map(function (customer) { + const options: JSX.Element[] = []; + this.props.store.commonStore.customers.map(function (customer: any) { return ( options.push() @@ -33,4 +32,7 @@ export default class SelectCustomer extends React.Component{ ); } -} \ No newline at end of file +} +const ObservedSelectCustomer = observer(SelectCustomer); + +export default ObservedSelectCustomer; diff --git a/src/AccountGoWeb/Scripts/Shared/Components/SelectDebitCredit.tsx b/src/GoodBooksReact/src/components/Shared/Components/SelectDebitCredit.tsx similarity index 73% rename from src/AccountGoWeb/Scripts/Shared/Components/SelectDebitCredit.tsx rename to src/GoodBooksReact/src/components/Shared/Components/SelectDebitCredit.tsx index f260047c1..22abb1bb3 100644 --- a/src/AccountGoWeb/Scripts/Shared/Components/SelectDebitCredit.tsx +++ b/src/GoodBooksReact/src/components/Shared/Components/SelectDebitCredit.tsx @@ -1,14 +1,13 @@ import * as React from "react"; import {observer} from "mobx-react"; -@observer -export default class SelectDebiCredit extends React.Component{ - onChangeDebitCredit(e) { +class SelectDebiCredit extends React.Component{ + onChangeDebitCredit(e: any) { if (this.props.row !== undefined) this.props.store.updateLineItem(this.props.row, "drcr", e.target.value); } render() { - var options = []; + const options: JSX.Element[] = []; options.push(); options.push(); @@ -19,4 +18,7 @@ export default class SelectDebiCredit extends React.Component{ ); } -} \ No newline at end of file +} +const ObservedSelectDebiCredit = observer(SelectDebiCredit); + +export default ObservedSelectDebiCredit; \ No newline at end of file diff --git a/src/AccountGoWeb/Scripts/Shared/Components/SelectLineItem.tsx b/src/GoodBooksReact/src/components/Shared/Components/SelectLineItem.tsx similarity index 90% rename from src/AccountGoWeb/Scripts/Shared/Components/SelectLineItem.tsx rename to src/GoodBooksReact/src/components/Shared/Components/SelectLineItem.tsx index cbb85ece2..dee644266 100644 --- a/src/AccountGoWeb/Scripts/Shared/Components/SelectLineItem.tsx +++ b/src/GoodBooksReact/src/components/Shared/Components/SelectLineItem.tsx @@ -1,9 +1,8 @@ import * as React from "react"; import {observer} from "mobx-react"; -@observer -export default class SelectLineItem extends React.Component{ - onChangeItem(e) { +class SelectLineItem extends React.Component{ + onChangeItem(e: any) { if (this.props.row !== undefined) this.props.store.updateLineItem(this.props.row, "itemId", e.target.value); @@ -36,11 +35,9 @@ export default class SelectLineItem extends React.Component{ } } - - for (var i = 0; i < this.props.store.commonStore.items.length; i++) { + for (let i = 0; i < this.props.store.commonStore.items.length; i++) { if (this.props.store.commonStore.items[i].id === parseInt(e.target.value)) { - if (this.props.row !== undefined) { this.props.store.updateLineItem(this.props.row, "measurementId", this.props.store.commonStore.items[i].sellMeasurementId); this.props.store.updateLineItem(this.props.row, "amount", this.props.store.commonStore.items[i].price); @@ -62,8 +59,8 @@ export default class SelectLineItem extends React.Component{ } render() { - var options = []; - this.props.store.commonStore.items.map(function (item) { + const options: JSX.Element[] = []; + this.props.store.commonStore.items.map(function (item: any) { return ( options.push() ); @@ -76,4 +73,7 @@ export default class SelectLineItem extends React.Component{ ); } -} \ No newline at end of file +} +const ObservedSelectLineItem = observer(SelectLineItem); + +export default ObservedSelectLineItem; \ No newline at end of file diff --git a/src/AccountGoWeb/Scripts/Shared/Components/SelectLineMeasurement.tsx b/src/GoodBooksReact/src/components/Shared/Components/SelectLineMeasurement.tsx similarity index 72% rename from src/AccountGoWeb/Scripts/Shared/Components/SelectLineMeasurement.tsx rename to src/GoodBooksReact/src/components/Shared/Components/SelectLineMeasurement.tsx index a9c95481b..58555a1da 100644 --- a/src/AccountGoWeb/Scripts/Shared/Components/SelectLineMeasurement.tsx +++ b/src/GoodBooksReact/src/components/Shared/Components/SelectLineMeasurement.tsx @@ -1,15 +1,14 @@ import * as React from "react"; import {observer} from "mobx-react"; -@observer -export default class SelectLineMeasurement extends React.Component{ - onChangeMeasurement(e) { +class SelectLineMeasurement extends React.Component{ + onChangeMeasurement(e: any) { if (this.props.row !== undefined) this.props.store.updateLineItem(this.props.row, "measurementId", e.target.value); } render() { - var options = []; - this.props.store.commonStore.measurements.map(function (measurement) { + const options: JSX.Element[] = []; + this.props.store.commonStore.measurements.map(function (measurement: any) { return ( options.push() ); @@ -22,4 +21,7 @@ export default class SelectLineMeasurement extends React.Component{ ); } -} \ No newline at end of file +} +const ObservedSelectLineMeasurement = observer(SelectLineMeasurement); + +export default ObservedSelectLineMeasurement; diff --git a/src/AccountGoWeb/Scripts/Shared/Components/SelectPaymentTerm.tsx b/src/GoodBooksReact/src/components/Shared/Components/SelectPaymentTerm.tsx similarity index 72% rename from src/AccountGoWeb/Scripts/Shared/Components/SelectPaymentTerm.tsx rename to src/GoodBooksReact/src/components/Shared/Components/SelectPaymentTerm.tsx index ee8bc7fc1..a779056de 100644 --- a/src/AccountGoWeb/Scripts/Shared/Components/SelectPaymentTerm.tsx +++ b/src/GoodBooksReact/src/components/Shared/Components/SelectPaymentTerm.tsx @@ -1,15 +1,14 @@ import * as React from "react"; import {observer} from "mobx-react"; -@observer -export default class SelectPaymentTerm extends React.Component{ - onChangePaymentTerm(e) { +class SelectPaymentTerm extends React.Component{ + onChangePaymentTerm(e: any) { this.props.store.changedPaymentTerm(e.target.value); } render() { - var options = []; - this.props.store.commonStore.paymentTerms.map(function (term) { + const options: JSX.Element[] = []; + this.props.store.commonStore.paymentTerms.map(function (term: any) { return ( options.push() ); @@ -22,4 +21,7 @@ export default class SelectPaymentTerm extends React.Component{ ); } -} \ No newline at end of file +} +const ObservedSelectPaymentTerm = observer(SelectPaymentTerm); + +export default ObservedSelectPaymentTerm; \ No newline at end of file diff --git a/src/AccountGoWeb/Scripts/Shared/Components/SelectQuotationStatus.tsx b/src/GoodBooksReact/src/components/Shared/Components/SelectQuotationStatus.tsx similarity index 70% rename from src/AccountGoWeb/Scripts/Shared/Components/SelectQuotationStatus.tsx rename to src/GoodBooksReact/src/components/Shared/Components/SelectQuotationStatus.tsx index faf7679ac..9c01ce70d 100644 --- a/src/AccountGoWeb/Scripts/Shared/Components/SelectQuotationStatus.tsx +++ b/src/GoodBooksReact/src/components/Shared/Components/SelectQuotationStatus.tsx @@ -1,15 +1,14 @@ import * as React from "react"; import {observer} from "mobx-react"; -@observer -export default class SelectQuotationStatus extends React.Component { - onChangeQuoteStatus(e) { +class SelectQuotationStatus extends React.Component { + onChangeQuoteStatus(e: any) { this.props.store.changedQuoteStatus(e.target.value); } render() { - var options = []; - this.props.store.commonStore.salesQuotationStatus.map(function (item) { + const options: React.ReactNode[] = []; + this.props.store.commonStore.salesQuotationStatus.map(function (item: any) { return ( options.push() ); @@ -24,6 +23,7 @@ export default class SelectQuotationStatus extends React.Component { ); } +} +const ObservedSelectQuotationStatus = observer(SelectQuotationStatus); - -} \ No newline at end of file +export default ObservedSelectQuotationStatus; diff --git a/src/AccountGoWeb/Scripts/Shared/Components/SelectVendor.tsx b/src/GoodBooksReact/src/components/Shared/Components/SelectVendor.tsx similarity index 76% rename from src/AccountGoWeb/Scripts/Shared/Components/SelectVendor.tsx rename to src/GoodBooksReact/src/components/Shared/Components/SelectVendor.tsx index 66aa7865f..7866304cd 100644 --- a/src/AccountGoWeb/Scripts/Shared/Components/SelectVendor.tsx +++ b/src/GoodBooksReact/src/components/Shared/Components/SelectVendor.tsx @@ -1,12 +1,11 @@ import * as React from "react"; import {observer} from "mobx-react"; -@observer -export default class SelectVendor extends React.Component{ - onChangeVendor(e) { +class SelectVendor extends React.Component{ + onChangeVendor(e: any) { this.props.store.changedVendor(e.target.value); - for (var i = 0; i < this.props.store.commonStore.vendors.length; i++) { + for (let i = 0; i < this.props.store.commonStore.vendors.length; i++) { if (this.props.store.commonStore.vendors[i].id === parseInt(e.target.value)) { this.props.store.changedPaymentTerm(this.props.store.commonStore.vendors[i].paymentTermId); @@ -18,8 +17,8 @@ export default class SelectVendor extends React.Component{ } render() { - var options = []; - this.props.store.commonStore.vendors.map(function (vendor) { + const options: JSX.Element[] = []; + this.props.store.commonStore.vendors.map(function (vendor: any) { return ( options.push() ); @@ -32,4 +31,7 @@ export default class SelectVendor extends React.Component{ ); } -} \ No newline at end of file +} +const ObservedSelectVendor = observer(SelectVendor); + +export default ObservedSelectVendor; diff --git a/src/AccountGoWeb/Scripts/Shared/Components/SelectVoucherType.tsx b/src/GoodBooksReact/src/components/Shared/Components/SelectVoucherType.tsx similarity index 59% rename from src/AccountGoWeb/Scripts/Shared/Components/SelectVoucherType.tsx rename to src/GoodBooksReact/src/components/Shared/Components/SelectVoucherType.tsx index ef11df2ae..a0f57c519 100644 --- a/src/AccountGoWeb/Scripts/Shared/Components/SelectVoucherType.tsx +++ b/src/GoodBooksReact/src/components/Shared/Components/SelectVoucherType.tsx @@ -1,14 +1,13 @@ import * as React from "react"; import {observer} from "mobx-react"; -@observer -export default class SelectVoucherType extends React.Component{ - onChangeVoucherType(e) { +class SelectVoucherType extends React.Component{ + onChangeVoucherType(e: any) { this.props.store.changedVoucherType(e.target.value); } render() { - var options = []; + const options: JSX.Element[] = []; options.push(); options.push(); options.push(); @@ -16,10 +15,19 @@ export default class SelectVoucherType extends React.Component{ options.push(); return ( - + + {options} ); } -} \ No newline at end of file +} + +const ObservedSelectVoucherType = observer(SelectVoucherType); + +export default ObservedSelectVoucherType; \ No newline at end of file diff --git a/src/GoodBooksReact/src/components/Shared/Config/index.tsx b/src/GoodBooksReact/src/components/Shared/Config/index.tsx new file mode 100644 index 000000000..636528c40 --- /dev/null +++ b/src/GoodBooksReact/src/components/Shared/Config/index.tsx @@ -0,0 +1,5 @@ +const Config = { + API_URL: `http://localhost:8001/api/`, +}; + +export default Config; \ No newline at end of file diff --git a/src/GoodBooksReact/src/components/Shared/Stores/AppState.tsx b/src/GoodBooksReact/src/components/Shared/Stores/AppState.tsx new file mode 100644 index 000000000..b48e3a804 --- /dev/null +++ b/src/GoodBooksReact/src/components/Shared/Stores/AppState.tsx @@ -0,0 +1,2 @@ +export default class AppState { +} \ No newline at end of file diff --git a/src/GoodBooksReact/src/components/Shared/Stores/Common/CommonStore.tsx b/src/GoodBooksReact/src/components/Shared/Stores/Common/CommonStore.tsx new file mode 100644 index 000000000..48f9b2748 --- /dev/null +++ b/src/GoodBooksReact/src/components/Shared/Stores/Common/CommonStore.tsx @@ -0,0 +1,152 @@ +import {makeObservable, observable} from 'mobx'; +import axios from "axios"; +import Config from '../../Config'; + +export default class CommonStore { + customers: string[] = []; + paymentTerms: string[] = []; + items: object[] = []; + measurements: string[] = []; + vendors: string[] = []; + accounts: string[] = []; + salesQuotationStatus: string[] = []; + + constructor() { + this.loadCustomersLookup(); + this.loadPaymentTermsLookup(); + this.loadItemsLookup(); + this.loadMeasurementsLookup(); + this.loadVendorsLookup(); + this.loadAccountsLookup(); + this.loadQuotationStatusLookup(); + + makeObservable(this, { + customers: observable, + paymentTerms: observable, + items: observable, + measurements: observable, + vendors: observable, + accounts: observable, + salesQuotationStatus: observable, + }); + } + + loadCustomersLookup() { + const localCustomers: string[] = []; + + axios.get(Config.API_URL + "common/customers") + .then(result => { + const data = result.data; + for (let i = 0; i < Object.keys(data).length; i++) { + localCustomers.push(data[i]); + } + this.customers = localCustomers; + }); + } + + loadPaymentTermsLookup() { + const localPaymentTerms: string[] = []; + axios.get(Config.API_URL + "common/paymentterms") + .then(result => { + const data = result.data; + for (let i = 0; i < Object.keys(data).length; i++) { + localPaymentTerms.push(data[i]); + } + this.paymentTerms = localPaymentTerms; + }); + } + + loadVendorsLookup() { + const localVendors: string[] = []; + + axios.get(Config.API_URL + "common/vendors") + .then(response => { + const data = response.data; + for (let i = 0; i < Object.keys(data).length; i++) { + localVendors.push(data[i]); + } + this.vendors = localVendors; + }) + .catch(error => { + console.error(error); + }); + } + + loadItemsLookup() { + const localItems: object[] = []; + + axios.get(Config.API_URL + "common/items") + .then(response => { + const data = response.data; + for (let i = 0; i < Object.keys(data).length; i++) { + localItems.push(data[i]); + } + this.items = localItems; + }) + .catch(error => { + console.error(error); + }); + } + + loadMeasurementsLookup() { + const localMeasurements: string[] = []; + axios.get(Config.API_URL + "common/measurements") + .then(result => { + const data = result.data; + for (let i = 0; i < Object.keys(data).length; i++) { + localMeasurements.push(data[i]); + } + this.measurements = localMeasurements; + }); + } + + loadQuotationStatusLookup() { + const localQuotationStatus: string[] = []; + axios.get(Config.API_URL + "common/salesquotationstatus") + .then(result => { + const data = result.data; + for (let i = 0; i < Object.keys(data).length; i++) + { + localQuotationStatus.push(data[i]); + } + this.salesQuotationStatus = localQuotationStatus; + }) + } + + loadAccountsLookup() { + const localAccounts: string[] = []; + axios.get(Config.API_URL + "common/postingaccounts") + .then(result => { + const data: string[] = result.data; + for (let i = 0; i < Object.keys(data).length; i++) { + localAccounts.push(data[i]); + } + this.accounts = localAccounts; + }); + } + + getApplicableTaxes(itemId: number, partyId: number) { + const result = axios.get(Config.API_URL + "tax/gettax?itemId=" + itemId + "&partyId=" + partyId); + result.then(function (result) { + return result.data; + }); + } + + getSalesLineTaxAmount(quantity: number, amount: number, discount: number, taxes: Array<{ rate: number }>) { + let lineTaxTotal = 0; + amount = (amount * quantity) - discount; + taxes.map(function (tax) { + lineTaxTotal = lineTaxTotal + (amount - (amount / (1 + (tax.rate / 100)))); + }); + return lineTaxTotal; + } + + getPurhcaseLineTaxAmount(quantity: number, amount: number, discount: number, taxes: Array<{ rate: number }>) { + let lineTaxTotal = 0; + amount = (amount * quantity) - discount; + taxes.map(function (tax) { + lineTaxTotal = lineTaxTotal + (amount - (amount / (1 + (tax.rate / 100)))); + }); + return lineTaxTotal; + } +} \ No newline at end of file diff --git a/src/AccountGoWeb/Scripts/Shared/Stores/Financials/JournalEntry.tsx b/src/GoodBooksReact/src/components/Shared/Stores/Financials/JournalEntry.tsx similarity index 73% rename from src/AccountGoWeb/Scripts/Shared/Stores/Financials/JournalEntry.tsx rename to src/GoodBooksReact/src/components/Shared/Stores/Financials/JournalEntry.tsx index a368e9362..c64e11634 100644 --- a/src/AccountGoWeb/Scripts/Shared/Stores/Financials/JournalEntry.tsx +++ b/src/GoodBooksReact/src/components/Shared/Stores/Financials/JournalEntry.tsx @@ -2,10 +2,10 @@ export default class JournalEntry { id: number; - voucherType: number; - journalDate: Date; - referenceNo: string; - memo: string; + voucherType: number = 0; + journalDate: Date = new Date(); + referenceNo: string = ""; + memo: string = ""; posted: boolean; readyForPosting: boolean; journalEntryLines: JournalEntryLine[] = []; diff --git a/src/AccountGoWeb/Scripts/Shared/Stores/Financials/JournalEntryLine.tsx b/src/GoodBooksReact/src/components/Shared/Stores/Financials/JournalEntryLine.tsx similarity index 52% rename from src/AccountGoWeb/Scripts/Shared/Stores/Financials/JournalEntryLine.tsx rename to src/GoodBooksReact/src/components/Shared/Stores/Financials/JournalEntryLine.tsx index f4ab923cd..934894a2c 100644 --- a/src/AccountGoWeb/Scripts/Shared/Stores/Financials/JournalEntryLine.tsx +++ b/src/GoodBooksReact/src/components/Shared/Stores/Financials/JournalEntryLine.tsx @@ -1,11 +1,11 @@ export default class JournalEntryLine { id: number = 0; - accountId: number; - drcr: number; - amount: number; - memo: string; + accountId: number = 0; + drcr: number = 0; + amount: number = 0; + memo: string = ""; - constructor(id, accountId, drcr, amount, memo) { + constructor(id: number, accountId: number, drcr: number, amount: number, memo: string) { this.id = id; this.accountId = accountId; this.drcr = drcr; diff --git a/src/AccountGoWeb/Scripts/Shared/Stores/Financials/JournalEntryStore.tsx b/src/GoodBooksReact/src/components/Shared/Stores/Financials/JournalEntryStore.tsx similarity index 59% rename from src/AccountGoWeb/Scripts/Shared/Stores/Financials/JournalEntryStore.tsx rename to src/GoodBooksReact/src/components/Shared/Stores/Financials/JournalEntryStore.tsx index c54244074..86a3a1e6a 100644 --- a/src/AccountGoWeb/Scripts/Shared/Stores/Financials/JournalEntryStore.tsx +++ b/src/GoodBooksReact/src/components/Shared/Stores/Financials/JournalEntryStore.tsx @@ -1,27 +1,25 @@ -import {observable, extendObservable, action, intercept, reaction} from 'mobx'; -import * as axios from "axios"; -import * as d3 from "d3"; - -import Config = require("Config"); +import {observable, extendObservable, makeObservable} from 'mobx'; +import axios from "axios"; +import Config from '../../Config'; import JournalEntry from './JournalEntry'; import JournalEntryLine from './JournalEntryLine'; import CommonStore from "../Common/CommonStore"; -let baseUrl = location.protocol +const baseUrl = location.protocol + "//" + location.hostname + (location.port && ":" + location.port) + "/"; export default class JournalEntryStore { - originalJournalEntry; - journalEntry; - commonStore; - @observable validationErrors; - @observable isDirty = false; - @observable initialized = false; - @observable editMode = false; + originalJournalEntry: JournalEntry = new JournalEntry(); + journalEntry: JournalEntry = new JournalEntry(); + commonStore: CommonStore = new CommonStore(); + validationErrors: string[] = []; + isDirty = false; + initialized = false; + editMode = false; constructor() { this.commonStore = new CommonStore(); @@ -36,26 +34,33 @@ export default class JournalEntryStore { journalEntryLines: this.journalEntry.journalEntryLines }); - let journalEntryId = window.location.search.split("?id=")[1]; + makeObservable(this, { + validationErrors: observable, + isDirty: observable, + initialized: observable, + editMode: observable, + }); + + const journalEntryId = window.location.search.split("?id=")[1]; if (journalEntryId !== undefined) this.getJournalEntry(parseInt(journalEntryId)) else this.changedEditMode(true); } - changeIsDirty(dirty) { + changeIsDirty(dirty: boolean) { this.isDirty = dirty; } - changeInitialized(initialized) { + changeInitialized(initialized: boolean) { this.initialized = initialized; } getJournalEntry(journalEntryId: number) { - axios.get(Config.apiUrl + "financials/journalentry?id=" + journalEntryId) - .then(function (result) { - for (var i = 0; i < result.data.journalEntryLines.length; i++) { - var item = result.data.journalEntryLines[i]; + axios.get(Config.API_URL + "financials/journalentry?id=" + journalEntryId) + .then((result) => { + for (let i = 0; i < result.data.journalEntryLines.length; i++) { + const item = result.data.journalEntryLines[i]; this.addLineItem(item.id, item.accountId, item.drCr, item.amount, item.memo); } this.journalEntry.id = result.data.id; @@ -66,29 +71,32 @@ export default class JournalEntryStore { this.journalEntry.posted = result.data.posted; this.journalEntry.readyForPosting = result.data.readyForPosting; - var nodes = document.getElementById("divJournalEntryForm").getElementsByTagName('*'); - for (var i = 0; i < nodes.length; i++) { + const nodes = document.getElementById("divJournalEntryForm")!.getElementsByTagName('*'); + for (let i = 0; i < nodes.length; i++) { nodes[i].className += " disabledControl"; } - }.bind(this)); + }); } saveNewJournalEntry() { if (this.validation() && this.validationErrors.length == 0) { - axios.post(Config.apiUrl + "financials/savejournalentry", JSON.stringify(this.journalEntry), + axios.post(Config.API_URL + "financials/savejournalentry", JSON.stringify(this.journalEntry), { headers: { 'Content-type': 'application/json' } }) - .then(function (response) { + .then(() => { window.location.href = baseUrl + 'financials/journalentries'; }) - .catch(function (error) { - error.data.map(function (err) { - this.validationErrors.push(err); - }.bind(this)); - }.bind(this)) + .catch((error) => { + if (axios.isAxiosError(error)) { + this.validationErrors.push(`Status: ${error.status} - Message: ${error.response?.data}`); + } else { + console.error(error); + this.validationErrors.push(`Error: ${error}`); + } + }) } } @@ -106,7 +114,7 @@ export default class JournalEntryStore { if (this.journalEntry.journalEntryLines.length < 2) this.validationErrors.push("You need at lest 2 journal entry lines for debit and credit."); if (this.journalEntry.journalEntryLines !== undefined && this.journalEntry.journalEntryLines.length > 0) { - for (var i = 0; i < this.journalEntry.journalEntryLines.length; i++) { + for (let i = 0; i < this.journalEntry.journalEntryLines.length; i++) { if (this.journalEntry.journalEntryLines[i].accountId === undefined) this.validationErrors.push("Account is required."); if (this.journalEntry.journalEntryLines[i].drcr === undefined) @@ -122,54 +130,58 @@ export default class JournalEntryStore { postJournal() { if (this.validation() && this.validationErrors.length == 0) { - axios.post(Config.apiUrl + "financials/postjournalentry", JSON.stringify(this.journalEntry), + axios.post(Config.API_URL + "financials/postjournalentry", JSON.stringify(this.journalEntry), { headers: { 'Content-type': 'application/json' } }) - .then(function (response) { + .then(() => { window.location.href = baseUrl + 'financials/journalentries'; }) - .catch(function (error) { - error.data.map(function (err) { - this.validationErrors.push(err); - }.bind(this)); - }.bind(this)) + .catch((error) => { + if (axios.isAxiosError(error)) { + this.validationErrors.push(`Status: ${error.status} - Message: ${error.response?.data}`); + } else { + console.error(error); + this.validationErrors.push(`Error: ${error}`); + } + }) } } - addLineItem(id, accountId, drcr, amount, memo) { - var newLineItem = new JournalEntryLine(id, accountId, drcr, amount, memo); + addLineItem(id: number, accountId: number, drcr: number, amount: number, memo: string) { + const newLineItem = new JournalEntryLine(id, accountId, drcr, amount, memo); this.journalEntry.journalEntryLines.push(extendObservable(newLineItem, newLineItem)); } - updateLineItem(row, targetProperty, value) { - if (this.journalEntry.journalEntryLines.length > 0) - this.journalEntry.journalEntryLines[row][targetProperty] = value; + updateLineItem(row: number, targetProperty: keyof JournalEntryLine, value: string | number) { + if (this.journalEntry.journalEntryLines.length > 0) { + (this.journalEntry.journalEntryLines[row] as Record)[targetProperty] = value; + } } - removeLineItem(row) { + removeLineItem(row: number) { this.journalEntry.journalEntryLines.splice(row, 1); } - changedJournalDate(date) { + changedJournalDate(date: Date) { this.journalEntry.journalDate = date; } - changedReferenceNo(refNo) { + changedReferenceNo(refNo: string) { this.journalEntry.referenceNo = refNo; } - changedMemo(memo) { + changedMemo(memo: string) { this.journalEntry.memo = memo; } - changedVoucherType(type) { + changedVoucherType(type: number) { this.journalEntry.voucherType = type; } - changedEditMode(editMode) { + changedEditMode(editMode: boolean) { this.editMode = editMode; } } \ No newline at end of file diff --git a/src/AccountGoWeb/Scripts/Shared/Stores/Financials/JournalEntryUIStore.tsx b/src/GoodBooksReact/src/components/Shared/Stores/Financials/JournalEntryUIStore.tsx similarity index 51% rename from src/AccountGoWeb/Scripts/Shared/Stores/Financials/JournalEntryUIStore.tsx rename to src/GoodBooksReact/src/components/Shared/Stores/Financials/JournalEntryUIStore.tsx index 1cd2a4b2b..778d96a3c 100644 --- a/src/AccountGoWeb/Scripts/Shared/Stores/Financials/JournalEntryUIStore.tsx +++ b/src/GoodBooksReact/src/components/Shared/Stores/Financials/JournalEntryUIStore.tsx @@ -1,7 +1,4 @@ -import {observable, autorun, reaction} from 'mobx'; -import * as mobx from "mobx"; - -import JournalEntryStore from "./JournalEntryStore"; +import JournalEntryStore from "./JournalEntryStore"; export default class JournalEntryUIStore { store; diff --git a/src/GoodBooksReact/src/components/Shared/Stores/Purchasing/PurchaseInvoice.tsx b/src/GoodBooksReact/src/components/Shared/Stores/Purchasing/PurchaseInvoice.tsx new file mode 100644 index 000000000..3afbb0cae --- /dev/null +++ b/src/GoodBooksReact/src/components/Shared/Stores/Purchasing/PurchaseInvoice.tsx @@ -0,0 +1,21 @@ +import PurchaseInvoiceLine from "./PurchaseInvoiceLine"; + +export default class PurchaseInvoice { + id: number = 0; + fromPurchaseOrderId: number = 0; + vendorId: number = 0; + invoiceDate: Date = new Date(); + paymentTermId: number = 0; + referenceNo: string = ""; + posted: boolean = false; + readyForPosting: boolean = false; + purchaseInvoiceLines: PurchaseInvoiceLine[] = []; + statusId: number = 0; + + constructor() { + this.id = 0; + this.posted = false; + this.readyForPosting = false; + + } +} \ No newline at end of file diff --git a/src/AccountGoWeb/Scripts/Shared/Stores/Sales/SalesInvoiceLine.tsx b/src/GoodBooksReact/src/components/Shared/Stores/Purchasing/PurchaseInvoiceLine.tsx similarity index 67% rename from src/AccountGoWeb/Scripts/Shared/Stores/Sales/SalesInvoiceLine.tsx rename to src/GoodBooksReact/src/components/Shared/Stores/Purchasing/PurchaseInvoiceLine.tsx index 24b35ab0c..da3570653 100644 --- a/src/AccountGoWeb/Scripts/Shared/Stores/Sales/SalesInvoiceLine.tsx +++ b/src/GoodBooksReact/src/components/Shared/Stores/Purchasing/PurchaseInvoiceLine.tsx @@ -1,5 +1,5 @@ -export default class SalesInvoiceLine { - id: number; +export default class PurchaseInvoiceLine { + id: number = 0; itemId: number; measurementId: number; quantity: number; @@ -8,7 +8,8 @@ remainingQtyToInvoice: number; code: string; - constructor(id, itemId, measurementId, quantity, amount, discount, code) { + constructor(id: number, itemId: number, measurementId: number, + quantity: number, amount: number, discount: number, code: string) { this.id = id; this.itemId = itemId; this.measurementId = measurementId; diff --git a/src/AccountGoWeb/Scripts/Shared/Stores/Purchasing/PurchaseInvoiceStore.tsx b/src/GoodBooksReact/src/components/Shared/Stores/Purchasing/PurchaseInvoiceStore.tsx similarity index 60% rename from src/AccountGoWeb/Scripts/Shared/Stores/Purchasing/PurchaseInvoiceStore.tsx rename to src/GoodBooksReact/src/components/Shared/Stores/Purchasing/PurchaseInvoiceStore.tsx index 321eee567..7447dc04a 100644 --- a/src/AccountGoWeb/Scripts/Shared/Stores/Purchasing/PurchaseInvoiceStore.tsx +++ b/src/GoodBooksReact/src/components/Shared/Stores/Purchasing/PurchaseInvoiceStore.tsx @@ -1,14 +1,13 @@ -import {observable, extendObservable, action, autorun, computed} from 'mobx'; -import * as axios from "axios"; - -import Config = require("Config"); +import {makeObservable, observable, extendObservable, autorun} from 'mobx'; +import axios from "axios"; +import Config from '../../Config'; import PurchaseInvoice from './PurchaseInvoice'; import PurchaseInvoiceLine from './PurchaseInvoiceLine'; import CommonStore from "../Common/CommonStore"; -let baseUrl = location.protocol +const baseUrl = location.protocol + "//" + location.hostname + (location.port && ":" + location.port) + "/"; @@ -16,11 +15,25 @@ let baseUrl = location.protocol export default class PurchaseOrderStore { purchaseInvoice; commonStore; - @observable validationErrors; - @observable editMode = false; - @observable purchaseInvoiceStatus; + validationErrors: string[] = []; + editMode = false; + purchaseInvoiceStatus: string = ""; + + RTotal = 0; + GTotal = 0; + TTotal = 0; + + constructor(purchId: number, invoiceId: number) { + + makeObservable(this, { + validationErrors: observable, + editMode: observable, + purchaseInvoiceStatus: observable, + RTotal: observable, + GTotal: observable, + TTotal: observable, + }); - constructor(purchId: any, invoiceId: any) { this.commonStore = new CommonStore(); this.purchaseInvoice = new PurchaseInvoice(); extendObservable(this.purchaseInvoice, { @@ -36,10 +49,9 @@ export default class PurchaseOrderStore { autorun(() => this.computeTotals()); if (purchId !== undefined) { - axios.get(Config.apiUrl + "api/purchasing/purchaseorder?id=" + purchId) - .then(function (result) { - - for (var i = 0; i < result.data.purchaseOrderLines.length; i++) { + axios.get(Config.API_URL + "purchasing/purchaseorder?id=" + purchId) + .then((result) => { + for (let i = 0; i < result.data.purchaseOrderLines.length; i++) { if (result.data.purchaseOrderLines[i].remainingQtyToInvoice == 0) continue; this.addLineItem( @@ -48,9 +60,9 @@ export default class PurchaseOrderStore { result.data.purchaseOrderLines[i].measurementId, result.data.purchaseOrderLines[i].remainingQtyToInvoice, result.data.purchaseOrderLines[i].amount, - result.data.purchaseOrderLines[i].discount + result.data.purchaseOrderLines[i].discount, + result.data.purchaseOrderLines[i].code ); - } this.purchaseInvoice.fromPurchaseOrderId = purchId; @@ -60,24 +72,24 @@ export default class PurchaseOrderStore { this.changedInvoiceDate(result.data.orderDate); this.computeTotals(); this.changedEditMode(true); - }.bind(this)) - .catch(function (error) { - }.bind(this)); + }) + .catch(() => {}); } else if (invoiceId !== undefined) { - axios.get(Config.apiUrl + "purchasing/purchaseinvoice?id=" + invoiceId) - .then(function (result) { - - for (var i = 0; i < result.data.purchaseInvoiceLines.length; i++) { + axios.get(Config.API_URL + "purchasing/purchaseinvoice?id=" + invoiceId) + .then((result) => { + for (let i = 0; i < result.data.purchaseInvoiceLines.length; i++) { this.addLineItem( result.data.purchaseInvoiceLines[i].id, result.data.purchaseInvoiceLines[i].itemId, result.data.purchaseInvoiceLines[i].measurementId, result.data.purchaseInvoiceLines[i].quantity, result.data.purchaseInvoiceLines[i].amount, - result.data.purchaseInvoiceLines[i].discount + result.data.purchaseInvoiceLines[i].discount, + result.data.purchaseInvoiceLines[i].code ); - this.updateLineItem(i, 'code', this.changeItemCode(result.data.purchaseInvoiceLines[i].itemId)); + const itemCode = this.changeItemCode(result.data.purchaseInvoiceLines[i].itemId) || ''; // Provide a default value if the item code is undefined + this.updateLineItem(i, 'code', itemCode); } this.purchaseInvoice.id = result.data.id; @@ -89,82 +101,80 @@ export default class PurchaseOrderStore { this.purchaseInvoice.readyForPosting = result.data.readyForPosting; this.getPurchaseInvoiceStatus(result.data.statusId); - this.computeTotals(); - var nodes = document.getElementById("divPurchaseInvoiceForm").getElementsByTagName('*'); - for (var i = 0; i < nodes.length; i++) { + const nodes = document.getElementById("divPurchaseInvoiceForm")!.getElementsByTagName('*'); + for (let i = 0; i < nodes.length; i++) { nodes[i].className += " disabledControl"; } - }.bind(this)) - .catch(function (error) { - }.bind(this)); - - + }) + .catch(() => {}); + } + else { + this.changedEditMode(true); } - else - this.changedEditMode(true); } - @observable RTotal = 0; - @observable GTotal = 0; - @observable TTotal = 0; - computeTotals() { - var rtotal = 0; - var ttotal = 0; - var gtotal = 0; + let rtotal = 0; + let ttotal = 0; - for (var i = 0; i < this.purchaseInvoice.purchaseInvoiceLines.length; i++) { - var lineItem = this.purchaseInvoice.purchaseInvoiceLines[i]; + for (let i = 0; i < this.purchaseInvoice.purchaseInvoiceLines.length; i++) { + const lineItem = this.purchaseInvoice.purchaseInvoiceLines[i]; rtotal = rtotal + this.getLineTotal(i); - axios.get(Config.apiUrl + "tax/gettax?itemId=" + lineItem.itemId + "&partyId=" + this.purchaseInvoice.vendorId + "&type=2") - .then(function (result) { + axios.get(Config.API_URL + "tax/gettax?itemId=" + lineItem.itemId + "&partyId=" + this.purchaseInvoice.vendorId + "&type=2") + .then((result) => { if (result.data.length > 0) { ttotal = ttotal + this.commonStore.getPurhcaseLineTaxAmount(lineItem.quantity, lineItem.amount, lineItem.discount, result.data); } this.TTotal = ttotal; this.GTotal = rtotal + ttotal; - }.bind(this)); + }); this.RTotal = rtotal; } } savePurchaseInvoice() { if (this.validation() && this.validationErrors.length === 0) { - axios.post(Config.apiUrl + "purchasing/savepurchaseinvoice", JSON.stringify(this.purchaseInvoice), + axios.post(Config.API_URL + "purchasing/savepurchaseinvoice", JSON.stringify(this.purchaseInvoice), { headers: { 'Content-type': 'application/json' } }) - .then(function (response) { + .then(() => { window.location.href = baseUrl + 'purchasing/purchaseinvoices'; }) - .catch(function (error) { - error.data.map(function (err) { - this.validationErrors.push(err); - }.bind(this)); - }.bind(this)) + .catch((error) => { + if (axios.isAxiosError(error)) { + this.validationErrors.push(`Status: ${error.status} - Message: ${error.response?.data}`); + } else { + console.error(error); + this.validationErrors.push(`Error: ${error}`); + } + }) } } postInvoice() { if (this.validation() && this.validationErrors.length === 0) { - axios.post(Config.apiUrl + "purchasing/postpurchaseinvoice", JSON.stringify(this.purchaseInvoice), + axios.post(Config.API_URL + "purchasing/postpurchaseinvoice", JSON.stringify(this.purchaseInvoice), { headers: { 'Content-type': 'application/json' } }) - .then(function (response) { + .then(() => { window.location.href = baseUrl + 'purchasing/purchaseinvoices'; }) - .catch(function (error) { - error.data.map(function (err) { - this.validationErrors.push(err); - }.bind(this)); - }.bind(this)) + .catch((error) => { + if (axios.isAxiosError(error)) { + this.validationErrors.push(`Status: ${error.status} - Message: ${error.response?.data}`); + } else { + console.error(error); + this.validationErrors.push(`Error: ${error}`); + } + }) } } @@ -179,24 +189,24 @@ export default class PurchaseOrderStore { if (this.purchaseInvoice.purchaseInvoiceLines === undefined || this.purchaseInvoice.purchaseInvoiceLines.length < 1) this.validationErrors.push("Enter at least 1 line item."); if (this.purchaseInvoice.purchaseInvoiceLines !== undefined && this.purchaseInvoice.purchaseInvoiceLines.length > 0) { - for (var i = 0; i < this.purchaseInvoice.purchaseInvoiceLines.length; i++) { + for (let i = 0; i < this.purchaseInvoice.purchaseInvoiceLines.length; i++) { if (this.purchaseInvoice.purchaseInvoiceLines[i].itemId === undefined - || this.purchaseInvoice.purchaseInvoiceLines[i].itemId === "") + || Number(this.purchaseInvoice.purchaseInvoiceLines[i].itemId) === 0) this.validationErrors.push("Item is required."); if (this.purchaseInvoice.purchaseInvoiceLines[i].measurementId === undefined - || this.purchaseInvoice.purchaseInvoiceLines[i].measurementId === "") + || Number(this.purchaseInvoice.purchaseInvoiceLines[i].measurementId) === 0) this.validationErrors.push("Uom is required."); if (this.purchaseInvoice.purchaseInvoiceLines[i].quantity === undefined - || this.purchaseInvoice.purchaseInvoiceLines[i].quantity === "" - || this.purchaseInvoice.purchaseInvoiceLines[i].quantity === 0) + || this.purchaseInvoice.purchaseInvoiceLines[i].quantity.toString() === "" + || parseInt(this.purchaseInvoice.purchaseInvoiceLines[i].quantity.toString()) === 0) this.validationErrors.push("Quantity is required."); if (this.purchaseInvoice.purchaseInvoiceLines[i].amount === undefined - || this.purchaseInvoice.purchaseInvoiceLines[i].amount === "" - || this.purchaseInvoice.purchaseInvoiceLines[i].amount === 0) + || this.purchaseInvoice.purchaseInvoiceLines[i].amount.toString() === "" + || parseInt(this.purchaseInvoice.purchaseInvoiceLines[i].amount.toString()) === 0) this.validationErrors.push("Amount is required."); if (this.getLineTotal(i) === undefined || this.getLineTotal(i).toString() === "NaN" - || this.getLineTotal(i) === 0) + || parseInt(this.getLineTotal(i).toString()) === 0) this.validationErrors.push("Invalid data."); } } @@ -207,7 +217,7 @@ export default class PurchaseOrderStore { validationLine() { this.validationErrors = []; if (this.purchaseInvoice.purchaseInvoiceLines !== undefined && this.purchaseInvoice.purchaseInvoiceLines.length > 0) { - for (var i = 0; i < this.purchaseInvoice.purchaseInvoiceLines.length; i++) { + for (let i = 0; i < this.purchaseInvoice.purchaseInvoiceLines.length; i++) { if (this.purchaseInvoice.purchaseInvoiceLines[i].itemId === undefined) this.validationErrors.push("Item is required."); if (this.purchaseInvoice.purchaseInvoiceLines[i].measurementId === undefined) @@ -222,30 +232,26 @@ export default class PurchaseOrderStore { } } else { - var itemId, measurementId, quantity, amount, discount; - itemId = (document.getElementById("optNewItemId") as HTMLInputElement).value; - measurementId = (document.getElementById("optNewMeasurementId") as HTMLInputElement).value; - quantity = (document.getElementById("txtNewQuantity") as HTMLInputElement).value; - amount = (document.getElementById("txtNewAmount") as HTMLInputElement).value; - discount = (document.getElementById("txtNewDiscount") as HTMLInputElement).value; + const itemId: number = Number((document.getElementById("optNewItemId") as HTMLInputElement).value); + const measurementId: number = Number((document.getElementById("optNewMeasurementId") as HTMLInputElement).value); + const quantity: number = Number((document.getElementById("txtNewQuantity") as HTMLInputElement).value); + const amount: number = Number((document.getElementById("txtNewAmount") as HTMLInputElement).value); - if (itemId == "" || itemId === undefined) + if (itemId == 0 || itemId === undefined) this.validationErrors.push("Item is required."); - if (measurementId == "" || measurementId === undefined) + if (measurementId == 0 || measurementId === undefined) this.validationErrors.push("Uom is required."); - if (quantity == "" || quantity === undefined) + if (quantity == 0 || quantity === undefined) this.validationErrors.push("Quantity is required."); - if (amount == "" || amount === undefined) + if (amount == 0 || amount === undefined) this.validationErrors.push("Amount is required."); } if (document.getElementById("optNewItemId")) { - var itemId, measurementId, quantity, amount, discount; - itemId = (document.getElementById("optNewItemId") as HTMLInputElement).value; - measurementId = (document.getElementById("optNewMeasurementId") as HTMLInputElement).value; - quantity = (document.getElementById("txtNewQuantity") as HTMLInputElement).value; - amount = (document.getElementById("txtNewAmount") as HTMLInputElement).value; - discount = (document.getElementById("txtNewDiscount") as HTMLInputElement).value; + const itemId: string = (document.getElementById("optNewItemId") as HTMLInputElement).value; + const measurementId: string = (document.getElementById("optNewMeasurementId") as HTMLInputElement).value; + const quantity: string = (document.getElementById("txtNewQuantity") as HTMLInputElement).value; + const amount: string = (document.getElementById("txtNewAmount") as HTMLInputElement).value; if (itemId == "" || itemId === undefined) this.validationErrors.push("Item is required."); @@ -262,49 +268,52 @@ export default class PurchaseOrderStore { } - changedReferenceNo(refNo) { + changedReferenceNo(refNo: string) { this.purchaseInvoice.referenceNo = refNo; } - changedVendor(vendorId) { + + changedVendor(vendorId: number) { this.purchaseInvoice.vendorId = vendorId; } - changedPaymentTerm(paymentTermId) { + changedPaymentTerm(paymentTermId: number) { this.purchaseInvoice.paymentTermId = paymentTermId; } - changedInvoiceDate(date) { + + changedInvoiceDate(date: Date) { this.purchaseInvoice.invoiceDate = date; } - addLineItem(id, itemId, measurementId, quantity, amount, discount) { - var newLineItem = new PurchaseInvoiceLine(id, itemId, measurementId, quantity, amount, discount); + addLineItem(id: number, itemId: number, measurementId: number, + quantity: number, amount: number, discount: number, code: string) { + const newLineItem = new PurchaseInvoiceLine(id, itemId, measurementId, quantity, amount, discount, code); this.purchaseInvoice.purchaseInvoiceLines.push(extendObservable(newLineItem, newLineItem)); } - removeLineItem(row) { + removeLineItem(row: number) { this.purchaseInvoice.purchaseInvoiceLines.splice(row, 1); } - updateLineItem(row, targetProperty, value) { + updateLineItem(row: number, targetProperty: keyof PurchaseInvoiceLine, value: string | number) { if (this.purchaseInvoice.purchaseInvoiceLines.length > 0) - this.purchaseInvoice.purchaseInvoiceLines[row][targetProperty] = value; + (this.purchaseInvoice.purchaseInvoiceLines[row] as Record)[targetProperty] = value; this.computeTotals(); } - getLineTotal(row) { + getLineTotal(row: number) { let lineSum = 0; - let lineItem = this.purchaseInvoice.purchaseInvoiceLines[row]; + const lineItem = this.purchaseInvoice.purchaseInvoiceLines[row]; lineSum = (lineItem.quantity * lineItem.amount) - lineItem.discount; return lineSum; } - changedEditMode(editMode) { + changedEditMode(editMode: boolean) { this.editMode = editMode; } - getPurchaseInvoiceStatus(statusId) { - var status = ""; + getPurchaseInvoiceStatus(statusId: number) { + let status = ""; if (statusId === 0) status = "Draft"; else if (statusId === 1) @@ -314,11 +323,12 @@ export default class PurchaseOrderStore { this.purchaseInvoiceStatus = status; } - changeItemCode(itemId) { + changeItemCode(itemId: number) { - for (var x = 0; x < this.commonStore.items.length; x++) { - if (this.commonStore.items[x].id === parseInt(itemId)) { - return this.commonStore.items[x].code; + for (let x = 0; x < this.commonStore.items.length; x++) { + const lineItem = this.commonStore.items[x] as PurchaseInvoiceLine + if (lineItem.id === itemId) { + return lineItem.code; } } } diff --git a/src/GoodBooksReact/src/components/Shared/Stores/Purchasing/PurchaseOrder.tsx b/src/GoodBooksReact/src/components/Shared/Stores/Purchasing/PurchaseOrder.tsx new file mode 100644 index 000000000..c613736b8 --- /dev/null +++ b/src/GoodBooksReact/src/components/Shared/Stores/Purchasing/PurchaseOrder.tsx @@ -0,0 +1,11 @@ +import PurchaseOrderLine from "./PurchaseOrderLine"; + +export default class PurchaseOrder { + id: number = 0; + vendorId: number = 0; + orderDate: Date = new Date(); + paymentTermId: number = 0; + referenceNo: string = ""; + statusId: number = 0; + purchaseOrderLines: PurchaseOrderLine[] = []; +} \ No newline at end of file diff --git a/src/AccountGoWeb/Scripts/Shared/Stores/Quotations/SalesQuotationLine.tsx b/src/GoodBooksReact/src/components/Shared/Stores/Purchasing/PurchaseOrderLine.tsx similarity index 61% rename from src/AccountGoWeb/Scripts/Shared/Stores/Quotations/SalesQuotationLine.tsx rename to src/GoodBooksReact/src/components/Shared/Stores/Purchasing/PurchaseOrderLine.tsx index 96b4367ef..9042a2cb3 100644 --- a/src/AccountGoWeb/Scripts/Shared/Stores/Quotations/SalesQuotationLine.tsx +++ b/src/GoodBooksReact/src/components/Shared/Stores/Purchasing/PurchaseOrderLine.tsx @@ -1,13 +1,13 @@ -export default class SalesQuotationLine { - id: number; +export default class PurchaseOrderLine { + id = 0; itemId: number; measurementId: number; quantity: number; amount: number; discount: number; - code: number; + code: string; - constructor(id, itemId, measurementId, quantity, amount, discount, code) { + constructor(id: number, itemId: number, measurementId: number, quantity: number, amount: number, discount: number, code: string) { this.id = id; this.itemId = itemId; this.measurementId = measurementId; diff --git a/src/AccountGoWeb/Scripts/Shared/Stores/Purchasing/PurchaseOrderStore.tsx b/src/GoodBooksReact/src/components/Shared/Stores/Purchasing/PurchaseOrderStore.tsx similarity index 62% rename from src/AccountGoWeb/Scripts/Shared/Stores/Purchasing/PurchaseOrderStore.tsx rename to src/GoodBooksReact/src/components/Shared/Stores/Purchasing/PurchaseOrderStore.tsx index 54b1823ff..38d219bfd 100644 --- a/src/AccountGoWeb/Scripts/Shared/Stores/Purchasing/PurchaseOrderStore.tsx +++ b/src/GoodBooksReact/src/components/Shared/Stores/Purchasing/PurchaseOrderStore.tsx @@ -1,26 +1,29 @@ -import {observable, extendObservable, action, autorun, computed} from 'mobx'; -import * as axios from "axios"; - -import Config = require("Config"); +import {observable, extendObservable, autorun, makeObservable} from 'mobx'; +import axios from "axios"; +import Config from '../../Config'; import PurchaseOrder from './PurchaseOrder'; import PurchaseOrderLine from './PurchaseOrderLine'; import CommonStore from "../Common/CommonStore"; -let baseUrl = location.protocol +const baseUrl = location.protocol + "//" + location.hostname + (location.port && ":" + location.port) + "/"; export default class PurchaseOrderStore { - purchaseOrder; - commonStore; - @observable validationErrors; - @observable editMode = false; - @observable purchaseOrderStatus; + purchaseOrder: PurchaseOrder; + commonStore: CommonStore; + validationErrors: string[] = []; + editMode = false; + purchaseOrderStatus: string = ""; + + RTotal = 0; + GTotal = 0; + TTotal = 0; - constructor(purchId: any) { + constructor(purchId: number) { this.commonStore = new CommonStore(); this.purchaseOrder = new PurchaseOrder(); extendObservable(this.purchaseOrder, { @@ -32,11 +35,20 @@ export default class PurchaseOrderStore { purchaseOrderLines: [] }); + makeObservable(this, { + validationErrors: observable, + editMode: observable, + purchaseOrderStatus: observable, + RTotal: observable, + GTotal: observable, + TTotal: observable, + }); + autorun(() => this.computeTotals()); if (purchId !== undefined) { - axios.get(Config.apiUrl + "purchasing/purchaseorder?id=" + purchId) - .then(function (result) { + axios.get(Config.API_URL + "purchasing/purchaseorder?id=" + purchId) + .then((result) => { this.purchaseOrder.id = result.data.id; this.purchaseOrder.paymentTermId = result.data.paymentTermId; this.purchaseOrder.referenceNo = result.data.referenceNo; @@ -44,102 +56,109 @@ export default class PurchaseOrderStore { this.changedVendor(result.data.vendorId); this.getPurchaseOrderStatus(result.data.statusId); this.purchaseOrder.orderDate = result.data.orderDate; - for (var i = 0; i < result.data.purchaseOrderLines.length; i++) { + for (let i = 0; i < result.data.purchaseOrderLines.length; i++) { this.addLineItem( result.data.purchaseOrderLines[i].id, result.data.purchaseOrderLines[i].itemId, result.data.purchaseOrderLines[i].measurementId, result.data.purchaseOrderLines[i].quantity, result.data.purchaseOrderLines[i].amount, - result.data.purchaseOrderLines[i].discount + result.data.purchaseOrderLines[i].discount, + result.data.purchaseOrderLines[i].code // Add the missing argument ); - this.updateLineItem(i, 'code', this.changeItemCode(result.data.purchaseOrderLines[i].itemId)); + this.updateLineItem(i, 'code', Number(this.changeItemCode(result.data.purchaseOrderLines[i].itemId))); } this.computeTotals(); - var nodes = document.getElementById("divPurchaseOrderForm").getElementsByTagName('*'); - for (var i = 0; i < nodes.length; i++) { + const nodes = document.getElementById("divPurchaseOrderForm")?.getElementsByTagName('*'); + for (let i = 0; nodes && i < nodes.length; i++) { nodes[i].className += " disabledControl"; } - }.bind(this)) - .catch(function (error) { - }.bind(this)); + }) + .catch(() => { + }); } else { this.changedEditMode(true); } } - @observable RTotal = 0; - @observable GTotal = 0; - @observable TTotal = 0; + changeItemCode(itemId: string) { + for (let x = 0; x < this.commonStore.items.length; x++) { + const item = this.commonStore.items[x] as PurchaseOrderLine + if (item.id === parseInt(itemId)) { + return item.code; + } + } + } computeTotals() { - var rtotal = 0; - var ttotal = 0; - var gtotal = 0; + let rtotal = 0; + let ttotal = 0; - for (var i = 0; i < this.purchaseOrder.purchaseOrderLines.length; i++) { - var lineItem = this.purchaseOrder.purchaseOrderLines[i]; + for (let i = 0; i < this.purchaseOrder.purchaseOrderLines.length; i++) { + const lineItem = this.purchaseOrder.purchaseOrderLines[i]; rtotal = rtotal + this.getLineTotal(i); - axios.get(Config.apiUrl + "tax/gettax?itemId=" + lineItem.itemId + "&partyId=" + this.purchaseOrder.vendorId + "&type=2") - .then(function (result) { + axios.get(Config.API_URL + "tax/gettax?itemId=" + lineItem.itemId + "&partyId=" + this.purchaseOrder.vendorId + "&type=2") + .then((result) => { if (result.data.length > 0) { ttotal = ttotal + this.commonStore.getPurhcaseLineTaxAmount(lineItem.quantity, lineItem.amount, lineItem.discount, result.data); } this.TTotal = ttotal; this.GTotal = rtotal + ttotal; - }.bind(this)); + }); this.RTotal = rtotal; } } savePurchaseOrder() { - if (this.purchaseOrder.orderDate === undefined) - this.purchaseOrder.orderDate = new Date(Date.now()).toISOString().substring(0, 10); + this.purchaseOrder.orderDate = new Date(new Date(Date.now()).toISOString().substring(0, 10)); if (this.validation() && this.validationErrors.length === 0) { - axios.post(Config.apiUrl + "purchasing/savepurchaseorder", JSON.stringify(this.purchaseOrder), + axios.post(Config.API_URL + "purchasing/savepurchaseorder", JSON.stringify(this.purchaseOrder), { headers: { 'Content-type': 'application/json' } }) - .then(function (response) { + .then(() => { window.location.href = baseUrl + 'purchasing/purchaseorders'; }) - .catch(function (error) { - error.data.map(function (err) { - this.validationErrors.push(err); - }.bind(this)); - }.bind(this)) + .catch((error) => { + if (axios.isAxiosError(error)) { + this.validationErrors.push(`Status: ${error.status} - Message: ${error.response?.data}`); + } else { + console.error(error); + this.validationErrors.push(`Error: ${error}`); + } + }) } } validation() { this.validationErrors = []; - if (this.purchaseOrder.vendorId === undefined || this.purchaseOrder.vendorId === "") + if (this.purchaseOrder.vendorId === undefined || this.purchaseOrder.vendorId.toString() === "") this.validationErrors.push("Vendor is required."); - if (this.purchaseOrder.paymentTermId === undefined || this.purchaseOrder.paymentTermId === "") + if (this.purchaseOrder.paymentTermId === undefined || this.purchaseOrder.paymentTermId.toString() === "") this.validationErrors.push("Payment term is required."); - if (this.purchaseOrder.orderDate === undefined || this.purchaseOrder.orderDate === "") + if (this.purchaseOrder.orderDate === undefined || this.purchaseOrder.orderDate.toString() === "") this.validationErrors.push("Date is required."); if (this.purchaseOrder.purchaseOrderLines === undefined || this.purchaseOrder.purchaseOrderLines.length < 1) this.validationErrors.push("Enter at least 1 line item."); if (this.purchaseOrder.purchaseOrderLines !== undefined && this.purchaseOrder.purchaseOrderLines.length > 0) { - for (var i = 0; i < this.purchaseOrder.purchaseOrderLines.length; i++) { + for (let i = 0; i < this.purchaseOrder.purchaseOrderLines.length; i++) { if (this.purchaseOrder.purchaseOrderLines[i].itemId === undefined - || this.purchaseOrder.purchaseOrderLines[i].itemId === "") + || this.purchaseOrder.purchaseOrderLines[i].itemId.toString() === "") this.validationErrors.push("Item is required."); if (this.purchaseOrder.purchaseOrderLines[i].measurementId === undefined - || this.purchaseOrder.purchaseOrderLines[i].measurementId === "") + || this.purchaseOrder.purchaseOrderLines[i].measurementId.toString() === "") this.validationErrors.push("Uom is required."); if (this.purchaseOrder.purchaseOrderLines[i].quantity === undefined - || this.purchaseOrder.purchaseOrderLines[i].quantity === "" + || this.purchaseOrder.purchaseOrderLines[i].quantity.toString() === "" || this.purchaseOrder.purchaseOrderLines[i].quantity === 0) this.validationErrors.push("Quantity is required."); if (this.purchaseOrder.purchaseOrderLines[i].amount === undefined - || this.purchaseOrder.purchaseOrderLines[i].amount === "" + || this.purchaseOrder.purchaseOrderLines[i].amount.toString() === "" || this.purchaseOrder.purchaseOrderLines[i].amount === 0) this.validationErrors.push("Amount is required."); if (this.getLineTotal(i) === undefined @@ -155,7 +174,7 @@ export default class PurchaseOrderStore { validationLine() { this.validationErrors = []; if (this.purchaseOrder.purchaseOrderLines !== undefined && this.purchaseOrder.purchaseOrderLines.length > 0) { - for (var i = 0; i < this.purchaseOrder.purchaseOrderLines.length; i++) { + for (let i = 0; i < this.purchaseOrder.purchaseOrderLines.length; i++) { if (this.purchaseOrder.purchaseOrderLines[i].itemId === undefined) this.validationErrors.push("Item is required."); if (this.purchaseOrder.purchaseOrderLines[i].measurementId === undefined) @@ -170,12 +189,10 @@ export default class PurchaseOrderStore { } } else { - var itemId, measurementId, quantity, amount, discount; - itemId = (document.getElementById("optNewItemId") as HTMLInputElement).value; - measurementId = (document.getElementById("optNewMeasurementId") as HTMLInputElement).value; - quantity = (document.getElementById("txtNewQuantity") as HTMLInputElement).value; - amount = (document.getElementById("txtNewAmount") as HTMLInputElement).value; - discount = (document.getElementById("txtNewDiscount") as HTMLInputElement).value; + const itemId: string = (document.getElementById("optNewItemId") as HTMLInputElement).value; + const measurementId: string = (document.getElementById("optNewMeasurementId") as HTMLInputElement).value; + const quantity: string = (document.getElementById("txtNewQuantity") as HTMLInputElement).value; + const amount: string = (document.getElementById("txtNewAmount") as HTMLInputElement).value; if (itemId == "" || itemId === undefined) this.validationErrors.push("Item is required."); @@ -188,13 +205,12 @@ export default class PurchaseOrderStore { } if (document.getElementById("optNewItemId")) { - var itemId, measurementId, quantity, amount, discount; - itemId = (document.getElementById("optNewItemId") as HTMLInputElement).value; - measurementId = (document.getElementById("optNewMeasurementId") as HTMLInputElement).value; - quantity = (document.getElementById("txtNewQuantity") as HTMLInputElement).value; - amount = (document.getElementById("txtNewAmount") as HTMLInputElement).value; - discount = (document.getElementById("txtNewDiscount") as HTMLInputElement).value; + const itemId: string = (document.getElementById("optNewItemId") as HTMLInputElement).value; + const measurementId: string = (document.getElementById("optNewMeasurementId") as HTMLInputElement).value; + const quantity: string = (document.getElementById("txtNewQuantity") as HTMLInputElement).value; + const amount: string = (document.getElementById("txtNewAmount") as HTMLInputElement).value; + if (itemId == "" || itemId === undefined) this.validationErrors.push("Item is required."); if (measurementId == "" || measurementId === undefined) @@ -209,50 +225,58 @@ export default class PurchaseOrderStore { return this.validationErrors.length === 0; } - changedReferenceNo(refNo) { + changedReferenceNo(refNo: string) { this.purchaseOrder.referenceNo = refNo; } - changedVendor(vendorId) { + + changedVendor(vendorId: number) { this.purchaseOrder.vendorId = vendorId; } - changedPaymentTerm(paymentTermId) { + changedPaymentTerm(paymentTermId: number) { this.purchaseOrder.paymentTermId = paymentTermId; } - changedOrderDate(date) { + changedOrderDate(date: Date) { this.purchaseOrder.orderDate = date; } - addLineItem(id, itemId, measurementId, quantity, amount, discount, code) { - var newLineItem = new PurchaseOrderLine(id, itemId, measurementId, quantity, amount, discount, code); + addLineItem(id: number, itemId: number, measurementId: number, quantity: number, amount: number, discount: number, code: string) { + const newLineItem = new PurchaseOrderLine(id, itemId, measurementId, quantity, amount, discount, code); this.purchaseOrder.purchaseOrderLines.push(extendObservable(newLineItem, newLineItem)); } - removeLineItem(row) { + removeLineItem(row: number) { this.purchaseOrder.purchaseOrderLines.splice(row, 1); } - updateLineItem(row, targetProperty, value) { + updateLineItem(row: number, targetProperty: keyof PurchaseOrderLine, value: string | number) { if (this.purchaseOrder.purchaseOrderLines.length > 0) - this.purchaseOrder.purchaseOrderLines[row][targetProperty] = value; + (this.purchaseOrder.purchaseOrderLines[row] as Record)[targetProperty] = value; this.computeTotals(); } - getLineTotal(row) { + // updateLineItem(row: number, targetProperty: keyof PurchaseInvoiceLine, value: string | number) { + // if (this.purchaseInvoice.purchaseInvoiceLines.length > 0) + // (this.purchaseInvoice.purchaseInvoiceLines[row] as Record)[targetProperty] = value; + + // this.computeTotals(); + // } + + getLineTotal(row: number) { let lineSum = 0; - let lineItem = this.purchaseOrder.purchaseOrderLines[row]; + const lineItem = this.purchaseOrder.purchaseOrderLines[row]; lineSum = (lineItem.quantity * lineItem.amount) - lineItem.discount; return lineSum; } - changedEditMode(editMode) { + changedEditMode(editMode: boolean) { this.editMode = editMode; } - getPurchaseOrderStatus(statusId) { - var status = ""; + getPurchaseOrderStatus(statusId: number) { + let status = ""; if (statusId === 0) status = "Draft"; else if (statusId === 1) @@ -268,12 +292,5 @@ export default class PurchaseOrderStore { this.purchaseOrderStatus = status; } - changeItemCode(itemId) { - for (var x = 0; x < this.commonStore.items.length; x++) { - if (this.commonStore.items[x].id === parseInt(itemId)) { - return this.commonStore.items[x].code; - } - } - } } \ No newline at end of file diff --git a/src/GoodBooksReact/src/components/Shared/Stores/Quotations/SalesQuotation.tsx b/src/GoodBooksReact/src/components/Shared/Stores/Quotations/SalesQuotation.tsx new file mode 100644 index 000000000..f94ad3dfd --- /dev/null +++ b/src/GoodBooksReact/src/components/Shared/Stores/Quotations/SalesQuotation.tsx @@ -0,0 +1,11 @@ +import SalesQuotationLine from "./SalesQuotationLine"; + +export default class SalesQuotation { + id: number = 0; + customerId: number = 0; + quotationDate: Date = new Date(); + paymentTermId: number = 0; + referenceNo: string = ""; + statusId: number = 0; + salesQuotationLines: SalesQuotationLine[] = []; +} \ No newline at end of file diff --git a/src/GoodBooksReact/src/components/Shared/Stores/Quotations/SalesQuotationLine.tsx b/src/GoodBooksReact/src/components/Shared/Stores/Quotations/SalesQuotationLine.tsx new file mode 100644 index 000000000..5fb6d3f26 --- /dev/null +++ b/src/GoodBooksReact/src/components/Shared/Stores/Quotations/SalesQuotationLine.tsx @@ -0,0 +1,21 @@ +export default class SalesQuotationLine { + id: number = 0; + itemId: number = 0; + measurementId: number = 0; + quantity: number = 0; + amount: number = 0; + discount: number = 0; + code: number = 0; + + constructor(id: number, itemId: number, measurementId: number, + quantity: number, amount: number, discount: number, code: number) { + + this.id = id; + this.itemId = itemId; + this.measurementId = measurementId; + this.quantity = quantity; + this.amount = amount; + this.discount = discount; + this.code = code; + } +} \ No newline at end of file diff --git a/src/AccountGoWeb/Scripts/Shared/Stores/Quotations/SalesQuotationStore.tsx b/src/GoodBooksReact/src/components/Shared/Stores/Quotations/SalesQuotationStore.tsx similarity index 58% rename from src/AccountGoWeb/Scripts/Shared/Stores/Quotations/SalesQuotationStore.tsx rename to src/GoodBooksReact/src/components/Shared/Stores/Quotations/SalesQuotationStore.tsx index 160ab2c09..ca9288c1e 100644 --- a/src/AccountGoWeb/Scripts/Shared/Stores/Quotations/SalesQuotationStore.tsx +++ b/src/GoodBooksReact/src/components/Shared/Stores/Quotations/SalesQuotationStore.tsx @@ -1,27 +1,29 @@ -import {observable, extendObservable, action, autorun, computed} from 'mobx'; -import * as axios from "axios"; -import * as d3 from "d3"; - -import Config = require("Config"); +import {observable, extendObservable, autorun, makeObservable} from 'mobx'; +import axios from "axios"; +import Config from '../../Config'; import SalesQuotation from './SalesQuotation'; import SalesQuotationLine from './SalesQuotationLine'; import CommonStore from "../Common/CommonStore"; -let baseUrl = location.protocol +const baseUrl = location.protocol + "//" + location.hostname + (location.port && ":" + location.port) + "/"; export default class SalesQuotationStore { - salesQuotation; - commonStore; - @observable salesQuotationStatus; - @observable validationErrors; - @observable editMode = false; - - constructor(quotationId) { + salesQuotation: SalesQuotation; + commonStore: CommonStore; + salesQuotationStatus: string = ""; + validationErrors: string[] = []; + editMode = false; + + RTotal = 0; + GTotal = 0; + TTotal = 0; + + constructor(quotationId: number) { this.commonStore = new CommonStore(); this.salesQuotation = new SalesQuotation(); extendObservable(this.salesQuotation, { @@ -33,13 +35,20 @@ export default class SalesQuotationStore { salesQuotationLines: [] }); - + makeObservable(this, { + validationErrors: observable, + salesQuotationStatus: observable, + editMode: observable, + RTotal: observable, + GTotal: observable, + TTotal: observable, + }); autorun(() => this.computeTotals()); if (quotationId !== undefined) { - var result = axios.get(Config.apiUrl + "sales/quotation?id=" + quotationId); - result.then(function (result) { + const result = axios.get(Config.API_URL + "sales/quotation?id=" + quotationId); + result.then((result) => { this.salesQuotation.id = result.data.id; this.salesQuotation.paymentTermId = result.data.paymentTermId; this.salesQuotation.referenceNo = result.data.referenceNo; @@ -47,66 +56,56 @@ export default class SalesQuotationStore { this.changedCustomer(result.data.customerId); this.getQuotationStatus(result.data.statusId); this.changedQuotationDate(result.data.quotationDate); - for (var i = 0; i < result.data.salesQuotationLines.length; i++) { + for (let i = 0; i < result.data.salesQuotationLines.length; i++) { this.addLineItem( result.data.salesQuotationLines[i].id, result.data.salesQuotationLines[i].itemId, result.data.salesQuotationLines[i].measurementId, result.data.salesQuotationLines[i].quantity, result.data.salesQuotationLines[i].amount, - result.data.salesQuotationLines[i].discount - + result.data.salesQuotationLines[i].discount, + result.data.salesQuotationLines[i].code ); - this.updateLineItem(i, 'code', this.changeItemCode(result.data.salesQuotationLines[i].itemId)); + this.updateLineItem(i, 'code', Number(this.changeItemCode(result.data.salesQuotationLines[i].itemId))); } this.computeTotals(); - var nodes = document.getElementById("divSalesQuotationForm").getElementsByTagName('*'); - for (var i = 0; i < nodes.length; i++) { + const nodes = document.getElementById("divSalesQuotationForm")?.getElementsByTagName('*'); + for (let i = 0; nodes && i < nodes.length; i++) { nodes[i].className += " disabledControl"; } - }.bind(this)); + }); } else { this.changedEditMode(true); } - } - @observable RTotal = 0; - @observable GTotal = 0; - @observable TTotal = 0; - - - - computeTotals() { - var rtotal = 0; - var ttotal = 0; - var gtotal = 0; + let rtotal = 0; + let ttotal = 0; - for (var i = 0; i < this.salesQuotation.salesQuotationLines.length; i++) { - var lineItem = this.salesQuotation.salesQuotationLines[i]; + for (let i = 0; i < this.salesQuotation.salesQuotationLines.length; i++) { + const lineItem = this.salesQuotation.salesQuotationLines[i]; rtotal = rtotal + this.getLineTotal(i); - axios.get(Config.apiUrl + "tax/gettax?itemId=" + lineItem.itemId + "&partyId=" + this.salesQuotation.customerId + "&type=1") - .then(function (result) { + axios.get(Config.API_URL + "tax/gettax?itemId=" + lineItem.itemId + "&partyId=" + this.salesQuotation.customerId + "&type=1") + .then((result) => { if (result.data.length > 0) { ttotal = ttotal + this.commonStore.getSalesLineTaxAmount(lineItem.quantity, lineItem.amount, lineItem.discount, result.data); } this.TTotal = ttotal; this.GTotal = rtotal + ttotal; - }.bind(this)); + }); this.RTotal = rtotal; } } saveNewQuotation() { - if (this.salesQuotation.quotationDate === undefined) - this.salesQuotation.quotationDate = new Date(Date.now()).toISOString().substring(0, 10); + this.salesQuotation.quotationDate = new Date(new Date(Date.now()).toISOString().substring(0, 10)); if (this.validation()) { if (this.validationErrors.length === 0) { - axios.post(Config.apiUrl + "sales/savequotation", JSON.stringify(this.salesQuotation), + axios.post(Config.API_URL + "sales/savequotation", JSON.stringify(this.salesQuotation), { headers: { @@ -114,23 +113,25 @@ export default class SalesQuotationStore { } } ) - .then(function (response) { + .then(() => { window.location.href = baseUrl + 'quotations'; }) - .catch(function (error) { - error.data.map(function (err) { - this.validationErrors.push(err); - }.bind(this)); - }.bind(this)); + .catch((error) => { + if (axios.isAxiosError(error)) { + this.validationErrors.push(`Status: ${error.status} - Message: ${error.response?.data}`); + } else { + console.error(error); + this.validationErrors.push(`Error: ${error}`); + } + }) } } } bookQuotation() { - if (this.validation()) { if (this.validationErrors.length === 0) { - axios.post(Config.apiUrl + "sales/bookquotation?id=" + parseInt(this.salesQuotation.id), + axios.post(Config.API_URL + "sales/bookquotation?id=" + String(this.salesQuotation.id), { headers: { @@ -138,14 +139,17 @@ export default class SalesQuotationStore { } } ) - .then(function (response) { + .then(() => { window.location.href = baseUrl + 'quotations'; }) - .catch(function (error) { - error.data.map(function (err) { - this.validationErrors.push(err); - }.bind(this)); - }.bind(this)); + .catch((error) => { + if (axios.isAxiosError(error)) { + this.validationErrors.push(`Status: ${error.status} - Message: ${error.response?.data}`); + } else { + console.error(error); + this.validationErrors.push(`Error: ${error}`); + } + }) } } } @@ -161,17 +165,16 @@ export default class SalesQuotationStore { if (this.salesQuotation.salesQuotationLines === undefined || this.salesQuotation.salesQuotationLines.length < 1) this.validationErrors.push("Enter at least 1 line item."); if (this.salesQuotation.salesQuotationLines !== undefined && this.salesQuotation.salesQuotationLines.length > 0) { - for (var i = 0; i < this.salesQuotation.salesQuotationLines.length; i++) { - if (this.salesQuotation.salesQuotationLines[i].itemId === undefined || this.salesQuotation.salesQuotationLines[i].itemId === "") + for (let i = 0; i < this.salesQuotation.salesQuotationLines.length; i++) { + if (typeof this.salesQuotation.salesQuotationLines[i].itemId !== 'number' || isNaN(this.salesQuotation.salesQuotationLines[i].itemId)) this.validationErrors.push("Item is required."); - if (this.salesQuotation.salesQuotationLines[i].measurementId === undefined || this.salesQuotation.salesQuotationLines[i].measurementId === "") + if (typeof this.salesQuotation.salesQuotationLines[i].measurementId !== 'number' || isNaN(this.salesQuotation.salesQuotationLines[i].measurementId)) this.validationErrors.push("Uom is required."); - if (this.salesQuotation.salesQuotationLines[i].quantity === undefined || this.salesQuotation.salesQuotationLines[i].quantity === "") + if (typeof this.salesQuotation.salesQuotationLines[i].quantity !== 'number' || isNaN(this.salesQuotation.salesQuotationLines[i].quantity)) this.validationErrors.push("Quantity is required."); - if (this.salesQuotation.salesQuotationLines[i].amount === undefined || this.salesQuotation.salesQuotationLines[i].amount === "") + if (typeof this.salesQuotation.salesQuotationLines[i].amount !== 'number' || isNaN(this.salesQuotation.salesQuotationLines[i].amount)) this.validationErrors.push("Amount is required."); - if (this.getLineTotal(i) === undefined - || this.getLineTotal(i).toString() === "NaN") + if (this.getLineTotal(i) === undefined || isNaN(this.getLineTotal(i))) this.validationErrors.push("Invalid data."); } } @@ -180,8 +183,8 @@ export default class SalesQuotationStore { } - getQuotationStatus(statusId) { - var status = ""; + getQuotationStatus(statusId: number) { + let status = ""; if (statusId === 0) status = "Draft"; else if (statusId === 1) @@ -204,7 +207,7 @@ export default class SalesQuotationStore { validationLine() { this.validationErrors = []; if (this.salesQuotation.salesQuotationLines !== undefined && this.salesQuotation.salesQuotationLines.length > 0) { - for (var i = 0; i < this.salesQuotation.salesQuotationLines.length; i++) { + for (let i = 0; i < this.salesQuotation.salesQuotationLines.length; i++) { if (this.salesQuotation.salesQuotationLines[i].itemId === undefined) this.validationErrors.push("Item is required."); if (this.salesQuotation.salesQuotationLines[i].measurementId === undefined) @@ -219,12 +222,10 @@ export default class SalesQuotationStore { } } else { - var itemId, measurementId, quantity, amount, discount; - itemId = (document.getElementById("optNewItemId") as HTMLInputElement).value; - measurementId = (document.getElementById("optNewMeasurementId") as HTMLInputElement).value; - quantity = (document.getElementById("txtNewQuantity") as HTMLInputElement).value; - amount = (document.getElementById("txtNewAmount") as HTMLInputElement).value; - discount = (document.getElementById("txtNewDiscount") as HTMLInputElement).value; + const itemId: string = (document.getElementById("optNewItemId") as HTMLInputElement).value; + const measurementId: string = (document.getElementById("optNewMeasurementId") as HTMLInputElement).value; + const quantity: string = (document.getElementById("txtNewQuantity") as HTMLInputElement).value; + const amount: string = (document.getElementById("txtNewAmount") as HTMLInputElement).value; if (itemId == "" || itemId === undefined) this.validationErrors.push("Item is required."); @@ -237,12 +238,10 @@ export default class SalesQuotationStore { } if (document.getElementById("optNewItemId")) { - var itemId, measurementId, quantity, amount, discount; - itemId = (document.getElementById("optNewItemId") as HTMLInputElement).value; - measurementId = (document.getElementById("optNewMeasurementId") as HTMLInputElement).value; - quantity = (document.getElementById("txtNewQuantity") as HTMLInputElement).value; - amount = (document.getElementById("txtNewAmount") as HTMLInputElement).value; - discount = (document.getElementById("txtNewDiscount") as HTMLInputElement).value; + const itemId: string = (document.getElementById("optNewItemId") as HTMLInputElement).value; + const measurementId: string = (document.getElementById("optNewMeasurementId") as HTMLInputElement).value; + const quantity: string = (document.getElementById("txtNewQuantity") as HTMLInputElement).value; + const amount: string = (document.getElementById("txtNewAmount") as HTMLInputElement).value; if (itemId == "" || itemId === undefined) this.validationErrors.push("Item is required."); @@ -258,57 +257,62 @@ export default class SalesQuotationStore { return this.validationErrors.length === 0; } - changedCustomer(custId) { + changedCustomer(custId: number) { this.salesQuotation.customerId = custId; } - changedPaymentTerm(termId) { + changedPaymentTerm(termId: number) { this.salesQuotation.paymentTermId = termId; } - changedQuotationDate(date) { + changedQuotationDate(date: Date) { this.salesQuotation.quotationDate = date; } - changedReferenceNo(refNo) { + changedReferenceNo(refNo: string) { this.salesQuotation.referenceNo = refNo; } - changedQuoteStatus(statusId) { + changedQuoteStatus(statusId: number) { this.salesQuotation.statusId = statusId; } - changeItemCode(itemId) { + changeItemCode(itemId: number) { - for (var x = 0; x < this.commonStore.items.length; x++) { - if (this.commonStore.items[x].id === parseInt(itemId)) { - return this.commonStore.items[x].code; + for (let x = 0; x < this.commonStore.items.length; x++) { + const lineItewm = this.commonStore.items[x] as SalesQuotationLine; + if (lineItewm.id === itemId) { + return lineItewm.code; } } } - addLineItem(id, itemId, measurementId, quantity, amount, discount, code) { - var newLineItem = new SalesQuotationLine(id, itemId, measurementId, quantity, amount, discount, code); + addLineItem(id: number, itemId: number, measurementId: number, + quantity: number, amount: number, discount: number, code: number) { + + const newLineItem = new SalesQuotationLine(id, itemId, measurementId, quantity, amount, discount, code); this.salesQuotation.salesQuotationLines.push(extendObservable(newLineItem, newLineItem)); } - removeLineItem(row) { + removeLineItem(row: number) { this.salesQuotation.salesQuotationLines.splice(row, 1); } - updateLineItem(row, targetProperty, value) { - //if (this.salesQuotation.salesQuotationLines.length > 0) - this.salesQuotation.salesQuotationLines[row][targetProperty] = value; + updateLineItem(row: number, targetProperty: keyof SalesQuotationLine, value: string | number) { + + if (this.salesQuotation.salesQuotationLines.length > 0) + (this.salesQuotation.salesQuotationLines[row] as Record)[targetProperty] = value; + this.computeTotals(); } - getLineTotal(row) { + getLineTotal(row: number) { let lineSum = 0; - let lineItem = this.salesQuotation.salesQuotationLines[row]; + const lineItem = this.salesQuotation.salesQuotationLines[row]; lineSum = (lineItem.quantity * lineItem.amount) - lineItem.discount; return lineSum; } - changedEditMode(editMode) { + changedEditMode(editMode: boolean) { this.editMode = editMode; } diff --git a/src/AccountGoWeb/Scripts/Shared/Stores/Sales/SalesInvoice.tsx b/src/GoodBooksReact/src/components/Shared/Stores/Sales/SalesInvoice.tsx similarity index 67% rename from src/AccountGoWeb/Scripts/Shared/Stores/Sales/SalesInvoice.tsx rename to src/GoodBooksReact/src/components/Shared/Stores/Sales/SalesInvoice.tsx index 9d2e92614..914026024 100644 --- a/src/AccountGoWeb/Scripts/Shared/Stores/Sales/SalesInvoice.tsx +++ b/src/GoodBooksReact/src/components/Shared/Stores/Sales/SalesInvoice.tsx @@ -2,11 +2,11 @@ export default class SalesInvoice { id: number; - fromSalesOrderId: any; - customerId: number; - invoiceDate: Date; - paymentTermId: number; - referenceNo: string; + fromSalesOrderId: number = 0; + customerId: number = 0; + invoiceDate: Date = new Date(); + paymentTermId: number = 0; + referenceNo: string= ""; posted: boolean; readyForPosting: boolean; salesInvoiceLines: SalesInvoiceLine[] = []; diff --git a/src/GoodBooksReact/src/components/Shared/Stores/Sales/SalesInvoiceLine.tsx b/src/GoodBooksReact/src/components/Shared/Stores/Sales/SalesInvoiceLine.tsx new file mode 100644 index 000000000..8a0b3cbdc --- /dev/null +++ b/src/GoodBooksReact/src/components/Shared/Stores/Sales/SalesInvoiceLine.tsx @@ -0,0 +1,21 @@ +export default class SalesInvoiceLine { + id: number = 0; + itemId: number = 0; + measurementId: number = 0; + quantity: number = 0; + amount: number = 0; + discount: number = 0; + remainingQtyToInvoice: number = 0; + code: string = ""; + + constructor(id: number, itemId: number, measurementId: number, quantity: number, amount: number, discount: number, code: string) { + this.id = id; + this.itemId = itemId; + this.measurementId = measurementId; + this.quantity = quantity; + this.amount = amount; + this.discount = discount; + this.remainingQtyToInvoice = quantity; + this.code = code; + } +} \ No newline at end of file diff --git a/src/AccountGoWeb/Scripts/Shared/Stores/Sales/SalesInvoiceStore.tsx b/src/GoodBooksReact/src/components/Shared/Stores/Sales/SalesInvoiceStore.tsx similarity index 64% rename from src/AccountGoWeb/Scripts/Shared/Stores/Sales/SalesInvoiceStore.tsx rename to src/GoodBooksReact/src/components/Shared/Stores/Sales/SalesInvoiceStore.tsx index 8fcf73db0..480b710e5 100644 --- a/src/AccountGoWeb/Scripts/Shared/Stores/Sales/SalesInvoiceStore.tsx +++ b/src/GoodBooksReact/src/components/Shared/Stores/Sales/SalesInvoiceStore.tsx @@ -1,14 +1,12 @@ -import {observable, extendObservable, action, autorun, computed} from 'mobx'; -import * as axios from "axios"; - -import Config = require("Config"); +import {observable, extendObservable, autorun, makeObservable} from 'mobx'; +import axios from "axios"; +import Config from '../../Config'; import SalesInvoice from './SalesInvoice'; import SalesInvoiceLine from './SalesInvoiceLine'; - import CommonStore from "../Common/CommonStore"; -let baseUrl = location.protocol +const baseUrl = location.protocol + "//" + location.hostname + (location.port && ":" + location.port) + "/"; @@ -16,11 +14,14 @@ let baseUrl = location.protocol export default class SalesStore { salesInvoice; commonStore; - @observable validationErrors; - @observable editMode = false; - - - constructor(orderId, invoiceId) { + validationErrors: string[] = []; + editMode = false; + + RTotal = 0; + GTotal = 0; + TTotal = 0; + + constructor(orderId: number, invoiceId: number) { this.commonStore = new CommonStore(); this.salesInvoice = new SalesInvoice(); extendObservable(this.salesInvoice, { @@ -33,14 +34,21 @@ export default class SalesStore { salesInvoiceLines: [] }); + makeObservable(this, { + validationErrors: observable, + editMode: observable, + RTotal: observable, + GTotal: observable, + TTotal: observable, + }); autorun(() => this.computeTotals()); if (orderId !== undefined) { - var result = axios.get(Config.apiUrl + "sales/salesorder?id=" + orderId); - result.then(function(result) { + const result = axios.get(Config.API_URL + "sales/salesorder?id=" + orderId); + result.then((result) => { - for (var i = 0; i < result.data.salesOrderLines.length; i++) { + for (let i = 0; i < result.data.salesOrderLines.length; i++) { if (result.data.salesOrderLines[i].remainingQtyToInvoice == 0) continue; this.addLineItem( @@ -49,7 +57,8 @@ export default class SalesStore { result.data.salesOrderLines[i].measurementId, result.data.salesOrderLines[i].remainingQtyToInvoice, result.data.salesOrderLines[i].amount, - result.data.salesOrderLines[i].discount + result.data.salesOrderLines[i].discount, + result.data.salesOrderLines[i].code ); } this.salesInvoice.fromSalesOrderId = orderId; @@ -60,20 +69,21 @@ export default class SalesStore { this.computeTotals(); this.changedEditMode(true); - }.bind(this)); + }); } else if (invoiceId !== undefined) { - var result = axios.get(Config.apiUrl + "sales/salesinvoice?id=" + invoiceId); - result.then(function(result) { - for (var i = 0; i < result.data.salesInvoiceLines.length; i++) { + const result = axios.get(Config.API_URL + "sales/salesinvoice?id=" + invoiceId); + result.then((result) => { + for (let i = 0; i < result.data.salesInvoiceLines.length; i++) { this.addLineItem( result.data.salesInvoiceLines[i].id, result.data.salesInvoiceLines[i].itemId, result.data.salesInvoiceLines[i].measurementId, result.data.salesInvoiceLines[i].quantity, result.data.salesInvoiceLines[i].amount, - result.data.salesInvoiceLines[i].discount + result.data.salesInvoiceLines[i].discount, + result.data.salesInvoiceLines[i].code ); - this.updateLineItem(i, 'code', this.changeItemCode(result.data.salesInvoiceLines[i].itemId)); + this.updateLineItem(i, 'code', this.changeItemCode(result.data.salesInvoiceLines[i].itemId)!); } this.salesInvoice.id = result.data.id; @@ -85,37 +95,31 @@ export default class SalesStore { this.salesInvoice.readyForPosting = result.data.readyForPosting; this.computeTotals(); - var nodes = document.getElementById("divSalesInvoiceForm").getElementsByTagName('*'); - for (var i = 0; i < nodes.length; i++) { + const nodes = document.getElementById("divSalesInvoiceForm")!.getElementsByTagName('*'); + for (let i = 0; i < nodes.length; i++) { nodes[i].className += " disabledControl"; } - }.bind(this)); + }); } else this.changedEditMode(true); } - - @observable RTotal = 0; - @observable GTotal = 0; - @observable TTotal = 0; - computeTotals() { - var rtotal = 0; - var ttotal = 0; - var gtotal = 0; + let rtotal = 0; + let ttotal = 0; - for (var i = 0; i < this.salesInvoice.salesInvoiceLines.length; i++) { - var lineItem = this.salesInvoice.salesInvoiceLines[i]; + for (let i = 0; i < this.salesInvoice.salesInvoiceLines.length; i++) { + const lineItem = this.salesInvoice.salesInvoiceLines[i]; rtotal = rtotal + this.getLineTotal(i); - axios.get(Config.apiUrl + "tax/gettax?itemId=" + lineItem.itemId + "&partyId=" + this.salesInvoice.customerId + "&type=1") - .then(function (result) { + axios.get(Config.API_URL + "tax/gettax?itemId=" + lineItem.itemId + "&partyId=" + this.salesInvoice.customerId + "&type=1") + .then((result) => { if (result.data.length > 0) { ttotal = ttotal + this.commonStore.getSalesLineTaxAmount(lineItem.quantity, lineItem.amount, lineItem.discount, result.data); } this.TTotal = ttotal; this.GTotal = rtotal + ttotal; - }.bind(this)); + }); this.RTotal = rtotal; } } @@ -123,53 +127,59 @@ export default class SalesStore { saveNewSalesInvoice() { if (this.salesInvoice.invoiceDate === undefined) - this.salesInvoice.invoiceDate = new Date(Date.now()).toISOString().substring(0, 10); + this.salesInvoice.invoiceDate = new Date(new Date(Date.now()).toISOString().substring(0, 10)); if (this.validation() && this.validationErrors.length === 0) { - axios.post(Config.apiUrl + "sales/savesalesinvoice", JSON.stringify(this.salesInvoice), + axios.post(Config.API_URL + "sales/savesalesinvoice", JSON.stringify(this.salesInvoice), { headers: { 'Content-type': 'application/json' } }) - .then(function (response) { + .then(() => { window.location.href = baseUrl + 'sales/salesinvoices'; }) - .catch(function (error) { - error.data.map(function (err) { - this.validationErrors.push(err); - }.bind(this)); - }.bind(this)) + .catch((error) => { + if (axios.isAxiosError(error)) { + this.validationErrors.push(`Status: ${error.status} - Message: ${error.response?.data}`); + } else { + console.error(error); + this.validationErrors.push(`Error: ${error}`); + } + }) } } printInvoice() { - var w = 800; - var h = 600; - var wLeft = window.screenLeft ? window.screenLeft : window.screenX; - var wTop = window.screenTop ? window.screenTop : window.screenY; + const w = 800; + const h = 600; + const wLeft = window.screenLeft ? window.screenLeft : window.screenX; + const wTop = window.screenTop ? window.screenTop : window.screenY; - var left = wLeft + (window.innerWidth / 2) - (w / 2); - var top = wTop + (window.innerHeight / 2) - (h / 2); + const left = wLeft + (window.innerWidth / 2) - (w / 2); + const top = wTop + (window.innerHeight / 2) - (h / 2); window.open(baseUrl + 'sales/salesinvoicepdf?id=' + this.salesInvoice.id, "_blank", 'toolbar=no, location=no, directories=no, status=no, menubar=no, scrollbars=no, resizable=no, copyhistory=no, width=' + w + ', height=' + h + ', top=' + top + ', left=' + left); } postInvoice() { if (this.validation() && this.validationErrors.length === 0) { - axios.post(Config.apiUrl + "sales/postsalesinvoice", JSON.stringify(this.salesInvoice), + axios.post(Config.API_URL + "sales/postsalesinvoice", JSON.stringify(this.salesInvoice), { headers: { 'Content-type': 'application/json' } }) - .then(function (response) { + .then(() => { window.location.href = baseUrl + 'sales/salesinvoices'; }) - .catch(function (error) { - error.data.map(function (err) { - this.validationErrors.push(err); - }.bind(this)); - }.bind(this)) + .catch((error) => { + if (axios.isAxiosError(error)) { + this.validationErrors.push(`Status: ${error.status} - Message: ${error.response?.data}`); + } else { + console.error(error); + this.validationErrors.push(`Error: ${error}`); + } + }) } } @@ -184,7 +194,7 @@ export default class SalesStore { if (this.salesInvoice.salesInvoiceLines === undefined || this.salesInvoice.salesInvoiceLines.length < 1) this.validationErrors.push("Enter at least 1 line item."); if (this.salesInvoice.salesInvoiceLines !== undefined && this.salesInvoice.salesInvoiceLines.length > 0) { - for (var i = 0; i < this.salesInvoice.salesInvoiceLines.length; i++) { + for (let i = 0; i < this.salesInvoice.salesInvoiceLines.length; i++) { if (this.salesInvoice.salesInvoiceLines[i].itemId === undefined) this.validationErrors.push("Item is required."); if (this.salesInvoice.salesInvoiceLines[i].measurementId === undefined) @@ -208,7 +218,7 @@ export default class SalesStore { validationLine() { this.validationErrors = []; if (this.salesInvoice.salesInvoiceLines !== undefined && this.salesInvoice.salesInvoiceLines.length > 0) { - for (var i = 0; i < this.salesInvoice.salesInvoiceLines.length; i++) { + for (let i = 0; i < this.salesInvoice.salesInvoiceLines.length; i++) { if (this.salesInvoice.salesInvoiceLines[i].itemId === undefined) this.validationErrors.push("Item is required."); if (this.salesInvoice.salesInvoiceLines[i].measurementId === undefined) @@ -223,12 +233,10 @@ export default class SalesStore { } } else { - var itemId, measurementId, quantity, amount, discount; - itemId = (document.getElementById("optNewItemId") as HTMLInputElement).value; - measurementId = (document.getElementById("optNewMeasurementId") as HTMLInputElement).value; - quantity = (document.getElementById("txtNewQuantity") as HTMLInputElement).value; - amount = (document.getElementById("txtNewAmount") as HTMLInputElement).value; - discount = (document.getElementById("txtNewDiscount") as HTMLInputElement).value; + const itemId: string = (document.getElementById("optNewItemId") as HTMLInputElement).value; + const measurementId: string = (document.getElementById("optNewMeasurementId") as HTMLInputElement).value; + const quantity: string = (document.getElementById("txtNewQuantity") as HTMLInputElement).value; + const amount: string = (document.getElementById("txtNewAmount") as HTMLInputElement).value; if (itemId == "" || itemId === undefined) this.validationErrors.push("Item is required."); @@ -241,12 +249,10 @@ export default class SalesStore { } if (document.getElementById("optNewItemId")) { - var itemId, measurementId, quantity, amount, discount; - itemId = (document.getElementById("optNewItemId") as HTMLInputElement).value; - measurementId = (document.getElementById("optNewMeasurementId") as HTMLInputElement).value; - quantity = (document.getElementById("txtNewQuantity") as HTMLInputElement).value; - amount = (document.getElementById("txtNewAmount") as HTMLInputElement).value; - discount = (document.getElementById("txtNewDiscount") as HTMLInputElement).value; + const itemId: string = (document.getElementById("optNewItemId") as HTMLInputElement).value; + const measurementId: string = (document.getElementById("optNewMeasurementId") as HTMLInputElement).value; + const quantity: string = (document.getElementById("txtNewQuantity") as HTMLInputElement).value; + const amount: string = (document.getElementById("txtNewAmount") as HTMLInputElement).value; if (itemId == "" || itemId === undefined) this.validationErrors.push("Item is required."); @@ -263,53 +269,55 @@ export default class SalesStore { } - changedReferenceNo(refNo) { + changedReferenceNo(refNo: string) { this.salesInvoice.referenceNo = refNo; } - changedCustomer(custId) { + + changedCustomer(custId: number) { this.salesInvoice.customerId = custId; } - changedInvoiceDate(date) { + changedInvoiceDate(date: Date) { this.salesInvoice.invoiceDate = date; } - changedPaymentTerm(termId) { + changedPaymentTerm(termId: number) { this.salesInvoice.paymentTermId = termId; } - addLineItem(id, itemId, measurementId, quantity, amount, discount, code) { - var newLineItem = new SalesInvoiceLine(id, itemId, measurementId, quantity, amount, discount, code); + addLineItem(id: number, itemId: number, measurementId: number, quantity: number, amount: number, discount: number, code: string) { + const newLineItem = new SalesInvoiceLine(id, itemId, measurementId, quantity, amount, discount, code); this.salesInvoice.salesInvoiceLines.push(extendObservable(newLineItem, newLineItem)); } - removeLineItem(row) { + removeLineItem(row: number) { this.salesInvoice.salesInvoiceLines.splice(row, 1); } - updateLineItem(row, targetProperty, value) { + updateLineItem(row: number, targetProperty: keyof SalesInvoiceLine, value: string | number) { if (this.salesInvoice.salesInvoiceLines.length > 0) - this.salesInvoice.salesInvoiceLines[row][targetProperty] = value; + (this.salesInvoice.salesInvoiceLines[row] as Record)[targetProperty] = value; this.computeTotals(); } - getLineTotal(row) { + getLineTotal(row: number) { let lineSum = 0; - let lineItem = this.salesInvoice.salesInvoiceLines[row]; + const lineItem = this.salesInvoice.salesInvoiceLines[row]; lineSum = (lineItem.quantity * lineItem.amount) - lineItem.discount; return lineSum; } - changedEditMode(editMode) { + changedEditMode(editMode: boolean) { this.editMode = editMode; } - changeItemCode(itemId) { + changeItemCode(itemId: number) { - for (var x = 0; x < this.commonStore.items.length; x++) { - if (this.commonStore.items[x].id === parseInt(itemId)) { - return this.commonStore.items[x].code; + for (let x = 0; x < this.commonStore.items.length; x++) { + const lineItem = this.commonStore.items[x] as SalesInvoiceLine; + if (lineItem.id === itemId) { + return lineItem.code; } } } diff --git a/src/GoodBooksReact/src/components/Shared/Stores/Sales/SalesOrder.tsx b/src/GoodBooksReact/src/components/Shared/Stores/Sales/SalesOrder.tsx new file mode 100644 index 000000000..5398fb0e8 --- /dev/null +++ b/src/GoodBooksReact/src/components/Shared/Stores/Sales/SalesOrder.tsx @@ -0,0 +1,12 @@ +import SalesOrderLine from "./SalesOrderLine"; + +export default class SalesOrder { + id: number = 0; + customerId: number = 0; + orderDate: Date = new Date(); + paymentTermId: number = 0; + referenceNo: string = ""; + statusId: number = 0; + quotationId: number = 0; + salesOrderLines: SalesOrderLine[] = []; +} \ No newline at end of file diff --git a/src/GoodBooksReact/src/components/Shared/Stores/Sales/SalesOrderLine.tsx b/src/GoodBooksReact/src/components/Shared/Stores/Sales/SalesOrderLine.tsx new file mode 100644 index 000000000..4fdf56719 --- /dev/null +++ b/src/GoodBooksReact/src/components/Shared/Stores/Sales/SalesOrderLine.tsx @@ -0,0 +1,19 @@ +export default class SalesOrderLine { + id: number = 0; + itemId: number = 0; + measurementId: number = 0; + quantity: number = 0; + amount: number = 0; + discount: number = 0; + code: string = ""; + + constructor(id: number, itemId: number, measurementId: number, quantity: number, amount: number, discount: number, code: string) { + this.id = id; + this.itemId = itemId; + this.measurementId = measurementId; + this.quantity = quantity; + this.amount = amount; + this.discount = discount; + this.code = code; + } +} \ No newline at end of file diff --git a/src/AccountGoWeb/Scripts/Shared/Stores/Sales/SalesOrderStore.tsx b/src/GoodBooksReact/src/components/Shared/Stores/Sales/SalesOrderStore.tsx similarity index 66% rename from src/AccountGoWeb/Scripts/Shared/Stores/Sales/SalesOrderStore.tsx rename to src/GoodBooksReact/src/components/Shared/Stores/Sales/SalesOrderStore.tsx index 5cd442fd9..3e9a46f17 100644 --- a/src/AccountGoWeb/Scripts/Shared/Stores/Sales/SalesOrderStore.tsx +++ b/src/GoodBooksReact/src/components/Shared/Stores/Sales/SalesOrderStore.tsx @@ -1,14 +1,13 @@ -import {observable, extendObservable, action, autorun, computed} from 'mobx'; -import * as axios from "axios"; - -import Config = require("Config"); +import {observable, extendObservable, autorun, makeObservable} from 'mobx'; +import axios from "axios"; +import Config from '../../Config'; import SalesOrder from './SalesOrder'; import SalesOrderLine from './SalesOrderLine'; import CommonStore from "../Common/CommonStore"; -let baseUrl = location.protocol +const baseUrl = location.protocol + "//" + location.hostname + (location.port && ":" + location.port) + "/"; @@ -16,12 +15,17 @@ let baseUrl = location.protocol export default class SalesOrderStore { salesOrder; commonStore; - @observable validationErrors; - @observable salesOrderStatus; - @observable salesQuotationStatus; - @observable editMode = false; - @observable hasQuotation = false; - constructor(quotationId, orderId) { + validationErrors: string[] = []; + salesOrderStatus: string = ""; + salesQuotationStatus: string = ""; + editMode = false; + hasQuotation = false; + + RTotal = 0; + GTotal = 0; + TTotal = 0; + + constructor(quotationId: number, orderId: number) { this.commonStore = new CommonStore(); this.salesOrder = new SalesOrder(); extendObservable(this.salesOrder, { @@ -33,11 +37,22 @@ export default class SalesOrderStore { salesOrderLines: [] }); + makeObservable(this, { + validationErrors: observable, + salesOrderStatus: observable, + salesQuotationStatus: observable, + editMode: observable, + hasQuotation: observable, + RTotal: observable, + GTotal: observable, + TTotal: observable, + }); + autorun(() => this.computeTotals()); if (quotationId !== undefined) { - var result = axios.get(Config.apiUrl + "sales/quotation?id=" + quotationId); - result.then(function (result) { + const result = axios.get(Config.API_URL + "sales/quotation?id=" + quotationId); + result.then((result) => { this.changedCustomer(result.data.customerId); this.salesOrder.paymentTermId = result.data.paymentTermId; this.salesOrder.referenceNo = result.data.referenceNo; @@ -46,49 +61,51 @@ export default class SalesOrderStore { this.hasQuotation = true; // this variable will serve as the footprint that it has a quotation and the edit button wil be disable. //for addition to save quotation to set status closed - order created this.salesOrder.quotationId = quotationId; - for (var i = 0; i < result.data.salesQuotationLines.length; i++) { + for (let i = 0; i < result.data.salesQuotationLines.length; i++) { this.addLineItem( result.data.salesQuotationLines[i].id, result.data.salesQuotationLines[i].itemId, result.data.salesQuotationLines[i].measurementId, result.data.salesQuotationLines[i].quantity, result.data.salesQuotationLines[i].amount, - result.data.salesQuotationLines[i].discount + result.data.salesQuotationLines[i].discount, + result.data.salesQuotationLines[i].code ); } this.computeTotals(); - var nodes = document.getElementById("divSalesOrderForm").getElementsByTagName('*'); - for (var i = 0; i < nodes.length; i++) { + const nodes = document.getElementById("divSalesOrderForm")!.getElementsByTagName('*'); + for (let i = 0; i < nodes.length; i++) { nodes[i].className += " disabledControl"; } - }.bind(this)); + }); } else if (orderId !== undefined) { - var result = axios.get(Config.apiUrl + "sales/salesorder?id=" + orderId); - result.then(function (result) { + const result = axios.get(Config.API_URL + "sales/salesorder?id=" + orderId); + result.then((result) => { this.salesOrder.id = result.data.id; this.changedCustomer(result.data.customerId); this.salesOrder.paymentTermId = result.data.paymentTermId; this.salesOrder.referenceNo = result.data.referenceNo; this.changedOrderDate(result.data.orderDate); this.getOrderStatus(result.data.statusId); - for (var i = 0; i < result.data.salesOrderLines.length; i++) { + for (let i = 0; i < result.data.salesOrderLines.length; i++) { this.addLineItem( result.data.salesOrderLines[i].id, result.data.salesOrderLines[i].itemId, result.data.salesOrderLines[i].measurementId, result.data.salesOrderLines[i].quantity, result.data.salesOrderLines[i].amount, - result.data.salesOrderLines[i].discount + result.data.salesOrderLines[i].discount, + result.data.salesOrderLines[i].code ); - this.updateLineItem(i, 'code', this.changeItemCode(result.data.salesOrderLines[i].itemId)); + this.updateLineItem(i, 'code', this.changeItemCode(result.data.salesOrderLines[i].itemId)!); } this.computeTotals(); - var nodes = document.getElementById("divSalesOrderForm").getElementsByTagName('*'); - for (var i = 0; i < nodes.length; i++) { + const nodes = document.getElementById("divSalesOrderForm")!.getElementsByTagName('*'); + for (let i = 0; i < nodes.length; i++) { nodes[i].className += " disabledControl"; } - }.bind(this)); + }); } else { @@ -96,52 +113,48 @@ export default class SalesOrderStore { } } - @observable RTotal = 0; - @observable GTotal = 0; - @observable TTotal = 0; - computeTotals() { - var rtotal = 0; - var ttotal = 0; - var gtotal = 0; + let rtotal = 0; + let ttotal = 0; - for (var i = 0; i < this.salesOrder.salesOrderLines.length; i++) { - var lineItem = this.salesOrder.salesOrderLines[i]; + for (let i = 0; i < this.salesOrder.salesOrderLines.length; i++) { + const lineItem = this.salesOrder.salesOrderLines[i]; rtotal = rtotal + this.getLineTotal(i); - axios.get(Config.apiUrl + "tax/gettax?itemId=" + lineItem.itemId + "&partyId=" + this.salesOrder.customerId + "&type=1") - .then(function (result) { + axios.get(Config.API_URL + "tax/gettax?itemId=" + lineItem.itemId + "&partyId=" + this.salesOrder.customerId + "&type=1") + .then((result) => { if (result.data.length > 0) { ttotal = ttotal + this.commonStore.getSalesLineTaxAmount(lineItem.quantity, lineItem.amount, lineItem.discount, result.data); } this.TTotal = ttotal; this.GTotal = rtotal + ttotal; - }.bind(this)); + }); this.RTotal = rtotal; } } saveNewSalesOrder() { - - if (this.salesOrder.orderDate === undefined) - this.salesOrder.orderDate = new Date(Date.now()).toISOString().substring(0, 10); + this.salesOrder.orderDate = new Date(new Date(Date.now()).toISOString().substring(0, 10)); if (this.validation() && this.validationErrors.length === 0) { - axios.post(Config.apiUrl + "sales/savesalesorder", JSON.stringify(this.salesOrder), + axios.post(Config.API_URL + "sales/savesalesorder", JSON.stringify(this.salesOrder), { headers: { 'Content-type': 'application/json' } }) - .then(function (response) { + .then(() => { window.location.href = baseUrl + 'sales/salesorders'; }) - .catch(function (error) { - error.data.map(function (err) { - this.validationErrors.push(err); - }.bind(this)); - }.bind(this)) + .catch((error) => { + if (axios.isAxiosError(error)) { + this.validationErrors.push(`Status: ${error.status} - Message: ${error.response?.data}`); + } else { + console.error(error); + this.validationErrors.push(`Error: ${error}`); + } + }) } } @@ -156,7 +169,7 @@ export default class SalesOrderStore { if (this.salesOrder.salesOrderLines === undefined || this.salesOrder.salesOrderLines.length < 1) this.validationErrors.push("Enter at least 1 line item."); if (this.salesOrder.salesOrderLines !== undefined && this.salesOrder.salesOrderLines.length > 0) { - for (var i = 0; i < this.salesOrder.salesOrderLines.length; i++) { + for (let i = 0; i < this.salesOrder.salesOrderLines.length; i++) { if (this.salesOrder.salesOrderLines[i].itemId === undefined) this.validationErrors.push("Item is required."); if (this.salesOrder.salesOrderLines[i].measurementId === undefined) @@ -180,7 +193,7 @@ export default class SalesOrderStore { validationLine() { this.validationErrors = []; if (this.salesOrder.salesOrderLines !== undefined && this.salesOrder.salesOrderLines.length > 0) { - for (var i = 0; i < this.salesOrder.salesOrderLines.length; i++) { + for (let i = 0; i < this.salesOrder.salesOrderLines.length; i++) { if (this.salesOrder.salesOrderLines[i].itemId === undefined) this.validationErrors.push("Item is required."); if (this.salesOrder.salesOrderLines[i].measurementId === undefined) @@ -195,12 +208,10 @@ export default class SalesOrderStore { } } else { - var itemId, measurementId, quantity, amount, discount; - itemId = (document.getElementById("optNewItemId") as HTMLInputElement).value; - measurementId = (document.getElementById("optNewMeasurementId") as HTMLInputElement).value; - quantity = (document.getElementById("txtNewQuantity") as HTMLInputElement).value; - amount = (document.getElementById("txtNewAmount") as HTMLInputElement).value; - discount = (document.getElementById("txtNewDiscount") as HTMLInputElement).value; + const itemId: string = (document.getElementById("optNewItemId") as HTMLInputElement).value; + const measurementId: string = (document.getElementById("optNewMeasurementId") as HTMLInputElement).value; + const quantity: string = (document.getElementById("txtNewQuantity") as HTMLInputElement).value; + const amount: string = (document.getElementById("txtNewAmount") as HTMLInputElement).value; if (itemId == "" || itemId === undefined) this.validationErrors.push("Item is required."); @@ -213,12 +224,10 @@ export default class SalesOrderStore { } if (document.getElementById("optNewItemId")) { - var itemId, measurementId, quantity, amount, discount; - itemId = (document.getElementById("optNewItemId") as HTMLInputElement).value; - measurementId = (document.getElementById("optNewMeasurementId") as HTMLInputElement).value; - quantity = (document.getElementById("txtNewQuantity") as HTMLInputElement).value; - amount = (document.getElementById("txtNewAmount") as HTMLInputElement).value; - discount = (document.getElementById("txtNewDiscount") as HTMLInputElement).value; + const itemId: string = (document.getElementById("optNewItemId") as HTMLInputElement).value; + const measurementId: string = (document.getElementById("optNewMeasurementId") as HTMLInputElement).value; + const quantity: string = (document.getElementById("txtNewQuantity") as HTMLInputElement).value; + const amount: string = (document.getElementById("txtNewAmount") as HTMLInputElement).value; if (itemId == "" || itemId === undefined) this.validationErrors.push("Item is required."); @@ -234,8 +243,8 @@ export default class SalesOrderStore { return this.validationErrors.length === 0; } - getQuotationStatus(statusId) { - var status = ""; + getQuotationStatus(statusId: number) { + let status = ""; if (statusId === 0) status = "Draft"; else if (statusId === 1) @@ -250,8 +259,8 @@ export default class SalesOrderStore { } - getOrderStatus(statusId) { - var status = ""; + getOrderStatus(statusId: number) { + let status = ""; if (statusId === 0) status = "Draft"; else if (statusId === 1) @@ -269,54 +278,55 @@ export default class SalesOrderStore { this.salesOrderStatus = status; } - changeItemCode(itemId) { - - for (var x = 0; x < this.commonStore.items.length; x++) { - if (this.commonStore.items[x].id === parseInt(itemId)) { - return this.commonStore.items[x].code; + changeItemCode(itemId: number) { + for (let x = 0; x < this.commonStore.items.length; x++) { + const lineItem = this.commonStore.items[x] as SalesOrderLine; + if (lineItem.id === itemId) { + return lineItem.code; } } } - changedReferenceNo(refNo) { + changedReferenceNo(refNo: string) { this.salesOrder.referenceNo = refNo; } - changedCustomer(custId) { + + changedCustomer(custId: number) { this.salesOrder.customerId = custId; } - changedOrderDate(date) { + changedOrderDate(date: Date) { this.salesOrder.orderDate = date; } - changedPaymentTerm(termId) { + changedPaymentTerm(termId: number) { this.salesOrder.paymentTermId = termId; } - addLineItem(id, itemId, measurementId, quantity, amount, discount, code) { - var newLineItem = new SalesOrderLine(id, itemId, measurementId, quantity, amount, discount, code); + addLineItem(id: number, itemId: number, measurementId: number, quantity: number, amount: number, discount: number, code: string) { + const newLineItem = new SalesOrderLine(id, itemId, measurementId, quantity, amount, discount, code); this.salesOrder.salesOrderLines.push(extendObservable(newLineItem, newLineItem)); } - removeLineItem(row) { + removeLineItem(row: number) { this.salesOrder.salesOrderLines.splice(row, 1); } - updateLineItem(row, targetProperty, value) { + updateLineItem(row: number, targetProperty: keyof SalesOrderLine, value: string | number) { if (this.salesOrder.salesOrderLines.length > 0) - this.salesOrder.salesOrderLines[row][targetProperty] = value; + (this.salesOrder.salesOrderLines[row] as Record)[targetProperty] = value; this.computeTotals(); } - getLineTotal(row) { + getLineTotal(row: number) { let lineSum = 0; - let lineItem = this.salesOrder.salesOrderLines[row]; + const lineItem = this.salesOrder.salesOrderLines[row]; lineSum = (lineItem.quantity * lineItem.amount) - lineItem.discount; return lineSum; } - changedEditMode(editMode) { + changedEditMode(editMode: boolean) { this.editMode = editMode; } } \ No newline at end of file diff --git a/src/GoodBooksReact/src/components/Shared/Stores/TaxSystem/Tax.tsx b/src/GoodBooksReact/src/components/Shared/Stores/TaxSystem/Tax.tsx new file mode 100644 index 000000000..7a7d2a025 --- /dev/null +++ b/src/GoodBooksReact/src/components/Shared/Stores/TaxSystem/Tax.tsx @@ -0,0 +1,6 @@ +export default class SalesOrder { + id: number = 0; + code: string = ''; + name: string = ''; + rate: number = 0; +} \ No newline at end of file diff --git a/src/AccountGoWeb/Scripts/Shared/Stores/TaxSystem/TaxConfigStore.tsx b/src/GoodBooksReact/src/components/Shared/Stores/TaxSystem/TaxConfigStore.tsx similarity index 100% rename from src/AccountGoWeb/Scripts/Shared/Stores/TaxSystem/TaxConfigStore.tsx rename to src/GoodBooksReact/src/components/Shared/Stores/TaxSystem/TaxConfigStore.tsx diff --git a/src/AccountGoWeb/Scripts/Test/Hello.tsx b/src/GoodBooksReact/src/components/Test/Hello.tsx similarity index 85% rename from src/AccountGoWeb/Scripts/Test/Hello.tsx rename to src/GoodBooksReact/src/components/Test/Hello.tsx index c0c769314..f6af1142c 100644 --- a/src/AccountGoWeb/Scripts/Test/Hello.tsx +++ b/src/GoodBooksReact/src/components/Test/Hello.tsx @@ -5,7 +5,7 @@ interface HelloProps { name: string; } -class Hello extends React.Component { +class Hello extends React.Component> { render() { return
    Hello, {this.props.name}. If you can see this message means you have successfully configured reactjs+typescript+webpack+babel-loader.
    ; } diff --git a/src/GoodBooksReact/src/components/src/@types/index.d.ts.delete b/src/GoodBooksReact/src/components/src/@types/index.d.ts.delete new file mode 100644 index 000000000..4c57082fe --- /dev/null +++ b/src/GoodBooksReact/src/components/src/@types/index.d.ts.delete @@ -0,0 +1,11 @@ +// Fixes TS2694 +declare global { + namespace React { + /** Fixes React 18 compatibility issues with formik: https://github.com/jaredpalmer/formik/issues/3546#issuecomment-1127014775 */ + type StatelessComponent

    = React.FunctionComponent

    ; + } + } + + // Fixes TS2669 + export {}; + \ No newline at end of file diff --git a/src/GoodBooksReact/src/index.css b/src/GoodBooksReact/src/index.css new file mode 100644 index 000000000..8e93d311a --- /dev/null +++ b/src/GoodBooksReact/src/index.css @@ -0,0 +1,68 @@ +:root { + font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif; + line-height: 1.5; + font-weight: 400; + + color-scheme: light dark; + color: rgba(255, 255, 255, 0.87); + background-color: #242424; + + font-synthesis: none; + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +a { + font-weight: 500; + color: #646cff; + text-decoration: inherit; +} +a:hover { + color: #535bf2; +} + +body { + margin: 0; + /* display: flex; */ + place-items: center; + min-width: 320px; + min-height: 100vh; +} + +h1 { + font-size: 3.2em; + line-height: 1.1; +} + +button { + border-radius: 8px; + border: 1px solid transparent; + padding: 0.6em 1.2em; + font-size: 1em; + font-weight: 500; + font-family: inherit; + background-color: #1a1a1a; + cursor: pointer; + transition: border-color 0.25s; +} +button:hover { + border-color: #646cff; +} +button:focus, +button:focus-visible { + outline: 4px auto -webkit-focus-ring-color; +} + +@media (prefers-color-scheme: light) { + :root { + color: #213547; + background-color: #ffffff; + } + a:hover { + color: #747bff; + } + button { + background-color: #f9f9f9; + } +} diff --git a/src/GoodBooksReact/src/main.tsx b/src/GoodBooksReact/src/main.tsx new file mode 100644 index 000000000..1ccafcbf1 --- /dev/null +++ b/src/GoodBooksReact/src/main.tsx @@ -0,0 +1,14 @@ +import React from 'react' +import ReactDOM from 'react-dom/client' +// import App from './App.tsx' +import './index.css' +import { RouterProvider } from 'react-router' +import {router} from './routes/Routes' +import "bootstrap/dist/css/bootstrap.min.css"; +import "bootstrap/dist/js/bootstrap.min.js"; + +ReactDOM.createRoot(document.getElementById('root')!).render( + + + , +) diff --git a/src/GoodBooksReact/src/routes/Routes.tsx b/src/GoodBooksReact/src/routes/Routes.tsx new file mode 100644 index 000000000..dc4ddd1b1 --- /dev/null +++ b/src/GoodBooksReact/src/routes/Routes.tsx @@ -0,0 +1,41 @@ +import { createBrowserRouter } from "react-router-dom"; +import App from "../App"; +import JournalEntry from "../components/Financials/JournalEntry" +import ObservedPurchaseInvoice from "../components/Purchasing/PurchaseInvoice"; +import ObservedAddPurchaseOrder from "../components/Purchasing/PurchaseOrder"; +import ObservedSalesInvoice from "../components/Sales/SalesInvoice"; +import ObservedSalesOrder from "../components/Sales/SalesOrder"; + + +export const router = createBrowserRouter([ + { + path: "/", + element: , + children: [ + { + path: "/journal-entry", + element: + }, + { + path: "/purchasing-invoice", + element: + }, + { + path: "/purchase-order", + element: + }, + { + path: "/sales-invoice", + element: + }, + { + path: "/sales-order", + element: + }, + { + path: "/company/:id", + element:

    About

    + } + ] + } +]); \ No newline at end of file diff --git a/src/GoodBooksReact/src/vite-env.d.ts b/src/GoodBooksReact/src/vite-env.d.ts new file mode 100644 index 000000000..11f02fe2a --- /dev/null +++ b/src/GoodBooksReact/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/src/GoodBooksReact/tsconfig.json b/src/GoodBooksReact/tsconfig.json new file mode 100644 index 000000000..bdd1de0fe --- /dev/null +++ b/src/GoodBooksReact/tsconfig.json @@ -0,0 +1,28 @@ +{ + "compilerOptions": { + "target": "ES2020", + "useDefineForClassFields": true, + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "module": "ESNext", + "skipLibCheck": true, + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx", + + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true + }, + "include": ["src"], + "references": [{ "path": "./tsconfig.node.json" }], + "exclude": [ + "node_modules" + ] +} diff --git a/src/GoodBooksReact/tsconfig.node.json b/src/GoodBooksReact/tsconfig.node.json new file mode 100644 index 000000000..42872c59f --- /dev/null +++ b/src/GoodBooksReact/tsconfig.node.json @@ -0,0 +1,10 @@ +{ + "compilerOptions": { + "composite": true, + "skipLibCheck": true, + "module": "ESNext", + "moduleResolution": "bundler", + "allowSyntheticDefaultImports": true + }, + "include": ["vite.config.ts"] +} diff --git a/src/GoodBooksReact/vite.config.ts b/src/GoodBooksReact/vite.config.ts new file mode 100644 index 000000000..5a33944a9 --- /dev/null +++ b/src/GoodBooksReact/vite.config.ts @@ -0,0 +1,7 @@ +import { defineConfig } from 'vite' +import react from '@vitejs/plugin-react' + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [react()], +}) diff --git a/src/AccountGoWeb/webpack.config.js b/src/GoodBooksReact/webpack.config.js similarity index 98% rename from src/AccountGoWeb/webpack.config.js rename to src/GoodBooksReact/webpack.config.js index 3578655ef..3b51f29a6 100644 --- a/src/AccountGoWeb/webpack.config.js +++ b/src/GoodBooksReact/webpack.config.js @@ -70,4 +70,4 @@ var config = { } }; -module.exports = config; \ No newline at end of file +module.exports = config; diff --git a/src/Infrastructure/Infrastructure.csproj b/src/Infrastructure/Infrastructure.csproj index 641831e7a..e6de2fed8 100644 --- a/src/Infrastructure/Infrastructure.csproj +++ b/src/Infrastructure/Infrastructure.csproj @@ -3,12 +3,13 @@ netstandard2.1 Library + true - + - + diff --git a/src/LibraryGDB/LibraryGDB.csproj b/src/LibraryGDB/LibraryGDB.csproj new file mode 100644 index 000000000..3811fc8a8 --- /dev/null +++ b/src/LibraryGDB/LibraryGDB.csproj @@ -0,0 +1,14 @@ + + + net10.0 + enable + enable + + + + + + + + + \ No newline at end of file diff --git a/src/LibraryGDB/Models/Account/LoginViewModel.cs b/src/LibraryGDB/Models/Account/LoginViewModel.cs new file mode 100644 index 000000000..55e4ddd8e --- /dev/null +++ b/src/LibraryGDB/Models/Account/LoginViewModel.cs @@ -0,0 +1,17 @@ +using System.ComponentModel.DataAnnotations; + +namespace LibraryGDB.Models.Account; + +public class LoginViewModel +{ + [Required] + [EmailAddress] + public string? Email { get; set; } + + [Required] + [DataType(DataType.Password)] + public string? Password { get; set; } + + [Display(Name = "Remember me?")] + public bool RememberMe { get; set; } +} diff --git a/src/LibraryGDB/Models/Account/RegisterViewModel.cs b/src/LibraryGDB/Models/Account/RegisterViewModel.cs new file mode 100644 index 000000000..f035c0f38 --- /dev/null +++ b/src/LibraryGDB/Models/Account/RegisterViewModel.cs @@ -0,0 +1,28 @@ +using System.ComponentModel.DataAnnotations; + +namespace LibraryGDB.Models.Account; + +public class RegisterViewModel +{ + [Required] + [EmailAddress] + [Display(Name = "Email")] + public string? Email { get; set; } + + [Required] + [StringLength(100, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 6)] + [DataType(DataType.Password)] + [Display(Name = "Password")] + public string? Password { get; set; } + + [DataType(DataType.Password)] + [Display(Name = "Confirm password")] + [Compare("Password", ErrorMessage = "The password and confirmation password do not match.")] + public string? ConfirmPassword { get; set; } + + [Display(Name = "First Name")] + public string? FirstName { get; set; } + + [Display(Name = "Last Name")] + public string? LastName { get; set; } +} diff --git a/src/LibraryGDB/Models/Bogus/Student.cs b/src/LibraryGDB/Models/Bogus/Student.cs new file mode 100644 index 000000000..1797888ba --- /dev/null +++ b/src/LibraryGDB/Models/Bogus/Student.cs @@ -0,0 +1,62 @@ +namespace LibraryGDB.Models.Bogus; + +public class Student +{ + required public int? Id { get; set; } + required public string FirstName { get; set; } + required public string LastName { get; set; } + required public string School { get; set; } + public static IQueryable GetStudents() + { + int ndx = 0; + List students = new List() { + new Student() { Id = ++ndx, FirstName="Max", LastName="Pao", School="Science" }, + new Student() { Id = ++ndx, FirstName="Tom", LastName="Fay", School="Mining" }, + new Student() { Id = ++ndx, FirstName="Ann", LastName="Sun", School="Nursing" }, + new Student() { Id = ++ndx, FirstName="Joe", LastName="Fox", School="Computing" }, + new Student() { Id = ++ndx, FirstName="Sue", LastName="Mai", School="Mining" }, + new Student() { Id = ++ndx, FirstName="Ben", LastName="Lau", School="Business" }, + new Student() { Id = ++ndx, FirstName="Zoe", LastName="Ray", School="Mining" }, + new Student() { Id = ++ndx, FirstName="Sam", LastName="Ash", School="Medicine" }, + new Student() { Id = ++ndx, FirstName="Dan", LastName="Lee", School="Computing" }, + new Student() { Id = ++ndx, FirstName="Pat", LastName="Day", School="Science" }, + new Student() { Id = ++ndx, FirstName="Kim", LastName="Rex", School="Computing" }, + new Student() { Id = ++ndx, FirstName="Tim", LastName="Ram", School="Business" }, + new Student() { Id = ++ndx, FirstName="Rob", LastName="Wei", School="Mining" }, + new Student() { Id = ++ndx, FirstName="Jan", LastName="Tex", School="Science" }, + new Student() { Id = ++ndx, FirstName="Jim", LastName="Kid", School="Business" }, + new Student() { Id = ++ndx, FirstName="Ben", LastName="Chu", School="Medicine" }, + new Student() { Id = ++ndx, FirstName="Mia", LastName="Tao", School="Computing" }, + new Student() { Id = ++ndx, FirstName="Ted", LastName="Day", School="Business" }, + new Student() { Id = ++ndx, FirstName="Amy", LastName="Roy", School="Science" }, + new Student() { Id = ++ndx, FirstName="Ian", LastName="Kit", School="Nursing" }, + new Student() { Id = ++ndx, FirstName="Liz", LastName="Tan", School="Medicine" }, + new Student() { Id = ++ndx, FirstName="Mat", LastName="Roy", School="Tourism" }, + new Student() { Id = ++ndx, FirstName="Deb", LastName="Luo", School="Medicine" }, + new Student() { Id = ++ndx, FirstName="Ana", LastName="Poe", School="Computing" }, + new Student() { Id = ++ndx, FirstName="Lyn", LastName="Raj", School="Science" }, + new Student() { Id = ++ndx, FirstName="Amy", LastName="Ash", School="Tourism" }, + new Student() { Id = ++ndx, FirstName="Kim", LastName="Kid", School="Mining" }, + new Student() { Id = ++ndx, FirstName="Bec", LastName="Fry", School="Nursing" }, + new Student() { Id = ++ndx, FirstName="Eva", LastName="Lap", School="Computing" }, + new Student() { Id = ++ndx, FirstName="Eli", LastName="Yim", School="Business" }, + new Student() { Id = ++ndx, FirstName="Sam", LastName="Hui", School="Science" }, + new Student() { Id = ++ndx, FirstName="Joe", LastName="Jin", School="Mining" }, + new Student() { Id = ++ndx, FirstName="Liz", LastName="Kuo", School="Agriculture" }, + new Student() { Id = ++ndx, FirstName="Ric", LastName="Mak", School="Tourism" }, + new Student() { Id = ++ndx, FirstName="Pam", LastName="Day", School="Computing" }, + new Student() { Id = ++ndx, FirstName="Stu", LastName="Gad", School="Business" }, + new Student() { Id = ++ndx, FirstName="Tom", LastName="Bee", School="Tourism" }, + new Student() { Id = ++ndx, FirstName="Bob", LastName="Lam", School="Agriculture" }, + new Student() { Id = ++ndx, FirstName="Jim", LastName="Ots", School="Medicine" }, + new Student() { Id = ++ndx, FirstName="Tom", LastName="Mag", School="Mining" }, + new Student() { Id = ++ndx, FirstName="Hal", LastName="Doe", School="Agriculture" }, + new Student() { Id = ++ndx, FirstName="Roy", LastName="Kim", School="Nursing" }, + new Student() { Id = ++ndx, FirstName="Vis", LastName="Cox", School="Science" }, + new Student() { Id = ++ndx, FirstName="Kay", LastName="Aga", School="Tourism" }, + new Student() { Id = ++ndx, FirstName="Reo", LastName="Hui", School="Business" }, + new Student() { Id = ++ndx, FirstName="Bob", LastName="Roe", School="Medicine" }, + }; + return students.AsQueryable(); + } +} diff --git a/src/LibraryGDB/Models/Constants.cs b/src/LibraryGDB/Models/Constants.cs new file mode 100644 index 000000000..c47aa4b7d --- /dev/null +++ b/src/LibraryGDB/Models/Constants.cs @@ -0,0 +1,6 @@ +namespace LibraryGDB.Models; + +public class Constants +{ + public const string API_URL = "http://goodbooksapi.azurewebsites.net/api/"; +} diff --git a/src/LibraryGDB/Models/Donations/DonationInvoiceViewModel.cs b/src/LibraryGDB/Models/Donations/DonationInvoiceViewModel.cs new file mode 100644 index 000000000..b3aba458a --- /dev/null +++ b/src/LibraryGDB/Models/Donations/DonationInvoiceViewModel.cs @@ -0,0 +1,34 @@ +using System; +using System.Collections.Generic; + +namespace LibraryGDB.Models.Donations +{ + public class DonationInvoiceViewModel + { + public int Id { get; set; } + public string? No { get; set; } + public int DonorId { get; set; } + public string? DonorName { get; set; } + public DateTime DonationDate { get; set; } + public decimal Amount { get; set; } + public string? ReferenceNo { get; set; } + public string? Purpose { get; set; } + public bool IsTaxReceiptIssued { get; set; } + public string? TaxReceiptNo { get; set; } + public bool Posted { get; set; } + public List? DonationInvoiceLines { get; set; } + } + + public class DonationInvoiceLineViewModel + { + public int Id { get; set; } + public int? ItemId { get; set; } + public string? ItemNo { get; set; } + public string? ItemDescription { get; set; } + public int? MeasurementId { get; set; } + public string? MeasurementDescription { get; set; } + public decimal? Quantity { get; set; } + public decimal? Amount { get; set; } + public string? Notes { get; set; } + } +} diff --git a/src/LibraryGDB/Models/Financial/AccountViewModel.cs b/src/LibraryGDB/Models/Financial/AccountViewModel.cs new file mode 100644 index 000000000..fd8974fb7 --- /dev/null +++ b/src/LibraryGDB/Models/Financial/AccountViewModel.cs @@ -0,0 +1,13 @@ +namespace LibraryGDB.Models.Financial +{ + public class AccountViewModel + { + public string? AccountCode { get; set; } + public string? AccountName { get; set; } + public decimal TotalBalance { get; set; } + public decimal TotalDebitBalance { get; set; } + public decimal TotalCreditBalance { get; set; } + public IList? ChildAccounts { get; set; } + + } +} diff --git a/src/LibraryGDB/Models/Financial/GeneralLedgerSetting.cs b/src/LibraryGDB/Models/Financial/GeneralLedgerSetting.cs new file mode 100644 index 000000000..f41dd90b1 --- /dev/null +++ b/src/LibraryGDB/Models/Financial/GeneralLedgerSetting.cs @@ -0,0 +1,13 @@ +namespace LibraryGDB.Models.Financial; + +public class GeneralLedgerSetting +{ + public int Id { get; set; } + public int? CompanyId { get; set; } + public string? CompanyCode { get; set; } + public int? PayableAccountId { get; set; } + public int? PurchaseDiscountAccountId { get; set; } + public int? GoodsReceiptNoteClearingAccountId { get; set; } + public int? SalesDiscountAccountId { get; set; } + public int? ShippingChargeAccountId { get; set; } +} diff --git a/src/LibraryGDB/Models/FinancialReports.cs b/src/LibraryGDB/Models/FinancialReports.cs new file mode 100644 index 000000000..24eeb4e7b --- /dev/null +++ b/src/LibraryGDB/Models/FinancialReports.cs @@ -0,0 +1,42 @@ +namespace LibraryGDB.Models; + +public class TrialBalance +{ + public int AccountId { get; set; } + public string? AccountCode { get; set; } + public string? AccountName { get; set; } + public decimal Debit { get; set; } + public decimal Credit { get; set; } +} + +public class BalanceSheet +{ + public int AccountId { get; set; } + public int AccountClassId { get; set; } + public string? AccountCode { get; set; } + public string? AccountName { get; set; } + public decimal Amount { get; set; } +} + +public class IncomeStatement +{ + public int AccountId { get; set; } + public bool IsExpense { get; set; } + public string? AccountCode { get; set; } + public string? AccountName { get; set; } + public decimal Amount { get; set; } +} + +public partial class MasterGeneralLedger +{ + public int Id { get; set; } + public int AccountId { get; set; } + public int CurrencyId { get; set; } + public string? DocumentType { get; set; } + public int TransactionNo { get; set; } + public string? AccountCode { get; set; } + public string? AccountName { get; set; } + public DateTime Date { get; set; } + public decimal Debit { get; set; } + public decimal Credit { get; set; } +} diff --git a/src/LibraryGDB/Models/ObjectExtensions.cs b/src/LibraryGDB/Models/ObjectExtensions.cs new file mode 100644 index 000000000..82057bca2 --- /dev/null +++ b/src/LibraryGDB/Models/ObjectExtensions.cs @@ -0,0 +1,14 @@ +using Newtonsoft.Json; + +namespace LibraryGDB.Models; + +public static class ObjectExtensions +{ + public static string ToJson(this object obj) + { + JsonSerializer js = JsonSerializer.Create(new JsonSerializerSettings()); + var jw = new StringWriter(); + js.Serialize(jw, obj); + return jw.ToString(); + } +} diff --git a/src/LibraryGDB/Models/Purchasing/AmountToPayValidationAttribute.cs b/src/LibraryGDB/Models/Purchasing/AmountToPayValidationAttribute.cs new file mode 100644 index 000000000..f120b9649 --- /dev/null +++ b/src/LibraryGDB/Models/Purchasing/AmountToPayValidationAttribute.cs @@ -0,0 +1,25 @@ +using System; +using System.ComponentModel.DataAnnotations; + +public class AmountToPayValidationAttribute : ValidationAttribute +{ + protected override ValidationResult? IsValid(object? value, ValidationContext validationContext) + { + // Access the containing object so we can read other properties + var instance = validationContext.ObjectInstance; + var balanceProperty = validationContext.ObjectType.GetProperty("Balance"); + if (balanceProperty == null) + return new ValidationResult("Balance property not found."); + + var balanceValue = (decimal)balanceProperty.GetValue(instance)!; + var amountToPay = value as decimal? ?? 0; + + if (amountToPay <= 0) + return new ValidationResult("Amount to pay cannot be zero."); + + if (amountToPay > balanceValue) + return new ValidationResult("Amount to pay cannot be greater than remaining amount to pay."); + + return ValidationResult.Success; + } +} \ No newline at end of file diff --git a/src/LibraryGDB/Models/Purchasing/Payment.cs b/src/LibraryGDB/Models/Purchasing/Payment.cs new file mode 100644 index 000000000..a77444bf5 --- /dev/null +++ b/src/LibraryGDB/Models/Purchasing/Payment.cs @@ -0,0 +1,18 @@ +namespace LibraryGDB.Models.Purchasing; + +public class Payment +{ + public int InvoiceId { get; set; } + public string? InvoiceNo { get; set; } + public int VendorId { get; set; } + public string? VendorName { get; set; } + public decimal InvoiceAmount { get; set; } + public decimal AmountPaid { get; set; } + public decimal Balance { get { return InvoiceAmount - AmountPaid; } } + + [AmountToPayValidation] + public decimal AmountToPay { get; set; } + [System.ComponentModel.DataAnnotations.Required] + public int? AccountId { get; set; } + public System.DateTime Date { get; set; } +} diff --git a/src/LibraryGDB/Models/Sales/AddSalesReceipt.cs b/src/LibraryGDB/Models/Sales/AddSalesReceipt.cs new file mode 100644 index 000000000..f67759fcc --- /dev/null +++ b/src/LibraryGDB/Models/Sales/AddSalesReceipt.cs @@ -0,0 +1,21 @@ +using System.ComponentModel.DataAnnotations; + +namespace LibraryGDB.Models.Sales; + +public class AddReceipt +{ + [System.ComponentModel.DataAnnotations.Required] + public int? AccountToDebitId { get; set; } + [System.ComponentModel.DataAnnotations.Required] + public int? AccountToCreditId { get; set; } + [System.ComponentModel.DataAnnotations.Required] + public int? CustomerId { get; set; } + public System.DateTime ReceiptDate {get;set;} + [Range(0.01, double.MaxValue, ErrorMessage = "Amount cannot be zero or negative.")] + public decimal Amount { get; set; } + + public AddReceipt() + { + ReceiptDate = System.DateTime.Now; + } +} diff --git a/src/LibraryGDB/Models/Sales/Allocate.cs b/src/LibraryGDB/Models/Sales/Allocate.cs new file mode 100644 index 000000000..c8aab57a0 --- /dev/null +++ b/src/LibraryGDB/Models/Sales/Allocate.cs @@ -0,0 +1,48 @@ +namespace LibraryGDB.Models.Sales; + +public class Allocate +{ + [System.ComponentModel.DataAnnotations.Required] + public int? CustomerId { get; set; } + [System.ComponentModel.DataAnnotations.Required] + public int? ReceiptId { get; set; } + [System.ComponentModel.DataAnnotations.Required] + public System.DateTime Date { get; set; } + public decimal Amount { get; set; } + public decimal RemainingAmountToAllocate { get; set; } + public decimal SumAllocatedAmount { get { return ComputeSumToAllocateAmount(); } } + public IList AllocationLines { get; set; } + + public Allocate() + { + AllocationLines = new List(); + } + + private decimal ComputeSumToAllocateAmount() + { + decimal sum = 0; + + foreach (var line in AllocationLines) { + sum += line.AmountToAllocate.GetValueOrDefault(); + } + + return sum; + } + + public bool IsValid() + { + if (RemainingAmountToAllocate < SumAllocatedAmount) + return false; + else + return true; + } +} + +public class AllocationLine +{ + [System.ComponentModel.DataAnnotations.Required] + public int? InvoiceId { get; set; } + public decimal? Amount { get; set; } + public decimal? AllocatedAmount { get; set; } + public decimal? AmountToAllocate { get; set; } +} diff --git a/src/LibraryGDB/Models/Sales/SalesQuotation.cs b/src/LibraryGDB/Models/Sales/SalesQuotation.cs new file mode 100644 index 000000000..ed337350e --- /dev/null +++ b/src/LibraryGDB/Models/Sales/SalesQuotation.cs @@ -0,0 +1,24 @@ +using System.ComponentModel.DataAnnotations; + +namespace LibraryGDB.Models.Sales; +public class SalesQuotations { + [System.ComponentModel.DataAnnotations.Required] + public int CustomerId { get; set; } + [System.ComponentModel.DataAnnotations.Required] + public int PaymentTermId { get; set; } + [System.ComponentModel.DataAnnotations.Required] + public int ItemId { get; set; } + [System.ComponentModel.DataAnnotations.Required] + public int Quantity { get; set; } + // [ExpressiveAnnotations.Attributes.AssertThat("Amount > 0", ErrorMessage = "Amount cannot be zero.")] + [Range(0.01, double.MaxValue, ErrorMessage = "Amount cannot be zero or negative.")] + public decimal Amount { get; set; } + public System.DateTime Date { get; set; } + public decimal Discount { get; set; } + + public SalesQuotations() + { + Date = System.DateTime.Now; + } + +} \ No newline at end of file diff --git a/src/LibraryGDB/Models/SelectListItemHelper.cs b/src/LibraryGDB/Models/SelectListItemHelper.cs new file mode 100644 index 000000000..3350dc017 --- /dev/null +++ b/src/LibraryGDB/Models/SelectListItemHelper.cs @@ -0,0 +1,149 @@ +using Microsoft.Extensions.Configuration; + +namespace LibraryGDB.Models; + +public static class SelectListItemHelper +{ + public static IConfiguration? _config; + + public static IEnumerable Accounts() + { + var accounts = GetAsync>("common/postingaccounts").Result; + + var selectAccounts = new HashSet(); + selectAccounts.Add(new Microsoft.AspNetCore.Mvc.Rendering.SelectListItem() { Value = "", Text = "" }); + foreach (var account in accounts) + selectAccounts.Add(new Microsoft.AspNetCore.Mvc.Rendering.SelectListItem() { Value = account.Id.ToString(), Text = account.AccountName }); + + return selectAccounts; + } + + public static IEnumerable TaxGroups() + { + var taxGroups = GetAsync>("tax/taxgroups").Result; + var selectTaxGroups = new HashSet(); + selectTaxGroups.Add(new Microsoft.AspNetCore.Mvc.Rendering.SelectListItem() { Value = "", Text = "" }); + foreach (var taxGroup in taxGroups) + selectTaxGroups.Add(new Microsoft.AspNetCore.Mvc.Rendering.SelectListItem() { Value = taxGroup.Id.ToString(), Text = taxGroup.Description }); + + return selectTaxGroups; + } + + public static IEnumerable ItemTaxGroups() + { + var itemtaxgroups = GetAsync>("tax/itemtaxgroups").Result; + var selectitemtaxgroups = new HashSet(); + selectitemtaxgroups.Add(new Microsoft.AspNetCore.Mvc.Rendering.SelectListItem() { Value = "", Text = "" }); + foreach (var taxGroup in itemtaxgroups) + selectitemtaxgroups.Add(new Microsoft.AspNetCore.Mvc.Rendering.SelectListItem() { Value = taxGroup.Id.ToString(), Text = taxGroup.Name }); + + return selectitemtaxgroups; + } + + public static IEnumerable PaymentTerms() + { + var paymentTerms = GetAsync>("common/paymentterms").Result; + var selectPaymentTerms = new HashSet(); + selectPaymentTerms.Add(new Microsoft.AspNetCore.Mvc.Rendering.SelectListItem() { Value = "", Text = "" }); + foreach (var term in paymentTerms) + selectPaymentTerms.Add(new Microsoft.AspNetCore.Mvc.Rendering.SelectListItem() { Value = term.Id.ToString(), Text = term.Description }); + + return selectPaymentTerms; + } + + public static IEnumerable UnitOfMeasurements() + { + var uoms = GetAsync>("common/measurements").Result; + var selectUOMS = new HashSet(); + selectUOMS.Add(new Microsoft.AspNetCore.Mvc.Rendering.SelectListItem() { Value = "", Text = "" }); + foreach (var item in uoms) + selectUOMS.Add(new Microsoft.AspNetCore.Mvc.Rendering.SelectListItem() { Value = item.Id.ToString(), Text = item.Description }); + + return selectUOMS; + } + + public static IEnumerable ItemCategories() + { + var categories = GetAsync>("common/itemcategories").Result; + var selectCategories = new HashSet(); + selectCategories.Add(new Microsoft.AspNetCore.Mvc.Rendering.SelectListItem() { Value = "", Text = "" }); + foreach (var item in categories) + selectCategories.Add(new Microsoft.AspNetCore.Mvc.Rendering.SelectListItem() { Value = item.Id.ToString(), Text = item.Name }); + + return selectCategories; + } + + public static IEnumerable CashBanks() + { + var cashBanks = GetAsync>("common/cashbanks").Result; + var selectCashBanks = new HashSet(); + selectCashBanks.Add(new Microsoft.AspNetCore.Mvc.Rendering.SelectListItem() { Value = "", Text = "" }); + foreach (var item in cashBanks) + selectCashBanks.Add(new Microsoft.AspNetCore.Mvc.Rendering.SelectListItem() { Value = item.Id.ToString(), Text = item.Name }); + + return selectCashBanks; + } + + public static IEnumerable Customers() + { + var customers = GetAsync>("sales/customers").Result; + var selectCustomers = new HashSet(); + selectCustomers.Add(new Microsoft.AspNetCore.Mvc.Rendering.SelectListItem() { Value = "", Text = "" }); + foreach (var item in customers) + selectCustomers.Add(new Microsoft.AspNetCore.Mvc.Rendering.SelectListItem() { Value = item.Id.ToString(), Text = item.Name }); + + return selectCustomers; + } + + public static IEnumerable Vendors() + { + var vendors = GetAsync>("purchasing/vendors").Result; + var selectVendors = new HashSet(); + selectVendors.Add(new Microsoft.AspNetCore.Mvc.Rendering.SelectListItem() { Value = "", Text = "" }); + foreach (var item in vendors) + selectVendors.Add(new Microsoft.AspNetCore.Mvc.Rendering.SelectListItem() { Value = item.Id.ToString(), Text = item.Name }); + + return selectVendors; + } + + public static IEnumerable Items() + { + var items = GetAsync>("inventory/items").Result; + var selectItems = new HashSet(); + selectItems.Add(new Microsoft.AspNetCore.Mvc.Rendering.SelectListItem() { Value = "", Text = "" }); + foreach (var item in items) + selectItems.Add(new Microsoft.AspNetCore.Mvc.Rendering.SelectListItem() { Value = item.Id.ToString(), Text = item.Description }); + + return selectItems; + } + + public static IEnumerable Measurements() + { + var measurements = GetAsync>("common/measurements").Result; + var selectMeasurements = new HashSet(); + selectMeasurements.Add(new Microsoft.AspNetCore.Mvc.Rendering.SelectListItem() { Value = "", Text = "" }); + foreach (var item in measurements) + selectMeasurements.Add(new Microsoft.AspNetCore.Mvc.Rendering.SelectListItem() { Value = item.Id.ToString(), Text = item.Description }); + + return selectMeasurements; + } + + #region Private methods + public static async System.Threading.Tasks.Task GetAsync(string uri) + { + string responseJson = string.Empty; + using (var client = new HttpClient()) + { + var baseUri = _config!["ApiUrl"]; + client.BaseAddress = new System.Uri(baseUri!); + client.DefaultRequestHeaders.Accept.Clear(); + var response = await client.GetAsync(baseUri + uri); + if (response.IsSuccessStatusCode) + { + responseJson = await response.Content.ReadAsStringAsync(); + } + } + return Newtonsoft.Json.JsonConvert.DeserializeObject(responseJson)!; + } + #endregion +} diff --git a/src/LibraryGDB/Models/TaxSystem/TaxSystemViewModel.cs b/src/LibraryGDB/Models/TaxSystem/TaxSystemViewModel.cs new file mode 100644 index 000000000..c4e35d315 --- /dev/null +++ b/src/LibraryGDB/Models/TaxSystem/TaxSystemViewModel.cs @@ -0,0 +1,9 @@ +using Dto.TaxSystem; + +namespace LibraryGDB.Models.TaxSystem; +public class TaxSystemViewModel +{ + public System.Collections.Generic.IEnumerable? Taxes { get; set; } + public System.Collections.Generic.IEnumerable? TaxGroups { get; set; } + public System.Collections.Generic.IEnumerable? ItemTaxGroups { get; set; } +} diff --git a/src/MigrationService/MigrationService.csproj b/src/MigrationService/MigrationService.csproj new file mode 100644 index 000000000..0b54fa51f --- /dev/null +++ b/src/MigrationService/MigrationService.csproj @@ -0,0 +1,19 @@ + + + + net10.0 + enable + enable + dotnet-MigrationService-dcc1af79-957f-42b3-8462-759768f83a25 + + + + + + + + + + + + diff --git a/src/MigrationService/Program.cs b/src/MigrationService/Program.cs new file mode 100644 index 000000000..db7411b66 --- /dev/null +++ b/src/MigrationService/Program.cs @@ -0,0 +1,17 @@ +using Api.Data; +using MigrationService; + +var builder = Host.CreateApplicationBuilder(args); + +builder.AddServiceDefaults(); + +builder.Services.AddHostedService(); + +builder.Services.AddOpenTelemetry() + .WithTracing(tracing => tracing.AddSource(Worker.ActivitySourceName)); + +builder.AddSqlServerDbContext("gdb-db"); +builder.AddSqlServerDbContext("gdb-db"); + +var host = builder.Build(); +host.Run(); diff --git a/src/MigrationService/Properties/launchSettings.json b/src/MigrationService/Properties/launchSettings.json new file mode 100644 index 000000000..7f5b75dde --- /dev/null +++ b/src/MigrationService/Properties/launchSettings.json @@ -0,0 +1,12 @@ +{ + "$schema": "https://json.schemastore.org/launchsettings.json", + "profiles": { + "MigrationService": { + "commandName": "Project", + "dotnetRunMessages": true, + "environmentVariables": { + "DOTNET_ENVIRONMENT": "Development" + } + } + } +} diff --git a/src/MigrationService/Worker.cs b/src/MigrationService/Worker.cs new file mode 100644 index 000000000..902f3b8fe --- /dev/null +++ b/src/MigrationService/Worker.cs @@ -0,0 +1,88 @@ +using System.Diagnostics; +using Api.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Storage; + +using OpenTelemetry.Trace; + +namespace MigrationService; + +public class Worker( + IServiceProvider serviceProvider, + IHostApplicationLifetime hostApplicationLifetime) : BackgroundService +{ + public const string ActivitySourceName = "Migrations"; + private static readonly ActivitySource s_activitySource = new(ActivitySourceName); + + protected override async Task ExecuteAsync(CancellationToken cancellationToken) + { + using var activity = s_activitySource.StartActivity("Migrating database", ActivityKind.Client); + + try + { + using var scope = serviceProvider.CreateScope(); + var dbContext = scope.ServiceProvider.GetRequiredService(); + + await EnsureDatabaseAsync(dbContext, cancellationToken); + await RunMigrationAsync(dbContext, cancellationToken); + // await SeedDataAsync(dbContext, cancellationToken); + } + catch (Exception ex) + { + // Use Activity.AddException; RecordException is obsolete and will be removed in a future version + activity?.AddException(ex); + throw; + } + + hostApplicationLifetime.StopApplication(); + } + + private static async Task EnsureDatabaseAsync(ApiDbContext dbContext, CancellationToken cancellationToken) + { + var dbCreator = dbContext.GetService(); + + var strategy = dbContext.Database.CreateExecutionStrategy(); + await strategy.ExecuteAsync(async () => + { + // Create the database if it does not exist. + // Do this first so there is then a database to start a transaction against. + if (!await dbCreator.ExistsAsync(cancellationToken)) + { + await dbCreator.CreateAsync(cancellationToken); + } + }); + } + + private static async Task RunMigrationAsync(ApiDbContext dbContext, CancellationToken cancellationToken) + { + var strategy = dbContext.Database.CreateExecutionStrategy(); + await strategy.ExecuteAsync(async () => + { + // Run migration in a transaction to avoid partial migration if it fails. + await using var transaction = await dbContext.Database.BeginTransactionAsync(cancellationToken); + await dbContext.Database.MigrateAsync(cancellationToken); + await transaction.CommitAsync(cancellationToken); + }); + } + + // private static async Task SeedDataAsync(ApiDbContext dbContext, CancellationToken cancellationToken) + // { + // SupportTicket firstTicket = new() + // { + // Title = "Test Ticket", + // Description = "Default ticket, please ignore!", + // Completed = true + // }; + + // var strategy = dbContext.Database.CreateExecutionStrategy(); + // await strategy.ExecuteAsync(async () => + // { + // // Seed the database + // await using var transaction = await dbContext.Database.BeginTransactionAsync(cancellationToken); + // await dbContext.Tickets.AddAsync(firstTicket, cancellationToken); + // await dbContext.SaveChangesAsync(cancellationToken); + // await transaction.CommitAsync(cancellationToken); + // }); + // } +} \ No newline at end of file diff --git a/src/Api/appsettings.Development.json b/src/MigrationService/appsettings.Development.json similarity index 80% rename from src/Api/appsettings.Development.json rename to src/MigrationService/appsettings.Development.json index 45fe774a9..b2dcdb674 100644 --- a/src/Api/appsettings.Development.json +++ b/src/MigrationService/appsettings.Development.json @@ -2,8 +2,7 @@ "Logging": { "LogLevel": { "Default": "Information", - "Microsoft": "Warning", "Microsoft.Hosting.Lifetime": "Information" } } -} \ No newline at end of file +} diff --git a/src/MigrationService/appsettings.json b/src/MigrationService/appsettings.json new file mode 100644 index 000000000..b2dcdb674 --- /dev/null +++ b/src/MigrationService/appsettings.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.Hosting.Lifetime": "Information" + } + } +} diff --git a/src/Modules/SampleModule/Controllers/SampleModuleController.cs b/src/Modules/SampleModule/Controllers/SampleModuleController.cs deleted file mode 100644 index cfb6ed38c..000000000 --- a/src/Modules/SampleModule/Controllers/SampleModuleController.cs +++ /dev/null @@ -1,10 +0,0 @@ -using Microsoft.AspNetCore.Mvc; - -namespace SampleModule.Controllers -{ - [Microsoft.AspNetCore.Authorization.Authorize] - public class SampleModuleController : Controller - { - public IActionResult Index() => View(); - } -} diff --git a/src/Modules/SampleModule/Models/ErrorViewModel.cs b/src/Modules/SampleModule/Models/ErrorViewModel.cs deleted file mode 100644 index c7ed3a4f1..000000000 --- a/src/Modules/SampleModule/Models/ErrorViewModel.cs +++ /dev/null @@ -1,11 +0,0 @@ -using System; - -namespace SampleModule.Models -{ - public class ErrorViewModel - { - public string RequestId { get; set; } - - public bool ShowRequestId => !string.IsNullOrEmpty(RequestId); - } -} \ No newline at end of file diff --git a/src/Modules/SampleModule/SampleModule.csproj b/src/Modules/SampleModule/SampleModule.csproj deleted file mode 100644 index 0cd412945..000000000 --- a/src/Modules/SampleModule/SampleModule.csproj +++ /dev/null @@ -1,12 +0,0 @@ - - - - netstandard2.1 - - - - - - - - diff --git a/src/Modules/SampleModule/Views/SampleModule/Index.cshtml b/src/Modules/SampleModule/Views/SampleModule/Index.cshtml deleted file mode 100644 index 34bebecb4..000000000 --- a/src/Modules/SampleModule/Views/SampleModule/Index.cshtml +++ /dev/null @@ -1,5 +0,0 @@ -@{ - ViewData["Title"] = "Sample Module"; -} - -

    Hello Sample Module!

    diff --git a/src/Modules/SampleModule/wwwroot/css/site.css b/src/Modules/SampleModule/wwwroot/css/site.css deleted file mode 100644 index e89c7811c..000000000 --- a/src/Modules/SampleModule/wwwroot/css/site.css +++ /dev/null @@ -1,37 +0,0 @@ -/* Please see documentation at https://docs.microsoft.com/aspnet/core/client-side/bundling-and-minification\ -for details on configuring this project to bundle and minify static web assets. */ -body { - padding-top: 50px; - padding-bottom: 20px; -} - -/* Wrapping element */ -/* Set some basic padding to keep content from hitting the edges */ -.body-content { - padding-left: 15px; - padding-right: 15px; -} - -/* Carousel */ -.carousel-caption p { - font-size: 20px; - line-height: 1.4; -} - -/* Make .svg files in the carousel display properly in older browsers */ -.carousel-inner .item img[src$=".svg"] { - width: 100%; -} - -/* QR code generator */ -#qrCode { - margin: 15px; -} - -/* Hide/rearrange for smaller screens */ -@media screen and (max-width: 767px) { - /* Hide captions */ - .carousel-caption { - display: none; - } -} diff --git a/src/Modules/SampleModule/wwwroot/css/site.min.css b/src/Modules/SampleModule/wwwroot/css/site.min.css deleted file mode 100644 index 5e93e30ae..000000000 --- a/src/Modules/SampleModule/wwwroot/css/site.min.css +++ /dev/null @@ -1 +0,0 @@ -body{padding-top:50px;padding-bottom:20px}.body-content{padding-left:15px;padding-right:15px}.carousel-caption p{font-size:20px;line-height:1.4}.carousel-inner .item img[src$=".svg"]{width:100%}#qrCode{margin:15px}@media screen and (max-width:767px){.carousel-caption{display:none}} \ No newline at end of file diff --git a/src/Modules/SampleModule/wwwroot/js/site.js b/src/Modules/SampleModule/wwwroot/js/site.js deleted file mode 100644 index ac49c1864..000000000 --- a/src/Modules/SampleModule/wwwroot/js/site.js +++ /dev/null @@ -1,4 +0,0 @@ -// Please see documentation at https://docs.microsoft.com/aspnet/core/client-side/bundling-and-minification -// for details on configuring this project to bundle and minify static web assets. - -// Write your JavaScript code. diff --git a/src/Modules/SampleModule/wwwroot/js/site.min.js b/src/Modules/SampleModule/wwwroot/js/site.min.js deleted file mode 100644 index e69de29bb..000000000 diff --git a/src/Services/Administration/AdministrationService.cs b/src/Services/Administration/AdministrationService.cs index 95b48565b..2909048fe 100644 --- a/src/Services/Administration/AdministrationService.cs +++ b/src/Services/Administration/AdministrationService.cs @@ -6,14 +6,22 @@ // //----------------------------------------------------------------------- +using AutoMapper; using Core.Data; using Core.Domain; using Core.Domain.Auditing; +using Core.Domain.Error; using Core.Domain.Financials; using Core.Domain.Security; using Core.Domain.TaxSystem; +using Services.Financial; +using Services.TaxSystem; +using System; using System.Collections.Generic; using System.Linq; +using System.Runtime.CompilerServices; +using System.Security.Cryptography; +using System.Threading.Tasks; namespace Services.Administration { @@ -30,6 +38,9 @@ public class AdministrationService : BaseService, IAdministrationService private readonly IRepository _accountRepo; private readonly IRepository _auditLogRepo; private readonly ISecurityRepository _securityRepository; + private readonly IFinancialService _financialService; + private readonly ITaxService _taxService; + private readonly IMapper _mapper; public AdministrationService(IRepository fiscalYearRepo, IRepository taxGroupRepo, @@ -41,6 +52,9 @@ public AdministrationService(IRepository fiscalYearRepo, IRepository accountRepo, IRepository auditLogRepo, ISecurityRepository securityRepository, + IFinancialService financialService, + ITaxService taxService, + IMapper mapper, IRepository company = null ) : base(null, generalLedgerSetting, paymentTermRepo, bankRepo) @@ -56,6 +70,9 @@ public AdministrationService(IRepository fiscalYearRepo, _accountRepo = accountRepo; _auditLogRepo = auditLogRepo; _securityRepository = securityRepository; + _financialService = financialService; + _taxService = taxService; + _mapper = mapper; } public ICollection GetAllTaxes(bool includeInActive) @@ -65,19 +82,192 @@ public ICollection GetAllTaxes(bool includeInActive) return query.ToList(); } + public async Task> CreateTaxAsync(Dto.TaxSystem.TaxForCreation taxForCreationDto) + { + var query = from f in _taxRepo.Table + where f.TaxName == taxForCreationDto.TaxName || f.TaxCode == taxForCreationDto.TaxCode + select f; + + if (query.Any()) + { + var message = $"Tax with name {taxForCreationDto.TaxName} or code {taxForCreationDto.TaxCode} already exists"; + return Result.Failure(Error.ValidationError(message)); + } + + var salesTaxAccount = _financialService.GetAccountByAccountCode(taxForCreationDto.SalesAccountId.ToString()); + var purchaseTaxAccount = _financialService.GetAccountByAccountCode(taxForCreationDto.PurchaseAccountId.ToString()); + + var taxEntity = _mapper.Map(taxForCreationDto); + taxEntity.SalesAccountId = salesTaxAccount.Id; + taxEntity.PurchasingAccountId = purchaseTaxAccount.Id; + taxEntity.SalesAccount = salesTaxAccount; + taxEntity.PurchasingAccount = purchaseTaxAccount; + + if (taxForCreationDto.TaxGroup != null) + { + var taxGroupEntity = AddNewTaxGroup(taxForCreationDto.TaxGroup); + taxEntity.TaxGroupTaxes.Add(new Core.Domain.TaxSystem.TaxGroupTax + { + TaxId = taxEntity.Id, + TaxGroupId = taxGroupEntity.Id, + TaxGroup = taxGroupEntity, + }); + } + + if (taxForCreationDto.ItemTaxGroup != null) + { + var itemTaxGroupEntity = AddNewItemTaxGroup(taxForCreationDto.ItemTaxGroup); + taxEntity.ItemTaxGroupTaxes.Add(new Core.Domain.TaxSystem.ItemTaxGroupTax + { + TaxId = taxEntity.Id, + ItemTaxGroupId = itemTaxGroupEntity.Id, + ItemTaxGroup = itemTaxGroupEntity, + }); + } + + await AddNewTaxAsync(taxEntity); + var taxToReturn = _mapper.Map(taxEntity); + + return Result.Success(taxToReturn); + } + public void AddNewTax(Tax tax) { _taxRepo.Insert(tax); } - public void UpdateTax(Tax tax) + public async Task AddNewTaxAsync(Tax tax) + { + await _taxRepo.InsertAsync(tax); + } + + public TaxGroup AddNewTaxGroup(Dto.TaxSystem.TaxGroup taxGroupDto) + { + var taxGroupEntity = _taxGroupRepo.GetById(taxGroupDto.Id); + if (taxGroupEntity is null) + { + taxGroupEntity = _mapper.Map(taxGroupDto); + _taxGroupRepo.Insert(taxGroupEntity); + } + + return taxGroupEntity; + } + + public ItemTaxGroup AddNewItemTaxGroup(Dto.TaxSystem.ItemTaxGroup itemTaxGroupDto) + { + var itemTaxGroupEntity = _itemTaxGroupRepo.GetById(itemTaxGroupDto.Id); + if (itemTaxGroupEntity is null) + { + itemTaxGroupEntity = _mapper.Map(itemTaxGroupDto); + _itemTaxGroupRepo.Insert(itemTaxGroupEntity); + } + + return itemTaxGroupEntity; + } + + public async Task> EditTaxAsync(Dto.TaxSystem.TaxForUpdate taxForUpdateDto) + { + if (taxForUpdateDto == null || taxForUpdateDto.Tax == null) + { + return Result.Failure(Error.ValidationError("Tax data is required.")); + } + + var taxEntity = _taxService.GetTaxById(taxForUpdateDto.Tax.Id); + if (taxEntity is null) + { + var message = $"Tax with id {taxForUpdateDto.Tax.Id} is not found."; + return Result.Failure(Error.RecordNotFound(message)); + } + + var salesTaxAccount = _financialService.GetAccountByAccountCode(taxForUpdateDto.SalesAccountId.ToString()); + var purchaseTaxAccount = _financialService.GetAccountByAccountCode(taxForUpdateDto.PurchaseAccountId.ToString()); + + _mapper.Map(taxForUpdateDto, taxEntity); + taxEntity.SalesAccountId = salesTaxAccount.Id; + taxEntity.PurchasingAccountId = purchaseTaxAccount.Id; + taxEntity.SalesAccount = salesTaxAccount; + taxEntity.PurchasingAccount = purchaseTaxAccount; + + taxEntity.TaxGroupTaxes.Clear(); + + if (taxForUpdateDto.TaxGroup != null) + { + var taxGroupEntity = AddNewTaxGroup(taxForUpdateDto.TaxGroup); + taxEntity.TaxGroupTaxes.Add(new Core.Domain.TaxSystem.TaxGroupTax + { + TaxId = taxEntity.Id, + TaxGroupId = taxGroupEntity.Id, + TaxGroup = taxGroupEntity, + }); + } + + taxEntity.ItemTaxGroupTaxes.Clear(); + + if (taxForUpdateDto.ItemTaxGroup != null) + { + var itemTaxGroupEntity = AddNewItemTaxGroup(taxForUpdateDto.ItemTaxGroup); + taxEntity.ItemTaxGroupTaxes.Add(new Core.Domain.TaxSystem.ItemTaxGroupTax + { + TaxId = taxEntity.Id, + ItemTaxGroupId = itemTaxGroupEntity.Id, + ItemTaxGroup = itemTaxGroupEntity + }); + } + + await UpdateTaxAsync(taxEntity); + var taxToRetun = _mapper.Map(taxEntity); + + return Result.Success(taxToRetun); + } + + public async Task UpdateTaxAsync(Tax tax) + { + await _taxRepo.UpdateAsync(tax); + } + + public async Task> DeleteTaxAsync(int taxId) + { + var tax = _taxRepo.GetById(taxId); + + if (tax is null) + { + var message = $"Tax with id {taxId} not found"; + return Result.Failure(Error.RecordNotFound(message)); + } + + await _taxRepo.DeleteAsync(tax); + + return Result.Success(null); + } + + public async Task> DeleteTaxGroupAsync(int taxGroupId) { - _taxRepo.Update(tax); + var taxGroup = _taxGroupRepo.GetById(taxGroupId); + + if (taxGroup is null) + { + var message = $"TaxGroup with id {taxGroupId} not found"; + return Result.Failure(Error.RecordNotFound(message)); + } + + await _taxGroupRepo.DeleteAsync(taxGroup); + + return Result.Success(null); } - public void DeleteTax(int id) + public async Task> DeleteItemTaxGroupAsync(int itemTaxGroupId) { - throw new System.NotImplementedException(); + var itemTaxGroup = _itemTaxGroupRepo.GetById(itemTaxGroupId); + + if (itemTaxGroup is null) + { + var message = $"ItemTaxGroup with id {itemTaxGroupId} not found"; + return Result.Failure(Error.RecordNotFound(message)); + } + + await _itemTaxGroupRepo.DeleteAsync(itemTaxGroup); + + return Result.Success(null); } public ICollection GetItemTaxGroups(bool includeInActive) @@ -123,7 +313,10 @@ public ICollection GetFinancialYears() public void SaveCompany(Company company) { - if (company.Id == 0) { _company.Insert(company); } + if (company.Id == 0) + { + _company.Insert(company); + } else { _company.Update(company); } } diff --git a/src/Services/Administration/IAdministrationService.cs b/src/Services/Administration/IAdministrationService.cs index d478b0a03..f95a19c15 100644 --- a/src/Services/Administration/IAdministrationService.cs +++ b/src/Services/Administration/IAdministrationService.cs @@ -12,6 +12,8 @@ using Core.Domain.Financials; using Core.Domain.Security; using Core.Domain.Auditing; +using Core.Domain.Error; +using System.Threading.Tasks; namespace Services.Administration { @@ -20,9 +22,16 @@ public interface IAdministrationService ICollection GetAllTaxes(bool includeInActive); ICollection GetItemTaxGroups(bool includeInActive); ICollection GetTaxGroups(bool includeInActive); + Task> CreateTaxAsync(Dto.TaxSystem.TaxForCreation taxForCreationDto); void AddNewTax(Tax tax); - void UpdateTax(Tax tax); - void DeleteTax(int id); + Task AddNewTaxAsync(Tax tax); + TaxGroup AddNewTaxGroup(Dto.TaxSystem.TaxGroup taxGroupDto); + ItemTaxGroup AddNewItemTaxGroup(Dto.TaxSystem.ItemTaxGroup itemTaxGroupDto); + Task> EditTaxAsync(Dto.TaxSystem.TaxForUpdate taxForUpdateDto); + Task UpdateTaxAsync(Tax tax); + Task> DeleteTaxAsync(int id); + Task> DeleteTaxGroupAsync(int id); + Task> DeleteItemTaxGroupAsync(int id); void InitializeCompany(); Company GetDefaultCompany(); ICollection GetPaymentTerms(); diff --git a/src/Services/Auditing/AuditableAttributeService.cs b/src/Services/Auditing/AuditableAttributeService.cs new file mode 100644 index 000000000..8bd1968ba --- /dev/null +++ b/src/Services/Auditing/AuditableAttributeService.cs @@ -0,0 +1,57 @@ +using System.Collections.Generic; +using System.Linq; +using Core.Data; +using Core.Domain.Auditing; + +namespace Services.Auditing +{ + /* + Service for managing auditable attributes. + */ + public class AuditableAttributeService : BaseService, IAuditableAttributeService + { + private readonly IRepository _attributeRepo; + + // Constructor + public AuditableAttributeService(IRepository attributeRepo) + : base(null, null, null, null) + { + _attributeRepo = attributeRepo; + } + + // Retrieve attributes by auditable entity ID + public IEnumerable GetAuditableAttributesByEntityId(int entityId) + { + return _attributeRepo.Table.Where(a => a.AuditableEntityId == entityId).ToList(); + } + + // Retrieve a specific auditable attribute by its ID + public AuditableAttribute GetAuditableAttributeById(int id) + { + return _attributeRepo.GetById(id); + } + + // Create a new auditable attribute + public void CreateAuditableAttribute(AuditableAttribute dto) + { + _attributeRepo.Insert(dto); + } + + // Update an existing auditable attribute + public void UpdateAuditableAttribute(AuditableAttribute dto) + { + _attributeRepo.Update(dto); + } + + // Delete an auditable attribute by its ID + public void DeleteAuditableAttribute(int id) + { + var attribute = _attributeRepo.GetById(id); + if (attribute == null) + { + return; + } + _attributeRepo.Delete(attribute); + } + } +} \ No newline at end of file diff --git a/src/Services/Auditing/AuditableEntityService.cs b/src/Services/Auditing/AuditableEntityService.cs new file mode 100644 index 000000000..ea910846b --- /dev/null +++ b/src/Services/Auditing/AuditableEntityService.cs @@ -0,0 +1,58 @@ +using Core.Data; +using Core.Domain.Auditing; +using System.Collections.Generic; +using System.Linq; + +namespace Services.Auditing +{ + /* + Service for managing auditable entities. + */ + public class AuditableEntityService : BaseService, IAuditableEntityService + { + + private readonly IRepository _entityRepo; + + // Constructor + public AuditableEntityService(IRepository entityRepo) + : base(null, null, null, null) + { + _entityRepo = entityRepo; + } + + // Retrieve all auditable entities + public IEnumerable GetAllAuditableEntities() + { + return _entityRepo.GetAllIncluding(e => e.AuditableAttributes).ToList(); + } + + // Retrieve a specific auditable entity by its ID + public AuditableEntity GetAuditableEntityById(int id) + { + return _entityRepo.GetAllIncluding(e => e.AuditableAttributes).FirstOrDefault(e => e.Id == id); + } + + // Create a new auditable entity + public void CreateAuditableEntity(AuditableEntity entity) + { + _entityRepo.Insert(entity); + } + + // Update an existing auditable entity + public void UpdateAuditableEntity(AuditableEntity entity) + { + _entityRepo.Update(entity); + } + + // Delete an auditable entity by its ID + public void DeleteAuditableEntity(int id) + { + var entity = _entityRepo.GetById(id); + if (entity == null) + { + return; + } + _entityRepo.Delete(entity); + } + } +} \ No newline at end of file diff --git a/src/Services/Auditing/IAuditableAttributeService.cs b/src/Services/Auditing/IAuditableAttributeService.cs new file mode 100644 index 000000000..102583809 --- /dev/null +++ b/src/Services/Auditing/IAuditableAttributeService.cs @@ -0,0 +1,17 @@ +using System.Collections.Generic; +using Core.Domain.Auditing; + +namespace Services.Auditing +{ + /* + Interface for managing auditable attributes. + */ + public interface IAuditableAttributeService + { + IEnumerable GetAuditableAttributesByEntityId(int entityId); + AuditableAttribute GetAuditableAttributeById(int id); + void CreateAuditableAttribute(AuditableAttribute dto); + void UpdateAuditableAttribute(AuditableAttribute dto); + void DeleteAuditableAttribute(int id); + } +} \ No newline at end of file diff --git a/src/Services/Auditing/IAuditableEntityService.cs b/src/Services/Auditing/IAuditableEntityService.cs new file mode 100644 index 000000000..9c1cda637 --- /dev/null +++ b/src/Services/Auditing/IAuditableEntityService.cs @@ -0,0 +1,17 @@ +using Core.Domain.Auditing; +using System.Collections.Generic; + +namespace Services.Auditing +{ + /* + Interface for managing auditable entities. + */ + public interface IAuditableEntityService + { + IEnumerable GetAllAuditableEntities(); + AuditableEntity GetAuditableEntityById(int id); + void CreateAuditableEntity(AuditableEntity entity); + void UpdateAuditableEntity(AuditableEntity entity); + void DeleteAuditableEntity(int id); + } +} \ No newline at end of file diff --git a/src/Services/Donations/DonationsService.cs b/src/Services/Donations/DonationsService.cs new file mode 100644 index 000000000..8a083d3fb --- /dev/null +++ b/src/Services/Donations/DonationsService.cs @@ -0,0 +1,205 @@ +using AutoMapper; +using Core.Data; +using Core.Domain; +using Core.Domain.Donations; +using Core.Domain.Error; +using Core.Domain.Financials; +using Microsoft.Extensions.Logging; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Services.Donations +{ + public class DonationsService : BaseService, IDonationsService + { + private readonly IMapper _mapper; + private readonly ILogger _logger; + private readonly IRepository _donationInvoiceRepo; + private readonly IRepository _donationInvoiceLineRepo; + + public DonationsService( + IMapper mapper, + ILogger logger, + IRepository donationInvoiceRepo, + IRepository donationInvoiceLineRepo, + IRepository sequenceNumberRepo, + IRepository generalLedgerSettingRepo, + IRepository paymentTermRepo, + IRepository bankRepo) + : base(sequenceNumberRepo, generalLedgerSettingRepo, paymentTermRepo, bankRepo) + { + _mapper = mapper; + _logger = logger; + _donationInvoiceRepo = donationInvoiceRepo; + _donationInvoiceLineRepo = donationInvoiceLineRepo; + } + + public IEnumerable GetDonationInvoices() + { + return _donationInvoiceRepo.GetAllIncluding( + d => d.Donor, + d => d.Donor.Party, + d => d.DonationInvoiceLines + ); + } + + public DonationInvoiceHeader GetDonationInvoiceById(int id) + { + return _donationInvoiceRepo.GetAllIncluding( + d => d.Donor, + d => d.Donor.Party, + d => d.DonationInvoiceLines + ).FirstOrDefault(d => d.Id == id); + } + + public Result CreateDonationInvoice(Dto.Donations.DonationInvoice donationInvoiceDto) + { + try + { + var donationInvoice = new DonationInvoiceHeader + { + No = GetNextNumber(SequenceNumberTypes.DonationInvoice).ToString(), + DonorId = donationInvoiceDto.DonorId, + Date = donationInvoiceDto.DonationDate, + ReferenceNo = donationInvoiceDto.ReferenceNo, + Purpose = donationInvoiceDto.Purpose, + IsTaxReceiptIssued = donationInvoiceDto.IsTaxReceiptIssued, + TaxReceiptNo = donationInvoiceDto.TaxReceiptNo, + Posted = donationInvoiceDto.Posted, + ModifiedBy = "system" + }; + + foreach (var lineDto in donationInvoiceDto.DonationInvoiceLines!) + { + var line = new DonationInvoiceLine + { + ItemId = lineDto.ItemId.GetValueOrDefault(), + MeasurementId = lineDto.MeasurementId.GetValueOrDefault(), + Quantity = lineDto.Quantity.GetValueOrDefault(), + Amount = lineDto.Amount.GetValueOrDefault(), + Notes = lineDto.Notes, + ModifiedBy = "system" + }; + donationInvoice.DonationInvoiceLines.Add(line); + } + + _donationInvoiceRepo.Insert(donationInvoice); + + donationInvoiceDto.Id = donationInvoice.Id; + donationInvoiceDto.No = donationInvoice.No; + + _logger.LogInformation("Created donation invoice {InvoiceNo} with id {Id}", donationInvoice.No, donationInvoice.Id); + + return Result.Success(donationInvoiceDto); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error creating donation invoice"); + return Result.Failure(new Error("CREATE_ERROR", ex.Message)); + } + } + + public Result UpdateDonationInvoice(Dto.Donations.DonationInvoice donationInvoiceDto) + { + try + { + var donationInvoice = GetDonationInvoiceById(donationInvoiceDto.Id); + + if (donationInvoice == null) + { + var message = $"Donation invoice {donationInvoiceDto.Id} not found."; + return Result.Failure(Error.RecordNotFound(message)); + } + + donationInvoice.DonorId = donationInvoiceDto.DonorId; + donationInvoice.Date = donationInvoiceDto.DonationDate; + donationInvoice.ReferenceNo = donationInvoiceDto.ReferenceNo; + donationInvoice.Purpose = donationInvoiceDto.Purpose; + donationInvoice.IsTaxReceiptIssued = donationInvoiceDto.IsTaxReceiptIssued; + donationInvoice.TaxReceiptNo = donationInvoiceDto.TaxReceiptNo; + donationInvoice.Posted = donationInvoiceDto.Posted; + donationInvoice.ModifiedBy = "system"; + + // Remove deleted lines + var linesToDelete = donationInvoice.DonationInvoiceLines + .Where(line => !donationInvoiceDto.DonationInvoiceLines!.Any(x => x.Id == line.Id)) + .ToList(); + + foreach (var line in linesToDelete) + { + _donationInvoiceLineRepo.Delete(line); + } + + // Update or add lines + foreach (var lineDto in donationInvoiceDto.DonationInvoiceLines!) + { + if (lineDto.Id > 0) + { + var existingLine = donationInvoice.DonationInvoiceLines.FirstOrDefault(l => l.Id == lineDto.Id); + if (existingLine != null) + { + existingLine.ItemId = lineDto.ItemId.GetValueOrDefault(); + existingLine.MeasurementId = lineDto.MeasurementId.GetValueOrDefault(); + existingLine.Quantity = lineDto.Quantity.GetValueOrDefault(); + existingLine.Amount = lineDto.Amount.GetValueOrDefault(); + existingLine.Notes = lineDto.Notes; + existingLine.ModifiedBy = "system"; + } + } + else + { + var newLine = new DonationInvoiceLine + { + DonationInvoiceHeaderId = donationInvoice.Id, + ItemId = lineDto.ItemId.GetValueOrDefault(), + MeasurementId = lineDto.MeasurementId.GetValueOrDefault(), + Quantity = lineDto.Quantity.GetValueOrDefault(), + Amount = lineDto.Amount.GetValueOrDefault(), + Notes = lineDto.Notes, + ModifiedBy = "system" + }; + donationInvoice.DonationInvoiceLines.Add(newLine); + } + } + + _donationInvoiceRepo.Update(donationInvoice); + + _logger.LogInformation("Updated donation invoice {Id}", donationInvoice.Id); + + return Result.Success(donationInvoiceDto); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error updating donation invoice"); + return Result.Failure(new Error("UPDATE_ERROR", ex.Message)); + } + } + + public async Task> DeleteDonationInvoiceAsync(int id) + { + try + { + var donationInvoice = GetDonationInvoiceById(id); + + if (donationInvoice == null) + { + var message = $"Donation invoice {id} not found."; + return Result.Failure(Error.RecordNotFound(message)); + } + + await _donationInvoiceRepo.DeleteAsync(donationInvoice); + + _logger.LogInformation("Deleted donation invoice {Id}", id); + + return Result.Success(null); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error deleting donation invoice"); + return Result.Failure(new Error("DELETE_ERROR", ex.Message)); + } + } + } +} diff --git a/src/Services/Donations/IDonationsService.cs b/src/Services/Donations/IDonationsService.cs new file mode 100644 index 000000000..4580ff4cf --- /dev/null +++ b/src/Services/Donations/IDonationsService.cs @@ -0,0 +1,16 @@ +using Core.Domain.Donations; +using Core.Domain.Error; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace Services.Donations +{ + public interface IDonationsService + { + IEnumerable GetDonationInvoices(); + DonationInvoiceHeader GetDonationInvoiceById(int id); + Result CreateDonationInvoice(Dto.Donations.DonationInvoice donationInvoiceDto); + Result UpdateDonationInvoice(Dto.Donations.DonationInvoice donationInvoiceDto); + Task> DeleteDonationInvoiceAsync(int id); + } +} diff --git a/src/Services/Financial/FinancialService.cs b/src/Services/Financial/FinancialService.cs index bb860e869..a6c1550f8 100644 --- a/src/Services/Financial/FinancialService.cs +++ b/src/Services/Financial/FinancialService.cs @@ -154,6 +154,7 @@ public Account GetAccountByAccountCode(string accountcode) a => a.ParentAccount, a => a.Company) .Where(a => a.AccountCode == accountcode) + //.OrderByDescending(a => a.Id) .FirstOrDefault(); return account; @@ -201,10 +202,11 @@ public bool ValidateGeneralLedgerEntry(GeneralLedgerHeader glEntry) throw new InvalidOperationException("One or more line(s) amount is zero."); var fiscalYear = CurrentFiscalYear(); - if (fiscalYear == null - || !FinancialHelper.InRange(glEntry.Date, fiscalYear.StartDate, fiscalYear.EndDate) - || !FinancialHelper.InRange(DateTime.Now, fiscalYear.StartDate, fiscalYear.EndDate)) - throw new InvalidOperationException("Date is out of range. Must within financial year."); + //TODO: Fix this + // if (fiscalYear == null + // || !FinancialHelper.InRange(glEntry.Date, fiscalYear.StartDate, fiscalYear.EndDate) + // || !FinancialHelper.InRange(DateTime.Now, fiscalYear.StartDate, fiscalYear.EndDate)) + // throw new InvalidOperationException("Date is out of range. Must within financial year."); //var duplicateAccounts = glEntry.GeneralLedgerLines.GroupBy(gl => gl.AccountId).Where(gl => gl.Count() > 1); //if (duplicateAccounts.Count() > 0) @@ -310,6 +312,7 @@ public void AddJournalEntry(JournalEntryHeader journalEntry) return accounts; } + // TODO: This generates an error public ICollection BalanceSheet(DateTime? from = default(DateTime?), DateTime? to = default(DateTime?)) { var assets = from a in _accountRepo.GetAllIncluding(a => a.AccountClass, @@ -509,10 +512,9 @@ public void AddJournalEntry(JournalEntryHeader journalEntry) /// public List> ComputeInputTax(int vendorId, int itemId, decimal quantity, decimal amount, decimal discount) { - decimal taxAmount = 0, amountXquantity = 0, discountAmount = 0, subTotalAmount = 0; + decimal taxAmount, amountXquantity, discountAmount = 0, subTotalAmount; var taxes = new List>(); - var item = _inventoryService.GetItemById(itemId); amountXquantity = amount * quantity; @@ -541,7 +543,7 @@ public List> ComputeInputTax(int vendorId, int itemId /// public List> ComputeOutputTax(int customerId, int itemId, decimal quantity, decimal amount, decimal discount) { - decimal taxAmount = 0, amountXquantity = 0, discountAmount = 0, subTotalAmount = 0; + decimal taxAmount, amountXquantity, discountAmount = 0, subTotalAmount; var taxes = new List>(); @@ -732,11 +734,12 @@ Interest Expense 150 var glSetting = _generalLedgerSettingRepo.Table.FirstOrDefault(); - var journalEntry = new JournalEntryHeader(); - journalEntry.Memo = "Closing entries"; - journalEntry.Date = DateTime.Now; - journalEntry.Posted = false; - journalEntry.VoucherType = JournalVoucherTypes.ClosingEntries; + var journalEntry = new JournalEntryHeader() { + Memo = "Closing entries", + Date = DateTime.Now, + Posted = false, + VoucherType = JournalVoucherTypes.ClosingEntries + }; return journalEntry; } diff --git a/src/Services/Purchasing/PurchasingService.cs b/src/Services/Purchasing/PurchasingService.cs index 800a77d5f..d29dc1672 100644 --- a/src/Services/Purchasing/PurchasingService.cs +++ b/src/Services/Purchasing/PurchasingService.cs @@ -17,6 +17,8 @@ using System.Linq; using System; using System.Threading; +using Microsoft.Extensions.Logging; +using Services.Sales; namespace Services.Purchasing { @@ -25,6 +27,8 @@ public partial class PurchasingService : BaseService, IPurchasingService private readonly IFinancialService _financialService; private readonly IInventoryService _inventoryService; + private readonly ILogger _logger; + private readonly IRepository _purchaseOrderRepo; private readonly IRepository _purchaseOrderLineRepo; private readonly IRepository _purchaseInvoiceRepo; @@ -57,7 +61,8 @@ public PurchasingService(IFinancialService financialService, IRepository paymentTermRepo, IRepository bankRepo, IRepository contactRepo, - IPurchaseOrderRepository purchaseOrderHeaderRepository + IPurchaseOrderRepository purchaseOrderHeaderRepository, + ILogger logger ) : base(sequenceNumberRepo, generalLedgerSettingRepo, paymentTermRepo, bankRepo) { @@ -78,6 +83,7 @@ IPurchaseOrderRepository purchaseOrderHeaderRepository _bankRepo = bankRepo; _purchaseOrderHeaderRepository = purchaseOrderHeaderRepository; _contactRepo = contactRepo; + _logger = logger; } public void AddPurchaseInvoice(PurchaseInvoiceHeader purchaseIvoice, int? purchaseOrderId) @@ -481,7 +487,7 @@ public void SavePurchaseInvoice(PurchaseInvoiceHeader purchaseInvoice, PurchaseO } catch (Exception ex) { - throw ex; + _logger.LogError(ex.Message); } } diff --git a/src/Services/Sales/ISalesService.cs b/src/Services/Sales/ISalesService.cs index 3439e1c27..d4f9d36a1 100644 --- a/src/Services/Sales/ISalesService.cs +++ b/src/Services/Sales/ISalesService.cs @@ -7,8 +7,10 @@ //----------------------------------------------------------------------- using Core.Domain; +using Core.Domain.Error; using Core.Domain.Sales; using System.Collections.Generic; +using System.Threading.Tasks; namespace Services.Sales { @@ -48,6 +50,9 @@ public partial interface ISalesService IEnumerable GetCustomerInvoices(int customerId); SalesQuoteHeader GetSalesQuotationById(int id); void SaveSalesInvoice(SalesInvoiceHeader salesInvoice, SalesOrderHeader salesOrder); + Result CreateSalesInvoice(Dto.Sales.SalesInvoice salesInvoiceDto); + Result UpdateSalesInvoice(Dto.Sales.SalesInvoice salesInvoiceDto); + Task> DeleteSalesInvoiceAsync(int id); void PostSalesInvoice(int invoiceId); Contact GetContacyById(int id); CustomerContact GetCustomerContact(int id); diff --git a/src/Services/Sales/SalesService.cs b/src/Services/Sales/SalesService.cs index cc5b472b5..9d963ffc0 100644 --- a/src/Services/Sales/SalesService.cs +++ b/src/Services/Sales/SalesService.cs @@ -17,6 +17,10 @@ using System; using System.Collections.Generic; using Core.Domain.TaxSystem; +using Microsoft.Extensions.Logging; +using AutoMapper; +using Core.Domain.Error; +using System.Threading.Tasks; namespace Services.Sales { @@ -25,6 +29,9 @@ public partial class SalesService : BaseService, ISalesService private readonly IFinancialService _financialService; private readonly IInventoryService _inventoryService; + private readonly IMapper _mapper; + private readonly ILogger _logger; + private readonly IRepository _salesOrderRepo; private readonly IRepository _salesOrderLineRepo; private readonly IRepository _salesInvoiceRepo; @@ -65,7 +72,9 @@ public SalesService(IFinancialService financialService, IRepository salesQuoteRepo, ISalesOrderRepository salesOrderRepository, IRepository customerContactRepo, - IRepository vendorContactRepo) + IRepository vendorContactRepo, + ILogger logger, + IMapper mapper) : base(sequenceNumberRepo, generalLedgerSetting, paymentTermRepo, bankRepo) { _financialService = financialService; @@ -89,6 +98,8 @@ public SalesService(IFinancialService financialService, _salesOrderLineRepo = salesOrderLineRepo; _customerContactRepo = customerContactRepo; _vendorContactRepo = vendorContactRepo; + _logger = logger; + _mapper = mapper; } public void AddSalesOrder(SalesOrderHeader salesOrder, bool toSave) @@ -305,7 +316,8 @@ public IEnumerable GetSalesInvoices() { var query = _salesInvoiceRepo.GetAllIncluding(inv => inv.Customer, inv => inv.Customer.Party, - inv => inv.SalesInvoiceLines); + inv => inv.SalesInvoiceLines, + inv => inv.CustomerAllocations); return query.AsEnumerable(); } @@ -321,6 +333,15 @@ public SalesInvoiceHeader GetSalesInvoiceById(int id) .Where(inv => inv.Id == id) .FirstOrDefault(); + if (invoice != null) + { + foreach (var line in invoice.SalesInvoiceLines) + { + line.Item = _itemRepo.GetById(line.ItemId); + line.Measurement = _measurementRepo.GetById(line.MeasurementId); + } + } + return invoice; } @@ -636,18 +657,17 @@ public void SaveContact(Contact contact) if (contact.Id > 0) { _contactRepo.Update(contact); - + } else { _contactRepo.Insert(contact); } - + } - catch (Exception e) + catch (Exception ex) { - - throw e; + _logger.LogError(ex.Message); } } @@ -701,9 +721,124 @@ public SalesQuoteHeader GetSalesQuotationById(int id) .Where(q => q.Id == id) .FirstOrDefault(); + if (quotation != null) + { + foreach (var line in quotation.SalesQuoteLines) + { + line.Item = _itemRepo.GetById(line.ItemId); + line.Measurement = _measurementRepo.GetById(line.MeasurementId); + } + } + return quotation; } + public Result CreateSalesInvoice(Dto.Sales.SalesInvoice salesInvoiceDto) + { + Core.Domain.Sales.SalesOrderHeader salesOrder = null; + Core.Domain.Sales.SalesInvoiceHeader salesInvoice = null; + + if (!salesInvoiceDto.FromSalesOrderId.HasValue) + { + salesOrder = _mapper.Map(salesInvoiceDto); + } + else + { + // your invoice is created from existing (open) sales order. + salesOrder = GetSalesOrderById(salesInvoiceDto.FromSalesOrderId.GetValueOrDefault()); + + if (salesOrder is null) + { + var message = $"Sales order {salesInvoiceDto.FromSalesOrderId} in SalesInvoice not found."; + return Result.Failure(Error.RecordNotFound(message)); + } + } + + salesInvoice = _mapper.Map(salesInvoiceDto); + + foreach (var invoiceLine in salesInvoice.SalesInvoiceLines) + { + if (invoiceLine.SalesOrderLineId == 0) + { + salesOrder.SalesOrderLines.Add(invoiceLine.SalesOrderLine); + invoiceLine.SalesOrderLineId = invoiceLine.SalesOrderLine.Id; + } + } + + _logger.LogInformation("SaveSalesInvoice API " + salesInvoice.CustomerId); + + SaveSalesInvoice(salesInvoice, salesOrder); + + return Result.Success(salesInvoiceDto); + } + + public Result UpdateSalesInvoice(Dto.Sales.SalesInvoice salesInvoiceDto) + { + Core.Domain.Sales.SalesInvoiceHeader salesInvoice = null; + Core.Domain.Sales.SalesOrderHeader salesOrder = null; + + salesInvoice = GetSalesInvoiceById(salesInvoiceDto.Id); + + if (salesInvoice is null) + { + var message = $"Sales invoice {salesInvoiceDto.Id} not found."; + return Result.Failure(Error.RecordNotFound(message)); + } + + if (salesInvoice.GeneralLedgerHeaderId.HasValue) + throw new Exception("Invoice is already posted. Update is not allowed."); + + _mapper.Map(salesInvoiceDto, salesInvoice); + + int existingOrderLineId = salesInvoice.SalesInvoiceLines.FirstOrDefault().SalesOrderLineId ??= 0; + foreach (var invoiceLine in salesInvoice.SalesInvoiceLines!) + { + salesOrder = GetSalesOrderLineById(invoiceLine!.SalesOrderLineId ??= 0).SalesOrderHeader; + + if (invoiceLine.Id == 0) + { + // use the last value of existingLine + salesOrder = GetSalesOrderLineById(existingOrderLineId).SalesOrderHeader; + salesOrder.SalesOrderLines.Add(invoiceLine.SalesOrderLine); + } + else + { + // TODO: Udpate Existing SalesOrderLine with SalesInvoiceLine. + invoiceLine.SalesOrderLine = GetSalesOrderLineById(invoiceLine.SalesOrderLineId.GetValueOrDefault()); + } + } + + var deleted = (from line in salesInvoice.SalesInvoiceLines + where !salesInvoiceDto.SalesInvoiceLines.Any(x => x.Id == line.Id) + select line).ToList(); + + foreach (var line in deleted) + { + salesInvoice.SalesInvoiceLines.Remove(line); + } + + _logger.LogInformation("SaveSalesInvoice API " + salesInvoice.CustomerId); + + SaveSalesInvoice(salesInvoice, salesOrder); + + return Result.Success(null); + } + + public async Task> DeleteSalesInvoiceAsync(int id) + { + var salesInvoice = GetSalesInvoiceById(id); + + if (salesInvoice is null) + { + var message = $"Sales invoice {id} not found."; + return Result.Failure(Error.RecordNotFound(message)); + } + + await _salesInvoiceRepo.DeleteAsync(salesInvoice); + + return Result.Success(null); + } + public void SaveSalesInvoice(SalesInvoiceHeader salesInvoice, SalesOrderHeader salesOrder) { // This method should be in a single transaction. when one fails, roll back all changes. @@ -730,6 +865,7 @@ public void SaveSalesInvoice(SalesInvoiceHeader salesInvoice, SalesOrderHeader s } else { + _logger.LogInformation("Invoice " + salesInvoice.CustomerId + " is being updated."); _salesInvoiceRepo.Update(salesInvoice); } @@ -737,7 +873,7 @@ public void SaveSalesInvoice(SalesInvoiceHeader salesInvoice, SalesOrderHeader s } catch (Exception ex) { - throw ex; + _logger.LogError(ex.Message); } } @@ -914,10 +1050,10 @@ group kvp by kvp.Key into g } } } - + public Contact GetContacyById(int id) { - + var contact = _contactRepo.GetAllIncluding(q => q.Party) .Where(q => q.Id == id) .FirstOrDefault(); @@ -926,7 +1062,7 @@ public Contact GetContacyById(int id) public CustomerContact GetCustomerContact(int id) { - return _customerContactRepo.GetById(id); + return _customerContactRepo.GetById(id); } @@ -937,8 +1073,8 @@ public void BookQuotation(int id) quoatation.Status = SalesQuoteStatus.Open; _salesQuoteRepo.Update(quoatation); - + } - + } } diff --git a/src/Services/Services.csproj b/src/Services/Services.csproj index 109f4d790..27dc0fdb4 100644 --- a/src/Services/Services.csproj +++ b/src/Services/Services.csproj @@ -1,12 +1,13 @@  - - netstandard2.1 + net10.0 Library - + + + + - \ No newline at end of file diff --git a/src/Services/TaxSystem/ITaxService.cs b/src/Services/TaxSystem/ITaxService.cs index 183b0e72b..2e3e9a40a 100644 --- a/src/Services/TaxSystem/ITaxService.cs +++ b/src/Services/TaxSystem/ITaxService.cs @@ -6,6 +6,7 @@ namespace Services.TaxSystem { public interface ITaxService { + Core.Domain.TaxSystem.Tax GetTaxById(int taxId); IEnumerable GetTaxes(bool includeInActive = false); IEnumerable GetTaxGroups(); IEnumerable GetItemTaxGroups(); diff --git a/src/Services/TaxSystem/TaxService.cs b/src/Services/TaxSystem/TaxService.cs index a857734a5..b03b83168 100644 --- a/src/Services/TaxSystem/TaxService.cs +++ b/src/Services/TaxSystem/TaxService.cs @@ -43,6 +43,18 @@ public TaxService(IRepository vendorRepo, _taxGroupTaxRepo = taxGroupTaxRepo; } + public Tax GetTaxById(int taxId) + { + var taxEntity = _taxRepo + .GetAllIncluding(s => s.SalesAccount, p => p.PurchasingAccount, tgt => tgt.TaxGroupTaxes, itgt => itgt.ItemTaxGroupTaxes) + .FirstOrDefault(tax => tax.Id == taxId); + + if(taxEntity is null) + throw new NotImplementedException("Tax not found"); + + return taxEntity; + } + public IEnumerable GetTaxes(bool includeInActive = false) { var taxes = _taxRepo diff --git a/src/src.AppHost/Program.cs b/src/src.AppHost/Program.cs new file mode 100644 index 000000000..0ca3597af --- /dev/null +++ b/src/src.AppHost/Program.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using Google.Protobuf.WellKnownTypes; + +var builder = DistributedApplication.CreateBuilder(args); + +var sqlServer = builder.AddSqlServer("gdb-sql-server") + .AddDatabase("gdb-db"); + +// var sqlServer = builder.AddSqlServer("gdb-sql-server") +// .WithImageRegistry("mcr.microsoft.com") +// .WithImage("mssql/server", "2022-latest") +// .WithEnvironment("ACCEPT_EULA", "Y") +// .WithEnvironment("MSSQL_SA_PASSWORD", "YourStrong!Passw0rd") +// .AddDatabase("gdb-db"); + +// read environment variable for connection string +var apiService = builder.AddProject("api") + .WithReference(sqlServer) + .WithHttpEndpoint(port: 8001, name: "api-http") + .WaitFor(sqlServer); + +builder.AddProject("mvc") + .WithHttpEndpoint(port: 8000, name: "mvc-http") + .WithReference(apiService) + .WaitFor(apiService); + +builder.AddProject("blazor") + .WithHttpEndpoint(port: 8002, name: "blazor-http") + .WithReference(apiService) + .WaitFor(apiService); + +builder.AddProject("migrations") + .WithReference(sqlServer) + .WaitFor(sqlServer); + +builder.Build().Run(); diff --git a/src/src.AppHost/Properties/launchSettings.json b/src/src.AppHost/Properties/launchSettings.json new file mode 100644 index 000000000..2b3d5596f --- /dev/null +++ b/src/src.AppHost/Properties/launchSettings.json @@ -0,0 +1,29 @@ +{ + "$schema": "https://json.schemastore.org/launchsettings.json", + "profiles": { + "https": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "applicationUrl": "https://localhost:17118;http://localhost:15251", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development", + "DOTNET_ENVIRONMENT": "Development", + "DOTNET_DASHBOARD_OTLP_ENDPOINT_URL": "https://localhost:21083", + "DOTNET_RESOURCE_SERVICE_ENDPOINT_URL": "https://localhost:22106" + } + }, + "http": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "applicationUrl": "http://localhost:15251", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development", + "DOTNET_ENVIRONMENT": "Development", + "DOTNET_DASHBOARD_OTLP_ENDPOINT_URL": "http://localhost:19070", + "DOTNET_RESOURCE_SERVICE_ENDPOINT_URL": "http://localhost:20256" + } + } + } +} diff --git a/src/src.AppHost/appsettings.Development.json b/src/src.AppHost/appsettings.Development.json new file mode 100644 index 000000000..0c208ae91 --- /dev/null +++ b/src/src.AppHost/appsettings.Development.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/src/src.AppHost/appsettings.json b/src/src.AppHost/appsettings.json new file mode 100644 index 000000000..31c092aa4 --- /dev/null +++ b/src/src.AppHost/appsettings.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning", + "Aspire.Hosting.Dcp": "Warning" + } + } +} diff --git a/src/src.AppHost/src.AppHost.csproj b/src/src.AppHost/src.AppHost.csproj new file mode 100644 index 000000000..6238035fb --- /dev/null +++ b/src/src.AppHost/src.AppHost.csproj @@ -0,0 +1,30 @@ + + + + + + Exe + net10.0 + enable + enable + true + 13ebf7e1-4bc3-4178-925c-d2b16f2f0006 + + + + + + + + + + + + + + + false + + + + diff --git a/src/src.ServiceDefaults/Extensions.cs b/src/src.ServiceDefaults/Extensions.cs new file mode 100644 index 000000000..ce94dc2c4 --- /dev/null +++ b/src/src.ServiceDefaults/Extensions.cs @@ -0,0 +1,111 @@ +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Diagnostics.HealthChecks; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Diagnostics.HealthChecks; +using Microsoft.Extensions.Logging; +using OpenTelemetry; +using OpenTelemetry.Metrics; +using OpenTelemetry.Trace; + +namespace Microsoft.Extensions.Hosting; + +// Adds common .NET Aspire services: service discovery, resilience, health checks, and OpenTelemetry. +// This project should be referenced by each service project in your solution. +// To learn more about using this project, see https://aka.ms/dotnet/aspire/service-defaults +public static class Extensions +{ + public static IHostApplicationBuilder AddServiceDefaults(this IHostApplicationBuilder builder) + { + builder.ConfigureOpenTelemetry(); + + builder.AddDefaultHealthChecks(); + + builder.Services.AddServiceDiscovery(); + + builder.Services.ConfigureHttpClientDefaults(http => + { + // Turn on resilience by default + http.AddStandardResilienceHandler(); + + // Turn on service discovery by default + http.AddServiceDiscovery(); + }); + + return builder; + } + + public static IHostApplicationBuilder ConfigureOpenTelemetry(this IHostApplicationBuilder builder) + { + builder.Logging.AddOpenTelemetry(logging => + { + logging.IncludeFormattedMessage = true; + logging.IncludeScopes = true; + }); + + builder.Services.AddOpenTelemetry() + .WithMetrics(metrics => + { + metrics.AddAspNetCoreInstrumentation() + .AddHttpClientInstrumentation() + .AddRuntimeInstrumentation(); + }) + .WithTracing(tracing => + { + tracing.AddAspNetCoreInstrumentation() + // Uncomment the following line to enable gRPC instrumentation (requires the OpenTelemetry.Instrumentation.GrpcNetClient package) + //.AddGrpcClientInstrumentation() + .AddHttpClientInstrumentation(); + }); + + builder.AddOpenTelemetryExporters(); + + return builder; + } + + private static IHostApplicationBuilder AddOpenTelemetryExporters(this IHostApplicationBuilder builder) + { + var useOtlpExporter = !string.IsNullOrWhiteSpace(builder.Configuration["OTEL_EXPORTER_OTLP_ENDPOINT"]); + + if (useOtlpExporter) + { + builder.Services.AddOpenTelemetry().UseOtlpExporter(); + } + + // Uncomment the following lines to enable the Azure Monitor exporter (requires the Azure.Monitor.OpenTelemetry.AspNetCore package) + //if (!string.IsNullOrEmpty(builder.Configuration["APPLICATIONINSIGHTS_CONNECTION_STRING"])) + //{ + // builder.Services.AddOpenTelemetry() + // .UseAzureMonitor(); + //} + + return builder; + } + + public static IHostApplicationBuilder AddDefaultHealthChecks(this IHostApplicationBuilder builder) + { + builder.Services.AddHealthChecks() + // Add a default liveness check to ensure app is responsive + .AddCheck("self", () => HealthCheckResult.Healthy(), ["live"]); + + return builder; + } + + public static WebApplication MapDefaultEndpoints(this WebApplication app) + { + // Adding health checks endpoints to applications in non-development environments has security implications. + // See https://aka.ms/dotnet/aspire/healthchecks for details before enabling these endpoints in non-development environments. + if (app.Environment.IsDevelopment()) + { + // All health checks must pass for app to be considered ready to accept traffic after starting + app.MapHealthChecks("/health"); + + // Only health checks tagged with the "live" tag must pass for app to be considered alive + app.MapHealthChecks("/alive", new HealthCheckOptions + { + Predicate = r => r.Tags.Contains("live") + }); + } + + return app; + } +} diff --git a/src/src.ServiceDefaults/src.ServiceDefaults.csproj b/src/src.ServiceDefaults/src.ServiceDefaults.csproj new file mode 100644 index 000000000..94c584be7 --- /dev/null +++ b/src/src.ServiceDefaults/src.ServiceDefaults.csproj @@ -0,0 +1,22 @@ + + + + net10.0 + enable + enable + true + + + + + + + + + + + + + + + diff --git a/src/src.sln b/src/src.sln new file mode 100644 index 000000000..ad66b4422 --- /dev/null +++ b/src/src.sln @@ -0,0 +1,105 @@ +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.5.2.0 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AccountGoWeb", "AccountGoWeb\AccountGoWeb.csproj", "{3FF38DDB-E5A7-4123-BA96-F7211942C2C4}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Api", "Api\Api.csproj", "{CBFA08EA-AECE-205F-7B85-326FD0B81BAD}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Core", "Core\Core.csproj", "{5ED06CAD-956C-C94D-BC0C-E89E30A887C2}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dto", "Dto\Dto.csproj", "{F0F02016-EB19-BCCD-8D56-0C58F454315B}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Infrastructure", "Infrastructure\Infrastructure.csproj", "{5872AEB3-BAA3-FE31-B0EB-0FB6DBEDCF83}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LibraryGDB", "LibraryGDB\LibraryGDB.csproj", "{3DC0BA77-8B1D-CE31-7A85-D1B20A6E73AF}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MigrationService", "MigrationService\MigrationService.csproj", "{33C31411-3ED1-6C91-4919-4D2E66530DE7}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Services", "Services\Services.csproj", "{97207750-6972-232F-BB7B-6106666106AF}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "src.AppHost", "src.AppHost\src.AppHost.csproj", "{CB02C962-558E-0687-A0D0-F6FB3E6E8D56}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "src.ServiceDefaults", "src.ServiceDefaults\src.ServiceDefaults.csproj", "{14C90216-BBAE-7F0B-7160-944A25162531}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "BlazorGDB", "BlazorGDB", "{25FF5F5E-EE09-4415-1C75-62ADC5BA4270}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Modules", "Modules", "{CA253913-39DE-BFD0-C9A3-4B7EC6FBDF17}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BlazorGDB", "BlazorGDB\BlazorGDB\BlazorGDB.csproj", "{3033AAE3-E551-B013-5051-D7D8D033C0F5}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BlazorGDB.Client", "BlazorGDB\BlazorGDB.Client\BlazorGDB.Client.csproj", "{8D3CD3EB-FB13-88D5-BFFB-76C0EE40624B}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SampleModule", "Modules\SampleModule\SampleModule.csproj", "{01D66C68-2E45-4F4C-1D54-27685DC878D2}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {3FF38DDB-E5A7-4123-BA96-F7211942C2C4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3FF38DDB-E5A7-4123-BA96-F7211942C2C4}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3FF38DDB-E5A7-4123-BA96-F7211942C2C4}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3FF38DDB-E5A7-4123-BA96-F7211942C2C4}.Release|Any CPU.Build.0 = Release|Any CPU + {CBFA08EA-AECE-205F-7B85-326FD0B81BAD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {CBFA08EA-AECE-205F-7B85-326FD0B81BAD}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CBFA08EA-AECE-205F-7B85-326FD0B81BAD}.Release|Any CPU.ActiveCfg = Release|Any CPU + {CBFA08EA-AECE-205F-7B85-326FD0B81BAD}.Release|Any CPU.Build.0 = Release|Any CPU + {5ED06CAD-956C-C94D-BC0C-E89E30A887C2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5ED06CAD-956C-C94D-BC0C-E89E30A887C2}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5ED06CAD-956C-C94D-BC0C-E89E30A887C2}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5ED06CAD-956C-C94D-BC0C-E89E30A887C2}.Release|Any CPU.Build.0 = Release|Any CPU + {F0F02016-EB19-BCCD-8D56-0C58F454315B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F0F02016-EB19-BCCD-8D56-0C58F454315B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F0F02016-EB19-BCCD-8D56-0C58F454315B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F0F02016-EB19-BCCD-8D56-0C58F454315B}.Release|Any CPU.Build.0 = Release|Any CPU + {5872AEB3-BAA3-FE31-B0EB-0FB6DBEDCF83}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5872AEB3-BAA3-FE31-B0EB-0FB6DBEDCF83}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5872AEB3-BAA3-FE31-B0EB-0FB6DBEDCF83}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5872AEB3-BAA3-FE31-B0EB-0FB6DBEDCF83}.Release|Any CPU.Build.0 = Release|Any CPU + {3DC0BA77-8B1D-CE31-7A85-D1B20A6E73AF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3DC0BA77-8B1D-CE31-7A85-D1B20A6E73AF}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3DC0BA77-8B1D-CE31-7A85-D1B20A6E73AF}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3DC0BA77-8B1D-CE31-7A85-D1B20A6E73AF}.Release|Any CPU.Build.0 = Release|Any CPU + {33C31411-3ED1-6C91-4919-4D2E66530DE7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {33C31411-3ED1-6C91-4919-4D2E66530DE7}.Debug|Any CPU.Build.0 = Debug|Any CPU + {33C31411-3ED1-6C91-4919-4D2E66530DE7}.Release|Any CPU.ActiveCfg = Release|Any CPU + {33C31411-3ED1-6C91-4919-4D2E66530DE7}.Release|Any CPU.Build.0 = Release|Any CPU + {97207750-6972-232F-BB7B-6106666106AF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {97207750-6972-232F-BB7B-6106666106AF}.Debug|Any CPU.Build.0 = Debug|Any CPU + {97207750-6972-232F-BB7B-6106666106AF}.Release|Any CPU.ActiveCfg = Release|Any CPU + {97207750-6972-232F-BB7B-6106666106AF}.Release|Any CPU.Build.0 = Release|Any CPU + {CB02C962-558E-0687-A0D0-F6FB3E6E8D56}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {CB02C962-558E-0687-A0D0-F6FB3E6E8D56}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CB02C962-558E-0687-A0D0-F6FB3E6E8D56}.Release|Any CPU.ActiveCfg = Release|Any CPU + {CB02C962-558E-0687-A0D0-F6FB3E6E8D56}.Release|Any CPU.Build.0 = Release|Any CPU + {14C90216-BBAE-7F0B-7160-944A25162531}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {14C90216-BBAE-7F0B-7160-944A25162531}.Debug|Any CPU.Build.0 = Debug|Any CPU + {14C90216-BBAE-7F0B-7160-944A25162531}.Release|Any CPU.ActiveCfg = Release|Any CPU + {14C90216-BBAE-7F0B-7160-944A25162531}.Release|Any CPU.Build.0 = Release|Any CPU + {3033AAE3-E551-B013-5051-D7D8D033C0F5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3033AAE3-E551-B013-5051-D7D8D033C0F5}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3033AAE3-E551-B013-5051-D7D8D033C0F5}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3033AAE3-E551-B013-5051-D7D8D033C0F5}.Release|Any CPU.Build.0 = Release|Any CPU + {8D3CD3EB-FB13-88D5-BFFB-76C0EE40624B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8D3CD3EB-FB13-88D5-BFFB-76C0EE40624B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8D3CD3EB-FB13-88D5-BFFB-76C0EE40624B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8D3CD3EB-FB13-88D5-BFFB-76C0EE40624B}.Release|Any CPU.Build.0 = Release|Any CPU + {01D66C68-2E45-4F4C-1D54-27685DC878D2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {01D66C68-2E45-4F4C-1D54-27685DC878D2}.Debug|Any CPU.Build.0 = Debug|Any CPU + {01D66C68-2E45-4F4C-1D54-27685DC878D2}.Release|Any CPU.ActiveCfg = Release|Any CPU + {01D66C68-2E45-4F4C-1D54-27685DC878D2}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {3033AAE3-E551-B013-5051-D7D8D033C0F5} = {25FF5F5E-EE09-4415-1C75-62ADC5BA4270} + {8D3CD3EB-FB13-88D5-BFFB-76C0EE40624B} = {25FF5F5E-EE09-4415-1C75-62ADC5BA4270} + {01D66C68-2E45-4F4C-1D54-27685DC878D2} = {CA253913-39DE-BFD0-C9A3-4B7EC6FBDF17} + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {8C54C75D-107E-4C75-9F3C-3F04747713FE} + EndGlobalSection +EndGlobal diff --git a/test/GoodBooks.BackendTests/Financial/AccountTests.cs b/test/GoodBooks.BackendTests/Financial/AccountTests.cs new file mode 100644 index 000000000..0ed345b5c --- /dev/null +++ b/test/GoodBooks.BackendTests/Financial/AccountTests.cs @@ -0,0 +1,64 @@ +using Dto.Financial; +using Xunit; + +namespace GoodBooks.BackendTests.Financial; + +public class AccountTests +{ + [Fact] + public void ChildAccounts_DefaultsToEmptyList() + { + var account = new Account(); + + Assert.NotNull(account.ChildAccounts); + Assert.Empty(account.ChildAccounts); + } + + [Fact] + public void TotalBalance_SumsAllDescendantBalances() + { + var account = new Account + { + Balance = 100m, // root balance is intentionally ignored by the DTO logic + ChildAccounts = + { + new Account { Balance = 50m }, + new Account + { + Balance = 25m, + ChildAccounts = + { + new Account { Balance = 10m }, + new Account { Balance = 5m } + } + } + } + }; + + Assert.Equal(90m, account.TotalBalance); + } + + [Fact] + public void TotalDebitAndCreditBalance_SumAllDescendants() + { + var account = new Account + { + ChildAccounts = + { + new Account { DebitBalance = 40m, CreditBalance = 5m }, + new Account + { + DebitBalance = 10m, + CreditBalance = 20m, + ChildAccounts = + { + new Account { DebitBalance = 2m, CreditBalance = 3m } + } + } + } + }; + + Assert.Equal(52m, account.TotalDebitBalance); + Assert.Equal(28m, account.TotalCreditBalance); + } +} diff --git a/test/GoodBooks.BackendTests/Financial/JournalEntryTests.cs b/test/GoodBooks.BackendTests/Financial/JournalEntryTests.cs new file mode 100644 index 000000000..b2aa69fc2 --- /dev/null +++ b/test/GoodBooks.BackendTests/Financial/JournalEntryTests.cs @@ -0,0 +1,48 @@ +using Dto.Financial; +using Xunit; + +namespace GoodBooks.BackendTests.Financial; + +public class JournalEntryTests +{ + [Fact] + public void JournalEntryLines_DefaultsToEmptyList() + { + var journalEntry = new JournalEntry(); + + Assert.NotNull(journalEntry.JournalEntryLines); + Assert.Empty(journalEntry.JournalEntryLines); + } + + [Fact] + public void DebitAmount_SumsDebitLinesOnly() + { + var journalEntry = new JournalEntry + { + JournalEntryLines = + { + new JournalEntryLine { DrCr = 1, Amount = 50m }, + new JournalEntryLine { DrCr = 2, Amount = 75m }, + new JournalEntryLine { DrCr = 1, Amount = null } // null amounts should be treated as zero + } + }; + + Assert.Equal(50m, journalEntry.debitAmount); + } + + [Fact] + public void CreditAmount_SumsCreditLinesOnly() + { + var journalEntry = new JournalEntry + { + JournalEntryLines = + { + new JournalEntryLine { DrCr = 2, Amount = 5m }, + new JournalEntryLine { DrCr = 2, Amount = 10m }, + new JournalEntryLine { DrCr = 1, Amount = 3m } + } + }; + + Assert.Equal(15m, journalEntry.creditAmount); + } +} diff --git a/test/GoodBooks.BackendTests/GoodBooks.BackendTests.csproj b/test/GoodBooks.BackendTests/GoodBooks.BackendTests.csproj new file mode 100644 index 000000000..09e1a4757 --- /dev/null +++ b/test/GoodBooks.BackendTests/GoodBooks.BackendTests.csproj @@ -0,0 +1,19 @@ + + + net10.0 + false + enable + enable + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + diff --git a/test/GoodBooks.ServicesTests/BaseServiceTests.cs b/test/GoodBooks.ServicesTests/BaseServiceTests.cs new file mode 100644 index 000000000..b3540fd72 --- /dev/null +++ b/test/GoodBooks.ServicesTests/BaseServiceTests.cs @@ -0,0 +1,205 @@ +using Core.Data; +using Core.Domain; +using Core.Domain.Financials; +using Moq; +using Services; + +namespace GoodBooks.ServicesTests; + +/// +/// Unit tests for the BaseService class. +/// Tests the sequence number generation and protected methods used by all services. +/// +public class BaseServiceTests +{ + private readonly Mock> _mockSequenceNumberRepo; + private readonly Mock> _mockGeneralLedgerSettingRepo; + private readonly Mock> _mockPaymentTermRepo; + private readonly Mock> _mockBankRepo; + + public BaseServiceTests() + { + _mockSequenceNumberRepo = new Mock>(); + _mockGeneralLedgerSettingRepo = new Mock>(); + _mockPaymentTermRepo = new Mock>(); + _mockBankRepo = new Mock>(); + } + + [Fact] + public void SequenceNumberRepository_WithNewSequence_ShouldInsertAndRetrieve() + { + // Arrange + var sequenceType = SequenceNumberTypes.JournalEntry; + var sequenceList = new List(); + + _mockSequenceNumberRepo + .Setup(r => r.Table) + .Returns(() => sequenceList.AsQueryable()); + + _mockSequenceNumberRepo + .Setup(r => r.Insert(It.IsAny())) + .Callback(s => sequenceList.Add(s)); + + // Act + var newSequence = new SequenceNumber + { + SequenceNumberType = sequenceType, + Description = "Test", + NextNumber = 1, + UsePrefix = false + }; + _mockSequenceNumberRepo.Object.Insert(newSequence); + var result = _mockSequenceNumberRepo.Object.Table.FirstOrDefault(s => s.SequenceNumberType == sequenceType); + + // Assert + Assert.NotNull(result); + Assert.Equal(sequenceType, result.SequenceNumberType); + Assert.Equal(1, result.NextNumber); + } + + [Fact] + public void SequenceNumberRepository_WithExistingSequence_ShouldUpdate() + { + // Arrange + var sequenceType = SequenceNumberTypes.JournalEntry; + var existingSequence = new SequenceNumber + { + Id = 1, + SequenceNumberType = sequenceType, + NextNumber = 42, + Description = "Test" + }; + var sequenceList = new List { existingSequence }; + + _mockSequenceNumberRepo + .Setup(r => r.Table) + .Returns(() => sequenceList.AsQueryable()); + + _mockSequenceNumberRepo + .Setup(r => r.Update(It.IsAny())) + .Callback(s => + { + var existing = sequenceList.FirstOrDefault(x => x.Id == s.Id); + if (existing != null) + { + existing.NextNumber = s.NextNumber; + } + }); + + // Act + existingSequence.NextNumber = 43; + _mockSequenceNumberRepo.Object.Update(existingSequence); + + // Assert + Assert.Equal(43, existingSequence.NextNumber); + _mockSequenceNumberRepo.Verify(r => r.Update(It.IsAny()), Times.Once); + } + + [Theory] + [InlineData(SequenceNumberTypes.JournalEntry)] + [InlineData(SequenceNumberTypes.PurchaseOrder)] + [InlineData(SequenceNumberTypes.SalesOrder)] + public void SequenceNumberRepository_WithDifferentTypes_ShouldHandleAllTypes(SequenceNumberTypes type) + { + // Arrange + var sequenceList = new List(); + + _mockSequenceNumberRepo + .Setup(r => r.Table) + .Returns(() => sequenceList.AsQueryable()); + + _mockSequenceNumberRepo + .Setup(r => r.Insert(It.IsAny())) + .Callback(s => sequenceList.Add(s)); + + // Act + var newSequence = new SequenceNumber + { + SequenceNumberType = type, + Description = Enum.GetName(typeof(SequenceNumberTypes), type), + NextNumber = 1, + UsePrefix = false + }; + _mockSequenceNumberRepo.Object.Insert(newSequence); + + // Assert + Assert.Single(sequenceList); + Assert.Equal(type, sequenceList.First().SequenceNumberType); + } + + [Fact] + public void GeneralLedgerSettingRepository_WhenExists_ShouldRetrieveSetting() + { + // Arrange + var glSetting = new GeneralLedgerSetting { Id = 1 }; + var settingsList = new List { glSetting }; + + _mockGeneralLedgerSettingRepo + .Setup(r => r.Table) + .Returns(settingsList.AsQueryable()); + + // Act + var result = _mockGeneralLedgerSettingRepo.Object.Table.FirstOrDefault(); + + // Assert + Assert.NotNull(result); + Assert.Equal(1, result.Id); + } + + [Fact] + public void GeneralLedgerSettingRepository_WhenEmpty_ShouldReturnNull() + { + // Arrange + var settingsList = new List(); + + _mockGeneralLedgerSettingRepo + .Setup(r => r.Table) + .Returns(settingsList.AsQueryable()); + + // Act + var result = _mockGeneralLedgerSettingRepo.Object.Table.FirstOrDefault(); + + // Assert + Assert.Null(result); + } + + [Fact] + public void BankRepository_ShouldReturnAllBanks() + { + // Arrange + var banks = new List + { + new Bank { Id = 1, BankName = "Bank A" }, + new Bank { Id = 2, BankName = "Bank B" } + }; + + _mockBankRepo + .Setup(r => r.Table) + .Returns(banks.AsQueryable()); + + // Act + var result = _mockBankRepo.Object.Table; + + // Assert + Assert.NotNull(result); + Assert.Equal(2, result.Count()); + } + + [Fact] + public void BankRepository_WhenEmpty_ShouldReturnEmptyList() + { + // Arrange + var banks = new List(); + + _mockBankRepo + .Setup(r => r.Table) + .Returns(banks.AsQueryable()); + + // Act + var result = _mockBankRepo.Object.Table; + + // Assert + Assert.NotNull(result); + Assert.Empty(result); + } +} diff --git a/test/GoodBooks.ServicesTests/Dto/Administration/CompanyDtoTests.cs b/test/GoodBooks.ServicesTests/Dto/Administration/CompanyDtoTests.cs new file mode 100644 index 000000000..121253a46 --- /dev/null +++ b/test/GoodBooks.ServicesTests/Dto/Administration/CompanyDtoTests.cs @@ -0,0 +1,138 @@ +using Dto; +using Dto.Administration; +using System.ComponentModel.DataAnnotations; +using Xunit; + +namespace GoodBooks.ServicesTests.Dto.Administration; + +/// +/// Unit tests for the Company DTO class. +/// Tests validation, properties, and data integrity. +/// +public class CompanyDtoTests +{ + [Fact] + public void Company_DefaultInitialization_ShouldHaveNullProperties() + { + // Arrange & Act + var company = new Company(); + + // Assert + Assert.Equal(0, company.Id); + Assert.Null(company.CompanyCode); + Assert.Null(company.Name); + Assert.Null(company.ShortName); + Assert.Null(company.CRA); + Assert.Null(company.Logo); + Assert.Null(company.ModifiedBy); + } + + [Fact] + public void Company_SetAllProperties_ShouldStoreValues() + { + // Arrange + var company = new Company(); + var code = "COMP001"; + var name = "Test Company Inc."; + var shortName = "TCI"; + var cra = "123456789"; + var logo = new byte[] { 1, 2, 3 }; + + // Act + company.Id = 1; + company.CompanyCode = code; + company.Name = name; + company.ShortName = shortName; + company.CRA = cra; + company.Logo = logo; + company.ModifiedBy = "Admin"; + + // Assert + Assert.Equal(1, company.Id); + Assert.Equal(code, company.CompanyCode); + Assert.Equal(name, company.Name); + Assert.Equal(shortName, company.ShortName); + Assert.Equal(cra, company.CRA); + Assert.NotNull(company.Logo); + Assert.Equal(3, company.Logo.Length); + Assert.Equal("Admin", company.ModifiedBy); + } + + [Fact] + public void Company_Logo_CanBeNull() + { + // Arrange + var company = new Company(); + + // Act + company.Logo = null; + + // Assert + Assert.Null(company.Logo); + } + + [Fact] + public void Company_Logo_CanStoreByteArray() + { + // Arrange + var company = new Company(); + var largeByteArray = new byte[1024]; + + // Act + company.Logo = largeByteArray; + + // Assert + Assert.NotNull(company.Logo); + Assert.Equal(1024, company.Logo.Length); + } + + [Theory] + [InlineData("COMP001", "Company One", "CO1", "111111111")] + [InlineData("COMP002", "Company Two", "CO2", "222222222")] + [InlineData("COMP003", "Company Three Inc.", "C3", "333333333")] + public void Company_WithValidData_ShouldInitializeCorrectly( + string code, string name, string shortName, string cra) + { + // Arrange & Act + var company = new Company + { + Id = 1, + CompanyCode = code, + Name = name, + ShortName = shortName, + CRA = cra + }; + + // Assert + Assert.Equal(1, company.Id); + Assert.Equal(code, company.CompanyCode); + Assert.Equal(name, company.Name); + Assert.Equal(shortName, company.ShortName); + Assert.Equal(cra, company.CRA); + } + + [Fact] + public void Company_ShouldInheritFromBaseDto() + { + // Arrange & Act + var company = new Company { Id = 1, ModifiedBy = "User" }; + + // Assert + Assert.IsAssignableFrom(company); + Assert.Equal(1, company.Id); + Assert.Equal("User", company.ModifiedBy); + } + + [Theory] + [InlineData("")] + [InlineData(" ")] + [InlineData("VeryLongCompanyCodeThatMightExceedExpectedLength")] + public void Company_StringProperties_ShouldAcceptAnyString(string value) + { + // Arrange & Act + var company = new Company { CompanyCode = value }; + + // Assert + Assert.Equal(value, company.CompanyCode); + } +} diff --git a/test/GoodBooks.ServicesTests/Dto/BaseDtoTests.cs b/test/GoodBooks.ServicesTests/Dto/BaseDtoTests.cs new file mode 100644 index 000000000..9edcac535 --- /dev/null +++ b/test/GoodBooks.ServicesTests/Dto/BaseDtoTests.cs @@ -0,0 +1,101 @@ +global using Dto; +using Xunit; + +namespace GoodBooks.ServicesTests.Dto; + +/// +/// Unit tests for the BaseDto class. +/// Tests the common properties and behavior of all DTOs. +/// +public class BaseDtoTests +{ + [Fact] + public void BaseDto_DefaultInitialization_ShouldHaveDefaultValues() + { + // Arrange & Act + var dto = new TestDto(); + + // Assert + Assert.Equal(0, dto.Id); + Assert.Null(dto.ModifiedBy); + } + + [Fact] + public void BaseDto_SetId_ShouldStoreValue() + { + // Arrange + var dto = new TestDto(); + var expectedId = 42; + + // Act + dto.Id = expectedId; + + // Assert + Assert.Equal(expectedId, dto.Id); + } + + [Fact] + public void BaseDto_SetModifiedBy_ShouldStoreValue() + { + // Arrange + var dto = new TestDto(); + var modifiedBy = "TestUser"; + + // Act + dto.ModifiedBy = modifiedBy; + + // Assert + Assert.Equal(modifiedBy, dto.ModifiedBy); + } + + [Fact] + public void BaseDto_SetProperties_ShouldAllowMultipleModifications() + { + // Arrange + var dto = new TestDto(); + + // Act + dto.Id = 1; + dto.ModifiedBy = "User1"; + dto.Id = 2; + dto.ModifiedBy = "User2"; + + // Assert + Assert.Equal(2, dto.Id); + Assert.Equal("User2", dto.ModifiedBy); + } + + [Theory] + [InlineData(1)] + [InlineData(100)] + [InlineData(int.MaxValue)] + public void BaseDto_Id_ShouldSupportVariousIntegerValues(int id) + { + // Arrange & Act + var dto = new TestDto { Id = id }; + + // Assert + Assert.Equal(id, dto.Id); + } + + [Theory] + [InlineData("")] + [InlineData("TestUser")] + [InlineData("user@example.com")] + [InlineData("User with spaces")] + public void BaseDto_ModifiedBy_ShouldSupportVariousStringValues(string modifiedBy) + { + // Arrange & Act + var dto = new TestDto { ModifiedBy = modifiedBy }; + + // Assert + Assert.Equal(modifiedBy, dto.ModifiedBy); + } +} + +/// +/// Concrete implementation of BaseDto for testing purposes. +/// +public class TestDto : BaseDto +{ +} diff --git a/test/GoodBooks.ServicesTests/Fixtures/MockRepositoryFixtures.cs b/test/GoodBooks.ServicesTests/Fixtures/MockRepositoryFixtures.cs new file mode 100644 index 000000000..fb81b195a --- /dev/null +++ b/test/GoodBooks.ServicesTests/Fixtures/MockRepositoryFixtures.cs @@ -0,0 +1,127 @@ +using Core.Data; +using Core.Domain; +using Core.Domain.Financials; +using Moq; + +namespace GoodBooks.ServicesTests.Fixtures; + +/// +/// Mock fixtures for common repository instances used in tests. +/// Provides factory methods for creating consistently configured mocks. +/// +public static class MockRepositoryFixtures +{ + /// + /// Creates a mock repository with an empty table. + /// + public static Mock> CreateEmptyRepository() where T : BaseEntity + { + var mock = new Mock>(); + mock.Setup(r => r.Table).Returns(new List().AsQueryable()); + return mock; + } + + /// + /// Creates a mock repository with sample data. + /// + public static Mock> CreateRepositoryWithData(List data) where T : BaseEntity + { + var mock = new Mock>(); + mock.Setup(r => r.Table).Returns(data.AsQueryable()); + return mock; + } + + /// + /// Creates a mock SequenceNumber repository with initial sequences. + /// + public static Mock> CreateSequenceNumberRepository( + params SequenceNumber[] sequences) + { + var mock = new Mock>(); + var sequenceList = new List(sequences); + + mock.Setup(r => r.Table).Returns(() => sequenceList.AsQueryable()); + mock.Setup(r => r.Insert(It.IsAny())) + .Callback(s => sequenceList.Add(s)); + mock.Setup(r => r.Update(It.IsAny())) + .Callback(s => + { + var existing = sequenceList.FirstOrDefault(x => x.Id == s.Id); + if (existing != null) + { + existing.NextNumber = s.NextNumber; + existing.Description = s.Description; + } + }); + + return mock; + } + + /// + /// Creates a mock GeneralLedgerSetting repository. + /// + public static Mock> CreateGeneralLedgerSettingRepository( + params GeneralLedgerSetting[] settings) + { + return CreateRepositoryWithData(new List(settings)); + } + + /// + /// Creates a mock Bank repository. + /// + public static Mock> CreateBankRepository(params Bank[] banks) + { + return CreateRepositoryWithData(new List(banks)); + } + + /// + /// Creates a mock PaymentTerm repository. + /// + public static Mock> CreatePaymentTermRepository( + params PaymentTerm[] paymentTerms) + { + return CreateRepositoryWithData(new List(paymentTerms)); + } + + /// + /// Creates sample sequence numbers for testing. + /// + public static SequenceNumber CreateSampleSequence( + int id = 1, + SequenceNumberTypes type = SequenceNumberTypes.JournalEntry, + int nextNumber = 1, + string description = "Test Sequence") + { + return new SequenceNumber + { + Id = id, + SequenceNumberType = type, + NextNumber = nextNumber, + Description = description, + UsePrefix = false + }; + } + + /// + /// Creates sample banks for testing. + /// + public static Bank CreateSampleBank(int id = 1, string name = "Test Bank") + { + return new Bank + { + Id = id, + BankName = name + }; + } + + /// + /// Creates sample GL settings for testing. + /// + public static GeneralLedgerSetting CreateSampleGeneralLedgerSetting(int id = 1) + { + return new GeneralLedgerSetting + { + Id = id + }; + } +} diff --git a/test/GoodBooks.ServicesTests/GoodBooks.ServicesTests.csproj b/test/GoodBooks.ServicesTests/GoodBooks.ServicesTests.csproj new file mode 100644 index 000000000..f95f5ce7a --- /dev/null +++ b/test/GoodBooks.ServicesTests/GoodBooks.ServicesTests.csproj @@ -0,0 +1,34 @@ + + + + net10.0 + enable + enable + false + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + + + + + + + + + + diff --git a/test/GoodBooks.ServicesTests/README.md b/test/GoodBooks.ServicesTests/README.md new file mode 100644 index 000000000..06c4b377e --- /dev/null +++ b/test/GoodBooks.ServicesTests/README.md @@ -0,0 +1,150 @@ +# GoodBooks.ServicesTests + +Unit tests for GoodBooks backend services using **xUnit** framework and **Moq** for mocking. + +**Current Test Coverage:** +- BaseServiceTests: 8 tests (repository operations) +- BaseDtoTests: 7 tests (base DTO class) +- CompanyDtoTests: 10 tests (Company DTO example) +- **Total: 31 tests** + +## Quick Start + +### Run All Tests +```bash +dotnet test test/GoodBooks.ServicesTests/ +``` + +### Run Tests with Watch Mode (auto-run on file changes) +```bash +cd test/GoodBooks.ServicesTests && dotnet watch test +``` + +### Run Specific Test Class +```bash +dotnet test test/GoodBooks.ServicesTests/ --filter "ClassName=BaseServiceTests" +``` + +### Run with Verbose Output +```bash +dotnet test test/GoodBooks.ServicesTests/ --verbosity normal +``` + +## Project Structure + +``` +GoodBooks.ServicesTests/ +├── BaseServiceTests.cs # 8 tests for repository operations +├── Dto/ +│ ├── BaseDtoTests.cs # 7 tests for BaseDto +│ └── Administration/ +│ └── CompanyDtoTests.cs # 10 tests for Company DTO +├── Fixtures/ +│ └── MockRepositoryFixtures.cs # Reusable mock factories +└── GoodBooks.ServicesTests.csproj +``` + +## Test Patterns + +All tests use the **AAA Pattern** (Arrange-Act-Assert): + +```csharp +[Fact] +public void TestName_Should_ExpectedBehavior() +{ + // Arrange: Set up test data + var mockRepo = new Mock>(); + + // Act: Execute code + var result = service.DoSomething(); + + // Assert: Verify results + Assert.Equal(expectedValue, result); +} +``` + +## Adding New Tests + +Create test files following the same pattern as existing tests. Use `MockRepositoryFixtures` for creating mock repositories: + +```csharp +using Xunit; +using GoodBooks.ServicesTests.Fixtures; + +namespace GoodBooks.ServicesTests.Services; + +public class NewServiceTests +{ + [Fact] + public void MethodName_Should_ExpectedBehavior() + { + // Arrange + var mockRepo = MockRepositoryFixtures.CreateEmptyRepository(); + + // Act + var result = mockRepo.Object.GetAll(); + + // Assert + Assert.NotNull(result); + } +} +``` + +## Test Fixtures + +Reusable mock factories in `MockRepositoryFixtures.cs`: +- `CreateEmptyRepository()` - Empty mock +- `CreateRepositoryWithData()` - Mock with sample data +- `CreateSequenceNumberRepository()` - Pre-configured sequence mock +- Sample data creators: `CreateSampleBank()`, `CreateSampleSequence()`, etc. +- [ ] Financial Services tests +- [ ] Inventory Services tests +- [ ] Purchasing Services tests +- [ ] Sales Services tests +- [ ] Security Services tests +- [ ] Tax System Services tests +- [ ] Integration tests with real database +- [ ] Performance benchmarks + +### Contributing +When adding new tests: +1. Follow the existing structure and naming conventions +2. Use appropriate fixtures for mock creation +3. Include XML documentation comments +4. Ensure all tests pass locally before committing +5. Aim for >80% code coverage + +## Troubleshooting + +### Tests Not Discovered +```bash +dotnet test test/GoodBooks.ServicesTests/ --verbosity diagnostic +``` + +### Mock Issues +Ensure repositories are properly configured: +```csharp +_mockRepo.Setup(r => r.Table).Returns(data.AsQueryable()); +_mockRepo.Setup(r => r.Insert(It.IsAny())); +``` + +### Async Test Issues +Use `async Task` with `await` for async tests: +```csharp +[Fact] +public async Task AsyncTest_Should_Work() +{ + var result = await service.GetDataAsync(); + Assert.NotNull(result); +} +``` + +## Resources + +- [xUnit Documentation](https://xunit.net/) +- [Moq Quick Start](https://github.com/moq/moq4/wiki/Quickstart) +- [Unit Testing Best Practices](https://docs.microsoft.com/en-us/dotnet/core/testing/unit-testing-best-practices) + +## License + +Copyright (c) AccountGo. All rights reserved. diff --git a/test/Module.Tests/Module.Tests.csproj b/test/Module.Tests/Module.Tests.csproj index 9d04ab11c..4ba7f7750 100644 --- a/test/Module.Tests/Module.Tests.csproj +++ b/test/Module.Tests/Module.Tests.csproj @@ -3,12 +3,17 @@ netcoreapp3.1 false + + true - - - + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + diff --git a/test/SampleModules/SampleNetStandard20/SampleNetStandard20.csproj b/test/SampleModules/SampleNetStandard20/SampleNetStandard20.csproj index 18d638898..96412b833 100644 --- a/test/SampleModules/SampleNetStandard20/SampleNetStandard20.csproj +++ b/test/SampleModules/SampleNetStandard20/SampleNetStandard20.csproj @@ -8,9 +8,9 @@ - - - + + +