Compare commits
226 Commits
release-20
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3d9a254b63 | ||
|
|
ff5533b647 | ||
|
|
b3845187f6 | ||
|
|
019c818a19 | ||
|
|
1137d826ae | ||
|
|
b529388871 | ||
|
|
aef17893d9 | ||
|
|
4f598b0017 | ||
|
|
b71ec627d7 | ||
|
|
af56f8ccad | ||
|
|
0b451ed62f | ||
|
|
11395279af | ||
|
|
a07cfe51aa | ||
|
|
ad397f6fe7 | ||
|
|
85fdec2e5a | ||
|
|
f1b75a7ff0 | ||
|
|
6690302ac3 | ||
|
|
1320222d79 | ||
|
|
baea8ce5e5 | ||
|
|
d69126a980 | ||
|
|
79a2b9f0a3 | ||
|
|
2a755f2bd3 | ||
|
|
edb8a79f94 | ||
|
|
4a5136deee | ||
|
|
f2eaba7e5f | ||
|
|
3b576c6a47 | ||
|
|
557fc48d05 | ||
|
|
353d5ad462 | ||
|
|
df55be5e19 | ||
|
|
35425c576a | ||
|
|
d4abc0fcfb | ||
|
|
11c0937214 | ||
|
|
12fe7ac9bb | ||
|
|
b997b928b8 | ||
|
|
10f57c6aeb | ||
|
|
254cbdf0d6 | ||
|
|
6b1286ba13 | ||
|
|
aea66f4616 | ||
|
|
c73340ec51 | ||
|
|
07904c35e6 | ||
|
|
474cdd56c6 | ||
|
|
83732fa1b5 | ||
|
|
6b1650a634 | ||
|
|
4f7d65f0e8 | ||
|
|
1f10a3f096 | ||
|
|
909c2a6d55 | ||
|
|
37666a00c7 | ||
|
|
b7925f311b | ||
|
|
97620881d3 | ||
|
|
0e68f26c57 | ||
|
|
9736d23653 | ||
|
|
261a5afbdd | ||
|
|
3a7c1765ee | ||
|
|
c16a8b023c | ||
|
|
4b16e6a004 | ||
|
|
bd8f035c46 | ||
|
|
bdcce37b5a | ||
|
|
7af5c50a27 | ||
|
|
5860da285e | ||
|
|
3ece576476 | ||
|
|
4dac420bed | ||
|
|
ddf338bc72 | ||
|
|
0cca61517c | ||
|
|
fe5e8b8e1c | ||
|
|
dfdc6a33ad | ||
|
|
35e2d626a0 | ||
|
|
228e21ba78 | ||
|
|
68df7cd728 | ||
|
|
ffd6eb4e68 | ||
|
|
e205209b12 | ||
|
|
3ea84a3597 | ||
|
|
b71c4398d2 | ||
|
|
c26ad130ed | ||
|
|
279a9ab037 | ||
|
|
4e00c03ef2 | ||
|
|
c813ddc0ac | ||
|
|
23f8b54011 | ||
|
|
8122f6f4e1 | ||
|
|
e76242bddd | ||
|
|
af0ba78ae9 | ||
|
|
1904b95adf | ||
|
|
d43cece48a | ||
|
|
ba7196cac1 | ||
|
|
7eecb001c6 | ||
|
|
0b01da936d | ||
|
|
b88c0d9b96 | ||
|
|
b7554e30ff | ||
|
|
b03cda3ccc | ||
|
|
7bfd424a64 | ||
|
|
9ee29e91ba | ||
|
|
b24059b538 | ||
|
|
a7b18d8151 | ||
|
|
23862ad3ac | ||
|
|
566da623bf | ||
|
|
69241ed36c | ||
|
|
c469f943f1 | ||
|
|
0a714f8484 | ||
|
|
1d2089b0c4 | ||
|
|
f975ecf551 | ||
|
|
e6d27d7810 | ||
|
|
f209c7359e | ||
|
|
33d4ab3d2f | ||
|
|
07bca1d638 | ||
|
|
09c21eafa7 | ||
|
|
e6d6e477d9 | ||
|
|
915483c109 | ||
|
|
c600d9f818 | ||
|
|
5147f853ae | ||
|
|
0288292058 | ||
|
|
8752448eed | ||
|
|
1d71bef7aa | ||
|
|
114ded790e | ||
|
|
ae638b16be | ||
|
|
133db833ee | ||
|
|
1f0cf50166 | ||
|
|
eb6b413adb | ||
|
|
6e93a5be79 | ||
|
|
ac3d523c0e | ||
|
|
75f0a49fc1 | ||
|
|
cab7a75ec1 | ||
|
|
b8af6fb6b2 | ||
|
|
13e0793219 | ||
|
|
f80912c617 | ||
|
|
66c643677d | ||
|
|
1451d6f584 | ||
|
|
5f4a05044b | ||
|
|
02a4784e58 | ||
|
|
9256566f89 | ||
|
|
9b61443b2f | ||
|
|
5169092892 | ||
|
|
017276c94a | ||
|
|
e96e8ea54a | ||
|
|
2b68a9f054 | ||
|
|
83e297cc9b | ||
|
|
9f5ccabb0c | ||
|
|
415fd2eb58 | ||
|
|
340aa73bde | ||
|
|
43561f237e | ||
|
|
311eb98236 | ||
|
|
8b2613b9f6 | ||
|
|
071bd2b85e | ||
|
|
1ef696efe8 | ||
|
|
c059a2eabd | ||
|
|
84fc89071a | ||
|
|
ccc03da62d | ||
|
|
ff4cd35ed3 | ||
|
|
f386a8f7e0 | ||
|
|
2e6b7c8ab5 | ||
|
|
85edd2296e | ||
|
|
869cbd43e0 | ||
|
|
aca9bcf496 | ||
|
|
146b8a6e40 | ||
|
|
9dc80b4965 | ||
|
|
04c726e964 | ||
|
|
f265d0ff45 | ||
|
|
e448e8d249 | ||
|
|
eda88aa048 | ||
|
|
da40e8f09c | ||
|
|
d6a47932e7 | ||
|
|
7160e0ee2e | ||
|
|
2220e2560f | ||
|
|
31de71f743 | ||
|
|
7ff563f644 | ||
|
|
a47ebaa9df | ||
|
|
88ce6a2c9e | ||
|
|
cad3a99f70 | ||
|
|
0ebd2ab17e | ||
|
|
9ca9b7a144 | ||
|
|
363f12f86f | ||
|
|
71127a6db9 | ||
|
|
dd8bddc971 | ||
|
|
ddd4d31f25 | ||
|
|
5305984b1a | ||
|
|
c95fb1e1f6 | ||
|
|
c1f370faaf | ||
|
|
13d421c2bc | ||
|
|
a1b59de44d | ||
|
|
439ce7de63 | ||
|
|
f8183399e2 | ||
|
|
e44cb3b955 | ||
|
|
562db3def9 | ||
|
|
ecb28b9328 | ||
|
|
cdef84e269 | ||
|
|
fc7d271a29 | ||
|
|
aef30c8cbe | ||
|
|
c0b99203da | ||
|
|
63191a3b61 | ||
|
|
3877a4670d | ||
|
|
bbbd3e757b | ||
|
|
83efd97bdf | ||
|
|
447b0e280c | ||
|
|
cd277f3f9a | ||
|
|
270122b135 | ||
|
|
32d8c6b896 | ||
|
|
91c27b7684 | ||
|
|
548c7a05e9 | ||
|
|
eaf1b3c8b8 | ||
|
|
19b544540e | ||
|
|
feeec9d1f5 | ||
|
|
4442c7cbbe | ||
|
|
1e8bc39c7f | ||
|
|
ce3deecd1b | ||
|
|
8e159a1c2a | ||
|
|
df74dc358c | ||
|
|
186896da15 | ||
|
|
4be13e002d | ||
|
|
cf30b97a16 | ||
|
|
44759b1aac | ||
|
|
5ab2f02c56 | ||
|
|
f595cae1b8 | ||
|
|
48a2bddfd9 | ||
|
|
fbcafd2014 | ||
|
|
9f7c8c99c0 | ||
|
|
92193d499a | ||
|
|
c32b38b783 | ||
|
|
08a37ff264 | ||
|
|
f00861702a | ||
|
|
8dfbcea0da | ||
|
|
8aa45baee8 | ||
|
|
f2f8728319 | ||
|
|
ded446de93 | ||
|
|
1524ec2473 | ||
|
|
a7bb32ec4a | ||
|
|
d08c6c99af | ||
|
|
8793606070 | ||
|
|
81c299dd99 |
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
environment_building_tools/logfile.log
|
||||||
312
azure-pipelines-staging-ccb.yml
Normal file
312
azure-pipelines-staging-ccb.yml
Normal file
@ -0,0 +1,312 @@
|
|||||||
|
# Pipeline側でKeyVaultやDocker、AppService等に対する操作権限を持ったServiceConenctionを作成しておくこと
|
||||||
|
# また、環境変数 STATIC_DICTATION_DEPLOYMENT_TOKEN の値として静的WebAppsのデプロイトークンを設定しておくこと
|
||||||
|
trigger:
|
||||||
|
branches:
|
||||||
|
include:
|
||||||
|
- release-ccb
|
||||||
|
tags:
|
||||||
|
include:
|
||||||
|
- stage-*
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
- job: initialize
|
||||||
|
displayName: Initialize
|
||||||
|
pool:
|
||||||
|
vmImage: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- checkout: self
|
||||||
|
clean: true
|
||||||
|
fetchDepth: 1
|
||||||
|
persistCredentials: true
|
||||||
|
- script: |
|
||||||
|
git fetch origin release-ccb:release-ccb
|
||||||
|
if git merge-base --is-ancestor $(Build.SourceVersion) release-ccb; then
|
||||||
|
echo "This commit is in the release-ccb branch."
|
||||||
|
else
|
||||||
|
echo "This commit is not in the release-ccb branch."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
displayName: 'タグが付けられたCommitがrelease-ccbブランチに存在するか確認'
|
||||||
|
- job: backend_test
|
||||||
|
dependsOn: initialize
|
||||||
|
condition: succeeded('initialize')
|
||||||
|
displayName: UnitTest
|
||||||
|
pool:
|
||||||
|
vmImage: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- checkout: self
|
||||||
|
clean: true
|
||||||
|
fetchDepth: 1
|
||||||
|
- task: Bash@3
|
||||||
|
displayName: Bash Script (Test)
|
||||||
|
inputs:
|
||||||
|
targetType: inline
|
||||||
|
workingDirectory: dictation_server/.devcontainer
|
||||||
|
script: |
|
||||||
|
docker-compose -f pipeline-docker-compose.yml build
|
||||||
|
docker-compose -f pipeline-docker-compose.yml up -d
|
||||||
|
docker-compose exec -T dictation_server sudo npm ci
|
||||||
|
docker-compose exec -T dictation_server sudo npm run migrate:up:test
|
||||||
|
docker-compose exec -T dictation_server sudo npm run test
|
||||||
|
- job: backend_build
|
||||||
|
dependsOn: backend_test
|
||||||
|
condition: succeeded('backend_test')
|
||||||
|
displayName: Build And Push Backend Image
|
||||||
|
pool:
|
||||||
|
name: odms-deploy-pipeline
|
||||||
|
steps:
|
||||||
|
- checkout: self
|
||||||
|
clean: true
|
||||||
|
fetchDepth: 1
|
||||||
|
- task: Npm@1
|
||||||
|
displayName: npm ci
|
||||||
|
inputs:
|
||||||
|
command: ci
|
||||||
|
workingDir: dictation_server
|
||||||
|
verbose: false
|
||||||
|
- task: Docker@0
|
||||||
|
displayName: build
|
||||||
|
inputs:
|
||||||
|
azureSubscriptionEndpoint: 'omds-service-connection-stg'
|
||||||
|
azureContainerRegistry: '{"loginServer":"crodmsregistrymaintenance.azurecr.io", "id" : "/subscriptions/108fb131-cdca-4729-a2be-e5bd8c0b3ba7/resourceGroups/maintenance-rg/providers/Microsoft.ContainerRegistry/registries/crOdmsRegistryMaintenance"}'
|
||||||
|
dockerFile: DockerfileServerDictation.dockerfile
|
||||||
|
imageName: odmscloud/staging/dictation:$(Build.SourceVersion)
|
||||||
|
buildArguments: |
|
||||||
|
BUILD_VERSION=$(Build.SourceVersion)
|
||||||
|
- task: Docker@0
|
||||||
|
displayName: push
|
||||||
|
inputs:
|
||||||
|
azureSubscriptionEndpoint: 'omds-service-connection-stg'
|
||||||
|
azureContainerRegistry: '{"loginServer":"crodmsregistrymaintenance.azurecr.io", "id" : "/subscriptions/108fb131-cdca-4729-a2be-e5bd8c0b3ba7/resourceGroups/maintenance-rg/providers/Microsoft.ContainerRegistry/registries/crOdmsRegistryMaintenance"}'
|
||||||
|
action: Push an image
|
||||||
|
imageName: odmscloud/staging/dictation:$(Build.SourceVersion)
|
||||||
|
- job: frontend_build_staging
|
||||||
|
dependsOn: backend_build
|
||||||
|
condition: succeeded('backend_build')
|
||||||
|
displayName: Build Frontend Files(staging)
|
||||||
|
variables:
|
||||||
|
storageAccountName: saomdspipeline
|
||||||
|
environment: staging
|
||||||
|
pool:
|
||||||
|
name: odms-deploy-pipeline
|
||||||
|
steps:
|
||||||
|
- checkout: self
|
||||||
|
clean: true
|
||||||
|
fetchDepth: 1
|
||||||
|
- task: Npm@1
|
||||||
|
displayName: npm ci
|
||||||
|
inputs:
|
||||||
|
command: ci
|
||||||
|
workingDir: dictation_client
|
||||||
|
verbose: false
|
||||||
|
- task: Bash@3
|
||||||
|
displayName: Bash Script
|
||||||
|
inputs:
|
||||||
|
targetType: inline
|
||||||
|
script: cd dictation_client && npm run build:stg
|
||||||
|
- task: ArchiveFiles@2
|
||||||
|
inputs:
|
||||||
|
rootFolderOrFile: dictation_client/build
|
||||||
|
includeRootFolder: false
|
||||||
|
archiveType: 'zip'
|
||||||
|
archiveFile: '$(Build.ArtifactStagingDirectory)/$(Build.SourceVersion).zip'
|
||||||
|
replaceExistingArchive: true
|
||||||
|
- task: AzureCLI@2
|
||||||
|
inputs:
|
||||||
|
azureSubscription: 'omds-service-connection-stg'
|
||||||
|
scriptType: 'bash'
|
||||||
|
scriptLocation: 'inlineScript'
|
||||||
|
inlineScript: |
|
||||||
|
az storage blob upload \
|
||||||
|
--auth-mode login \
|
||||||
|
--account-name $(storageAccountName) \
|
||||||
|
--container-name $(environment) \
|
||||||
|
--name $(Build.SourceVersion).zip \
|
||||||
|
--type block \
|
||||||
|
--overwrite \
|
||||||
|
--file $(Build.ArtifactStagingDirectory)/$(Build.SourceVersion).zip
|
||||||
|
- job: function_test
|
||||||
|
dependsOn: frontend_build_staging
|
||||||
|
condition: succeeded('frontend_build_staging')
|
||||||
|
displayName: UnitTest
|
||||||
|
pool:
|
||||||
|
vmImage: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- checkout: self
|
||||||
|
clean: true
|
||||||
|
fetchDepth: 1
|
||||||
|
- task: Bash@3
|
||||||
|
displayName: Bash Script (Test)
|
||||||
|
inputs:
|
||||||
|
targetType: inline
|
||||||
|
workingDirectory: dictation_function/.devcontainer
|
||||||
|
script: |
|
||||||
|
docker-compose -f pipeline-docker-compose.yml build
|
||||||
|
docker-compose -f pipeline-docker-compose.yml up -d
|
||||||
|
docker-compose exec -T dictation_function sudo npm ci
|
||||||
|
docker-compose exec -T dictation_function sudo npm run test
|
||||||
|
- job: function_build
|
||||||
|
dependsOn: function_test
|
||||||
|
condition: succeeded('function_test')
|
||||||
|
displayName: Build And Push Function Image
|
||||||
|
pool:
|
||||||
|
name: odms-deploy-pipeline
|
||||||
|
steps:
|
||||||
|
- checkout: self
|
||||||
|
clean: true
|
||||||
|
fetchDepth: 1
|
||||||
|
- task: Npm@1
|
||||||
|
displayName: npm ci
|
||||||
|
inputs:
|
||||||
|
command: ci
|
||||||
|
workingDir: dictation_function
|
||||||
|
verbose: false
|
||||||
|
- task: Docker@0
|
||||||
|
displayName: build
|
||||||
|
inputs:
|
||||||
|
azureSubscriptionEndpoint: 'omds-service-connection-stg'
|
||||||
|
azureContainerRegistry: '{"loginServer":"crodmsregistrymaintenance.azurecr.io", "id" : "/subscriptions/108fb131-cdca-4729-a2be-e5bd8c0b3ba7/resourceGroups/maintenance-rg/providers/Microsoft.ContainerRegistry/registries/crOdmsRegistryMaintenance"}'
|
||||||
|
dockerFile: DockerfileFunctionDictation.dockerfile
|
||||||
|
imageName: odmscloud/staging/dictation_function:$(Build.SourceVersion)
|
||||||
|
buildArguments: |
|
||||||
|
BUILD_VERSION=$(Build.SourceVersion)
|
||||||
|
- task: Docker@0
|
||||||
|
displayName: push
|
||||||
|
inputs:
|
||||||
|
azureSubscriptionEndpoint: 'omds-service-connection-stg'
|
||||||
|
azureContainerRegistry: '{"loginServer":"crodmsregistrymaintenance.azurecr.io", "id" : "/subscriptions/108fb131-cdca-4729-a2be-e5bd8c0b3ba7/resourceGroups/maintenance-rg/providers/Microsoft.ContainerRegistry/registries/crOdmsRegistryMaintenance"}'
|
||||||
|
action: Push an image
|
||||||
|
imageName: odmscloud/staging/dictation_function:$(Build.SourceVersion)
|
||||||
|
- job: backend_deploy
|
||||||
|
dependsOn: function_build
|
||||||
|
condition: succeeded('function_build')
|
||||||
|
displayName: Backend Deploy
|
||||||
|
pool:
|
||||||
|
vmImage: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- checkout: self
|
||||||
|
clean: true
|
||||||
|
fetchDepth: 1
|
||||||
|
- task: AzureWebAppContainer@1
|
||||||
|
inputs:
|
||||||
|
azureSubscription: 'omds-service-connection-stg'
|
||||||
|
appName: 'app-odms-dictation-stg'
|
||||||
|
deployToSlotOrASE: true
|
||||||
|
resourceGroupName: 'stg-application-rg'
|
||||||
|
slotName: 'staging'
|
||||||
|
containers: 'crodmsregistrymaintenance.azurecr.io/odmscloud/staging/dictation:$(Build.SourceVersion)'
|
||||||
|
- job: frontend_deploy
|
||||||
|
dependsOn: backend_deploy
|
||||||
|
condition: succeeded('backend_deploy')
|
||||||
|
displayName: Deploy Frontend Files
|
||||||
|
variables:
|
||||||
|
storageAccountName: saomdspipeline
|
||||||
|
environment: staging
|
||||||
|
pool:
|
||||||
|
vmImage: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- checkout: self
|
||||||
|
clean: true
|
||||||
|
fetchDepth: 1
|
||||||
|
- task: AzureCLI@2
|
||||||
|
inputs:
|
||||||
|
azureSubscription: 'omds-service-connection-stg'
|
||||||
|
scriptType: 'bash'
|
||||||
|
scriptLocation: 'inlineScript'
|
||||||
|
inlineScript: |
|
||||||
|
az storage blob download \
|
||||||
|
--auth-mode login \
|
||||||
|
--account-name $(storageAccountName) \
|
||||||
|
--container-name $(environment) \
|
||||||
|
--name $(Build.SourceVersion).zip \
|
||||||
|
--file $(Build.SourcesDirectory)/$(Build.SourceVersion).zip
|
||||||
|
- task: Bash@3
|
||||||
|
displayName: Bash Script
|
||||||
|
inputs:
|
||||||
|
targetType: inline
|
||||||
|
script: unzip $(Build.SourcesDirectory)/$(Build.SourceVersion).zip -d $(Build.SourcesDirectory)/$(Build.SourceVersion)
|
||||||
|
- task: AzureStaticWebApp@0
|
||||||
|
displayName: 'Static Web App: '
|
||||||
|
inputs:
|
||||||
|
workingDirectory: '$(Build.SourcesDirectory)'
|
||||||
|
app_location: '/$(Build.SourceVersion)'
|
||||||
|
config_file_location: /dictation_client
|
||||||
|
skip_app_build: true
|
||||||
|
skip_api_build: true
|
||||||
|
is_static_export: false
|
||||||
|
verbose: false
|
||||||
|
azure_static_web_apps_api_token: $(STATIC_DICTATION_DEPLOYMENT_TOKEN)
|
||||||
|
- job: function_deploy
|
||||||
|
dependsOn: frontend_deploy
|
||||||
|
condition: succeeded('frontend_deploy')
|
||||||
|
displayName: Function Deploy
|
||||||
|
pool:
|
||||||
|
vmImage: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- checkout: self
|
||||||
|
clean: true
|
||||||
|
fetchDepth: 1
|
||||||
|
- task: AzureFunctionAppContainer@1
|
||||||
|
inputs:
|
||||||
|
azureSubscription: 'omds-service-connection-stg'
|
||||||
|
appName: 'func-odms-dictation-stg'
|
||||||
|
imageName: 'crodmsregistrymaintenance.azurecr.io/odmscloud/staging/dictation_function:$(Build.SourceVersion)'
|
||||||
|
- job: smoke_test
|
||||||
|
dependsOn: function_deploy
|
||||||
|
condition: succeeded('function_deploy')
|
||||||
|
displayName: 'smoke test'
|
||||||
|
pool:
|
||||||
|
name: odms-deploy-pipeline
|
||||||
|
steps:
|
||||||
|
- checkout: self
|
||||||
|
clean: true
|
||||||
|
fetchDepth: 1
|
||||||
|
# スモークテスト用にjobを確保
|
||||||
|
- job: swap_slot
|
||||||
|
dependsOn: smoke_test
|
||||||
|
condition: succeeded('smoke_test')
|
||||||
|
displayName: 'Swap Staging and Production'
|
||||||
|
pool:
|
||||||
|
name: odms-deploy-pipeline
|
||||||
|
steps:
|
||||||
|
- checkout: self
|
||||||
|
clean: true
|
||||||
|
fetchDepth: 1
|
||||||
|
- task: AzureAppServiceManage@0
|
||||||
|
displayName: 'Azure App Service Manage: app-odms-dictation-stg'
|
||||||
|
inputs:
|
||||||
|
azureSubscription: 'omds-service-connection-stg'
|
||||||
|
action: 'Swap Slots'
|
||||||
|
WebAppName: 'app-odms-dictation-stg'
|
||||||
|
ResourceGroupName: 'stg-application-rg'
|
||||||
|
SourceSlot: 'staging'
|
||||||
|
SwapWithProduction: true
|
||||||
|
- job: migration
|
||||||
|
dependsOn: swap_slot
|
||||||
|
condition: succeeded('swap_slot')
|
||||||
|
displayName: DB migration
|
||||||
|
pool:
|
||||||
|
name: odms-deploy-pipeline
|
||||||
|
steps:
|
||||||
|
- checkout: self
|
||||||
|
clean: true
|
||||||
|
fetchDepth: 1
|
||||||
|
- task: AzureKeyVault@2
|
||||||
|
displayName: 'Azure Key Vault: kv-odms-secret-stg'
|
||||||
|
inputs:
|
||||||
|
ConnectedServiceName: 'omds-service-connection-stg'
|
||||||
|
KeyVaultName: kv-odms-secret-stg
|
||||||
|
- task: CmdLine@2
|
||||||
|
displayName: migration
|
||||||
|
inputs:
|
||||||
|
script: >2
|
||||||
|
# DB接続情報書き換え
|
||||||
|
sed -i -e "s/DB_NAME_CCB/$(db-name-ccb)/g" ./dictation_server/db/dbconfig.yml
|
||||||
|
sed -i -e "s/DB_PASS/$(admin-db-pass)/g" ./dictation_server/db/dbconfig.yml
|
||||||
|
sed -i -e "s/DB_USERNAME/$(admin-db-user)/g" ./dictation_server/db/dbconfig.yml
|
||||||
|
sed -i -e "s/DB_PORT/$(db-port)/g" ./dictation_server/db/dbconfig.yml
|
||||||
|
sed -i -e "s/DB_HOST/$(db-host)/g" ./dictation_server/db/dbconfig.yml
|
||||||
|
sql-migrate --version
|
||||||
|
cat ./dictation_server/db/dbconfig.yml
|
||||||
|
# migration実行
|
||||||
|
sql-migrate up -config=./dictation_server/db/dbconfig.yml -env=ci_ccb
|
||||||
363
azure-pipelines-staging-ph1-enhance.yml
Normal file
363
azure-pipelines-staging-ph1-enhance.yml
Normal file
@ -0,0 +1,363 @@
|
|||||||
|
# Pipeline側でKeyVaultやDocker、AppService等に対する操作権限を持ったServiceConnectionを作成しておくこと
|
||||||
|
# また、環境変数 STATIC_DICTATION_DEPLOYMENT_TOKEN の値として静的WebAppsのデプロイトークンを設定しておくこと
|
||||||
|
trigger:
|
||||||
|
branches:
|
||||||
|
include:
|
||||||
|
- release-ph1-enhance
|
||||||
|
tags:
|
||||||
|
include:
|
||||||
|
- stage-*
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
- job: initialize
|
||||||
|
displayName: Initialize
|
||||||
|
pool:
|
||||||
|
vmImage: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- checkout: self
|
||||||
|
clean: true
|
||||||
|
fetchDepth: 1
|
||||||
|
persistCredentials: true
|
||||||
|
- script: |
|
||||||
|
git fetch origin release-ph1-enhance:release-ph1-enhance
|
||||||
|
if git merge-base --is-ancestor $(Build.SourceVersion) release-ph1-enhance; then
|
||||||
|
echo "This commit is in the release-ph1-enhance branch."
|
||||||
|
else
|
||||||
|
echo "This commit is not in the release-ph1-enhance branch."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
displayName: 'タグが付けられたCommitがrelease-ph1-enhanceブランチに存在するか確認'
|
||||||
|
- job: backend_test
|
||||||
|
dependsOn: initialize
|
||||||
|
condition: succeeded('initialize')
|
||||||
|
displayName: UnitTest
|
||||||
|
pool:
|
||||||
|
vmImage: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- checkout: self
|
||||||
|
clean: true
|
||||||
|
fetchDepth: 1
|
||||||
|
- task: Bash@3
|
||||||
|
displayName: Bash Script (Test)
|
||||||
|
inputs:
|
||||||
|
targetType: inline
|
||||||
|
workingDirectory: dictation_server/.devcontainer
|
||||||
|
script: |
|
||||||
|
sudo curl -L "https://github.com/docker/compose/releases/download/v2.20.3/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
|
||||||
|
sudo chmod +x /usr/local/bin/docker-compose
|
||||||
|
docker-compose --version
|
||||||
|
docker-compose -f pipeline-docker-compose.yml build
|
||||||
|
docker-compose -f pipeline-docker-compose.yml up -d
|
||||||
|
docker-compose exec -T dictation_server sudo npm ci
|
||||||
|
docker-compose exec -T dictation_server sudo npm run migrate:up:test
|
||||||
|
docker-compose exec -T dictation_server sudo npm run test
|
||||||
|
- job: backend_build
|
||||||
|
dependsOn: backend_test
|
||||||
|
condition: succeeded('backend_test')
|
||||||
|
displayName: Build And Push Backend Image
|
||||||
|
pool:
|
||||||
|
name: odms-deploy-pipeline
|
||||||
|
steps:
|
||||||
|
- checkout: self
|
||||||
|
clean: true
|
||||||
|
fetchDepth: 1
|
||||||
|
- task: Npm@1
|
||||||
|
displayName: npm ci
|
||||||
|
inputs:
|
||||||
|
command: ci
|
||||||
|
workingDir: dictation_server
|
||||||
|
verbose: false
|
||||||
|
- task: Docker@0
|
||||||
|
displayName: build
|
||||||
|
inputs:
|
||||||
|
azureSubscriptionEndpoint: 'omds-service-connection-stg'
|
||||||
|
azureContainerRegistry: '{"loginServer":"crodmsregistrymaintenance.azurecr.io", "id" : "/subscriptions/108fb131-cdca-4729-a2be-e5bd8c0b3ba7/resourceGroups/maintenance-rg/providers/Microsoft.ContainerRegistry/registries/crOdmsRegistryMaintenance"}'
|
||||||
|
dockerFile: DockerfileServerDictation.dockerfile
|
||||||
|
imageName: odmscloud/staging/dictation:$(Build.SourceVersion)
|
||||||
|
buildArguments: |
|
||||||
|
BUILD_VERSION=$(Build.SourceVersion)
|
||||||
|
- task: Docker@0
|
||||||
|
displayName: push
|
||||||
|
inputs:
|
||||||
|
azureSubscriptionEndpoint: 'omds-service-connection-stg'
|
||||||
|
azureContainerRegistry: '{"loginServer":"crodmsregistrymaintenance.azurecr.io", "id" : "/subscriptions/108fb131-cdca-4729-a2be-e5bd8c0b3ba7/resourceGroups/maintenance-rg/providers/Microsoft.ContainerRegistry/registries/crOdmsRegistryMaintenance"}'
|
||||||
|
action: Push an image
|
||||||
|
imageName: odmscloud/staging/dictation:$(Build.SourceVersion)
|
||||||
|
- job: frontend_build_staging
|
||||||
|
dependsOn: backend_build
|
||||||
|
condition: succeeded('backend_build')
|
||||||
|
displayName: Build Frontend Files(staging)
|
||||||
|
variables:
|
||||||
|
storageAccountName: saomdspipeline
|
||||||
|
environment: staging
|
||||||
|
pool:
|
||||||
|
name: odms-deploy-pipeline
|
||||||
|
steps:
|
||||||
|
- checkout: self
|
||||||
|
clean: true
|
||||||
|
fetchDepth: 1
|
||||||
|
- task: Npm@1
|
||||||
|
displayName: npm ci
|
||||||
|
inputs:
|
||||||
|
command: ci
|
||||||
|
workingDir: dictation_client
|
||||||
|
verbose: false
|
||||||
|
- task: Bash@3
|
||||||
|
displayName: Bash Script
|
||||||
|
inputs:
|
||||||
|
targetType: inline
|
||||||
|
script: cd dictation_client && npm run build:stg
|
||||||
|
- task: ArchiveFiles@2
|
||||||
|
inputs:
|
||||||
|
rootFolderOrFile: dictation_client/build
|
||||||
|
includeRootFolder: false
|
||||||
|
archiveType: 'zip'
|
||||||
|
archiveFile: '$(Build.ArtifactStagingDirectory)/$(Build.SourceVersion).zip'
|
||||||
|
replaceExistingArchive: true
|
||||||
|
- task: AzureCLI@2
|
||||||
|
inputs:
|
||||||
|
azureSubscription: 'omds-service-connection-stg'
|
||||||
|
scriptType: 'bash'
|
||||||
|
scriptLocation: 'inlineScript'
|
||||||
|
inlineScript: |
|
||||||
|
az storage blob upload \
|
||||||
|
--auth-mode login \
|
||||||
|
--account-name $(storageAccountName) \
|
||||||
|
--container-name $(environment) \
|
||||||
|
--name $(Build.SourceVersion).zip \
|
||||||
|
--type block \
|
||||||
|
--overwrite \
|
||||||
|
--file $(Build.ArtifactStagingDirectory)/$(Build.SourceVersion).zip
|
||||||
|
- job: frontend_build_production
|
||||||
|
dependsOn: frontend_build_staging
|
||||||
|
condition: succeeded('frontend_build_staging')
|
||||||
|
displayName: Build Frontend Files(production)
|
||||||
|
variables:
|
||||||
|
storageAccountName: saomdspipeline
|
||||||
|
environment: production
|
||||||
|
pool:
|
||||||
|
name: odms-deploy-pipeline
|
||||||
|
steps:
|
||||||
|
- checkout: self
|
||||||
|
clean: true
|
||||||
|
fetchDepth: 1
|
||||||
|
- task: Npm@1
|
||||||
|
displayName: npm ci
|
||||||
|
inputs:
|
||||||
|
command: ci
|
||||||
|
workingDir: dictation_client
|
||||||
|
verbose: false
|
||||||
|
- task: Bash@3
|
||||||
|
displayName: Bash Script
|
||||||
|
inputs:
|
||||||
|
targetType: inline
|
||||||
|
script: cd dictation_client && npm run build:prod
|
||||||
|
- task: ArchiveFiles@2
|
||||||
|
inputs:
|
||||||
|
rootFolderOrFile: dictation_client/build
|
||||||
|
includeRootFolder: false
|
||||||
|
archiveType: 'zip'
|
||||||
|
archiveFile: '$(Build.ArtifactStagingDirectory)/$(Build.SourceVersion).zip'
|
||||||
|
replaceExistingArchive: true
|
||||||
|
- task: AzureCLI@2
|
||||||
|
inputs:
|
||||||
|
azureSubscription: 'omds-service-connection-stg'
|
||||||
|
scriptType: 'bash'
|
||||||
|
scriptLocation: 'inlineScript'
|
||||||
|
inlineScript: |
|
||||||
|
az storage blob upload \
|
||||||
|
--auth-mode login \
|
||||||
|
--account-name $(storageAccountName) \
|
||||||
|
--container-name $(environment) \
|
||||||
|
--name $(Build.SourceVersion).zip \
|
||||||
|
--type block \
|
||||||
|
--overwrite \
|
||||||
|
--file $(Build.ArtifactStagingDirectory)/$(Build.SourceVersion).zip
|
||||||
|
- job: function_test
|
||||||
|
dependsOn: frontend_build_production
|
||||||
|
condition: succeeded('frontend_build_production')
|
||||||
|
displayName: UnitTest
|
||||||
|
pool:
|
||||||
|
vmImage: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- checkout: self
|
||||||
|
clean: true
|
||||||
|
fetchDepth: 1
|
||||||
|
- task: Bash@3
|
||||||
|
displayName: Bash Script (Test)
|
||||||
|
inputs:
|
||||||
|
targetType: inline
|
||||||
|
workingDirectory: dictation_function/.devcontainer
|
||||||
|
script: |
|
||||||
|
sudo curl -L "https://github.com/docker/compose/releases/download/v2.20.3/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
|
||||||
|
sudo chmod +x /usr/local/bin/docker-compose
|
||||||
|
docker-compose --version
|
||||||
|
docker-compose -f pipeline-docker-compose.yml build
|
||||||
|
docker-compose -f pipeline-docker-compose.yml up -d
|
||||||
|
docker-compose exec -T dictation_function sudo npm ci
|
||||||
|
docker-compose exec -T dictation_function sudo npm run test
|
||||||
|
- job: function_build
|
||||||
|
dependsOn: function_test
|
||||||
|
condition: succeeded('function_test')
|
||||||
|
displayName: Build And Push Function Image
|
||||||
|
pool:
|
||||||
|
name: odms-deploy-pipeline
|
||||||
|
steps:
|
||||||
|
- checkout: self
|
||||||
|
clean: true
|
||||||
|
fetchDepth: 1
|
||||||
|
- task: Npm@1
|
||||||
|
displayName: npm ci
|
||||||
|
inputs:
|
||||||
|
command: ci
|
||||||
|
workingDir: dictation_function
|
||||||
|
verbose: false
|
||||||
|
- task: Docker@0
|
||||||
|
displayName: build
|
||||||
|
inputs:
|
||||||
|
azureSubscriptionEndpoint: 'omds-service-connection-stg'
|
||||||
|
azureContainerRegistry: '{"loginServer":"crodmsregistrymaintenance.azurecr.io", "id" : "/subscriptions/108fb131-cdca-4729-a2be-e5bd8c0b3ba7/resourceGroups/maintenance-rg/providers/Microsoft.ContainerRegistry/registries/crOdmsRegistryMaintenance"}'
|
||||||
|
dockerFile: DockerfileFunctionDictation.dockerfile
|
||||||
|
imageName: odmscloud/staging/dictation_function:$(Build.SourceVersion)
|
||||||
|
buildArguments: |
|
||||||
|
BUILD_VERSION=$(Build.SourceVersion)
|
||||||
|
- task: Docker@0
|
||||||
|
displayName: push
|
||||||
|
inputs:
|
||||||
|
azureSubscriptionEndpoint: 'omds-service-connection-stg'
|
||||||
|
azureContainerRegistry: '{"loginServer":"crodmsregistrymaintenance.azurecr.io", "id" : "/subscriptions/108fb131-cdca-4729-a2be-e5bd8c0b3ba7/resourceGroups/maintenance-rg/providers/Microsoft.ContainerRegistry/registries/crOdmsRegistryMaintenance"}'
|
||||||
|
action: Push an image
|
||||||
|
imageName: odmscloud/staging/dictation_function:$(Build.SourceVersion)
|
||||||
|
- job: backend_deploy
|
||||||
|
dependsOn: function_build
|
||||||
|
condition: succeeded('function_build')
|
||||||
|
displayName: Backend Deploy
|
||||||
|
pool:
|
||||||
|
vmImage: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- checkout: self
|
||||||
|
clean: true
|
||||||
|
fetchDepth: 1
|
||||||
|
- task: AzureWebAppContainer@1
|
||||||
|
inputs:
|
||||||
|
azureSubscription: 'omds-service-connection-stg'
|
||||||
|
appName: 'app-odms-dictation-stg'
|
||||||
|
deployToSlotOrASE: true
|
||||||
|
resourceGroupName: 'stg-application-rg'
|
||||||
|
slotName: 'staging'
|
||||||
|
containers: 'crodmsregistrymaintenance.azurecr.io/odmscloud/staging/dictation:$(Build.SourceVersion)'
|
||||||
|
- job: frontend_deploy
|
||||||
|
dependsOn: backend_deploy
|
||||||
|
condition: succeeded('backend_deploy')
|
||||||
|
displayName: Deploy Frontend Files
|
||||||
|
variables:
|
||||||
|
storageAccountName: saomdspipeline
|
||||||
|
environment: staging
|
||||||
|
pool:
|
||||||
|
vmImage: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- checkout: self
|
||||||
|
clean: true
|
||||||
|
fetchDepth: 1
|
||||||
|
- task: AzureCLI@2
|
||||||
|
inputs:
|
||||||
|
azureSubscription: 'omds-service-connection-stg'
|
||||||
|
scriptType: 'bash'
|
||||||
|
scriptLocation: 'inlineScript'
|
||||||
|
inlineScript: |
|
||||||
|
az storage blob download \
|
||||||
|
--auth-mode login \
|
||||||
|
--account-name $(storageAccountName) \
|
||||||
|
--container-name $(environment) \
|
||||||
|
--name $(Build.SourceVersion).zip \
|
||||||
|
--file $(Build.SourcesDirectory)/$(Build.SourceVersion).zip
|
||||||
|
- task: Bash@3
|
||||||
|
displayName: Bash Script
|
||||||
|
inputs:
|
||||||
|
targetType: inline
|
||||||
|
script: unzip $(Build.SourcesDirectory)/$(Build.SourceVersion).zip -d $(Build.SourcesDirectory)/$(Build.SourceVersion)
|
||||||
|
- task: AzureStaticWebApp@0
|
||||||
|
displayName: 'Static Web App: '
|
||||||
|
inputs:
|
||||||
|
workingDirectory: '$(Build.SourcesDirectory)'
|
||||||
|
app_location: '/$(Build.SourceVersion)'
|
||||||
|
config_file_location: /dictation_client
|
||||||
|
skip_app_build: true
|
||||||
|
skip_api_build: true
|
||||||
|
is_static_export: false
|
||||||
|
verbose: false
|
||||||
|
azure_static_web_apps_api_token: $(STATIC_DICTATION_DEPLOYMENT_TOKEN)
|
||||||
|
- job: function_deploy
|
||||||
|
dependsOn: frontend_deploy
|
||||||
|
condition: succeeded('frontend_deploy')
|
||||||
|
displayName: Function Deploy
|
||||||
|
pool:
|
||||||
|
vmImage: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- checkout: self
|
||||||
|
clean: true
|
||||||
|
fetchDepth: 1
|
||||||
|
- task: AzureFunctionAppContainer@1
|
||||||
|
inputs:
|
||||||
|
azureSubscription: 'omds-service-connection-stg'
|
||||||
|
appName: 'func-odms-dictation-stg'
|
||||||
|
imageName: 'crodmsregistrymaintenance.azurecr.io/odmscloud/staging/dictation_function:$(Build.SourceVersion)'
|
||||||
|
- job: smoke_test
|
||||||
|
dependsOn: function_deploy
|
||||||
|
condition: succeeded('function_deploy')
|
||||||
|
displayName: 'smoke test'
|
||||||
|
pool:
|
||||||
|
name: odms-deploy-pipeline
|
||||||
|
steps:
|
||||||
|
- checkout: self
|
||||||
|
clean: true
|
||||||
|
fetchDepth: 1
|
||||||
|
# スモークテスト用にjobを確保
|
||||||
|
- job: swap_slot
|
||||||
|
dependsOn: smoke_test
|
||||||
|
condition: succeeded('smoke_test')
|
||||||
|
displayName: 'Swap Staging and Production'
|
||||||
|
pool:
|
||||||
|
name: odms-deploy-pipeline
|
||||||
|
steps:
|
||||||
|
- checkout: self
|
||||||
|
clean: true
|
||||||
|
fetchDepth: 1
|
||||||
|
- task: AzureAppServiceManage@0
|
||||||
|
displayName: 'Azure App Service Manage: app-odms-dictation-stg'
|
||||||
|
inputs:
|
||||||
|
azureSubscription: 'omds-service-connection-stg'
|
||||||
|
action: 'Swap Slots'
|
||||||
|
WebAppName: 'app-odms-dictation-stg'
|
||||||
|
ResourceGroupName: 'stg-application-rg'
|
||||||
|
SourceSlot: 'staging'
|
||||||
|
SwapWithProduction: true
|
||||||
|
- job: migration
|
||||||
|
dependsOn: swap_slot
|
||||||
|
condition: succeeded('swap_slot')
|
||||||
|
displayName: DB migration
|
||||||
|
pool:
|
||||||
|
name: odms-deploy-pipeline
|
||||||
|
steps:
|
||||||
|
- checkout: self
|
||||||
|
clean: true
|
||||||
|
fetchDepth: 1
|
||||||
|
- task: AzureKeyVault@2
|
||||||
|
displayName: 'Azure Key Vault: kv-odms-secret-stg'
|
||||||
|
inputs:
|
||||||
|
ConnectedServiceName: 'omds-service-connection-stg'
|
||||||
|
KeyVaultName: kv-odms-secret-stg
|
||||||
|
- task: CmdLine@2
|
||||||
|
displayName: migration
|
||||||
|
inputs:
|
||||||
|
script: >2
|
||||||
|
# DB接続情報書き換え
|
||||||
|
sed -i -e "s/DB_NAME/$(db-name-ph1-enhance)/g" ./dictation_server/db/dbconfig.yml
|
||||||
|
sed -i -e "s/DB_PASS/$(admin-db-pass)/g" ./dictation_server/db/dbconfig.yml
|
||||||
|
sed -i -e "s/DB_USERNAME/$(admin-db-user)/g" ./dictation_server/db/dbconfig.yml
|
||||||
|
sed -i -e "s/DB_PORT/$(db-port)/g" ./dictation_server/db/dbconfig.yml
|
||||||
|
sed -i -e "s/DB_HOST/$(db-host)/g" ./dictation_server/db/dbconfig.yml
|
||||||
|
sql-migrate --version
|
||||||
|
cat ./dictation_server/db/dbconfig.yml
|
||||||
|
# migration実行
|
||||||
|
sql-migrate up -config=./dictation_server/db/dbconfig.yml -env=ci
|
||||||
@ -43,11 +43,14 @@ jobs:
|
|||||||
targetType: inline
|
targetType: inline
|
||||||
workingDirectory: dictation_server/.devcontainer
|
workingDirectory: dictation_server/.devcontainer
|
||||||
script: |
|
script: |
|
||||||
docker-compose -f pipeline-docker-compose.yml build
|
sudo curl -L "https://github.com/docker/compose/releases/download/v2.20.3/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
|
||||||
docker-compose -f pipeline-docker-compose.yml up -d
|
sudo chmod +x /usr/local/bin/docker-compose
|
||||||
docker-compose exec -T dictation_server sudo npm ci
|
docker-compose --version
|
||||||
docker-compose exec -T dictation_server sudo npm run migrate:up:test
|
docker-compose -f pipeline-docker-compose.yml build
|
||||||
docker-compose exec -T dictation_server sudo npm run test
|
docker-compose -f pipeline-docker-compose.yml up -d
|
||||||
|
docker-compose exec -T dictation_server sudo npm ci
|
||||||
|
docker-compose exec -T dictation_server sudo npm run migrate:up:test
|
||||||
|
docker-compose exec -T dictation_server sudo npm run test
|
||||||
- job: backend_build
|
- job: backend_build
|
||||||
dependsOn: backend_test
|
dependsOn: backend_test
|
||||||
condition: succeeded('backend_test')
|
condition: succeeded('backend_test')
|
||||||
@ -170,9 +173,32 @@ jobs:
|
|||||||
--type block \
|
--type block \
|
||||||
--overwrite \
|
--overwrite \
|
||||||
--file $(Build.ArtifactStagingDirectory)/$(Build.SourceVersion).zip
|
--file $(Build.ArtifactStagingDirectory)/$(Build.SourceVersion).zip
|
||||||
- job: function_build
|
- job: function_test
|
||||||
dependsOn: frontend_build_production
|
dependsOn: frontend_build_production
|
||||||
condition: succeeded('frontend_build_production')
|
condition: succeeded('frontend_build_production')
|
||||||
|
displayName: UnitTest
|
||||||
|
pool:
|
||||||
|
vmImage: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- checkout: self
|
||||||
|
clean: true
|
||||||
|
fetchDepth: 1
|
||||||
|
- task: Bash@3
|
||||||
|
displayName: Bash Script (Test)
|
||||||
|
inputs:
|
||||||
|
targetType: inline
|
||||||
|
workingDirectory: dictation_function/.devcontainer
|
||||||
|
script: |
|
||||||
|
sudo curl -L "https://github.com/docker/compose/releases/download/v2.20.3/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
|
||||||
|
sudo chmod +x /usr/local/bin/docker-compose
|
||||||
|
docker-compose --version
|
||||||
|
docker-compose -f pipeline-docker-compose.yml build
|
||||||
|
docker-compose -f pipeline-docker-compose.yml up -d
|
||||||
|
docker-compose exec -T dictation_function sudo npm ci
|
||||||
|
docker-compose exec -T dictation_function sudo npm run test
|
||||||
|
- job: function_build
|
||||||
|
dependsOn: function_test
|
||||||
|
condition: succeeded('function_test')
|
||||||
displayName: Build And Push Function Image
|
displayName: Build And Push Function Image
|
||||||
pool:
|
pool:
|
||||||
name: odms-deploy-pipeline
|
name: odms-deploy-pipeline
|
||||||
@ -186,32 +212,6 @@ jobs:
|
|||||||
command: ci
|
command: ci
|
||||||
workingDir: dictation_function
|
workingDir: dictation_function
|
||||||
verbose: false
|
verbose: false
|
||||||
- task: AzureKeyVault@2
|
|
||||||
displayName: 'Azure Key Vault: kv-odms-secret-stg'
|
|
||||||
inputs:
|
|
||||||
ConnectedServiceName: 'omds-service-connection-stg'
|
|
||||||
KeyVaultName: kv-odms-secret-stg
|
|
||||||
SecretsFilter: '*'
|
|
||||||
- task: Bash@3
|
|
||||||
displayName: Bash Script (Test)
|
|
||||||
inputs:
|
|
||||||
targetType: inline
|
|
||||||
script: |
|
|
||||||
cd dictation_function
|
|
||||||
npm run test
|
|
||||||
env:
|
|
||||||
TENANT_NAME: xxxxxxxxxxxx
|
|
||||||
SIGNIN_FLOW_NAME: xxxxxxxxxxxx
|
|
||||||
ADB2C_TENANT_ID: $(adb2c-tenant-id)
|
|
||||||
ADB2C_CLIENT_ID: $(adb2c-client-id)
|
|
||||||
ADB2C_CLIENT_SECRET: $(adb2c-client-secret)
|
|
||||||
ADB2C_ORIGIN: xxxxxx
|
|
||||||
SENDGRID_API_KEY: $(sendgrid-api-key)
|
|
||||||
MAIL_FROM: xxxxxx
|
|
||||||
APP_DOMAIN: xxxxxxxxx
|
|
||||||
REDIS_HOST: xxxxxxxxxxxx
|
|
||||||
REDIS_PORT: 0
|
|
||||||
REDIS_PASSWORD: xxxxxxxxxxxx
|
|
||||||
- task: Docker@0
|
- task: Docker@0
|
||||||
displayName: build
|
displayName: build
|
||||||
inputs:
|
inputs:
|
||||||
|
|||||||
@ -18,7 +18,8 @@ export const makePassword = (): string => {
|
|||||||
let autoGeneratedPassword: string = "";
|
let autoGeneratedPassword: string = "";
|
||||||
|
|
||||||
while (!valid) {
|
while (!valid) {
|
||||||
// パスワードをランダムに決定
|
autoGeneratedPassword = "";
|
||||||
|
// パスワードをランダムに決定+
|
||||||
while (autoGeneratedPassword.length < passLength) {
|
while (autoGeneratedPassword.length < passLength) {
|
||||||
// 上で決定したcharsの中からランダムに1文字ずつ追加
|
// 上で決定したcharsの中からランダムに1文字ずつ追加
|
||||||
const index = Math.floor(Math.random() * chars.length);
|
const index = Math.floor(Math.random() * chars.length);
|
||||||
@ -30,6 +31,11 @@ export const makePassword = (): string => {
|
|||||||
valid =
|
valid =
|
||||||
autoGeneratedPassword.length == passLength &&
|
autoGeneratedPassword.length == passLength &&
|
||||||
charaTypePattern.test(autoGeneratedPassword);
|
charaTypePattern.test(autoGeneratedPassword);
|
||||||
|
if (!valid) {
|
||||||
|
// autoGeneratedPasswordをログに出す
|
||||||
|
console.log("Password is not valid");
|
||||||
|
console.log(autoGeneratedPassword);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return autoGeneratedPassword;
|
return autoGeneratedPassword;
|
||||||
};
|
};
|
||||||
|
|||||||
@ -79,6 +79,7 @@ export class AccountsService {
|
|||||||
HttpStatus.INTERNAL_SERVER_ERROR
|
HttpStatus.INTERNAL_SERVER_ERROR
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
this.logger.log("idpにユーザーを作成成功");
|
||||||
|
|
||||||
// メールアドレス重複エラー
|
// メールアドレス重複エラー
|
||||||
if (isConflictError(externalUser)) {
|
if (isConflictError(externalUser)) {
|
||||||
@ -90,6 +91,7 @@ export class AccountsService {
|
|||||||
HttpStatus.BAD_REQUEST
|
HttpStatus.BAD_REQUEST
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
this.logger.log("メールアドレスは重複していません");
|
||||||
|
|
||||||
let account: Account;
|
let account: Account;
|
||||||
let user: User;
|
let user: User;
|
||||||
@ -138,6 +140,7 @@ export class AccountsService {
|
|||||||
account.id,
|
account.id,
|
||||||
country
|
country
|
||||||
);
|
);
|
||||||
|
this.logger.log("コンテナー作成成功");
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
this.logger.error(`[${context.getTrackingId()}] error=${e}`);
|
this.logger.error(`[${context.getTrackingId()}] error=${e}`);
|
||||||
this.logger.error(
|
this.logger.error(
|
||||||
|
|||||||
@ -106,8 +106,10 @@ export class RegisterController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (const AccountsFile of accountsObject) {
|
for (const AccountsFile of accountsObject) {
|
||||||
|
this.logger.log("ランダムパスワード生成開始");
|
||||||
// ランダムなパスワードを生成する
|
// ランダムなパスワードを生成する
|
||||||
const ramdomPassword = makePassword();
|
const ramdomPassword = makePassword();
|
||||||
|
this.logger.log("ランダムパスワード生成完了");
|
||||||
// roleの設定
|
// roleの設定
|
||||||
// roleの値がnullなら"none"、null以外ならroleの値、
|
// roleの値がnullなら"none"、null以外ならroleの値、
|
||||||
// また、roleの値が"author"なら"author"を設定
|
// また、roleの値が"author"なら"author"を設定
|
||||||
@ -123,6 +125,7 @@ export class RegisterController {
|
|||||||
// ありえないが、roleの値が"none"または"author"の文字列以外の場合はエラーを返す
|
// ありえないが、roleの値が"none"または"author"の文字列以外の場合はエラーを返す
|
||||||
throw new Error("Invalid role value");
|
throw new Error("Invalid role value");
|
||||||
}
|
}
|
||||||
|
this.logger.log("account生成開始");
|
||||||
await this.accountsService.createAccount(
|
await this.accountsService.createAccount(
|
||||||
context,
|
context,
|
||||||
AccountsFile.companyName,
|
AccountsFile.companyName,
|
||||||
|
|||||||
@ -73,7 +73,8 @@ export class TransferController {
|
|||||||
const matchList = line.match(regExp);
|
const matchList = line.match(regExp);
|
||||||
if (matchList) {
|
if (matchList) {
|
||||||
matchList.forEach((match) => {
|
matchList.forEach((match) => {
|
||||||
const replaced = match.replace(/,/g, " ");
|
// カンマを\に変換
|
||||||
|
const replaced = match.replace(/,/g, "\\");
|
||||||
line = line.replace(match, replaced);
|
line = line.replace(match, replaced);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -95,49 +96,50 @@ export class TransferController {
|
|||||||
HttpStatus.BAD_REQUEST
|
HttpStatus.BAD_REQUEST
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
// data[1]がundefinedの場合、配列には格納しない
|
||||||
csvInputFile.push({
|
if (data[1] !== undefined) {
|
||||||
type: data[0],
|
// バックスラッシュをカンマに戻す
|
||||||
account_id: data[1],
|
data.forEach((value, index) => {
|
||||||
parent_id: data[2],
|
data[index] = value.replace(/\\/g, ",");
|
||||||
email: data[3],
|
});
|
||||||
company_name: data[4],
|
csvInputFile.push({
|
||||||
first_name: data[5],
|
type: data[0],
|
||||||
last_name: data[6],
|
account_id: data[1],
|
||||||
country: data[7],
|
parent_id: data[2],
|
||||||
state: data[8],
|
email: data[3],
|
||||||
start_date: data[9],
|
company_name: data[4],
|
||||||
expired_date: data[10],
|
first_name: data[5],
|
||||||
user_email: data[11],
|
last_name: data[6],
|
||||||
author_id: data[12],
|
country: data[7],
|
||||||
recording_mode: data[13],
|
state: data[8],
|
||||||
wt1: data[14],
|
start_date: data[9],
|
||||||
wt2: data[15],
|
expired_date: data[10],
|
||||||
wt3: data[16],
|
user_email: data[11],
|
||||||
wt4: data[17],
|
author_id: data[12],
|
||||||
wt5: data[18],
|
recording_mode: data[13],
|
||||||
wt6: data[19],
|
wt1: data[14],
|
||||||
wt7: data[20],
|
wt2: data[15],
|
||||||
wt8: data[21],
|
wt3: data[16],
|
||||||
wt9: data[22],
|
wt4: data[17],
|
||||||
wt10: data[23],
|
wt5: data[18],
|
||||||
wt11: data[24],
|
wt6: data[19],
|
||||||
wt12: data[25],
|
wt7: data[20],
|
||||||
wt13: data[26],
|
wt8: data[21],
|
||||||
wt14: data[27],
|
wt9: data[22],
|
||||||
wt15: data[28],
|
wt10: data[23],
|
||||||
wt16: data[29],
|
wt11: data[24],
|
||||||
wt17: data[30],
|
wt12: data[25],
|
||||||
wt18: data[31],
|
wt13: data[26],
|
||||||
wt19: data[32],
|
wt14: data[27],
|
||||||
wt20: data[33],
|
wt15: data[28],
|
||||||
});
|
wt16: data[29],
|
||||||
|
wt17: data[30],
|
||||||
|
wt18: data[31],
|
||||||
|
wt19: data[32],
|
||||||
|
wt20: data[33],
|
||||||
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
// 最後の行がundefinedの場合はその行を削除
|
|
||||||
if (csvInputFile[csvInputFile.length - 1].account_id === undefined) {
|
|
||||||
csvInputFile.pop();
|
|
||||||
}
|
|
||||||
|
|
||||||
// 各データのバリデーションチェック
|
// 各データのバリデーションチェック
|
||||||
await this.transferService.validateInputData(context, csvInputFile);
|
await this.transferService.validateInputData(context, csvInputFile);
|
||||||
|
|
||||||
@ -153,12 +155,12 @@ export class TransferController {
|
|||||||
// アカウントID numberとstring対応表の出力
|
// アカウントID numberとstring対応表の出力
|
||||||
const accountsMappingFiles: AccountsMappingFile[] = [];
|
const accountsMappingFiles: AccountsMappingFile[] = [];
|
||||||
accountIdMap.forEach((value, key) => {
|
accountIdMap.forEach((value, key) => {
|
||||||
const accountsMappingFile = new AccountsMappingFile();
|
const accountsMappingFile = new AccountsMappingFile();
|
||||||
accountsMappingFile.accountIdNumber = value;
|
accountsMappingFile.accountIdNumber = value;
|
||||||
accountsMappingFile.accountIdText = key
|
accountsMappingFile.accountIdText = key;
|
||||||
accountsMappingFiles.push(accountsMappingFile)
|
accountsMappingFiles.push(accountsMappingFile);
|
||||||
});
|
});
|
||||||
|
|
||||||
fs.writeFileSync(
|
fs.writeFileSync(
|
||||||
`${inputFilePath}account_map.json`,
|
`${inputFilePath}account_map.json`,
|
||||||
JSON.stringify(accountsMappingFiles)
|
JSON.stringify(accountsMappingFiles)
|
||||||
@ -188,6 +190,14 @@ export class TransferController {
|
|||||||
LicensesFile
|
LicensesFile
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// AuthorIDが重複している場合通番を付与する
|
||||||
|
const transferDuplicateAuthorResultUsers =
|
||||||
|
await this.transferService.transferDuplicateAuthor(
|
||||||
|
context,
|
||||||
|
resultDuplicateEmail.accountsFileLines,
|
||||||
|
resultDuplicateEmail.usersFileLines
|
||||||
|
);
|
||||||
|
|
||||||
// transferResponseCsvを4つのJSONファイルの出力する(出力先はinputと同じにする)
|
// transferResponseCsvを4つのJSONファイルの出力する(出力先はinputと同じにする)
|
||||||
const outputFilePath = body.inputFilePath;
|
const outputFilePath = body.inputFilePath;
|
||||||
const WorktypesFile = transferResponseCsv.worktypesFileLines;
|
const WorktypesFile = transferResponseCsv.worktypesFileLines;
|
||||||
@ -195,7 +205,7 @@ export class TransferController {
|
|||||||
context,
|
context,
|
||||||
outputFilePath,
|
outputFilePath,
|
||||||
resultDuplicateEmail.accountsFileLines,
|
resultDuplicateEmail.accountsFileLines,
|
||||||
resultDuplicateEmail.usersFileLines,
|
transferDuplicateAuthorResultUsers,
|
||||||
resultDuplicateEmail.licensesFileLines,
|
resultDuplicateEmail.licensesFileLines,
|
||||||
WorktypesFile
|
WorktypesFile
|
||||||
);
|
);
|
||||||
|
|||||||
@ -54,6 +54,12 @@ export class TransferService {
|
|||||||
let userIdIndex = 0;
|
let userIdIndex = 0;
|
||||||
// authorIdとuserIdの対応関係を保持するMapを定義
|
// authorIdとuserIdの対応関係を保持するMapを定義
|
||||||
const authorIdToUserIdMap: Map<string, number> = new Map();
|
const authorIdToUserIdMap: Map<string, number> = new Map();
|
||||||
|
|
||||||
|
// countryのリストを生成
|
||||||
|
const countryAccounts = csvInputFile.filter(
|
||||||
|
(item) => item.type === "Country"
|
||||||
|
);
|
||||||
|
|
||||||
// csvInputFileを一行読み込みする
|
// csvInputFileを一行読み込みする
|
||||||
csvInputFile.forEach((line) => {
|
csvInputFile.forEach((line) => {
|
||||||
// typeが"USER"以外の場合、アカウントデータの作成を行う
|
// typeが"USER"以外の場合、アカウントデータの作成を行う
|
||||||
@ -63,8 +69,13 @@ export class TransferService {
|
|||||||
(country) => country.label === line.country
|
(country) => country.label === line.country
|
||||||
)?.value;
|
)?.value;
|
||||||
// adminNameの変換(last_name + " "+ first_name)
|
// adminNameの変換(last_name + " "+ first_name)
|
||||||
const adminName = `${line.last_name} ${line.first_name}`;
|
// もしline.last_nameとline.first_nameが存在しない場合、line.admin_mailをnameにする
|
||||||
|
let adminName = line.email;
|
||||||
|
if (line.last_name && line.first_name) {
|
||||||
|
adminName = `${line.last_name} ${line.first_name}`;
|
||||||
|
// スペースが前後に入っている場合があるのでTrimする
|
||||||
|
adminName = adminName.trim();
|
||||||
|
}
|
||||||
// ランダムパスワードの生成(データ登録ツール側で行うのでやらない)
|
// ランダムパスワードの生成(データ登録ツール側で行うのでやらない)
|
||||||
// common/password/password.tsのmakePasswordを使用
|
// common/password/password.tsのmakePasswordを使用
|
||||||
// const autoGeneratedPassword = makePassword();
|
// const autoGeneratedPassword = makePassword();
|
||||||
@ -100,8 +111,13 @@ export class TransferService {
|
|||||||
authorId: null,
|
authorId: null,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
// typeが"USER"の場合
|
// typeが"USER"の場合、かつcountryのアカウントIDに所属していない場合
|
||||||
if (line.type == MIGRATION_TYPE.USER) {
|
if (
|
||||||
|
line.type == MIGRATION_TYPE.USER &&
|
||||||
|
!countryAccounts.some(
|
||||||
|
(countryAccount) => countryAccount.account_id === line.account_id
|
||||||
|
)
|
||||||
|
) {
|
||||||
// line.author_idが存在する場合のみユーザーデータを作成する
|
// line.author_idが存在する場合のみユーザーデータを作成する
|
||||||
if (line.author_id) {
|
if (line.author_id) {
|
||||||
// userIdIndexをインクリメントする
|
// userIdIndexをインクリメントする
|
||||||
@ -128,8 +144,8 @@ export class TransferService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ライセンスのデータの作成を行う
|
// ライセンスのデータの作成を行う
|
||||||
// line.expired_dateが"9999/12/31 23:59:59"のデータの場合はデモライセンスなので登録しない
|
// line.expired_dateが"9999/12/31"で始まるデータの場合はデモライセンスなので登録しない
|
||||||
if (line.expired_date !== "9999/12/31 23:59:59") {
|
if (!line.expired_date.startsWith("9999/12/31")) {
|
||||||
// authorIdが設定されてる場合、statusは"allocated"、allocated_user_idは対象のユーザID
|
// authorIdが設定されてる場合、statusは"allocated"、allocated_user_idは対象のユーザID
|
||||||
// されていない場合、statusは"reusable"、allocated_user_idはnull
|
// されていない場合、statusは"reusable"、allocated_user_idはnull
|
||||||
let status: string;
|
let status: string;
|
||||||
@ -224,46 +240,38 @@ export class TransferService {
|
|||||||
const relocatedAccounts: AccountsFile[] = [];
|
const relocatedAccounts: AccountsFile[] = [];
|
||||||
const dealerRecords: Map<number, number> = new Map();
|
const dealerRecords: Map<number, number> = new Map();
|
||||||
|
|
||||||
// accountsFileTypeをループ
|
const countryAccounts = accountsFileType.filter(
|
||||||
accountsFileType.forEach((account) => {
|
(item) => item.type === MIGRATION_TYPE.COUNTRY
|
||||||
// Distributorの場合はdealerを検索し、COUNTRYかチェックする
|
);
|
||||||
if (account.type === MIGRATION_TYPE.DISTRIBUTOR) {
|
|
||||||
const distributorParent = accountsFileType.find(
|
const notCountryAccounts = accountsFileType.filter(
|
||||||
(a) => a.accountId === account.dealerAccountId
|
(item) => item.type !== MIGRATION_TYPE.COUNTRY
|
||||||
);
|
);
|
||||||
if (distributorParent.type === MIGRATION_TYPE.COUNTRY) {
|
|
||||||
dealerRecords.set(
|
notCountryAccounts.forEach((notCountryAccount) => {
|
||||||
account.accountId,
|
let assignDealerAccountId = notCountryAccount.dealerAccountId;
|
||||||
distributorParent.dealerAccountId // Countryの親、BCのIDを設定
|
// 親アカウントIDがcountryの場合、countryの親アカウントIDを設定する
|
||||||
);
|
for (const countryAccount of countryAccounts) {
|
||||||
|
if (countryAccount.accountId === notCountryAccount.dealerAccountId) {
|
||||||
|
assignDealerAccountId = countryAccount.dealerAccountId;
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
dealerRecords.set(account.accountId, account.dealerAccountId);
|
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
|
||||||
// AccountsFileTypeのループを行い、階層情報の置換と新たな配列へのpushを行う
|
const assignType = this.getAccountType(notCountryAccount.type);
|
||||||
accountsFileType.forEach((account) => {
|
|
||||||
// Countryのレコードは除外する
|
|
||||||
if (account.type !== MIGRATION_TYPE.COUNTRY) {
|
|
||||||
const dealerAccountId =
|
|
||||||
dealerRecords.get(account.accountId) ?? account.dealerAccountId;
|
|
||||||
const type = this.getAccountType(account.type);
|
|
||||||
const newAccount: AccountsFile = {
|
|
||||||
accountId: account.accountId,
|
|
||||||
type: type,
|
|
||||||
companyName: account.companyName,
|
|
||||||
country: account.country,
|
|
||||||
dealerAccountId: dealerAccountId,
|
|
||||||
adminName: account.adminName,
|
|
||||||
adminMail: account.adminMail,
|
|
||||||
userId: account.userId,
|
|
||||||
role: account.role,
|
|
||||||
authorId: account.authorId,
|
|
||||||
};
|
|
||||||
|
|
||||||
relocatedAccounts.push(newAccount);
|
const newAccount: AccountsFile = {
|
||||||
}
|
accountId: notCountryAccount.accountId,
|
||||||
|
type: assignType,
|
||||||
|
companyName: notCountryAccount.companyName,
|
||||||
|
country: notCountryAccount.country,
|
||||||
|
dealerAccountId: assignDealerAccountId,
|
||||||
|
adminName: notCountryAccount.adminName,
|
||||||
|
adminMail: notCountryAccount.adminMail,
|
||||||
|
userId: notCountryAccount.userId,
|
||||||
|
role: notCountryAccount.role,
|
||||||
|
authorId: notCountryAccount.authorId,
|
||||||
|
};
|
||||||
|
relocatedAccounts.push(newAccount);
|
||||||
});
|
});
|
||||||
|
|
||||||
return relocatedAccounts;
|
return relocatedAccounts;
|
||||||
@ -359,6 +367,8 @@ export class TransferService {
|
|||||||
);
|
);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
// エラー配列を定義
|
||||||
|
let errorArray: string[] = [];
|
||||||
// アカウントに対するworktypeのMap配列を作成する
|
// アカウントに対するworktypeのMap配列を作成する
|
||||||
const accountWorktypeMap = new Map<string, string[]>();
|
const accountWorktypeMap = new Map<string, string[]>();
|
||||||
// csvInputFileのバリデーションチェックを行う
|
// csvInputFileのバリデーションチェックを行う
|
||||||
@ -378,6 +388,13 @@ export class TransferService {
|
|||||||
HttpStatus.BAD_REQUEST
|
HttpStatus.BAD_REQUEST
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
// typeがUSER以外の場合で、countryがnullの場合エラー配列に格納する
|
||||||
|
if (line.type !== MIGRATION_TYPE.USER) {
|
||||||
|
if (!line.country) {
|
||||||
|
// countryがnullの場合エラー配列に格納する
|
||||||
|
errorArray.push(`country is null. index=${index}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
// countryのバリデーションチェック
|
// countryのバリデーションチェック
|
||||||
if (line.country) {
|
if (line.country) {
|
||||||
if (!COUNTRY_LIST.find((country) => country.label === line.country)) {
|
if (!COUNTRY_LIST.find((country) => country.label === line.country)) {
|
||||||
@ -446,6 +463,15 @@ export class TransferService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
// エラー配列に値が存在する場合はエラーファイルを出力する
|
||||||
|
if (errorArray.length > 0) {
|
||||||
|
const errorFileJson = JSON.stringify(errorArray);
|
||||||
|
fs.writeFileSync(`error.json`, errorFileJson);
|
||||||
|
throw new HttpException(
|
||||||
|
`errorArray is invalid. errorArray=${errorArray}`,
|
||||||
|
HttpStatus.BAD_REQUEST
|
||||||
|
);
|
||||||
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
this.logger.error(`[${context.getTrackingId()}] error=${e}`);
|
this.logger.error(`[${context.getTrackingId()}] error=${e}`);
|
||||||
throw new HttpException(
|
throw new HttpException(
|
||||||
@ -485,7 +511,7 @@ export class TransferService {
|
|||||||
// accountsFileLinesの行ループ
|
// accountsFileLinesの行ループ
|
||||||
accountsFileLines.forEach((account) => {
|
accountsFileLines.forEach((account) => {
|
||||||
const duplicateAdminMail = newAccountsFileLines.find(
|
const duplicateAdminMail = newAccountsFileLines.find(
|
||||||
(a) => a.adminMail === account.adminMail
|
(a) => a.adminMail.toLowerCase() === account.adminMail.toLowerCase() // メールアドレスは大文字小文字を区別しない
|
||||||
);
|
);
|
||||||
|
|
||||||
if (duplicateAdminMail) {
|
if (duplicateAdminMail) {
|
||||||
@ -505,7 +531,7 @@ export class TransferService {
|
|||||||
// usersFileLinesの行ループ
|
// usersFileLinesの行ループ
|
||||||
usersFileLines.forEach((user) => {
|
usersFileLines.forEach((user) => {
|
||||||
const duplicateUserEmail = newUsersFileLines.find(
|
const duplicateUserEmail = newUsersFileLines.find(
|
||||||
(u) => u.email === user.email
|
(u) => u.email.toLowerCase() === user.email.toLowerCase() // メールアドレスは大文字小文字を区別しない
|
||||||
);
|
);
|
||||||
|
|
||||||
if (duplicateUserEmail) {
|
if (duplicateUserEmail) {
|
||||||
@ -527,7 +553,7 @@ export class TransferService {
|
|||||||
}
|
}
|
||||||
// newAccountsFileLinesとの突合せ
|
// newAccountsFileLinesとの突合せ
|
||||||
const duplicateAdminUserEmail = newAccountsFileLines.find(
|
const duplicateAdminUserEmail = newAccountsFileLines.find(
|
||||||
(a) => a.adminMail === user.email
|
(a) => a.adminMail.toLowerCase() === user.email.toLowerCase() // メールアドレスは大文字小文字を区別しない
|
||||||
);
|
);
|
||||||
// 重複がある場合
|
// 重複がある場合
|
||||||
if (duplicateAdminUserEmail) {
|
if (duplicateAdminUserEmail) {
|
||||||
@ -598,4 +624,74 @@ export class TransferService {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* transferDuplicateAuthor
|
||||||
|
* @param accountsFileLines: AccountsFile[]
|
||||||
|
* @param usersFileLines: UsersFile[]
|
||||||
|
* @returns UsersFile[]
|
||||||
|
*/
|
||||||
|
async transferDuplicateAuthor(
|
||||||
|
context: Context,
|
||||||
|
accountsFileLines: AccountsFile[],
|
||||||
|
usersFileLines: UsersFile[]
|
||||||
|
): Promise<UsersFile[]> {
|
||||||
|
// パラメータ内容が長大なのでログには出さない
|
||||||
|
this.logger.log(
|
||||||
|
`[IN] [${context.getTrackingId()}] ${this.transferDuplicateAuthor.name}`
|
||||||
|
);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const newUsersFileLines: UsersFile[] = [];
|
||||||
|
|
||||||
|
for (const accountsFileLine of accountsFileLines) {
|
||||||
|
let duplicateSequence: number = 2;
|
||||||
|
let authorIdList: String[] = [];
|
||||||
|
|
||||||
|
// メールアドレス重複時はアカウントにもAuthorIdが設定されるので重複チェック用のリストに追加しておく
|
||||||
|
if (accountsFileLine.authorId) {
|
||||||
|
authorIdList.push(accountsFileLine.authorId);
|
||||||
|
}
|
||||||
|
|
||||||
|
const targetaccountUsers = usersFileLines.filter(
|
||||||
|
(item) => item.accountId === accountsFileLine.accountId
|
||||||
|
);
|
||||||
|
|
||||||
|
for (const targetaccountUser of targetaccountUsers) {
|
||||||
|
let assignAuthorId = targetaccountUser.authorId;
|
||||||
|
if (authorIdList.includes(targetaccountUser.authorId)) {
|
||||||
|
// 同じauthorIdがいる場合、自分のauthorIdに連番を付与する
|
||||||
|
assignAuthorId = assignAuthorId + duplicateSequence;
|
||||||
|
duplicateSequence = duplicateSequence + 1;
|
||||||
|
}
|
||||||
|
authorIdList.push(targetaccountUser.authorId);
|
||||||
|
|
||||||
|
// 新しいAuthorIdのユーザに詰め替え
|
||||||
|
const newUser: UsersFile = {
|
||||||
|
accountId: targetaccountUser.accountId,
|
||||||
|
userId: targetaccountUser.userId,
|
||||||
|
name: targetaccountUser.name,
|
||||||
|
role: targetaccountUser.role,
|
||||||
|
authorId: assignAuthorId,
|
||||||
|
email: targetaccountUser.email,
|
||||||
|
};
|
||||||
|
newUsersFileLines.push(newUser);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return newUsersFileLines;
|
||||||
|
} catch (e) {
|
||||||
|
this.logger.error(`[${context.getTrackingId()}] error=${e}`);
|
||||||
|
throw new HttpException(
|
||||||
|
makeErrorResponse("E009999"),
|
||||||
|
HttpStatus.INTERNAL_SERVER_ERROR
|
||||||
|
);
|
||||||
|
} finally {
|
||||||
|
this.logger.log(
|
||||||
|
`[OUT] [${context.getTrackingId()}] ${
|
||||||
|
this.transferDuplicateAuthor.name
|
||||||
|
}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -74,6 +74,9 @@ export class UsersService {
|
|||||||
accountId,
|
accountId,
|
||||||
authorId
|
authorId
|
||||||
);
|
);
|
||||||
|
this.logger.log(
|
||||||
|
`[${context.getTrackingId()}] isAuthorIdDuplicated=${isAuthorIdDuplicated}`
|
||||||
|
);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
this.logger.error(`[${context.getTrackingId()}] error=${e}`);
|
this.logger.error(`[${context.getTrackingId()}] error=${e}`);
|
||||||
throw new HttpException(
|
throw new HttpException(
|
||||||
@ -88,9 +91,10 @@ export class UsersService {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
this.logger.log("ランダムパスワード生成開始");
|
||||||
// ランダムなパスワードを生成する
|
// ランダムなパスワードを生成する
|
||||||
const ramdomPassword = makePassword();
|
const ramdomPassword = makePassword();
|
||||||
|
this.logger.log("ランダムパスワード生成完了");
|
||||||
|
|
||||||
//Azure AD B2Cにユーザーを新規登録する
|
//Azure AD B2Cにユーザーを新規登録する
|
||||||
let externalUser: { sub: string } | ConflictError;
|
let externalUser: { sub: string } | ConflictError;
|
||||||
|
|||||||
@ -53,31 +53,50 @@ export class VerificationService {
|
|||||||
// 件数情報の取得
|
// 件数情報の取得
|
||||||
this.logger.log(`入力ファイルから件数情報を取得する`);
|
this.logger.log(`入力ファイルから件数情報を取得する`);
|
||||||
|
|
||||||
const accountCountFromFile = csvInputFiles.filter(
|
const accountFromFile = csvInputFiles.filter(
|
||||||
(item) => item.type !== "USER" && item.type !== "Country"
|
(item) => item.type !== "USER" && item.type !== "Country"
|
||||||
).length;
|
);
|
||||||
|
const accountCountFromFile = accountFromFile.length;
|
||||||
|
|
||||||
const cardLicensesCountFromFile = cardlicensesInputFiles.length;
|
const cardLicensesCountFromFile = cardlicensesInputFiles.length;
|
||||||
|
|
||||||
const licensesCountFromFile =
|
const licensesCountFromFile =
|
||||||
csvInputFiles.filter(
|
csvInputFiles.filter(
|
||||||
(item) =>
|
(item) =>
|
||||||
item.type === "USER" && item.expired_date !== "9999/12/31 23:59:59"
|
item.type === "USER" && !item.expired_date.startsWith("9999/12/31")
|
||||||
).length + cardLicensesCountFromFile;
|
).length + cardLicensesCountFromFile;
|
||||||
|
|
||||||
// 管理ユーザ数のカウント
|
// 管理ユーザ数のカウント
|
||||||
const administratorCountFromFile = accountCountFromFile;
|
const administratorCountFromFile = accountCountFromFile;
|
||||||
|
|
||||||
// 一般ユーザ数のカウント
|
// 一般ユーザ数のカウント
|
||||||
const normaluserCountFromFile = csvInputFiles.filter(
|
// countryのアカウントに所属するユーザをカウント対象外とする
|
||||||
(item) => item.type === "USER" && item.user_email.length !== 0
|
const countryAccountFromFile = csvInputFiles.filter(
|
||||||
).length;
|
(item) => item.type === "Country"
|
||||||
|
);
|
||||||
|
|
||||||
|
// USER、かつuser_emailが設定なし、かつcountryのアカウントID以外をユーザとする
|
||||||
|
const normaluserFromFile = csvInputFiles.filter(
|
||||||
|
(item) =>
|
||||||
|
item.type === "USER" &&
|
||||||
|
item.user_email.length !== 0 &&
|
||||||
|
!countryAccountFromFile.some(
|
||||||
|
(countryItem) => countryItem.account_id === item.account_id
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
const normaluserCountFromFile = normaluserFromFile.length;
|
||||||
|
|
||||||
// ユーザ重複数のカウント
|
// ユーザ重複数のカウント
|
||||||
let mailAdresses: string[] = [];
|
let mailAdresses: string[] = [];
|
||||||
csvInputFiles.forEach((item) => {
|
accountFromFile.forEach((item) => {
|
||||||
// メールアドレスの要素を配列に追加(入力データとして管理者とユーザの両方に入ることはない)
|
// メールアドレスの要素を配列に追加
|
||||||
if (item.email.length !== 0) {
|
if (item.email.length !== 0) {
|
||||||
mailAdresses.push(item.email);
|
mailAdresses.push(item.email);
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
normaluserFromFile.forEach((item) => {
|
||||||
|
// メールアドレスの要素を配列に追加
|
||||||
if (item.user_email.length !== 0) {
|
if (item.user_email.length !== 0) {
|
||||||
mailAdresses.push(item.user_email);
|
mailAdresses.push(item.user_email);
|
||||||
}
|
}
|
||||||
@ -86,7 +105,8 @@ export class VerificationService {
|
|||||||
// 重複する要素を抽出
|
// 重複する要素を抽出
|
||||||
const duplicates: { [key: string]: number } = {};
|
const duplicates: { [key: string]: number } = {};
|
||||||
mailAdresses.forEach((str) => {
|
mailAdresses.forEach((str) => {
|
||||||
duplicates[str] = (duplicates[str] || 0) + 1;
|
duplicates[str.toLowerCase()] =
|
||||||
|
(duplicates[str.toLowerCase()] || 0) + 1;
|
||||||
});
|
});
|
||||||
|
|
||||||
// 重複する要素と件数を表示
|
// 重複する要素と件数を表示
|
||||||
@ -147,7 +167,7 @@ export class VerificationService {
|
|||||||
VerificationResultDetails,
|
VerificationResultDetails,
|
||||||
csvInputFiles.filter(
|
csvInputFiles.filter(
|
||||||
(item) =>
|
(item) =>
|
||||||
item.type === "USER" && item.expired_date !== "9999/12/31 23:59:59"
|
item.type === "USER" && !item.expired_date.startsWith("9999/12/31")
|
||||||
),
|
),
|
||||||
licenses.filter((item) => item.expiry_date !== null),
|
licenses.filter((item) => item.expiry_date !== null),
|
||||||
accountsMappingInputFiles
|
accountsMappingInputFiles
|
||||||
@ -232,7 +252,11 @@ export class VerificationService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// dateを任意のフォーマットに変換する
|
// dateを任意のフォーマットに変換する
|
||||||
const getFormattedDate = (date: Date | null, format: string) => {
|
const getFormattedDate = (
|
||||||
|
date: Date | null,
|
||||||
|
format: string,
|
||||||
|
padHours: boolean = false // trueの場合、hhについてゼロパディングする(00→0、01→1、23→23)
|
||||||
|
) => {
|
||||||
if (!date) {
|
if (!date) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@ -244,9 +268,13 @@ const getFormattedDate = (date: Date | null, format: string) => {
|
|||||||
s: date.getSeconds(),
|
s: date.getSeconds(),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// hhの値をゼロパディングするかどうかのフラグを確認
|
||||||
|
const hourSymbol = padHours ? "hh" : "h";
|
||||||
|
|
||||||
const formatted = format.replace(/(M+|d+|h+|m+|s+)/g, (v) =>
|
const formatted = format.replace(/(M+|d+|h+|m+|s+)/g, (v) =>
|
||||||
(
|
(
|
||||||
(v.length > 1 ? "0" : "") + symbol[v.slice(-1) as keyof typeof symbol]
|
(v.length > 1 && v !== hourSymbol ? "0" : "") +
|
||||||
|
symbol[v.slice(-1) as keyof typeof symbol]
|
||||||
).slice(-2)
|
).slice(-2)
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -354,18 +382,26 @@ function compareCardLicenses(
|
|||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const formattedActivated = getFormattedDate(
|
const formattedFileActivated = getFormattedDate(
|
||||||
filterdCardLicenses[0].activated_at,
|
cardlicensesInputFile.activated_at
|
||||||
`yyyy/MM/dd hh:mm:ss`
|
? new Date(cardlicensesInputFile.activated_at)
|
||||||
|
: null,
|
||||||
|
`yyyy/MM/dd hh:mm:ss`,
|
||||||
|
true
|
||||||
);
|
);
|
||||||
if (cardlicensesInputFile.activated_at !== formattedActivated) {
|
const formattedDbActivated = getFormattedDate(
|
||||||
|
filterdCardLicenses[0].activated_at,
|
||||||
|
`yyyy/MM/dd hh:mm:ss`,
|
||||||
|
true
|
||||||
|
);
|
||||||
|
if (formattedFileActivated !== formattedDbActivated) {
|
||||||
const VerificationResultDetailsOne: VerificationResultDetails = {
|
const VerificationResultDetailsOne: VerificationResultDetails = {
|
||||||
input: "cardLicenses",
|
input: "cardLicenses",
|
||||||
inputRow: row,
|
inputRow: row,
|
||||||
diffTargetTable: "cardLicenses",
|
diffTargetTable: "cardLicenses",
|
||||||
columnName: "activated_at",
|
columnName: "activated_at",
|
||||||
fileData: cardlicensesInputFile.activated_at,
|
fileData: formattedFileActivated,
|
||||||
databaseData: formattedActivated,
|
databaseData: formattedDbActivated,
|
||||||
reason: "内容不一致",
|
reason: "内容不一致",
|
||||||
};
|
};
|
||||||
VerificationResultDetails.push(VerificationResultDetailsOne);
|
VerificationResultDetails.push(VerificationResultDetailsOne);
|
||||||
@ -542,12 +578,21 @@ function compareLicenses(
|
|||||||
VerificationResultDetails.push(VerificationResultDetailsOne);
|
VerificationResultDetails.push(VerificationResultDetailsOne);
|
||||||
isNoError = false;
|
isNoError = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// expiry_dateについて、時はゼロパディングした値で比較する(×01~09 ○1~9)
|
||||||
if (
|
if (
|
||||||
!licensesFromDatabase[i] ||
|
!licensesFromDatabase[i] ||
|
||||||
licensesFromFile[i].expired_date !==
|
getFormattedDate(
|
||||||
|
licensesFromFile[i].expired_date
|
||||||
|
? new Date(licensesFromFile[i].expired_date)
|
||||||
|
: null,
|
||||||
|
`yyyy/MM/dd hh:mm:ss`,
|
||||||
|
true
|
||||||
|
) !==
|
||||||
getFormattedDate(
|
getFormattedDate(
|
||||||
licensesFromDatabase[i].expiry_date,
|
licensesFromDatabase[i].expiry_date,
|
||||||
`yyyy/MM/dd hh:mm:ss`
|
`yyyy/MM/dd hh:mm:ss`,
|
||||||
|
true
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
const VerificationResultDetailsOne: VerificationResultDetails = {
|
const VerificationResultDetailsOne: VerificationResultDetails = {
|
||||||
@ -555,11 +600,18 @@ function compareLicenses(
|
|||||||
inputRow: licensesFromFile[i].row,
|
inputRow: licensesFromFile[i].row,
|
||||||
diffTargetTable: "licenses",
|
diffTargetTable: "licenses",
|
||||||
columnName: "expired_date",
|
columnName: "expired_date",
|
||||||
fileData: licensesFromFile[i].expired_date,
|
fileData: getFormattedDate(
|
||||||
|
licensesFromFile[i].expired_date
|
||||||
|
? new Date(licensesFromFile[i].expired_date)
|
||||||
|
: null,
|
||||||
|
`yyyy/MM/dd hh:mm:ss`,
|
||||||
|
true
|
||||||
|
),
|
||||||
databaseData: licensesFromDatabase[i]
|
databaseData: licensesFromDatabase[i]
|
||||||
? getFormattedDate(
|
? getFormattedDate(
|
||||||
licensesFromDatabase[i].expiry_date,
|
licensesFromDatabase[i].expiry_date,
|
||||||
`yyyy/MM/dd hh:mm:ss`
|
`yyyy/MM/dd hh:mm:ss`,
|
||||||
|
true
|
||||||
)
|
)
|
||||||
: "undifined",
|
: "undifined",
|
||||||
reason: "内容不一致",
|
reason: "内容不一致",
|
||||||
|
|||||||
@ -64,41 +64,57 @@ export class AdB2cService {
|
|||||||
this.logger.log(
|
this.logger.log(
|
||||||
`[IN] [${context.getTrackingId()}] ${this.createUser.name}`
|
`[IN] [${context.getTrackingId()}] ${this.createUser.name}`
|
||||||
);
|
);
|
||||||
try {
|
|
||||||
// ユーザをADB2Cに登録
|
const retryCount: number = 3;
|
||||||
const newUser = await this.graphClient.api("users/").post({
|
let retry = 0;
|
||||||
accountEnabled: true,
|
|
||||||
displayName: username,
|
while (retry < retryCount) {
|
||||||
passwordPolicies: "DisableStrongPassword",
|
try {
|
||||||
passwordProfile: {
|
// ユーザをADB2Cに登録
|
||||||
forceChangePasswordNextSignIn: false,
|
const newUser = await this.graphClient.api("users/").post({
|
||||||
password: password,
|
accountEnabled: true,
|
||||||
},
|
displayName: username,
|
||||||
identities: [
|
passwordPolicies: "DisableStrongPassword",
|
||||||
{
|
passwordProfile: {
|
||||||
signinType: ADB2C_SIGN_IN_TYPE.EMAILADDRESS,
|
forceChangePasswordNextSignIn: false,
|
||||||
issuer: `${this.tenantName}.onmicrosoft.com`,
|
password: password,
|
||||||
issuerAssignedId: email,
|
|
||||||
},
|
},
|
||||||
],
|
identities: [
|
||||||
});
|
{
|
||||||
return { sub: newUser.id };
|
signinType: ADB2C_SIGN_IN_TYPE.EMAILADDRESS,
|
||||||
} catch (e) {
|
issuer: `${this.tenantName}.onmicrosoft.com`,
|
||||||
this.logger.error(`[${context.getTrackingId()}] error=${e}`);
|
issuerAssignedId: email,
|
||||||
if (e?.statusCode === 400 && e?.body) {
|
},
|
||||||
const error = JSON.parse(e.body);
|
],
|
||||||
|
});
|
||||||
|
this.logger.log(
|
||||||
|
`[${context.getTrackingId()}] [ADB2C CREATE] newUser: ${newUser}`
|
||||||
|
);
|
||||||
|
return { sub: newUser.id };
|
||||||
|
} catch (e) {
|
||||||
|
this.logger.error(`[${context.getTrackingId()}] error=${e}`);
|
||||||
|
|
||||||
|
if (e?.statusCode === 400 && e?.body) {
|
||||||
|
const error = JSON.parse(e.body);
|
||||||
|
|
||||||
// エラーが競合エラーである場合は、メールアドレス重複としてエラーを返す
|
// エラーが競合エラーである場合は、メールアドレス重複としてエラーを返す
|
||||||
if (error?.details?.find((x) => x.code === "ObjectConflict")) {
|
if (error?.details?.find((x) => x.code === "ObjectConflict")) {
|
||||||
return { reason: "email", message: "ObjectConflict" };
|
return { reason: "email", message: "ObjectConflict" };
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
throw e;
|
if (++retry < retryCount) {
|
||||||
} finally {
|
this.logger.log(`ADB2Cエラー発生。5秒sleepしてリトライします (${retry}/${retryCount})...`);
|
||||||
this.logger.log(
|
await new Promise(resolve => setTimeout(resolve, 5000));
|
||||||
`[OUT] [${context.getTrackingId()}] ${this.createUser.name}`
|
} else {
|
||||||
);
|
this.logger.log(`リトライ数が上限に達したのでエラーを返却します`);
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
this.logger.log(
|
||||||
|
`[OUT] [${context.getTrackingId()}] ${this.createUser.name}`
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -17,10 +17,6 @@ RUN bash /tmp/library-scripts/common-debian.sh "${INSTALL_ZSH}" "${USERNAME}" "$
|
|||||||
&& apt-get install default-jre -y \
|
&& apt-get install default-jre -y \
|
||||||
&& apt-get clean -y && rm -rf /var/lib/apt/lists/* /tmp/library-scripts
|
&& apt-get clean -y && rm -rf /var/lib/apt/lists/* /tmp/library-scripts
|
||||||
|
|
||||||
|
|
||||||
# Update NPM
|
|
||||||
RUN npm install -g npm
|
|
||||||
|
|
||||||
# Install mob
|
# Install mob
|
||||||
RUN curl -sL install.mob.sh | sh
|
RUN curl -sL install.mob.sh | sh
|
||||||
|
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
VITE_STAGE=production
|
VITE_STAGE=production
|
||||||
VITE_B2C_CLIENTID=b0ec473b-6b2b-4f12-adc6-39a24ebe6a3f
|
VITE_B2C_CLIENTID=ea6e2535-c914-4889-8659-7ca1ec2e420d
|
||||||
VITE_B2C_AUTHORITY=https://adb2codmsprod.b2clogin.com/adb2codmsprod.onmicrosoft.com/b2c_1_signin_prod
|
VITE_B2C_AUTHORITY=https://adb2codmsproduction.b2clogin.com/adb2codmsproduction.onmicrosoft.com/b2c_1_signin_production
|
||||||
VITE_B2C_KNOWNAUTHORITIES=adb2codmsprod.b2clogin.com
|
VITE_B2C_KNOWNAUTHORITIES=adb2codmsproduction.b2clogin.com
|
||||||
VITE_DESK_TOP_APP_SCHEME=odms-desktopapp
|
VITE_DESK_TOP_APP_SCHEME=odms-desktopapp
|
||||||
@ -1,5 +1,5 @@
|
|||||||
VITE_STAGE=staging
|
VITE_STAGE=staging
|
||||||
VITE_B2C_CLIENTID=5d8f0db9-4506-41d6-a5bb-5ec39f6eba8d
|
VITE_B2C_CLIENTID=6ddb8ca0-c39e-4eba-a3c1-d18ea289a315
|
||||||
VITE_B2C_AUTHORITY=https://adb2codmsstg.b2clogin.com/adb2codmsstg.onmicrosoft.com/b2c_1_signin_stg
|
VITE_B2C_AUTHORITY=https://adb2codmsstaging.b2clogin.com/adb2codmsstaging.onmicrosoft.com/b2c_1_signin_staging
|
||||||
VITE_B2C_KNOWNAUTHORITIES=adb2codmsstg.b2clogin.com
|
VITE_B2C_KNOWNAUTHORITIES=adb2codmsstaging.b2clogin.com
|
||||||
VITE_DESK_TOP_APP_SCHEME=odms-desktopapp
|
VITE_DESK_TOP_APP_SCHEME=odms-desktopapp
|
||||||
@ -27,6 +27,7 @@ module.exports = {
|
|||||||
rules: {
|
rules: {
|
||||||
"react/jsx-uses-react": "off",
|
"react/jsx-uses-react": "off",
|
||||||
"react/react-in-jsx-scope": "off",
|
"react/react-in-jsx-scope": "off",
|
||||||
|
"react/require-default-props": "off",
|
||||||
"react/function-component-definition": [
|
"react/function-component-definition": [
|
||||||
"error",
|
"error",
|
||||||
{
|
{
|
||||||
|
|||||||
5
dictation_client/jest.config.js
Normal file
5
dictation_client/jest.config.js
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
/** @type {import('ts-jest').JestConfigWithTsJest} */
|
||||||
|
module.exports = {
|
||||||
|
preset: 'ts-jest',
|
||||||
|
testEnvironment: 'node',
|
||||||
|
};
|
||||||
6032
dictation_client/package-lock.json
generated
6032
dictation_client/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -15,7 +15,8 @@
|
|||||||
"typecheck": "tsc --noEmit",
|
"typecheck": "tsc --noEmit",
|
||||||
"codegen": "sh codegen.sh",
|
"codegen": "sh codegen.sh",
|
||||||
"lint": "eslint --cache . --ext .js,.ts,.tsx",
|
"lint": "eslint --cache . --ext .js,.ts,.tsx",
|
||||||
"lint:fix": "npm run lint -- --fix"
|
"lint:fix": "npm run lint -- --fix",
|
||||||
|
"test": "jest"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@azure/msal-browser": "^2.33.0",
|
"@azure/msal-browser": "^2.33.0",
|
||||||
@ -25,7 +26,6 @@
|
|||||||
"@testing-library/jest-dom": "^5.16.4",
|
"@testing-library/jest-dom": "^5.16.4",
|
||||||
"@testing-library/react": "^13.3.0",
|
"@testing-library/react": "^13.3.0",
|
||||||
"@testing-library/user-event": "^14.2.1",
|
"@testing-library/user-event": "^14.2.1",
|
||||||
"@types/jest": "^27.5.2",
|
|
||||||
"@types/node": "^17.0.45",
|
"@types/node": "^17.0.45",
|
||||||
"@types/react": "^18.0.14",
|
"@types/react": "^18.0.14",
|
||||||
"@types/react-dom": "^18.0.6",
|
"@types/react-dom": "^18.0.6",
|
||||||
@ -38,6 +38,7 @@
|
|||||||
"jwt-decode": "^3.1.2",
|
"jwt-decode": "^3.1.2",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"luxon": "^3.3.0",
|
"luxon": "^3.3.0",
|
||||||
|
"papaparse": "^5.4.1",
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
"react-google-recaptcha-v3": "^1.10.0",
|
"react-google-recaptcha-v3": "^1.10.0",
|
||||||
@ -56,8 +57,10 @@
|
|||||||
"@esbuild-plugins/node-modules-polyfill": "^0.2.2",
|
"@esbuild-plugins/node-modules-polyfill": "^0.2.2",
|
||||||
"@mdx-js/react": "^2.1.2",
|
"@mdx-js/react": "^2.1.2",
|
||||||
"@openapitools/openapi-generator-cli": "^2.5.2",
|
"@openapitools/openapi-generator-cli": "^2.5.2",
|
||||||
|
"@types/jest": "^29.5.12",
|
||||||
"@types/lodash": "^4.14.191",
|
"@types/lodash": "^4.14.191",
|
||||||
"@types/luxon": "^3.2.0",
|
"@types/luxon": "^3.2.0",
|
||||||
|
"@types/papaparse": "^5.3.14",
|
||||||
"@types/react": "^18.0.0",
|
"@types/react": "^18.0.0",
|
||||||
"@types/react-dom": "^18.0.0",
|
"@types/react-dom": "^18.0.0",
|
||||||
"@types/redux-mock-store": "^1.0.3",
|
"@types/redux-mock-store": "^1.0.3",
|
||||||
@ -67,16 +70,18 @@
|
|||||||
"babel-loader": "^8.2.5",
|
"babel-loader": "^8.2.5",
|
||||||
"eslint": "^8.19.0",
|
"eslint": "^8.19.0",
|
||||||
"eslint-config-airbnb": "^19.0.4",
|
"eslint-config-airbnb": "^19.0.4",
|
||||||
"eslint-config-prettier": "^8.5.0",
|
"eslint-config-prettier": "^8.10.0",
|
||||||
"eslint-plugin-import": "^2.26.0",
|
"eslint-plugin-import": "^2.26.0",
|
||||||
"eslint-plugin-jsx-a11y": "^6.6.0",
|
"eslint-plugin-jsx-a11y": "^6.6.0",
|
||||||
"eslint-plugin-prettier": "^4.2.1",
|
"eslint-plugin-prettier": "^4.2.1",
|
||||||
"eslint-plugin-react": "^7.30.1",
|
"eslint-plugin-react": "^7.30.1",
|
||||||
"eslint-plugin-react-hooks": "^4.6.0",
|
"eslint-plugin-react-hooks": "^4.6.0",
|
||||||
|
"jest": "^29.7.0",
|
||||||
"license-checker": "^25.0.1",
|
"license-checker": "^25.0.1",
|
||||||
"prettier": "^2.7.1",
|
"prettier": "^2.8.8",
|
||||||
"redux-mock-store": "^1.5.4",
|
"redux-mock-store": "^1.5.4",
|
||||||
"sass": "^1.58.3",
|
"sass": "^1.58.3",
|
||||||
|
"ts-jest": "^29.1.2",
|
||||||
"typescript": "^4.7.4",
|
"typescript": "^4.7.4",
|
||||||
"vite": "^4.1.4",
|
"vite": "^4.1.4",
|
||||||
"vite-plugin-env-compatible": "^1.1.1",
|
"vite-plugin-env-compatible": "^1.1.1",
|
||||||
@ -99,4 +104,4 @@
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -11,13 +11,29 @@ import { selectSnackber } from "features/ui/selectors";
|
|||||||
import { closeSnackbar } from "features/ui/uiSlice";
|
import { closeSnackbar } from "features/ui/uiSlice";
|
||||||
import { UNAUTHORIZED_TO_CONTINUE_ERROR_CODES } from "components/auth/constants";
|
import { UNAUTHORIZED_TO_CONTINUE_ERROR_CODES } from "components/auth/constants";
|
||||||
import { clearUserInfo } from "features/login";
|
import { clearUserInfo } from "features/login";
|
||||||
|
import { UpdateTokenTimer } from "components/auth/updateTokenTimer";
|
||||||
|
|
||||||
|
/*
|
||||||
|
UpdateTokenTimerをApp.tsxに移動する(2024年6月27日)
|
||||||
|
各画面ごとにトークンの期限チェック~自動更新を行う処理を配置していたが、
|
||||||
|
全画面で共通の処理であることと、画面遷移時にチェックのインターバルがリセットされることを考慮し、App.tsxに移動する。
|
||||||
|
*/
|
||||||
|
|
||||||
const App = (): JSX.Element => {
|
const App = (): JSX.Element => {
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
const { instance } = useMsal();
|
const { instance } = useMsal();
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
const [t, i18n] = useTranslation();
|
const [t, i18n] = useTranslation();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
// すべてのリクエストのヘッダーにX-Requested-Withを追加
|
||||||
|
globalAxios.interceptors.request.use((config) => {
|
||||||
|
// headersがあれば追加、なければ新規作成
|
||||||
|
config.headers = config.headers || {};
|
||||||
|
// X-Requested-Withを追加
|
||||||
|
config.headers["X-Requested-With"] = "XMLHttpRequest";
|
||||||
|
return config;
|
||||||
|
});
|
||||||
const id = globalAxios.interceptors.response.use(
|
const id = globalAxios.interceptors.response.use(
|
||||||
(response: AxiosResponse) => response,
|
(response: AxiosResponse) => response,
|
||||||
(e: AxiosError<{ code?: string }>) => {
|
(e: AxiosError<{ code?: string }>) => {
|
||||||
@ -73,6 +89,7 @@ const App = (): JSX.Element => {
|
|||||||
/>
|
/>
|
||||||
<BrowserRouter>
|
<BrowserRouter>
|
||||||
<AppRouter />
|
<AppRouter />
|
||||||
|
<UpdateTokenTimer />
|
||||||
</BrowserRouter>
|
</BrowserRouter>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@ -10,6 +10,8 @@ import licenseCardIssue from "features/license/licenseCardIssue/licenseCardIssue
|
|||||||
import licenseCardActivate from "features/license/licenseCardActivate/licenseCardActivateSlice";
|
import licenseCardActivate from "features/license/licenseCardActivate/licenseCardActivateSlice";
|
||||||
import licenseSummary from "features/license/licenseSummary/licenseSummarySlice";
|
import licenseSummary from "features/license/licenseSummary/licenseSummarySlice";
|
||||||
import partnerLicense from "features/license/partnerLicense/partnerLicenseSlice";
|
import partnerLicense from "features/license/partnerLicense/partnerLicenseSlice";
|
||||||
|
import licenseTrialIssue from "features/license/licenseTrialIssue/licenseTrialIssueSlice";
|
||||||
|
import searchPartners from "features/license/searchPartner/searchPartnerSlice";
|
||||||
import dictation from "features/dictation/dictationSlice";
|
import dictation from "features/dictation/dictationSlice";
|
||||||
import partner from "features/partner/partnerSlice";
|
import partner from "features/partner/partnerSlice";
|
||||||
import licenseOrderHistory from "features/license/licenseOrderHistory/licenseOrderHistorySlice";
|
import licenseOrderHistory from "features/license/licenseOrderHistory/licenseOrderHistorySlice";
|
||||||
@ -35,6 +37,8 @@ export const store = configureStore({
|
|||||||
licenseSummary,
|
licenseSummary,
|
||||||
licenseOrderHistory,
|
licenseOrderHistory,
|
||||||
partnerLicense,
|
partnerLicense,
|
||||||
|
licenseTrialIssue,
|
||||||
|
searchPartners,
|
||||||
dictation,
|
dictation,
|
||||||
partner,
|
partner,
|
||||||
typistGroup,
|
typistGroup,
|
||||||
|
|||||||
18
dictation_client/src/assets/images/change_circle.svg
Normal file
18
dictation_client/src/assets/images/change_circle.svg
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- Generator: Adobe Illustrator 28.3.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||||
|
<svg version="1.1" id="レイヤー_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px"
|
||||||
|
y="0px" viewBox="0 0 48 48" style="enable-background:new 0 0 48 48;" xml:space="preserve">
|
||||||
|
<style type="text/css">
|
||||||
|
.st0{fill:#282828;}
|
||||||
|
</style>
|
||||||
|
<path class="st0" d="M24.1,38l5.7-5.7l-5.7-5.6L22,28.8l2.1,2.1c-0.9,0-1.8-0.1-2.7-0.4c-0.9-0.3-1.7-0.9-2.4-1.6
|
||||||
|
c-0.7-0.7-1.2-1.4-1.5-2.3C17.2,25.8,17,24.9,17,24c0-0.6,0.1-1.1,0.2-1.7s0.4-1.1,0.6-1.7l-2.2-2.2c-0.6,0.8-1,1.7-1.2,2.6
|
||||||
|
C14.1,22.1,14,23,14,24c0,1.3,0.2,2.5,0.8,3.8C15.2,29,16,30.1,17,31s2,1.7,3.2,2.2c1.2,0.5,2.4,0.7,3.7,0.8L22,35.9L24.1,38z
|
||||||
|
M32.4,29.5c0.6-0.8,1-1.7,1.2-2.6C33.9,25.9,34,25,34,24c0-1.3-0.2-2.5-0.7-3.8s-1.2-2.4-2.2-3.3s-2.1-1.7-3.3-2.2
|
||||||
|
c-1.2-0.5-2.5-0.7-3.7-0.7l1.9-1.9L23.9,10l-5.7,5.7l5.7,5.6l2.1-2.1L23.8,17c0.9,0,1.8,0.2,2.8,0.5s1.7,0.9,2.4,1.5
|
||||||
|
s1.2,1.4,1.5,2.3c0.4,0.9,0.5,1.7,0.5,2.6c0,0.6-0.1,1.1-0.2,1.7c-0.1,0.6-0.4,1.1-0.6,1.6L32.4,29.5z M24,44
|
||||||
|
c-2.7,0-5.3-0.5-7.8-1.6s-4.6-2.5-6.4-4.3s-3.2-3.9-4.3-6.4S4,26.7,4,24c0-2.8,0.5-5.4,1.6-7.8s2.5-4.5,4.3-6.3s3.9-3.2,6.4-4.3
|
||||||
|
S21.3,4,24,4c2.8,0,5.4,0.5,7.8,1.6s4.6,2.5,6.4,4.3s3.2,3.9,4.3,6.3c1.1,2.4,1.6,5,1.6,7.8c0,2.7-0.5,5.3-1.6,7.8
|
||||||
|
c-1,2.4-2.5,4.6-4.3,6.4s-3.9,3.2-6.4,4.3S26.8,44,24,44z M24,41c4.7,0,8.8-1.7,12-5c3.3-3.3,5-7.3,5-12c0-4.7-1.6-8.8-5-12.1
|
||||||
|
c-3.3-3.3-7.3-5-12-5c-4.7,0-8.7,1.7-12,5S7,19.3,7,24c0,4.7,1.7,8.7,5,12C15.3,39.3,19.3,41,24,41z"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.5 KiB |
7
dictation_client/src/assets/images/search.svg
Normal file
7
dictation_client/src/assets/images/search.svg
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="48px" height="48px" viewBox="0 0 48 48" version="1.1">
|
||||||
|
<g id="surface1">
|
||||||
|
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(15.686275%,15.686275%,15.686275%);fill-opacity:1;" d="M 42.949219 37.109375 L 33.898438 28.0625 L 33.007812 28.949219 L 31.476562 27.414062 C 33.183594 24.882812 34.046875 21.945312 34.042969 19.011719 C 34.046875 15.171875 32.578125 11.320312 29.644531 8.390625 C 26.722656 5.464844 22.867188 4 19.019531 4.003906 C 15.179688 4 11.324219 5.46875 8.398438 8.390625 C 5.46875 11.320312 4 15.167969 4.003906 19.011719 C 4 22.851562 5.46875 26.703125 8.394531 29.628906 C 11.328125 32.554688 15.179688 34.019531 19.023438 34.019531 C 21.957031 34.019531 24.902344 33.160156 27.433594 31.453125 L 28.96875 32.988281 L 28.082031 33.871094 L 37.136719 42.917969 C 37.847656 43.636719 38.796875 43.996094 39.738281 43.996094 C 40.671875 43.996094 41.621094 43.636719 42.335938 42.917969 L 42.949219 42.308594 C 43.664062 41.59375 44.027344 40.644531 44.027344 39.707031 C 44.027344 38.769531 43.664062 37.824219 42.949219 37.109375 Z M 19.023438 32.003906 C 15.6875 32.003906 12.359375 30.738281 9.824219 28.199219 C 7.285156 25.667969 6.019531 22.34375 6.019531 19.011719 C 6.019531 15.675781 7.289062 12.351562 9.824219 9.820312 C 12.359375 7.285156 15.6875 6.019531 19.019531 6.019531 C 22.355469 6.019531 25.683594 7.285156 28.21875 9.820312 C 30.757812 12.351562 32.023438 15.675781 32.027344 19.011719 C 32.023438 22.34375 30.757812 25.667969 28.222656 28.199219 C 25.683594 30.738281 22.355469 32.003906 19.023438 32.003906 Z M 28.78125 30.421875 C 29.074219 30.171875 29.367188 29.910156 29.648438 29.628906 C 29.929688 29.351562 30.191406 29.058594 30.445312 28.761719 L 31.820312 30.136719 L 30.15625 31.800781 Z M 41.523438 40.882812 L 40.910156 41.492188 C 40.582031 41.820312 40.164062 41.976562 39.734375 41.980469 C 39.308594 41.980469 38.890625 41.820312 38.5625 41.496094 L 30.9375 33.875 L 33.898438 30.917969 L 41.523438 38.535156 C 41.847656 38.863281 42.007812 39.28125 42.007812 39.707031 C 42.007812 40.136719 41.847656 40.554688 41.523438 40.882812 Z M 41.523438 40.882812 "/>
|
||||||
|
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(15.686275%,15.686275%,15.686275%);fill-opacity:1;" d="M 25.695312 12.34375 C 23.855469 10.507812 21.433594 9.585938 19.023438 9.585938 C 16.609375 9.585938 14.191406 10.507812 12.351562 12.34375 C 10.511719 14.179688 9.589844 16.601562 9.59375 19.011719 C 9.589844 21.421875 10.511719 23.84375 12.351562 25.675781 C 14.191406 27.511719 16.609375 28.433594 19.019531 28.433594 C 21.433594 28.433594 23.855469 27.511719 25.695312 25.675781 C 27.535156 23.839844 28.453125 21.421875 28.453125 19.011719 C 28.457031 16.601562 27.53125 14.183594 25.695312 12.34375 Z M 24.503906 24.488281 C 22.992188 25.996094 21.011719 26.753906 19.019531 26.75 C 17.03125 26.75 15.050781 25.996094 13.539062 24.488281 C 12.027344 22.976562 11.277344 21 11.277344 19.011719 C 11.277344 17.023438 12.027344 15.042969 13.539062 13.53125 C 15.054688 12.023438 17.03125 11.269531 19.023438 11.265625 C 21.011719 11.269531 22.992188 12.023438 24.503906 13.53125 C 26.015625 15.042969 26.769531 17.023438 26.769531 19.011719 C 26.769531 21 26.015625 22.976562 24.503906 24.488281 Z M 24.503906 24.488281 "/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 3.3 KiB |
1
dictation_client/src/assets/images/shuffle.svg
Normal file
1
dictation_client/src/assets/images/shuffle.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?><svg id="_レイヤー_1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 80 37.17"><defs><style>.cls-1{fill:#282828;}.cls-1,.cls-2{stroke-width:0px;}.cls-2{fill:#e6e6e6;}</style></defs><path class="cls-2" d="M42.13,35.07l-2.15,2.1L3,5.1v6.1H0V0h11.15v3h-6l36.98,32.07Z"/><path class="cls-1" d="M39.98,37.17l-2.1-2.15L74.9,3h-6.1V0h11.2v11.15h-3v-6l-37.03,32.02Z"/></svg>
|
||||||
|
After Width: | Height: | Size: 409 B |
1
dictation_client/src/assets/images/upload.svg
Normal file
1
dictation_client/src/assets/images/upload.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" height="48" viewBox="0 -960 960 960" width="48"><path d="M450-313v-371L330-564l-43-43 193-193 193 193-43 43-120-120v371h-60ZM220-160q-24 0-42-18t-18-42v-143h60v143h520v-143h60v143q0 24-18 42t-42 18H220Z"/></svg>
|
||||||
|
After Width: | Height: | Size: 251 B |
@ -54,6 +54,7 @@ export const errorCodes = [
|
|||||||
"E010809", // ライセンス発行キャンセル不可エラー(ステータスが変えられている場合)
|
"E010809", // ライセンス発行キャンセル不可エラー(ステータスが変えられている場合)
|
||||||
"E010810", // ライセンス発行キャンセル不可エラー(発行から一定期間経過した場合)
|
"E010810", // ライセンス発行キャンセル不可エラー(発行から一定期間経過した場合)
|
||||||
"E010811", // ライセンス発行キャンセル不可エラー(発行したライセンスが割り当てされている場合)
|
"E010811", // ライセンス発行キャンセル不可エラー(発行したライセンスが割り当てされている場合)
|
||||||
|
"E010812", // ライセンス未割当エラー
|
||||||
"E010908", // タイピストグループ不在エラー
|
"E010908", // タイピストグループ不在エラー
|
||||||
"E010909", // タイピストグループ名重複エラー
|
"E010909", // タイピストグループ名重複エラー
|
||||||
"E011001", // ワークタイプ重複エラー
|
"E011001", // ワークタイプ重複エラー
|
||||||
@ -62,4 +63,26 @@ export const errorCodes = [
|
|||||||
"E011004", // ワークタイプ使用中エラー
|
"E011004", // ワークタイプ使用中エラー
|
||||||
"E013001", // ワークフローのAuthorIDとWorktypeIDのペア重複エラー
|
"E013001", // ワークフローのAuthorIDとWorktypeIDのペア重複エラー
|
||||||
"E013002", // ワークフロー不在エラー
|
"E013002", // ワークフロー不在エラー
|
||||||
|
"E014001", // ユーザー削除エラー(削除しようとしたユーザーがすでに削除済みだった)
|
||||||
|
"E014002", // ユーザー削除エラー(削除しようとしたユーザーが管理者だった)
|
||||||
|
"E014003", // ユーザー削除エラー(削除しようとしたAuthorのAuthorIDがWorkflowに指定されていた)
|
||||||
|
"E014004", // ユーザー削除エラー(削除しようとしたTypistがWorkflowのTypist候補として指定されていた)
|
||||||
|
"E014005", // ユーザー削除エラー(削除しようとしたTypistがUserGroupに所属していた)
|
||||||
|
"E014006", // ユーザー削除エラー(削除しようとしたユーザが所有者の未完了のタスクが残っている)
|
||||||
|
"E014007", // ユーザー削除エラー(削除しようとしたユーザーが有効なライセンスを持っていた)
|
||||||
|
"E014009", // ユーザー削除エラー(削除しようとしたTypistが未完了のタスクのルーティングに設定されている)
|
||||||
|
"E015001", // タイピストグループ削除済みエラー
|
||||||
|
"E015002", // タイピストグループがワークフローに紐づいているエラー
|
||||||
|
"E015003", // タイピストグループがルーティングされているエラー
|
||||||
|
"E016001", // テンプレートファイル削除エラー(削除しようとしたテンプレートファイルがすでに削除済みだった)
|
||||||
|
"E016002", // テンプレートファイル削除エラー(削除しようとしたテンプレートファイルがWorkflowに指定されていた)
|
||||||
|
"E016003", // テンプレートファイル削除エラー(削除しようとしたテンプレートファイルが未完了のタスクに紐づいていた)
|
||||||
|
"E017001", // 親アカウント変更不可エラー(指定したアカウントが存在しない)
|
||||||
|
"E017002", // 親アカウント変更不可エラー(階層関係が不正)
|
||||||
|
"E017003", // 親アカウント変更不可エラー(リージョンが同一でない)
|
||||||
|
"E018001", // パートナーアカウント削除エラー(削除条件を満たしていない)
|
||||||
|
"E019001", // パートナーアカウント取得不可エラー(階層構造が不正)
|
||||||
|
"E020001", // パートナーアカウント変更エラー(変更条件を満たしていない)
|
||||||
|
"E021001", // 音声ファイル名変更不可エラー(権限不足)
|
||||||
|
"E021002", // 音声ファイル名変更不可エラー(同名ファイルが存在)
|
||||||
] as const;
|
] as const;
|
||||||
|
|||||||
@ -6,4 +6,4 @@ export type ErrorObject = {
|
|||||||
statusCode?: number;
|
statusCode?: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type ErrorCodeType = typeof errorCodes[number];
|
export type ErrorCodeType = (typeof errorCodes)[number];
|
||||||
|
|||||||
153
dictation_client/src/common/parser.test.ts
Normal file
153
dictation_client/src/common/parser.test.ts
Normal file
@ -0,0 +1,153 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/naming-convention */
|
||||||
|
// Jestによるparser.tsのテスト
|
||||||
|
import fs from "fs";
|
||||||
|
import { CSVType, parseCSV } from "./parser";
|
||||||
|
|
||||||
|
describe("parse", () => {
|
||||||
|
it("指定形式のCSV文字列をパースできる", async () => {
|
||||||
|
const text = fs.readFileSync("src/common/test/test_001.csv", "utf-8");
|
||||||
|
const actualData = await parseCSV(text);
|
||||||
|
const expectData: CSVType[] = [
|
||||||
|
{
|
||||||
|
name: "hoge",
|
||||||
|
email: "sample@example.com",
|
||||||
|
role: 1,
|
||||||
|
author_id: "HOGE",
|
||||||
|
auto_assign: 1,
|
||||||
|
notification: 1,
|
||||||
|
encryption: 1,
|
||||||
|
encryption_password: "abcd",
|
||||||
|
prompt: 0,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
expect(actualData).toEqual(expectData);
|
||||||
|
});
|
||||||
|
it("指定形式のヘッダでない場合、例外が送出される | author_id(値がoptionial)がない", async () => {
|
||||||
|
const text = fs.readFileSync("src/common/test/test_002.csv", "utf-8");
|
||||||
|
try {
|
||||||
|
await parseCSV(text);
|
||||||
|
fail("例外が発生しませんでした");
|
||||||
|
} catch (e) {
|
||||||
|
expect(e).toEqual(new Error("Invalid CSV format"));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
it("指定形式のヘッダでない場合、例外が送出される | email(値が必須)がない", async () => {
|
||||||
|
const text = fs.readFileSync("src/common/test/test_003.csv", "utf-8");
|
||||||
|
try {
|
||||||
|
await parseCSV(text);
|
||||||
|
fail("例外が発生しませんでした");
|
||||||
|
} catch (e) {
|
||||||
|
expect(e).toEqual(new Error("Invalid CSV format"));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
it("指定形式のヘッダでない場合、例外が送出される | emailがスペルミス", async () => {
|
||||||
|
const text = fs.readFileSync("src/common/test/test_004.csv", "utf-8");
|
||||||
|
try {
|
||||||
|
await parseCSV(text);
|
||||||
|
fail("例外が発生しませんでした");
|
||||||
|
} catch (e) {
|
||||||
|
expect(e).toEqual(new Error("Invalid CSV format"));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
it("指定形式のCSV文字列をパースできる | 抜けているパラメータ(文字列)はnullとなる", async () => {
|
||||||
|
const text = fs.readFileSync("src/common/test/test_005.csv", "utf-8");
|
||||||
|
const actualData = await parseCSV(text);
|
||||||
|
const expectData: CSVType[] = [
|
||||||
|
{
|
||||||
|
name: "hoge",
|
||||||
|
email: "sample@example.com",
|
||||||
|
role: 1,
|
||||||
|
author_id: null,
|
||||||
|
auto_assign: 1,
|
||||||
|
notification: 1,
|
||||||
|
encryption: 1,
|
||||||
|
encryption_password: "abcd",
|
||||||
|
prompt: 0,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
expect(actualData).toEqual(expectData);
|
||||||
|
});
|
||||||
|
it("指定形式のCSV文字列をパースできる | 抜けているパラメータ(数値)はnullとなる", async () => {
|
||||||
|
const text = fs.readFileSync("src/common/test/test_006.csv", "utf-8");
|
||||||
|
const actualData = await parseCSV(text);
|
||||||
|
const expectData: CSVType[] = [
|
||||||
|
{
|
||||||
|
name: "hoge",
|
||||||
|
email: "sample@example.com",
|
||||||
|
role: null,
|
||||||
|
author_id: "HOGE",
|
||||||
|
auto_assign: 1,
|
||||||
|
notification: 1,
|
||||||
|
encryption: 1,
|
||||||
|
encryption_password: "abcd",
|
||||||
|
prompt: 0,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
expect(actualData).toEqual(expectData);
|
||||||
|
});
|
||||||
|
it("指定形式のCSV文字列をパースできる | 余計なパラメータがあっても問題はない", async () => {
|
||||||
|
const text = fs.readFileSync("src/common/test/test_007.csv", "utf-8");
|
||||||
|
const actualData = await parseCSV(text);
|
||||||
|
const expectData: CSVType[] = [
|
||||||
|
{
|
||||||
|
name: "hoge",
|
||||||
|
email: "sample@example.com",
|
||||||
|
role: 1,
|
||||||
|
author_id: "HOGE",
|
||||||
|
auto_assign: 1,
|
||||||
|
notification: 1,
|
||||||
|
encryption: 1,
|
||||||
|
encryption_password: "abcd",
|
||||||
|
prompt: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "hoge2",
|
||||||
|
email: "sample2@example.com",
|
||||||
|
role: 1,
|
||||||
|
author_id: "HOGE2",
|
||||||
|
auto_assign: 1,
|
||||||
|
notification: 1,
|
||||||
|
encryption: 1,
|
||||||
|
encryption_password: "abcd2",
|
||||||
|
prompt: 0,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
expect(actualData.length).toBe(expectData.length);
|
||||||
|
|
||||||
|
// 余計なパラメータ格納用に __parsed_extra: string[] というプロパティが作られてしまうので、既知のプロパティ毎に比較
|
||||||
|
for (let i = 0; i < actualData.length; i += 1) {
|
||||||
|
const actualValue = actualData[i];
|
||||||
|
const expectValue = expectData[i];
|
||||||
|
expect(actualValue.author_id).toEqual(expectValue.author_id);
|
||||||
|
expect(actualValue.auto_assign).toEqual(expectValue.auto_assign);
|
||||||
|
expect(actualValue.email).toEqual(expectValue.email);
|
||||||
|
expect(actualValue.encryption).toEqual(expectValue.encryption);
|
||||||
|
expect(actualValue.encryption_password).toEqual(
|
||||||
|
expectValue.encryption_password
|
||||||
|
);
|
||||||
|
expect(actualValue.name).toEqual(expectValue.name);
|
||||||
|
expect(actualValue.notification).toEqual(expectValue.notification);
|
||||||
|
expect(actualValue.prompt).toEqual(expectValue.prompt);
|
||||||
|
expect(actualValue.role).toEqual(expectValue.role);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it("author_id,encryption_passwordが数値のみの場合でも、文字列として変換できる", async () => {
|
||||||
|
const text = fs.readFileSync("src/common/test/test_008.csv", "utf-8");
|
||||||
|
const actualData = await parseCSV(text);
|
||||||
|
const expectData: CSVType[] = [
|
||||||
|
{
|
||||||
|
name: "hoge",
|
||||||
|
email: "sample@example.com",
|
||||||
|
role: 1,
|
||||||
|
author_id: "1111",
|
||||||
|
auto_assign: 1,
|
||||||
|
notification: 1,
|
||||||
|
encryption: 1,
|
||||||
|
encryption_password: "222222",
|
||||||
|
prompt: 0,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
expect(actualData).toEqual(expectData);
|
||||||
|
});
|
||||||
|
});
|
||||||
74
dictation_client/src/common/parser.ts
Normal file
74
dictation_client/src/common/parser.ts
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/naming-convention */
|
||||||
|
import Papa, { ParseResult } from "papaparse";
|
||||||
|
|
||||||
|
export type CSVType = {
|
||||||
|
name: string | null;
|
||||||
|
email: string | null;
|
||||||
|
role: number | null;
|
||||||
|
author_id: string | null;
|
||||||
|
auto_assign: number | null;
|
||||||
|
notification: number;
|
||||||
|
encryption: number | null;
|
||||||
|
encryption_password: string | null;
|
||||||
|
prompt: number | null;
|
||||||
|
};
|
||||||
|
|
||||||
|
// CSVTypeのプロパティ名を文字列の配列で定義する
|
||||||
|
const CSVTypeFields: (keyof CSVType)[] = [
|
||||||
|
"name",
|
||||||
|
"email",
|
||||||
|
"role",
|
||||||
|
"author_id",
|
||||||
|
"auto_assign",
|
||||||
|
"notification",
|
||||||
|
"encryption",
|
||||||
|
"encryption_password",
|
||||||
|
"prompt",
|
||||||
|
];
|
||||||
|
|
||||||
|
// 2つの配列が等しいかどうかを判定する
|
||||||
|
const equals = (lhs: string[], rhs: string[]) => {
|
||||||
|
if (lhs.length !== rhs.length) return false;
|
||||||
|
for (let i = 0; i < lhs.length; i += 1) {
|
||||||
|
if (lhs[i] !== rhs[i]) return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
/** CSVファイルをCSVType型に変換するパーサー */
|
||||||
|
export const parseCSV = async (csvString: string): Promise<CSVType[]> =>
|
||||||
|
new Promise((resolve, reject) => {
|
||||||
|
Papa.parse<CSVType>(csvString, {
|
||||||
|
download: false,
|
||||||
|
worker: false, // XXX: workerを使うとエラーが発生するためfalseに設定
|
||||||
|
header: true,
|
||||||
|
dynamicTyping: {
|
||||||
|
// author_id, encryption_passwordは数値のみの場合、numberに変換されたくないためdynamicTypingをtrueにしない
|
||||||
|
role: true,
|
||||||
|
auto_assign: true,
|
||||||
|
notification: true,
|
||||||
|
encryption: true,
|
||||||
|
prompt: true,
|
||||||
|
},
|
||||||
|
// dynamicTypingがfalseの場合、空文字をnullに変換できないためtransformを使用する
|
||||||
|
transform: (value, field) => {
|
||||||
|
if (field === "author_id" || field === "encryption_password") {
|
||||||
|
// 空文字の場合はnullに変換する
|
||||||
|
if (value === "") {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
},
|
||||||
|
complete: (results: ParseResult<CSVType>) => {
|
||||||
|
// ヘッダーがCSVTypeFieldsと一致しない場合はエラーを返す
|
||||||
|
if (!equals(results.meta.fields ?? [], CSVTypeFields)) {
|
||||||
|
reject(new Error("Invalid CSV format"));
|
||||||
|
}
|
||||||
|
resolve(results.data);
|
||||||
|
},
|
||||||
|
error: (error: Error) => {
|
||||||
|
reject(error);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
2
dictation_client/src/common/test/test_001.csv
Normal file
2
dictation_client/src/common/test/test_001.csv
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
name,email,role,author_id,auto_assign,notification,encryption,encryption_password,prompt
|
||||||
|
hoge,sample@example.com,1,"HOGE",1,1,1,abcd,0
|
||||||
|
2
dictation_client/src/common/test/test_002.csv
Normal file
2
dictation_client/src/common/test/test_002.csv
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
name,email,role,auto_assign,notification,encryption,encryption_password,prompt
|
||||||
|
hoge,sample@example.com,1,"HOGE",1,1,1,abcd,0
|
||||||
|
Can't render this file because it has a wrong number of fields in line 2.
|
2
dictation_client/src/common/test/test_003.csv
Normal file
2
dictation_client/src/common/test/test_003.csv
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
name,role,author_id,auto_assign,notification,encryption,encryption_password,prompt
|
||||||
|
hoge,sample@example.com,1,"HOGE",1,1,1,abcd,0
|
||||||
|
Can't render this file because it has a wrong number of fields in line 2.
|
2
dictation_client/src/common/test/test_004.csv
Normal file
2
dictation_client/src/common/test/test_004.csv
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
name,emeil,role,author_id,auto_assign,notification,encryption,encryption_password,prompt
|
||||||
|
hoge,sample@example.com,1,"HOGE",1,1,1,abcd,0
|
||||||
|
2
dictation_client/src/common/test/test_005.csv
Normal file
2
dictation_client/src/common/test/test_005.csv
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
name,email,role,author_id,auto_assign,notification,encryption,encryption_password,prompt
|
||||||
|
hoge,sample@example.com,1,,1,1,1,abcd,0
|
||||||
|
2
dictation_client/src/common/test/test_006.csv
Normal file
2
dictation_client/src/common/test/test_006.csv
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
name,email,role,author_id,auto_assign,notification,encryption,encryption_password,prompt
|
||||||
|
hoge,sample@example.com,,"HOGE",1,1,1,abcd,0
|
||||||
|
3
dictation_client/src/common/test/test_007.csv
Normal file
3
dictation_client/src/common/test/test_007.csv
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
name,email,role,author_id,auto_assign,notification,encryption,encryption_password,prompt
|
||||||
|
hoge,sample@example.com,1,"HOGE",1,1,1,abcd,0,x
|
||||||
|
hoge2,sample2@example.com,1,"HOGE2",1,1,1,abcd2,0,1,32,4,aa
|
||||||
|
Can't render this file because it has a wrong number of fields in line 2.
|
2
dictation_client/src/common/test/test_008.csv
Normal file
2
dictation_client/src/common/test/test_008.csv
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
name,email,role,author_id,auto_assign,notification,encryption,encryption_password,prompt
|
||||||
|
hoge,sample@example.com,1,1111,1,1,1,222222,0
|
||||||
|
@ -47,6 +47,7 @@ export const KEYS_TO_PRESERVE = [
|
|||||||
"accessToken",
|
"accessToken",
|
||||||
"refreshToken",
|
"refreshToken",
|
||||||
"displayInfo",
|
"displayInfo",
|
||||||
|
"filterCriteria",
|
||||||
"sortCriteria",
|
"sortCriteria",
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import React, { useCallback } from "react";
|
import React, { useCallback, useLayoutEffect, useState } from "react";
|
||||||
import { AppDispatch } from "app/store";
|
import { AppDispatch } from "app/store";
|
||||||
import { decodeToken } from "common/decodeToken";
|
import { decodeToken } from "common/decodeToken";
|
||||||
import { useInterval } from "common/useInterval";
|
import { useInterval } from "common/useInterval";
|
||||||
@ -17,41 +17,58 @@ import { TOKEN_UPDATE_INTERVAL_MS, TOKEN_UPDATE_TIME } from "./constants";
|
|||||||
export const UpdateTokenTimer = () => {
|
export const UpdateTokenTimer = () => {
|
||||||
const dispatch: AppDispatch = useDispatch();
|
const dispatch: AppDispatch = useDispatch();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
// トークンの更新中かどうか
|
||||||
|
const [isUpdating, setIsUpdating] = useState(false);
|
||||||
|
|
||||||
const delegattionToken = useSelector(selectDelegationAccessToken);
|
const delegattionToken = useSelector(selectDelegationAccessToken);
|
||||||
|
|
||||||
// 期限が5分以内であれば更新APIを呼ぶ
|
// 期限が5分以内であれば更新APIを呼ぶ
|
||||||
const updateToken = useCallback(async () => {
|
const updateToken = useCallback(async () => {
|
||||||
// localStorageからトークンを取得
|
if (isUpdating) {
|
||||||
const jwt = loadAccessToken();
|
return;
|
||||||
// 現在時刻を取得
|
|
||||||
const now = DateTime.local().toSeconds();
|
|
||||||
// selectorに以下の判定処理を移したかったが、初期表示時の値でしか判定できないのでComponent内に置く
|
|
||||||
if (jwt) {
|
|
||||||
const token = decodeToken(jwt);
|
|
||||||
if (token) {
|
|
||||||
const { exp } = token;
|
|
||||||
if (exp - now <= TOKEN_UPDATE_TIME) {
|
|
||||||
await dispatch(updateTokenAsync());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
setIsUpdating(true);
|
||||||
// 代行操作トークン更新処理
|
try {
|
||||||
if (delegattionToken) {
|
// localStorageからトークンを取得
|
||||||
const token = decodeToken(delegattionToken);
|
const jwt = loadAccessToken();
|
||||||
if (token) {
|
// 現在時刻を取得
|
||||||
const { exp } = token;
|
const now = DateTime.local().toSeconds();
|
||||||
if (exp - now <= TOKEN_UPDATE_TIME) {
|
// selectorに以下の判定処理を移したかったが、初期表示時の値でしか判定できないのでComponent内に置く
|
||||||
const { meta } = await dispatch(updateDelegationTokenAsync());
|
if (jwt) {
|
||||||
if (meta.requestStatus === "rejected") {
|
const token = decodeToken(jwt);
|
||||||
dispatch(cleanupDelegateAccount());
|
if (token) {
|
||||||
navigate("/partners");
|
const { exp } = token;
|
||||||
|
if (exp - now <= TOKEN_UPDATE_TIME) {
|
||||||
|
await dispatch(updateTokenAsync());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 代行操作トークン更新処理
|
||||||
|
if (delegattionToken) {
|
||||||
|
const token = decodeToken(delegattionToken);
|
||||||
|
if (token) {
|
||||||
|
const { exp } = token;
|
||||||
|
if (exp - now <= TOKEN_UPDATE_TIME) {
|
||||||
|
const { meta } = await dispatch(updateDelegationTokenAsync());
|
||||||
|
if (meta.requestStatus === "rejected") {
|
||||||
|
dispatch(cleanupDelegateAccount());
|
||||||
|
navigate("/partners");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.error("Token update error:", e);
|
||||||
|
} finally {
|
||||||
|
setIsUpdating(false);
|
||||||
}
|
}
|
||||||
}, [dispatch, delegattionToken, navigate]);
|
}, [isUpdating, delegattionToken, dispatch, navigate]);
|
||||||
|
useLayoutEffect(() => {
|
||||||
|
updateToken();
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, []);
|
||||||
|
|
||||||
useInterval(updateToken, TOKEN_UPDATE_INTERVAL_MS);
|
useInterval(updateToken, TOKEN_UPDATE_INTERVAL_MS);
|
||||||
|
|
||||||
|
|||||||
@ -6,8 +6,50 @@ import { getTranslationID } from "translation";
|
|||||||
const Footer: React.FC = () => {
|
const Footer: React.FC = () => {
|
||||||
const [t] = useTranslation();
|
const [t] = useTranslation();
|
||||||
return (
|
return (
|
||||||
<footer className={`${styles.footer}`}>
|
<footer
|
||||||
<div>{t(getTranslationID("common.label.copyRight"))}</div>
|
className={`${styles.footer}`}
|
||||||
|
style={{
|
||||||
|
padding: "0.5rem",
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "column",
|
||||||
|
alignItems: "center",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
marginBottom: "0.5rem",
|
||||||
|
textAlign: "center",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{t(getTranslationID("common.label.copyRight"))}
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: "flex",
|
||||||
|
gap: "1rem",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<a
|
||||||
|
href="https://download.omsystem.com/pages/odms_download/odms_cloud_eula/eula/en/"
|
||||||
|
target="_blank"
|
||||||
|
className={styles.linkTx}
|
||||||
|
style={{ color: "#999999" }}
|
||||||
|
data-tag="open-eula"
|
||||||
|
rel="noreferrer"
|
||||||
|
>
|
||||||
|
{t(getTranslationID("common.label.eula"))}
|
||||||
|
</a>
|
||||||
|
<a
|
||||||
|
href="https://download.omsystem.com/pages/odms_download/odms_cloud_eula/privacy_notice/en/"
|
||||||
|
target="_blank"
|
||||||
|
className={styles.linkTx}
|
||||||
|
style={{ color: "#999999" }}
|
||||||
|
data-tag="open-privacy-notice"
|
||||||
|
rel="noreferrer"
|
||||||
|
>
|
||||||
|
{t(getTranslationID("common.label.privacyNotice"))}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
</footer>
|
</footer>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -4,6 +4,7 @@ import {
|
|||||||
updateAccountInfoAsync,
|
updateAccountInfoAsync,
|
||||||
getAccountRelationsAsync,
|
getAccountRelationsAsync,
|
||||||
deleteAccountAsync,
|
deleteAccountAsync,
|
||||||
|
updateFileDeleteSettingAsync,
|
||||||
} from "./operations";
|
} from "./operations";
|
||||||
|
|
||||||
const initialState: AccountState = {
|
const initialState: AccountState = {
|
||||||
@ -15,6 +16,8 @@ const initialState: AccountState = {
|
|||||||
tier: 0,
|
tier: 0,
|
||||||
country: "",
|
country: "",
|
||||||
delegationPermission: false,
|
delegationPermission: false,
|
||||||
|
autoFileDelete: false,
|
||||||
|
fileRetentionDays: 0,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
dealers: [],
|
dealers: [],
|
||||||
@ -29,6 +32,8 @@ const initialState: AccountState = {
|
|||||||
secondryAdminUserId: undefined,
|
secondryAdminUserId: undefined,
|
||||||
},
|
},
|
||||||
isLoading: false,
|
isLoading: false,
|
||||||
|
autoFileDelete: false,
|
||||||
|
fileRetentionDays: 0,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -64,6 +69,20 @@ export const accountSlice = createSlice({
|
|||||||
const { secondryAdminUserId } = action.payload;
|
const { secondryAdminUserId } = action.payload;
|
||||||
state.apps.updateAccountInfo.secondryAdminUserId = secondryAdminUserId;
|
state.apps.updateAccountInfo.secondryAdminUserId = secondryAdminUserId;
|
||||||
},
|
},
|
||||||
|
changeAutoFileDelete: (
|
||||||
|
state,
|
||||||
|
action: PayloadAction<{ autoFileDelete: boolean }>
|
||||||
|
) => {
|
||||||
|
const { autoFileDelete } = action.payload;
|
||||||
|
state.apps.autoFileDelete = autoFileDelete;
|
||||||
|
},
|
||||||
|
changeFileRetentionDays: (
|
||||||
|
state,
|
||||||
|
action: PayloadAction<{ fileRetentionDays: number }>
|
||||||
|
) => {
|
||||||
|
const { fileRetentionDays } = action.payload;
|
||||||
|
state.apps.fileRetentionDays = fileRetentionDays;
|
||||||
|
},
|
||||||
cleanupApps: (state) => {
|
cleanupApps: (state) => {
|
||||||
state.domain = initialState.domain;
|
state.domain = initialState.domain;
|
||||||
},
|
},
|
||||||
@ -85,6 +104,10 @@ export const accountSlice = createSlice({
|
|||||||
action.payload.accountInfo.account.primaryAdminUserId;
|
action.payload.accountInfo.account.primaryAdminUserId;
|
||||||
state.apps.updateAccountInfo.secondryAdminUserId =
|
state.apps.updateAccountInfo.secondryAdminUserId =
|
||||||
action.payload.accountInfo.account.secondryAdminUserId;
|
action.payload.accountInfo.account.secondryAdminUserId;
|
||||||
|
state.apps.autoFileDelete =
|
||||||
|
action.payload.accountInfo.account.autoFileDelete;
|
||||||
|
state.apps.fileRetentionDays =
|
||||||
|
action.payload.accountInfo.account.fileRetentionDays;
|
||||||
state.apps.isLoading = false;
|
state.apps.isLoading = false;
|
||||||
});
|
});
|
||||||
builder.addCase(getAccountRelationsAsync.rejected, (state) => {
|
builder.addCase(getAccountRelationsAsync.rejected, (state) => {
|
||||||
@ -99,6 +122,15 @@ export const accountSlice = createSlice({
|
|||||||
builder.addCase(updateAccountInfoAsync.rejected, (state) => {
|
builder.addCase(updateAccountInfoAsync.rejected, (state) => {
|
||||||
state.apps.isLoading = false;
|
state.apps.isLoading = false;
|
||||||
});
|
});
|
||||||
|
builder.addCase(updateFileDeleteSettingAsync.pending, (state) => {
|
||||||
|
state.apps.isLoading = true;
|
||||||
|
});
|
||||||
|
builder.addCase(updateFileDeleteSettingAsync.fulfilled, (state) => {
|
||||||
|
state.apps.isLoading = false;
|
||||||
|
});
|
||||||
|
builder.addCase(updateFileDeleteSettingAsync.rejected, (state) => {
|
||||||
|
state.apps.isLoading = false;
|
||||||
|
});
|
||||||
builder.addCase(deleteAccountAsync.pending, (state) => {
|
builder.addCase(deleteAccountAsync.pending, (state) => {
|
||||||
state.apps.isLoading = true;
|
state.apps.isLoading = true;
|
||||||
});
|
});
|
||||||
@ -115,6 +147,8 @@ export const {
|
|||||||
changeDealerPermission,
|
changeDealerPermission,
|
||||||
changePrimaryAdministrator,
|
changePrimaryAdministrator,
|
||||||
changeSecondryAdministrator,
|
changeSecondryAdministrator,
|
||||||
|
changeAutoFileDelete,
|
||||||
|
changeFileRetentionDays,
|
||||||
cleanupApps,
|
cleanupApps,
|
||||||
} = accountSlice.actions;
|
} = accountSlice.actions;
|
||||||
export default accountSlice.reducer;
|
export default accountSlice.reducer;
|
||||||
|
|||||||
@ -9,6 +9,7 @@ import {
|
|||||||
UpdateAccountInfoRequest,
|
UpdateAccountInfoRequest,
|
||||||
UsersApi,
|
UsersApi,
|
||||||
DeleteAccountRequest,
|
DeleteAccountRequest,
|
||||||
|
UpdateFileDeleteSettingRequest,
|
||||||
} from "../../api/api";
|
} from "../../api/api";
|
||||||
import { Configuration } from "../../api/configuration";
|
import { Configuration } from "../../api/configuration";
|
||||||
import { ViewAccountRelationsInfo } from "./types";
|
import { ViewAccountRelationsInfo } from "./types";
|
||||||
@ -38,7 +39,7 @@ export const getAccountRelationsAsync = createAsyncThunk<
|
|||||||
headers: { authorization: `Bearer ${accessToken}` },
|
headers: { authorization: `Bearer ${accessToken}` },
|
||||||
});
|
});
|
||||||
const dealers = await accountsApi.getDealers();
|
const dealers = await accountsApi.getDealers();
|
||||||
const users = await usersApi.getUsers({
|
const users = await usersApi.getUsers(undefined, undefined, {
|
||||||
headers: { authorization: `Bearer ${accessToken}` },
|
headers: { authorization: `Bearer ${accessToken}` },
|
||||||
});
|
});
|
||||||
return {
|
return {
|
||||||
@ -112,6 +113,58 @@ export const updateAccountInfoAsync = createAsyncThunk<
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const updateFileDeleteSettingAsync = createAsyncThunk<
|
||||||
|
{
|
||||||
|
/* Empty Object */
|
||||||
|
},
|
||||||
|
{ autoFileDelete: boolean; fileRetentionDays: number },
|
||||||
|
{
|
||||||
|
// rejectした時の返却値の型
|
||||||
|
rejectValue: {
|
||||||
|
error: ErrorObject;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
>("accounts/updateFileDeleteSettingAsync", async (args, thunkApi) => {
|
||||||
|
// apiのConfigurationを取得する
|
||||||
|
const { getState } = thunkApi;
|
||||||
|
const state = getState() as RootState;
|
||||||
|
const { configuration } = state.auth;
|
||||||
|
const accessToken = getAccessToken(state.auth);
|
||||||
|
const config = new Configuration(configuration);
|
||||||
|
const accountApi = new AccountsApi(config);
|
||||||
|
|
||||||
|
const requestParam: UpdateFileDeleteSettingRequest = {
|
||||||
|
autoFileDelete: args.autoFileDelete,
|
||||||
|
retentionDays: args.fileRetentionDays,
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
await accountApi.updateFileDeleteSetting(requestParam, {
|
||||||
|
headers: { authorization: `Bearer ${accessToken}` },
|
||||||
|
});
|
||||||
|
thunkApi.dispatch(
|
||||||
|
openSnackbar({
|
||||||
|
level: "info",
|
||||||
|
message: getTranslationID("common.message.success"),
|
||||||
|
})
|
||||||
|
);
|
||||||
|
return {};
|
||||||
|
} catch (e) {
|
||||||
|
const error = createErrorObject(e);
|
||||||
|
|
||||||
|
const errorMessage = getTranslationID("common.message.internalServerError");
|
||||||
|
|
||||||
|
thunkApi.dispatch(
|
||||||
|
openSnackbar({
|
||||||
|
level: "error",
|
||||||
|
message: errorMessage,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
return thunkApi.rejectWithValue({ error });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
export const deleteAccountAsync = createAsyncThunk<
|
export const deleteAccountAsync = createAsyncThunk<
|
||||||
{
|
{
|
||||||
/* Empty Object */
|
/* Empty Object */
|
||||||
|
|||||||
@ -16,3 +16,18 @@ export const selectIsLoading = (state: RootState) =>
|
|||||||
state.account.apps.isLoading;
|
state.account.apps.isLoading;
|
||||||
export const selectUpdateAccountInfo = (state: RootState) =>
|
export const selectUpdateAccountInfo = (state: RootState) =>
|
||||||
state.account.apps.updateAccountInfo;
|
state.account.apps.updateAccountInfo;
|
||||||
|
export const selectFileDeleteSetting = (state: RootState) => {
|
||||||
|
const { autoFileDelete, fileRetentionDays } = state.account.apps;
|
||||||
|
return {
|
||||||
|
autoFileDelete,
|
||||||
|
fileRetentionDays,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
export const selectInputValidationErrors = (state: RootState) => {
|
||||||
|
const { fileRetentionDays } = state.account.apps;
|
||||||
|
const hasFileRetentionDaysError =
|
||||||
|
fileRetentionDays <= 0 || fileRetentionDays >= 1000;
|
||||||
|
return {
|
||||||
|
hasFileRetentionDaysError,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|||||||
@ -19,4 +19,6 @@ export interface Domain {
|
|||||||
export interface Apps {
|
export interface Apps {
|
||||||
updateAccountInfo: UpdateAccountInfoRequest;
|
updateAccountInfo: UpdateAccountInfoRequest;
|
||||||
isLoading: boolean;
|
isLoading: boolean;
|
||||||
|
autoFileDelete: boolean;
|
||||||
|
fileRetentionDays: number;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -6,7 +6,7 @@ export const STATUS = {
|
|||||||
BACKUP: "Backup",
|
BACKUP: "Backup",
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
export type StatusType = typeof STATUS[keyof typeof STATUS];
|
export type StatusType = (typeof STATUS)[keyof typeof STATUS];
|
||||||
|
|
||||||
export const LIMIT_TASK_NUM = 100;
|
export const LIMIT_TASK_NUM = 100;
|
||||||
|
|
||||||
@ -26,7 +26,7 @@ export const SORTABLE_COLUMN = {
|
|||||||
TranscriptionFinishedDate: "TRANSCRIPTION_FINISHED_DATE",
|
TranscriptionFinishedDate: "TRANSCRIPTION_FINISHED_DATE",
|
||||||
} as const;
|
} as const;
|
||||||
export type SortableColumnType =
|
export type SortableColumnType =
|
||||||
typeof SORTABLE_COLUMN[keyof typeof SORTABLE_COLUMN];
|
(typeof SORTABLE_COLUMN)[keyof typeof SORTABLE_COLUMN];
|
||||||
|
|
||||||
export const isSortableColumnType = (
|
export const isSortableColumnType = (
|
||||||
value: string
|
value: string
|
||||||
@ -36,14 +36,14 @@ export const isSortableColumnType = (
|
|||||||
};
|
};
|
||||||
|
|
||||||
export type SortableColumnList =
|
export type SortableColumnList =
|
||||||
typeof SORTABLE_COLUMN[keyof typeof SORTABLE_COLUMN];
|
(typeof SORTABLE_COLUMN)[keyof typeof SORTABLE_COLUMN];
|
||||||
|
|
||||||
export const DIRECTION = {
|
export const DIRECTION = {
|
||||||
ASC: "ASC",
|
ASC: "ASC",
|
||||||
DESC: "DESC",
|
DESC: "DESC",
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
export type DirectionType = typeof DIRECTION[keyof typeof DIRECTION];
|
export type DirectionType = (typeof DIRECTION)[keyof typeof DIRECTION];
|
||||||
|
|
||||||
// DirectionTypeの型チェック関数
|
// DirectionTypeの型チェック関数
|
||||||
export const isDirectionType = (arg: string): arg is DirectionType =>
|
export const isDirectionType = (arg: string): arg is DirectionType =>
|
||||||
|
|||||||
@ -11,6 +11,8 @@ import {
|
|||||||
playbackAsync,
|
playbackAsync,
|
||||||
updateAssigneeAsync,
|
updateAssigneeAsync,
|
||||||
cancelAsync,
|
cancelAsync,
|
||||||
|
deleteTaskAsync,
|
||||||
|
renameFileAsync,
|
||||||
} from "./operations";
|
} from "./operations";
|
||||||
import {
|
import {
|
||||||
SORTABLE_COLUMN,
|
SORTABLE_COLUMN,
|
||||||
@ -41,6 +43,8 @@ const initialState: DictationState = {
|
|||||||
direction: DIRECTION.ASC,
|
direction: DIRECTION.ASC,
|
||||||
paramName: SORTABLE_COLUMN.JobNumber,
|
paramName: SORTABLE_COLUMN.JobNumber,
|
||||||
selectedTask: undefined,
|
selectedTask: undefined,
|
||||||
|
authorId: "",
|
||||||
|
fileName: "",
|
||||||
assignee: {
|
assignee: {
|
||||||
selected: [],
|
selected: [],
|
||||||
pool: [],
|
pool: [],
|
||||||
@ -76,6 +80,14 @@ export const dictationSlice = createSlice({
|
|||||||
const { paramName } = action.payload;
|
const { paramName } = action.payload;
|
||||||
state.apps.paramName = paramName;
|
state.apps.paramName = paramName;
|
||||||
},
|
},
|
||||||
|
changeAuthorId: (state, action: PayloadAction<{ authorId: string }>) => {
|
||||||
|
const { authorId } = action.payload;
|
||||||
|
state.apps.authorId = authorId;
|
||||||
|
},
|
||||||
|
changeFileName: (state, action: PayloadAction<{ fileName: string }>) => {
|
||||||
|
const { fileName } = action.payload;
|
||||||
|
state.apps.fileName = fileName;
|
||||||
|
},
|
||||||
changeSelectedTask: (state, action: PayloadAction<{ task: Task }>) => {
|
changeSelectedTask: (state, action: PayloadAction<{ task: Task }>) => {
|
||||||
const { task } = action.payload;
|
const { task } = action.payload;
|
||||||
state.apps.selectedTask = task;
|
state.apps.selectedTask = task;
|
||||||
@ -218,6 +230,25 @@ export const dictationSlice = createSlice({
|
|||||||
builder.addCase(backupTasksAsync.rejected, (state) => {
|
builder.addCase(backupTasksAsync.rejected, (state) => {
|
||||||
state.apps.isDownloading = false;
|
state.apps.isDownloading = false;
|
||||||
});
|
});
|
||||||
|
builder.addCase(deleteTaskAsync.pending, (state) => {
|
||||||
|
state.apps.isLoading = true;
|
||||||
|
});
|
||||||
|
builder.addCase(deleteTaskAsync.fulfilled, (state) => {
|
||||||
|
state.apps.isLoading = false;
|
||||||
|
});
|
||||||
|
builder.addCase(deleteTaskAsync.rejected, (state) => {
|
||||||
|
state.apps.isLoading = false;
|
||||||
|
});
|
||||||
|
|
||||||
|
builder.addCase(renameFileAsync.pending, (state) => {
|
||||||
|
state.apps.isLoading = true;
|
||||||
|
});
|
||||||
|
builder.addCase(renameFileAsync.fulfilled, (state) => {
|
||||||
|
state.apps.isLoading = false;
|
||||||
|
});
|
||||||
|
builder.addCase(renameFileAsync.rejected, (state) => {
|
||||||
|
state.apps.isLoading = false;
|
||||||
|
});
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -225,6 +256,8 @@ export const {
|
|||||||
changeDisplayInfo,
|
changeDisplayInfo,
|
||||||
changeDirection,
|
changeDirection,
|
||||||
changeParamName,
|
changeParamName,
|
||||||
|
changeAuthorId,
|
||||||
|
changeFileName,
|
||||||
changeSelectedTask,
|
changeSelectedTask,
|
||||||
changeAssignee,
|
changeAssignee,
|
||||||
changeBackupTaskChecked,
|
changeBackupTaskChecked,
|
||||||
|
|||||||
@ -35,6 +35,8 @@ export const listTasksAsync = createAsyncThunk<
|
|||||||
filter?: string;
|
filter?: string;
|
||||||
direction: DirectionType;
|
direction: DirectionType;
|
||||||
paramName: SortableColumnType;
|
paramName: SortableColumnType;
|
||||||
|
authorId?: string;
|
||||||
|
fileName?: string;
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
// rejectした時の返却値の型
|
// rejectした時の返却値の型
|
||||||
@ -43,7 +45,8 @@ export const listTasksAsync = createAsyncThunk<
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
>("dictations/listTasksAsync", async (args, thunkApi) => {
|
>("dictations/listTasksAsync", async (args, thunkApi) => {
|
||||||
const { limit, offset, filter, direction, paramName } = args;
|
const { limit, offset, filter, direction, paramName, authorId, fileName } =
|
||||||
|
args;
|
||||||
|
|
||||||
// apiのConfigurationを取得する
|
// apiのConfigurationを取得する
|
||||||
const { getState } = thunkApi;
|
const { getState } = thunkApi;
|
||||||
@ -60,6 +63,8 @@ export const listTasksAsync = createAsyncThunk<
|
|||||||
filter,
|
filter,
|
||||||
direction,
|
direction,
|
||||||
paramName,
|
paramName,
|
||||||
|
authorId,
|
||||||
|
fileName,
|
||||||
{
|
{
|
||||||
headers: { authorization: `Bearer ${accessToken}` },
|
headers: { authorization: `Bearer ${accessToken}` },
|
||||||
}
|
}
|
||||||
@ -80,6 +85,136 @@ export const listTasksAsync = createAsyncThunk<
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const getTaskFiltersAsync = createAsyncThunk<
|
||||||
|
{
|
||||||
|
authorId?: string;
|
||||||
|
fileName?: string;
|
||||||
|
},
|
||||||
|
void,
|
||||||
|
{
|
||||||
|
// rejectした時の返却値の型
|
||||||
|
rejectValue: {
|
||||||
|
error: ErrorObject;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
>("dictations/getTaskFiltersAsync", async (args, thunkApi) => {
|
||||||
|
// apiのConfigurationを取得する
|
||||||
|
const { getState } = thunkApi;
|
||||||
|
const state = getState() as RootState;
|
||||||
|
const { configuration } = state.auth;
|
||||||
|
const accessToken = getAccessToken(state.auth);
|
||||||
|
const config = new Configuration(configuration);
|
||||||
|
const usersApi = new UsersApi(config);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const usertaskfilter = await usersApi.getTaskFilter({
|
||||||
|
headers: { authorization: `Bearer ${accessToken}` },
|
||||||
|
});
|
||||||
|
const { authorId, fileName } = usertaskfilter.data;
|
||||||
|
return { authorId, fileName };
|
||||||
|
} catch (e) {
|
||||||
|
// e ⇒ errorObjectに変換"
|
||||||
|
const error = createErrorObject(e);
|
||||||
|
thunkApi.dispatch(
|
||||||
|
openSnackbar({
|
||||||
|
level: "error",
|
||||||
|
message: getTranslationID("common.message.internalServerError"),
|
||||||
|
})
|
||||||
|
);
|
||||||
|
return thunkApi.rejectWithValue({ error });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export const updateTaskFiltersAsync = createAsyncThunk<
|
||||||
|
{
|
||||||
|
/** empty */
|
||||||
|
},
|
||||||
|
{
|
||||||
|
filterConditionAuthorId: string;
|
||||||
|
filterConditionFileName: string;
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// rejectした時の返却値の型
|
||||||
|
rejectValue: {
|
||||||
|
error: ErrorObject;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
>("dictations/updateTaskFiltersAsync", async (args, thunkApi) => {
|
||||||
|
const { filterConditionAuthorId, filterConditionFileName } = args;
|
||||||
|
|
||||||
|
// apiのConfigurationを取得する
|
||||||
|
const { getState } = thunkApi;
|
||||||
|
const state = getState() as RootState;
|
||||||
|
const { configuration } = state.auth;
|
||||||
|
const accessToken = getAccessToken(state.auth);
|
||||||
|
const config = new Configuration(configuration);
|
||||||
|
const usersApi = new UsersApi(config);
|
||||||
|
try {
|
||||||
|
return await usersApi.updateTaskFilter(
|
||||||
|
{ filterConditionAuthorId, filterConditionFileName },
|
||||||
|
{
|
||||||
|
headers: { authorization: `Bearer ${accessToken}` },
|
||||||
|
}
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
// e ⇒ errorObjectに変換"
|
||||||
|
const error = createErrorObject(e);
|
||||||
|
thunkApi.dispatch(
|
||||||
|
openSnackbar({
|
||||||
|
level: "error",
|
||||||
|
message: getTranslationID("common.message.internalServerError"),
|
||||||
|
})
|
||||||
|
);
|
||||||
|
return thunkApi.rejectWithValue({ error });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export const updateSortColumnAsync = createAsyncThunk<
|
||||||
|
{
|
||||||
|
/** empty */
|
||||||
|
},
|
||||||
|
{
|
||||||
|
direction: DirectionType;
|
||||||
|
paramName: SortableColumnType;
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// rejectした時の返却値の型
|
||||||
|
rejectValue: {
|
||||||
|
error: ErrorObject;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
>("dictations/updateSortColumnAsync", async (args, thunkApi) => {
|
||||||
|
const { direction, paramName } = args;
|
||||||
|
|
||||||
|
// apiのConfigurationを取得する
|
||||||
|
const { getState } = thunkApi;
|
||||||
|
const state = getState() as RootState;
|
||||||
|
const { configuration } = state.auth;
|
||||||
|
const accessToken = getAccessToken(state.auth);
|
||||||
|
const config = new Configuration(configuration);
|
||||||
|
|
||||||
|
const usersApi = new UsersApi(config);
|
||||||
|
|
||||||
|
try {
|
||||||
|
return await usersApi.updateSortCriteria(
|
||||||
|
{ direction, paramName },
|
||||||
|
{
|
||||||
|
headers: { authorization: `Bearer ${accessToken}` },
|
||||||
|
}
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
// e ⇒ errorObjectに変換"
|
||||||
|
const error = createErrorObject(e);
|
||||||
|
thunkApi.dispatch(
|
||||||
|
openSnackbar({
|
||||||
|
level: "error",
|
||||||
|
message: getTranslationID("common.message.internalServerError"),
|
||||||
|
})
|
||||||
|
);
|
||||||
|
return thunkApi.rejectWithValue({ error });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
export const getSortColumnAsync = createAsyncThunk<
|
export const getSortColumnAsync = createAsyncThunk<
|
||||||
{
|
{
|
||||||
direction: DirectionType;
|
direction: DirectionType;
|
||||||
@ -280,6 +415,8 @@ export const playbackAsync = createAsyncThunk<
|
|||||||
direction: DirectionType;
|
direction: DirectionType;
|
||||||
paramName: SortableColumnType;
|
paramName: SortableColumnType;
|
||||||
audioFileId: number;
|
audioFileId: number;
|
||||||
|
filterConditionAuthorId: string;
|
||||||
|
filterConditionFileName: string;
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
// rejectした時の返却値の型
|
// rejectした時の返却値の型
|
||||||
@ -288,7 +425,13 @@ export const playbackAsync = createAsyncThunk<
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
>("dictations/playbackAsync", async (args, thunkApi) => {
|
>("dictations/playbackAsync", async (args, thunkApi) => {
|
||||||
const { audioFileId, direction, paramName } = args;
|
const {
|
||||||
|
audioFileId,
|
||||||
|
direction,
|
||||||
|
paramName,
|
||||||
|
filterConditionAuthorId,
|
||||||
|
filterConditionFileName,
|
||||||
|
} = args;
|
||||||
|
|
||||||
// apiのConfigurationを取得する
|
// apiのConfigurationを取得する
|
||||||
const { getState } = thunkApi;
|
const { getState } = thunkApi;
|
||||||
@ -305,6 +448,12 @@ export const playbackAsync = createAsyncThunk<
|
|||||||
headers: { authorization: `Bearer ${accessToken}` },
|
headers: { authorization: `Bearer ${accessToken}` },
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
await usersApi.updateTaskFilter(
|
||||||
|
{ filterConditionAuthorId, filterConditionFileName },
|
||||||
|
{
|
||||||
|
headers: { authorization: `Bearer ${accessToken}` },
|
||||||
|
}
|
||||||
|
);
|
||||||
await tasksApi.checkout(audioFileId, {
|
await tasksApi.checkout(audioFileId, {
|
||||||
headers: { authorization: `Bearer ${accessToken}` },
|
headers: { authorization: `Bearer ${accessToken}` },
|
||||||
});
|
});
|
||||||
@ -343,6 +492,30 @@ export const playbackAsync = createAsyncThunk<
|
|||||||
);
|
);
|
||||||
return thunkApi.rejectWithValue({ error });
|
return thunkApi.rejectWithValue({ error });
|
||||||
}
|
}
|
||||||
|
// ライセンスの有効期限が切れている場合
|
||||||
|
if (error.code === "E010805") {
|
||||||
|
thunkApi.dispatch(
|
||||||
|
openSnackbar({
|
||||||
|
level: "error",
|
||||||
|
message: getTranslationID(
|
||||||
|
"dictationPage.message.licenseExpiredError"
|
||||||
|
),
|
||||||
|
})
|
||||||
|
);
|
||||||
|
return thunkApi.rejectWithValue({ error });
|
||||||
|
}
|
||||||
|
// ライセンスが未割当の場合
|
||||||
|
if (error.code === "E010812") {
|
||||||
|
thunkApi.dispatch(
|
||||||
|
openSnackbar({
|
||||||
|
level: "error",
|
||||||
|
message: getTranslationID(
|
||||||
|
"dictationPage.message.licenseNotAssignedError"
|
||||||
|
),
|
||||||
|
})
|
||||||
|
);
|
||||||
|
return thunkApi.rejectWithValue({ error });
|
||||||
|
}
|
||||||
|
|
||||||
thunkApi.dispatch(
|
thunkApi.dispatch(
|
||||||
openSnackbar({
|
openSnackbar({
|
||||||
@ -363,6 +536,8 @@ export const cancelAsync = createAsyncThunk<
|
|||||||
paramName: SortableColumnType;
|
paramName: SortableColumnType;
|
||||||
audioFileId: number;
|
audioFileId: number;
|
||||||
isTypist: boolean;
|
isTypist: boolean;
|
||||||
|
filterConditionAuthorId: string;
|
||||||
|
filterConditionFileName: string;
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
// rejectした時の返却値の型
|
// rejectした時の返却値の型
|
||||||
@ -371,7 +546,14 @@ export const cancelAsync = createAsyncThunk<
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
>("dictations/cancelAsync", async (args, thunkApi) => {
|
>("dictations/cancelAsync", async (args, thunkApi) => {
|
||||||
const { audioFileId, direction, paramName, isTypist } = args;
|
const {
|
||||||
|
audioFileId,
|
||||||
|
direction,
|
||||||
|
paramName,
|
||||||
|
isTypist,
|
||||||
|
filterConditionAuthorId,
|
||||||
|
filterConditionFileName,
|
||||||
|
} = args;
|
||||||
|
|
||||||
// apiのConfigurationを取得する
|
// apiのConfigurationを取得する
|
||||||
const { getState } = thunkApi;
|
const { getState } = thunkApi;
|
||||||
@ -382,15 +564,25 @@ export const cancelAsync = createAsyncThunk<
|
|||||||
const tasksApi = new TasksApi(config);
|
const tasksApi = new TasksApi(config);
|
||||||
const usersApi = new UsersApi(config);
|
const usersApi = new UsersApi(config);
|
||||||
try {
|
try {
|
||||||
// ユーザーがタイピストである場合に、ソート条件を保存する
|
// ユーザーがタイピストである場合に、ソート条件と検索条件を保存する
|
||||||
if (isTypist) {
|
if (isTypist) {
|
||||||
await usersApi.updateSortCriteria(
|
await usersApi.updateSortCriteria(
|
||||||
{ direction, paramName },
|
{
|
||||||
|
direction,
|
||||||
|
paramName,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
headers: { authorization: `Bearer ${accessToken}` },
|
||||||
|
}
|
||||||
|
);
|
||||||
|
await usersApi.updateTaskFilter(
|
||||||
|
{ filterConditionAuthorId, filterConditionFileName },
|
||||||
{
|
{
|
||||||
headers: { authorization: `Bearer ${accessToken}` },
|
headers: { authorization: `Bearer ${accessToken}` },
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
await tasksApi.cancel(audioFileId, {
|
await tasksApi.cancel(audioFileId, {
|
||||||
headers: { authorization: `Bearer ${accessToken}` },
|
headers: { authorization: `Bearer ${accessToken}` },
|
||||||
});
|
});
|
||||||
@ -426,6 +618,93 @@ export const cancelAsync = createAsyncThunk<
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const reopenAsync = createAsyncThunk<
|
||||||
|
{
|
||||||
|
/** empty */
|
||||||
|
},
|
||||||
|
{
|
||||||
|
direction: DirectionType;
|
||||||
|
paramName: SortableColumnType;
|
||||||
|
audioFileId: number;
|
||||||
|
isTypist: boolean;
|
||||||
|
filterConditionAuthorId: string;
|
||||||
|
filterConditionFileName: string;
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// rejectした時の返却値の型
|
||||||
|
rejectValue: {
|
||||||
|
error: ErrorObject;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
>("dictations/reopenAsync", async (args, thunkApi) => {
|
||||||
|
const {
|
||||||
|
audioFileId,
|
||||||
|
direction,
|
||||||
|
paramName,
|
||||||
|
isTypist,
|
||||||
|
filterConditionAuthorId,
|
||||||
|
filterConditionFileName,
|
||||||
|
} = args;
|
||||||
|
|
||||||
|
// apiのConfigurationを取得する
|
||||||
|
const { getState } = thunkApi;
|
||||||
|
const state = getState() as RootState;
|
||||||
|
const { configuration } = state.auth;
|
||||||
|
const accessToken = getAccessToken(state.auth);
|
||||||
|
const config = new Configuration(configuration);
|
||||||
|
const tasksApi = new TasksApi(config);
|
||||||
|
const usersApi = new UsersApi(config);
|
||||||
|
try {
|
||||||
|
// ユーザーがタイピストである場合に、ソート条件と検索条件を保存する
|
||||||
|
if (isTypist) {
|
||||||
|
await usersApi.updateSortCriteria(
|
||||||
|
{ direction, paramName },
|
||||||
|
{
|
||||||
|
headers: { authorization: `Bearer ${accessToken}` },
|
||||||
|
}
|
||||||
|
);
|
||||||
|
await usersApi.updateTaskFilter(
|
||||||
|
{ filterConditionAuthorId, filterConditionFileName },
|
||||||
|
{
|
||||||
|
headers: { authorization: `Bearer ${accessToken}` },
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
await tasksApi.reopen(audioFileId, {
|
||||||
|
headers: { authorization: `Bearer ${accessToken}` },
|
||||||
|
});
|
||||||
|
thunkApi.dispatch(
|
||||||
|
openSnackbar({
|
||||||
|
level: "info",
|
||||||
|
message: getTranslationID("common.message.success"),
|
||||||
|
})
|
||||||
|
);
|
||||||
|
return {};
|
||||||
|
} catch (e) {
|
||||||
|
// e ⇒ errorObjectに変換"
|
||||||
|
const error = createErrorObject(e);
|
||||||
|
|
||||||
|
// ステータスが[Finished]以外、またはタスクが存在しない場合、またはtypistで自分のタスクでない場合
|
||||||
|
if (error.code === "E010601" || error.code === "E010603") {
|
||||||
|
thunkApi.dispatch(
|
||||||
|
openSnackbar({
|
||||||
|
level: "error",
|
||||||
|
message: getTranslationID("dictationPage.message.reopenFailedError"),
|
||||||
|
})
|
||||||
|
);
|
||||||
|
return thunkApi.rejectWithValue({ error });
|
||||||
|
}
|
||||||
|
|
||||||
|
thunkApi.dispatch(
|
||||||
|
openSnackbar({
|
||||||
|
level: "error",
|
||||||
|
message: getTranslationID("common.message.internalServerError"),
|
||||||
|
})
|
||||||
|
);
|
||||||
|
return thunkApi.rejectWithValue({ error });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
export const listBackupPopupTasksAsync = createAsyncThunk<
|
export const listBackupPopupTasksAsync = createAsyncThunk<
|
||||||
TasksResponse,
|
TasksResponse,
|
||||||
{
|
{
|
||||||
@ -456,6 +735,8 @@ export const listBackupPopupTasksAsync = createAsyncThunk<
|
|||||||
BACKUP_POPUP_LIST_STATUS.join(","), // ステータスはFinished,Backupのみ
|
BACKUP_POPUP_LIST_STATUS.join(","), // ステータスはFinished,Backupのみ
|
||||||
DIRECTION.DESC,
|
DIRECTION.DESC,
|
||||||
SORTABLE_COLUMN.Status,
|
SORTABLE_COLUMN.Status,
|
||||||
|
undefined, // backupポップアップ表示時には検索条件は未指定
|
||||||
|
undefined, // backupポップアップ表示時には検索条件は未指定
|
||||||
{
|
{
|
||||||
headers: { authorization: `Bearer ${accessToken}` },
|
headers: { authorization: `Bearer ${accessToken}` },
|
||||||
}
|
}
|
||||||
@ -541,10 +822,21 @@ export const backupTasksAsync = createAsyncThunk<
|
|||||||
a.click();
|
a.click();
|
||||||
a.parentNode?.removeChild(a);
|
a.parentNode?.removeChild(a);
|
||||||
|
|
||||||
// eslint-disable-next-line no-await-in-loop
|
// バックアップ済みに更新
|
||||||
await tasksApi.backup(task.audioFileId, {
|
try {
|
||||||
headers: { authorization: `Bearer ${accessToken}` },
|
// eslint-disable-next-line no-await-in-loop
|
||||||
});
|
await tasksApi.backup(task.audioFileId, {
|
||||||
|
headers: { authorization: `Bearer ${accessToken}` },
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
// e ⇒ errorObjectに変換
|
||||||
|
const error = createErrorObject(e);
|
||||||
|
if (error.code === "E010603") {
|
||||||
|
// タスクが削除済みの場合は成功扱いとする
|
||||||
|
} else {
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -556,8 +848,22 @@ export const backupTasksAsync = createAsyncThunk<
|
|||||||
);
|
);
|
||||||
return {};
|
return {};
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// e ⇒ errorObjectに変換"
|
// e ⇒ errorObjectに変換
|
||||||
const error = createErrorObject(e);
|
const error = createErrorObject(e);
|
||||||
|
if (error.code === "E010603") {
|
||||||
|
// 存在しない音声ファイルをダウンロードしようとした場合
|
||||||
|
thunkApi.dispatch(
|
||||||
|
openSnackbar({
|
||||||
|
level: "error",
|
||||||
|
message: getTranslationID(
|
||||||
|
"dictationPage.message.fileAlreadyDeletedError"
|
||||||
|
),
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
return thunkApi.rejectWithValue({ error });
|
||||||
|
}
|
||||||
|
|
||||||
thunkApi.dispatch(
|
thunkApi.dispatch(
|
||||||
openSnackbar({
|
openSnackbar({
|
||||||
level: "error",
|
level: "error",
|
||||||
@ -568,3 +874,143 @@ export const backupTasksAsync = createAsyncThunk<
|
|||||||
return thunkApi.rejectWithValue({ error });
|
return thunkApi.rejectWithValue({ error });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const deleteTaskAsync = createAsyncThunk<
|
||||||
|
{
|
||||||
|
// empty
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// パラメータ
|
||||||
|
audioFileId: number;
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// rejectした時の返却値の型
|
||||||
|
rejectValue: {
|
||||||
|
error: ErrorObject;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
>("dictations/deleteTaskAsync", async (args, thunkApi) => {
|
||||||
|
const { audioFileId } = args;
|
||||||
|
|
||||||
|
// apiのConfigurationを取得する
|
||||||
|
const { getState } = thunkApi;
|
||||||
|
const state = getState() as RootState;
|
||||||
|
const { configuration } = state.auth;
|
||||||
|
const accessToken = getAccessToken(state.auth);
|
||||||
|
const config = new Configuration(configuration);
|
||||||
|
const tasksApi = new TasksApi(config);
|
||||||
|
|
||||||
|
try {
|
||||||
|
await tasksApi.deleteTask(audioFileId, {
|
||||||
|
headers: { authorization: `Bearer ${accessToken}` },
|
||||||
|
});
|
||||||
|
|
||||||
|
thunkApi.dispatch(
|
||||||
|
openSnackbar({
|
||||||
|
level: "info",
|
||||||
|
message: getTranslationID("common.message.success"),
|
||||||
|
})
|
||||||
|
);
|
||||||
|
return {};
|
||||||
|
} catch (e) {
|
||||||
|
// e ⇒ errorObjectに変換"
|
||||||
|
const error = createErrorObject(e);
|
||||||
|
|
||||||
|
let message = getTranslationID("common.message.internalServerError");
|
||||||
|
|
||||||
|
if (error.statusCode === 400) {
|
||||||
|
if (error.code === "E010603") {
|
||||||
|
// タスクが削除済みの場合は成功扱いとする
|
||||||
|
thunkApi.dispatch(
|
||||||
|
openSnackbar({
|
||||||
|
level: "info",
|
||||||
|
message: getTranslationID("common.message.success"),
|
||||||
|
})
|
||||||
|
);
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (error.code === "E010601") {
|
||||||
|
// タスクがInprogressの場合はエラー
|
||||||
|
message = getTranslationID("dictationPage.message.deleteFailedError");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
thunkApi.dispatch(
|
||||||
|
openSnackbar({
|
||||||
|
level: "error",
|
||||||
|
message,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
return thunkApi.rejectWithValue({ error });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export const renameFileAsync = createAsyncThunk<
|
||||||
|
{
|
||||||
|
// empty
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// パラメータ
|
||||||
|
audioFileId: number;
|
||||||
|
fileName: string;
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// rejectした時の返却値の型
|
||||||
|
rejectValue: {
|
||||||
|
error: ErrorObject;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
>("dictations/renameFileAsync", async (args, thunkApi) => {
|
||||||
|
const { audioFileId, fileName } = args;
|
||||||
|
|
||||||
|
// apiのConfigurationを取得する
|
||||||
|
const { getState } = thunkApi;
|
||||||
|
const state = getState() as RootState;
|
||||||
|
const { configuration } = state.auth;
|
||||||
|
const accessToken = getAccessToken(state.auth);
|
||||||
|
const config = new Configuration(configuration);
|
||||||
|
const filesApi = new FilesApi(config);
|
||||||
|
|
||||||
|
try {
|
||||||
|
await filesApi.fileRename(
|
||||||
|
{ fileName, audioFileId },
|
||||||
|
{ headers: { authorization: `Bearer ${accessToken}` } }
|
||||||
|
);
|
||||||
|
|
||||||
|
thunkApi.dispatch(
|
||||||
|
openSnackbar({
|
||||||
|
level: "info",
|
||||||
|
message: getTranslationID("common.message.success"),
|
||||||
|
})
|
||||||
|
);
|
||||||
|
return {};
|
||||||
|
} catch (e) {
|
||||||
|
// e ⇒ errorObjectに変換"
|
||||||
|
const error = createErrorObject(e);
|
||||||
|
|
||||||
|
let message = getTranslationID("common.message.internalServerError");
|
||||||
|
|
||||||
|
// 変更権限がない場合はエラー
|
||||||
|
if (error.code === "E021001") {
|
||||||
|
message = getTranslationID("dictationPage.message.fileRenameFailedError");
|
||||||
|
}
|
||||||
|
|
||||||
|
// ファイル名が既に存在する場合はエラー
|
||||||
|
if (error.code === "E021002") {
|
||||||
|
message = getTranslationID(
|
||||||
|
"dictationPage.message.fileNameAleadyExistsError"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
thunkApi.dispatch(
|
||||||
|
openSnackbar({
|
||||||
|
level: "error",
|
||||||
|
message,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
return thunkApi.rejectWithValue({ error });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|||||||
@ -72,6 +72,12 @@ export const selectDirection = (state: RootState) =>
|
|||||||
export const selectParamName = (state: RootState) =>
|
export const selectParamName = (state: RootState) =>
|
||||||
state.dictation.apps.paramName;
|
state.dictation.apps.paramName;
|
||||||
|
|
||||||
|
export const selectAuthorId = (state: RootState) =>
|
||||||
|
state.dictation.apps.authorId;
|
||||||
|
|
||||||
|
export const selectFilename = (state: RootState) =>
|
||||||
|
state.dictation.apps.fileName;
|
||||||
|
|
||||||
export const selectSelectedTask = (state: RootState) =>
|
export const selectSelectedTask = (state: RootState) =>
|
||||||
state.dictation.apps.selectedTask;
|
state.dictation.apps.selectedTask;
|
||||||
|
|
||||||
|
|||||||
@ -25,6 +25,8 @@ export interface Apps {
|
|||||||
displayInfo: DisplayInfoType;
|
displayInfo: DisplayInfoType;
|
||||||
direction: DirectionType;
|
direction: DirectionType;
|
||||||
paramName: SortableColumnType;
|
paramName: SortableColumnType;
|
||||||
|
authorId: string;
|
||||||
|
fileName: string;
|
||||||
selectedTask?: Task;
|
selectedTask?: Task;
|
||||||
selectedFileTask?: Task;
|
selectedFileTask?: Task;
|
||||||
assignee: {
|
assignee: {
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
import { createSlice } from "@reduxjs/toolkit";
|
import { createSlice } from "@reduxjs/toolkit";
|
||||||
import { LicenseCardActivateState } from "./state";
|
import { LicenseCardActivateState } from "./state";
|
||||||
|
import { activateCardLicenseAsync } from "./operations";
|
||||||
|
|
||||||
const initialState: LicenseCardActivateState = {
|
const initialState: LicenseCardActivateState = {
|
||||||
apps: {
|
apps: {
|
||||||
@ -14,6 +15,17 @@ export const licenseCardActivateSlice = createSlice({
|
|||||||
state.apps = initialState.apps;
|
state.apps = initialState.apps;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
extraReducers: (builder) => {
|
||||||
|
builder.addCase(activateCardLicenseAsync.pending, (state) => {
|
||||||
|
state.apps.isLoading = true;
|
||||||
|
});
|
||||||
|
builder.addCase(activateCardLicenseAsync.fulfilled, (state) => {
|
||||||
|
state.apps.isLoading = false;
|
||||||
|
});
|
||||||
|
builder.addCase(activateCardLicenseAsync.rejected, (state) => {
|
||||||
|
state.apps.isLoading = false;
|
||||||
|
});
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export const { cleanupApps } = licenseCardActivateSlice.actions;
|
export const { cleanupApps } = licenseCardActivateSlice.actions;
|
||||||
|
|||||||
@ -7,3 +7,8 @@ export const STATUS = {
|
|||||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||||
ORDER_CANCELED: "Order Canceled",
|
ORDER_CANCELED: "Order Canceled",
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
|
export const LICENSE_TYPE = {
|
||||||
|
NORMAL: "NORMAL",
|
||||||
|
TRIAL: "TRIAL",
|
||||||
|
} as const;
|
||||||
|
|||||||
@ -3,7 +3,12 @@ import type { RootState } from "app/store";
|
|||||||
import { getTranslationID } from "translation";
|
import { getTranslationID } from "translation";
|
||||||
import { openSnackbar } from "features/ui/uiSlice";
|
import { openSnackbar } from "features/ui/uiSlice";
|
||||||
import { getAccessToken } from "features/auth";
|
import { getAccessToken } from "features/auth";
|
||||||
import { AccountsApi, LicensesApi } from "../../../api/api";
|
import {
|
||||||
|
AccountsApi,
|
||||||
|
LicensesApi,
|
||||||
|
SearchPartner,
|
||||||
|
PartnerLicenseInfo,
|
||||||
|
} from "../../../api/api";
|
||||||
import { Configuration } from "../../../api/configuration";
|
import { Configuration } from "../../../api/configuration";
|
||||||
import { ErrorObject, createErrorObject } from "../../../common/errors";
|
import { ErrorObject, createErrorObject } from "../../../common/errors";
|
||||||
import { OrderHistoryView } from "./types";
|
import { OrderHistoryView } from "./types";
|
||||||
@ -15,6 +20,7 @@ export const getLicenseOrderHistoriesAsync = createAsyncThunk<
|
|||||||
// パラメータ
|
// パラメータ
|
||||||
limit: number;
|
limit: number;
|
||||||
offset: number;
|
offset: number;
|
||||||
|
selectedRow?: PartnerLicenseInfo | SearchPartner;
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
// rejectした時の返却値の型
|
// rejectした時の返却値の型
|
||||||
@ -23,7 +29,7 @@ export const getLicenseOrderHistoriesAsync = createAsyncThunk<
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
>("licenses/licenseOrderHisotyAsync", async (args, thunkApi) => {
|
>("licenses/licenseOrderHisotyAsync", async (args, thunkApi) => {
|
||||||
const { limit, offset } = args;
|
const { limit, offset, selectedRow } = args;
|
||||||
// apiのConfigurationを取得する
|
// apiのConfigurationを取得する
|
||||||
const { getState } = thunkApi;
|
const { getState } = thunkApi;
|
||||||
const state = getState() as RootState;
|
const state = getState() as RootState;
|
||||||
@ -33,7 +39,6 @@ export const getLicenseOrderHistoriesAsync = createAsyncThunk<
|
|||||||
const accountsApi = new AccountsApi(config);
|
const accountsApi = new AccountsApi(config);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const { selectedRow } = state.partnerLicense.apps;
|
|
||||||
let accountId = 0;
|
let accountId = 0;
|
||||||
let companyName = "";
|
let companyName = "";
|
||||||
// 他の画面から指定されていない場合はログインアカウントのidを取得する
|
// 他の画面から指定されていない場合はログインアカウントのidを取得する
|
||||||
@ -46,7 +51,9 @@ export const getLicenseOrderHistoriesAsync = createAsyncThunk<
|
|||||||
companyName = getMyAccountResponse.data.account.companyName;
|
companyName = getMyAccountResponse.data.account.companyName;
|
||||||
} else {
|
} else {
|
||||||
accountId = selectedRow.accountId;
|
accountId = selectedRow.accountId;
|
||||||
companyName = selectedRow.companyName;
|
// パートナーライセンスとパートナー検索で型が異なるため、型ガードで推論させる
|
||||||
|
if ("companyName" in selectedRow) companyName = selectedRow.companyName;
|
||||||
|
if ("name" in selectedRow) companyName = selectedRow.name;
|
||||||
}
|
}
|
||||||
|
|
||||||
const res = await accountsApi.getOrderHistories(
|
const res = await accountsApi.getOrderHistories(
|
||||||
|
|||||||
@ -1,6 +1,10 @@
|
|||||||
import { createSlice } from "@reduxjs/toolkit";
|
import { createSlice } from "@reduxjs/toolkit";
|
||||||
import { LicenseSummaryState } from "./state";
|
import { LicenseSummaryState } from "./state";
|
||||||
import { getCompanyNameAsync, getLicenseSummaryAsync } from "./operations";
|
import {
|
||||||
|
getCompanyNameAsync,
|
||||||
|
getLicenseSummaryAsync,
|
||||||
|
updateRestrictionStatusAsync,
|
||||||
|
} from "./operations";
|
||||||
|
|
||||||
const initialState: LicenseSummaryState = {
|
const initialState: LicenseSummaryState = {
|
||||||
domain: {
|
domain: {
|
||||||
@ -35,12 +39,30 @@ export const licenseSummarySlice = createSlice({
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
extraReducers: (builder) => {
|
extraReducers: (builder) => {
|
||||||
|
builder.addCase(getLicenseSummaryAsync.pending, (state) => {
|
||||||
|
state.apps.isLoading = true;
|
||||||
|
});
|
||||||
builder.addCase(getLicenseSummaryAsync.fulfilled, (state, action) => {
|
builder.addCase(getLicenseSummaryAsync.fulfilled, (state, action) => {
|
||||||
state.domain.licenseSummaryInfo = action.payload;
|
state.domain.licenseSummaryInfo = action.payload;
|
||||||
|
state.apps.isLoading = false;
|
||||||
});
|
});
|
||||||
|
builder.addCase(getLicenseSummaryAsync.rejected, (state) => {
|
||||||
|
state.apps.isLoading = false;
|
||||||
|
});
|
||||||
|
// 画面側ではgetLicenseSummaryAsyncと並行して呼び出されているため、レーシングを考慮してこちらではisLoadingを更新しない
|
||||||
|
// 本来は両方の完了を待ってからisLoadingを更新するべきだが、現時点ではスピード重視のためケアしない。
|
||||||
builder.addCase(getCompanyNameAsync.fulfilled, (state, action) => {
|
builder.addCase(getCompanyNameAsync.fulfilled, (state, action) => {
|
||||||
state.domain.accountInfo.companyName = action.payload.companyName;
|
state.domain.accountInfo.companyName = action.payload.companyName;
|
||||||
});
|
});
|
||||||
|
builder.addCase(updateRestrictionStatusAsync.pending, (state) => {
|
||||||
|
state.apps.isLoading = true;
|
||||||
|
});
|
||||||
|
builder.addCase(updateRestrictionStatusAsync.fulfilled, (state) => {
|
||||||
|
state.apps.isLoading = false;
|
||||||
|
});
|
||||||
|
builder.addCase(updateRestrictionStatusAsync.rejected, (state) => {
|
||||||
|
state.apps.isLoading = false;
|
||||||
|
});
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -7,7 +7,9 @@ import {
|
|||||||
AccountsApi,
|
AccountsApi,
|
||||||
GetCompanyNameResponse,
|
GetCompanyNameResponse,
|
||||||
GetLicenseSummaryResponse,
|
GetLicenseSummaryResponse,
|
||||||
|
SearchPartner,
|
||||||
PartnerLicenseInfo,
|
PartnerLicenseInfo,
|
||||||
|
UpdateRestrictionStatusRequest,
|
||||||
} from "../../../api/api";
|
} from "../../../api/api";
|
||||||
import { Configuration } from "../../../api/configuration";
|
import { Configuration } from "../../../api/configuration";
|
||||||
import { ErrorObject, createErrorObject } from "../../../common/errors";
|
import { ErrorObject, createErrorObject } from "../../../common/errors";
|
||||||
@ -16,7 +18,7 @@ export const getLicenseSummaryAsync = createAsyncThunk<
|
|||||||
// 正常時の戻り値の型
|
// 正常時の戻り値の型
|
||||||
GetLicenseSummaryResponse,
|
GetLicenseSummaryResponse,
|
||||||
// 引数
|
// 引数
|
||||||
{ selectedRow?: PartnerLicenseInfo },
|
{ selectedRow?: PartnerLicenseInfo | SearchPartner },
|
||||||
{
|
{
|
||||||
// rejectした時の返却値の型
|
// rejectした時の返却値の型
|
||||||
rejectValue: {
|
rejectValue: {
|
||||||
@ -72,7 +74,7 @@ export const getCompanyNameAsync = createAsyncThunk<
|
|||||||
// 正常時の戻り値の型
|
// 正常時の戻り値の型
|
||||||
GetCompanyNameResponse,
|
GetCompanyNameResponse,
|
||||||
// 引数
|
// 引数
|
||||||
{ selectedRow?: PartnerLicenseInfo },
|
{ selectedRow?: PartnerLicenseInfo | SearchPartner },
|
||||||
{
|
{
|
||||||
// rejectした時の返却値の型
|
// rejectした時の返却値の型
|
||||||
rejectValue: {
|
rejectValue: {
|
||||||
@ -123,3 +125,58 @@ export const getCompanyNameAsync = createAsyncThunk<
|
|||||||
return thunkApi.rejectWithValue({ error });
|
return thunkApi.rejectWithValue({ error });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const updateRestrictionStatusAsync = createAsyncThunk<
|
||||||
|
{
|
||||||
|
/* Empty Object */
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accountId: number;
|
||||||
|
restricted: boolean;
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// rejectした時の返却値の型
|
||||||
|
rejectValue: {
|
||||||
|
error: ErrorObject;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
>("accounts/updateRestrictionStatusAsync", async (args, thunkApi) => {
|
||||||
|
// apiのConfigurationを取得する
|
||||||
|
const { getState } = thunkApi;
|
||||||
|
const state = getState() as RootState;
|
||||||
|
const { configuration } = state.auth;
|
||||||
|
const accessToken = getAccessToken(state.auth);
|
||||||
|
const config = new Configuration(configuration);
|
||||||
|
const accountApi = new AccountsApi(config);
|
||||||
|
|
||||||
|
const requestParam: UpdateRestrictionStatusRequest = {
|
||||||
|
accountId: args.accountId,
|
||||||
|
restricted: args.restricted,
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
await accountApi.updateRestrictionStatus(requestParam, {
|
||||||
|
headers: { authorization: `Bearer ${accessToken}` },
|
||||||
|
});
|
||||||
|
thunkApi.dispatch(
|
||||||
|
openSnackbar({
|
||||||
|
level: "info",
|
||||||
|
message: getTranslationID("common.message.success"),
|
||||||
|
})
|
||||||
|
);
|
||||||
|
return {};
|
||||||
|
} catch (e) {
|
||||||
|
const error = createErrorObject(e);
|
||||||
|
|
||||||
|
// このAPIでは個別のエラーメッセージは不要
|
||||||
|
const errorMessage = getTranslationID("common.message.internalServerError");
|
||||||
|
thunkApi.dispatch(
|
||||||
|
openSnackbar({
|
||||||
|
level: "error",
|
||||||
|
message: errorMessage,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
return thunkApi.rejectWithValue({ error });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|||||||
@ -1,10 +1,11 @@
|
|||||||
import { RootState } from "app/store";
|
import { RootState } from "app/store";
|
||||||
|
|
||||||
// 各値はそのまま画面に表示するので、licenseSummaryInfoとして値を取得する
|
// 各値はそのまま画面に表示するので、licenseSummaryInfoとして値を取得する
|
||||||
export const selecLicenseSummaryInfo = (state: RootState) =>
|
export const selectLicenseSummaryInfo = (state: RootState) =>
|
||||||
state.licenseSummary.domain.licenseSummaryInfo;
|
state.licenseSummary.domain.licenseSummaryInfo;
|
||||||
|
|
||||||
export const selectCompanyName = (state: RootState) =>
|
export const selectCompanyName = (state: RootState) =>
|
||||||
state.licenseSummary.domain.accountInfo.companyName;
|
state.licenseSummary.domain.accountInfo.companyName;
|
||||||
|
|
||||||
export const selectIsLoading = (state: RootState) => state.license;
|
export const selectIsLoading = (state: RootState) =>
|
||||||
|
state.licenseSummary.apps.isLoading;
|
||||||
|
|||||||
@ -0,0 +1,2 @@
|
|||||||
|
export const ISSUED_TRIAL_LICENSE_QUANTITY = 10;
|
||||||
|
export const TRIAL_LICENSE_EXPIRATION_DAY = 30;
|
||||||
@ -0,0 +1,5 @@
|
|||||||
|
export * from "./state";
|
||||||
|
export * from "./operations";
|
||||||
|
export * from "./selectors";
|
||||||
|
export * from "./licenseTrialIssueSlice";
|
||||||
|
export * from "./constants";
|
||||||
@ -0,0 +1,60 @@
|
|||||||
|
import { createSlice } from "@reduxjs/toolkit";
|
||||||
|
import { convertLocalToUTCDate } from "common/convertLocalToUTCDate";
|
||||||
|
import { LicenseTrialIssueState } from "./state";
|
||||||
|
import { issueTrialLicenseAsync } from "./operations";
|
||||||
|
import {
|
||||||
|
TRIAL_LICENSE_EXPIRATION_DAY,
|
||||||
|
ISSUED_TRIAL_LICENSE_QUANTITY,
|
||||||
|
} from "./constants";
|
||||||
|
|
||||||
|
const initialState: LicenseTrialIssueState = {
|
||||||
|
apps: {
|
||||||
|
isLoading: false,
|
||||||
|
expirationDate: "",
|
||||||
|
quantity: ISSUED_TRIAL_LICENSE_QUANTITY,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
export const licenseTrialIssueSlice = createSlice({
|
||||||
|
name: "licenseTrialIssue",
|
||||||
|
initialState,
|
||||||
|
reducers: {
|
||||||
|
cleanupApps: (state) => {
|
||||||
|
state.apps = initialState.apps;
|
||||||
|
},
|
||||||
|
setExpirationDate: (state) => {
|
||||||
|
// 有効期限を設定
|
||||||
|
const currentDate = new Date();
|
||||||
|
const expiryDate = new Date();
|
||||||
|
expiryDate.setDate(currentDate.getDate() + TRIAL_LICENSE_EXPIRATION_DAY);
|
||||||
|
// タイムゾーンオフセットを考慮して、ローカルタイムでの日付を取得
|
||||||
|
const expirationDateLocal = convertLocalToUTCDate(expiryDate);
|
||||||
|
const expirationDateWithoutTime = new Date(
|
||||||
|
expirationDateLocal.getFullYear(),
|
||||||
|
expirationDateLocal.getMonth(),
|
||||||
|
expirationDateLocal.getDate()
|
||||||
|
);
|
||||||
|
const expirationYear = expirationDateWithoutTime.getFullYear();
|
||||||
|
const expirationMonth = expirationDateWithoutTime.getMonth() + 1; // getMonth() の結果は0から始まるため、1を足して実際の月に合わせる
|
||||||
|
const expirationDay = expirationDateWithoutTime.getDate();
|
||||||
|
const formattedExpirationDate = `${expirationYear}/${expirationMonth}/${expirationDay} (${TRIAL_LICENSE_EXPIRATION_DAY})`;
|
||||||
|
|
||||||
|
state.apps.expirationDate = formattedExpirationDate;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
extraReducers: (builder) => {
|
||||||
|
builder.addCase(issueTrialLicenseAsync.pending, (state) => {
|
||||||
|
state.apps.isLoading = true;
|
||||||
|
});
|
||||||
|
builder.addCase(issueTrialLicenseAsync.fulfilled, (state) => {
|
||||||
|
state.apps.isLoading = false;
|
||||||
|
});
|
||||||
|
builder.addCase(issueTrialLicenseAsync.rejected, (state) => {
|
||||||
|
state.apps.isLoading = false;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export const { cleanupApps, setExpirationDate } =
|
||||||
|
licenseTrialIssueSlice.actions;
|
||||||
|
|
||||||
|
export default licenseTrialIssueSlice.reducer;
|
||||||
@ -0,0 +1,84 @@
|
|||||||
|
import { createAsyncThunk } from "@reduxjs/toolkit";
|
||||||
|
import type { RootState } from "app/store";
|
||||||
|
import { getTranslationID } from "translation";
|
||||||
|
import { openSnackbar } from "features/ui/uiSlice";
|
||||||
|
import { getAccessToken } from "features/auth";
|
||||||
|
import {
|
||||||
|
LicensesApi,
|
||||||
|
SearchPartner,
|
||||||
|
PartnerLicenseInfo,
|
||||||
|
} from "../../../api/api";
|
||||||
|
import { Configuration } from "../../../api/configuration";
|
||||||
|
import { ErrorObject, createErrorObject } from "../../../common/errors";
|
||||||
|
|
||||||
|
export const issueTrialLicenseAsync = createAsyncThunk<
|
||||||
|
{
|
||||||
|
/* Empty Object */
|
||||||
|
},
|
||||||
|
{
|
||||||
|
selectedRow?: PartnerLicenseInfo | SearchPartner;
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// rejectした時の返却値の型
|
||||||
|
rejectValue: {
|
||||||
|
error: ErrorObject;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
>("licenses/issueTrialLicenseAsync", async (args, thunkApi) => {
|
||||||
|
const { selectedRow } = args;
|
||||||
|
// apiのConfigurationを取得する
|
||||||
|
const { getState } = thunkApi;
|
||||||
|
const state = getState() as RootState;
|
||||||
|
const { configuration } = state.auth;
|
||||||
|
const accessToken = getAccessToken(state.auth);
|
||||||
|
const config = new Configuration(configuration);
|
||||||
|
const licensesApi = new LicensesApi(config);
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (!selectedRow) {
|
||||||
|
// アカウントが選択されていない場合はエラーとする。
|
||||||
|
const errorMessage = getTranslationID(
|
||||||
|
"trialLicenseIssuePopupPage.message.accountNotSelected"
|
||||||
|
);
|
||||||
|
thunkApi.dispatch(
|
||||||
|
openSnackbar({
|
||||||
|
level: "error",
|
||||||
|
message: errorMessage,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
// トライアルライセンス発行処理を実行
|
||||||
|
await licensesApi.issueTrialLicenses(
|
||||||
|
{
|
||||||
|
issuedAccount: selectedRow.accountId,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
headers: { authorization: `Bearer ${accessToken}` },
|
||||||
|
}
|
||||||
|
);
|
||||||
|
thunkApi.dispatch(
|
||||||
|
openSnackbar({
|
||||||
|
level: "info",
|
||||||
|
message: getTranslationID("common.message.success"),
|
||||||
|
})
|
||||||
|
);
|
||||||
|
return {};
|
||||||
|
} catch (e) {
|
||||||
|
// e ⇒ errorObjectに変換"
|
||||||
|
const error = createErrorObject(e);
|
||||||
|
|
||||||
|
const errorMessage = getTranslationID("common.message.internalServerError");
|
||||||
|
|
||||||
|
thunkApi.dispatch(
|
||||||
|
openSnackbar({
|
||||||
|
level: "error",
|
||||||
|
message: errorMessage,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
return thunkApi.rejectWithValue({ error });
|
||||||
|
}
|
||||||
|
});
|
||||||
@ -0,0 +1,10 @@
|
|||||||
|
import { RootState } from "app/store";
|
||||||
|
|
||||||
|
export const selectIsLoading = (state: RootState) =>
|
||||||
|
state.licenseTrialIssue.apps.isLoading;
|
||||||
|
|
||||||
|
export const selectExpirationDate = (state: RootState) =>
|
||||||
|
state.licenseTrialIssue.apps.expirationDate;
|
||||||
|
|
||||||
|
export const selectNumberOfLicenses = (state: RootState) =>
|
||||||
|
state.licenseTrialIssue.apps.quantity;
|
||||||
@ -0,0 +1,9 @@
|
|||||||
|
export interface LicenseTrialIssueState {
|
||||||
|
apps: Apps;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Apps {
|
||||||
|
isLoading: boolean;
|
||||||
|
expirationDate: string;
|
||||||
|
quantity: number;
|
||||||
|
}
|
||||||
@ -105,3 +105,82 @@ export const getPartnerLicenseAsync = createAsyncThunk<
|
|||||||
return thunkApi.rejectWithValue({ error });
|
return thunkApi.rejectWithValue({ error });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const switchParentAsync = createAsyncThunk<
|
||||||
|
{
|
||||||
|
/* Empty Object */
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// パラメータ
|
||||||
|
to: number;
|
||||||
|
children: number[];
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// rejectした時の返却値の型
|
||||||
|
rejectValue: {
|
||||||
|
error: ErrorObject;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
>("accounts/switchParentAsync", async (args, thunkApi) => {
|
||||||
|
// apiのConfigurationを取得する
|
||||||
|
const { getState } = thunkApi;
|
||||||
|
const state = getState() as RootState;
|
||||||
|
const { configuration } = state.auth;
|
||||||
|
const accessToken = getAccessToken(state.auth);
|
||||||
|
const config = new Configuration(configuration);
|
||||||
|
const accountsApi = new AccountsApi(config);
|
||||||
|
|
||||||
|
const { to, children } = args;
|
||||||
|
|
||||||
|
try {
|
||||||
|
await accountsApi.switchParent(
|
||||||
|
{
|
||||||
|
to,
|
||||||
|
children,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
headers: { authorization: `Bearer ${accessToken}` },
|
||||||
|
}
|
||||||
|
);
|
||||||
|
thunkApi.dispatch(
|
||||||
|
openSnackbar({
|
||||||
|
level: "info",
|
||||||
|
message: getTranslationID("common.message.success"),
|
||||||
|
})
|
||||||
|
);
|
||||||
|
return {};
|
||||||
|
} catch (e) {
|
||||||
|
// e ⇒ errorObjectに変換"
|
||||||
|
const error = createErrorObject(e);
|
||||||
|
|
||||||
|
let errorMessage = getTranslationID("common.message.internalServerError");
|
||||||
|
|
||||||
|
// TODO:エラー処理
|
||||||
|
if (error.code === "E017001") {
|
||||||
|
errorMessage = getTranslationID(
|
||||||
|
"changeOwnerPopup.message.accountNotFoundError"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (error.code === "E017002") {
|
||||||
|
errorMessage = getTranslationID(
|
||||||
|
"changeOwnerPopup.message.hierarchyMismatchError"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (error.code === "E017003") {
|
||||||
|
errorMessage = getTranslationID(
|
||||||
|
"changeOwnerPopup.message.regionMismatchError"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
thunkApi.dispatch(
|
||||||
|
openSnackbar({
|
||||||
|
level: "error",
|
||||||
|
message: errorMessage,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
return thunkApi.rejectWithValue({ error });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|||||||
@ -1,7 +1,11 @@
|
|||||||
import { PayloadAction, createSlice } from "@reduxjs/toolkit";
|
import { PayloadAction, createSlice } from "@reduxjs/toolkit";
|
||||||
import { PartnerLicenseInfo } from "api";
|
import { PartnerLicenseInfo } from "api";
|
||||||
import { PartnerLicensesState, HierarchicalElement } from "./state";
|
import { PartnerLicensesState, HierarchicalElement } from "./state";
|
||||||
import { getMyAccountAsync, getPartnerLicenseAsync } from "./operations";
|
import {
|
||||||
|
getMyAccountAsync,
|
||||||
|
getPartnerLicenseAsync,
|
||||||
|
switchParentAsync,
|
||||||
|
} from "./operations";
|
||||||
import { ACCOUNTS_VIEW_LIMIT } from "./constants";
|
import { ACCOUNTS_VIEW_LIMIT } from "./constants";
|
||||||
|
|
||||||
const initialState: PartnerLicensesState = {
|
const initialState: PartnerLicensesState = {
|
||||||
@ -12,6 +16,8 @@ const initialState: PartnerLicensesState = {
|
|||||||
tier: 0,
|
tier: 0,
|
||||||
country: "",
|
country: "",
|
||||||
delegationPermission: false,
|
delegationPermission: false,
|
||||||
|
autoFileDelete: false,
|
||||||
|
fileRetentionDays: 0,
|
||||||
},
|
},
|
||||||
total: 0,
|
total: 0,
|
||||||
ownPartnerLicense: {
|
ownPartnerLicense: {
|
||||||
@ -19,6 +25,7 @@ const initialState: PartnerLicensesState = {
|
|||||||
tier: 0,
|
tier: 0,
|
||||||
companyName: "",
|
companyName: "",
|
||||||
stockLicense: 0,
|
stockLicense: 0,
|
||||||
|
allocatedLicense: 0,
|
||||||
issuedRequested: 0,
|
issuedRequested: 0,
|
||||||
shortage: 0,
|
shortage: 0,
|
||||||
issueRequesting: 0,
|
issueRequesting: 0,
|
||||||
@ -33,6 +40,9 @@ const initialState: PartnerLicensesState = {
|
|||||||
hierarchicalElements: [],
|
hierarchicalElements: [],
|
||||||
isLoading: true,
|
isLoading: true,
|
||||||
selectedRow: undefined,
|
selectedRow: undefined,
|
||||||
|
isLicenseOrderHistoryOpen: false,
|
||||||
|
isViewDetailsOpen: false,
|
||||||
|
isSearchPopupOpen: false,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -61,6 +71,9 @@ export const partnerLicenseSlice = createSlice({
|
|||||||
const { deleteCount } = action.payload;
|
const { deleteCount } = action.payload;
|
||||||
state.apps.hierarchicalElements.splice(-deleteCount);
|
state.apps.hierarchicalElements.splice(-deleteCount);
|
||||||
},
|
},
|
||||||
|
clearHierarchicalElement: (state) => {
|
||||||
|
state.apps.hierarchicalElements = [];
|
||||||
|
},
|
||||||
changeSelectedRow: (
|
changeSelectedRow: (
|
||||||
state,
|
state,
|
||||||
action: PayloadAction<{ value?: PartnerLicenseInfo }>
|
action: PayloadAction<{ value?: PartnerLicenseInfo }>
|
||||||
@ -79,6 +92,24 @@ export const partnerLicenseSlice = createSlice({
|
|||||||
state.apps.limit = limit;
|
state.apps.limit = limit;
|
||||||
state.apps.offset = offset;
|
state.apps.offset = offset;
|
||||||
},
|
},
|
||||||
|
setIsLicenseOrderHistoryOpen: (
|
||||||
|
state,
|
||||||
|
action: PayloadAction<{ value: boolean }>
|
||||||
|
) => {
|
||||||
|
state.apps.isLicenseOrderHistoryOpen = action.payload.value;
|
||||||
|
},
|
||||||
|
setIsViewDetailsOpen: (
|
||||||
|
state,
|
||||||
|
action: PayloadAction<{ value: boolean }>
|
||||||
|
) => {
|
||||||
|
state.apps.isViewDetailsOpen = action.payload.value;
|
||||||
|
},
|
||||||
|
setIsSearchPopupOpen: (
|
||||||
|
state,
|
||||||
|
action: PayloadAction<{ value: boolean }>
|
||||||
|
) => {
|
||||||
|
state.apps.isSearchPopupOpen = action.payload.value;
|
||||||
|
},
|
||||||
},
|
},
|
||||||
extraReducers: (builder) => {
|
extraReducers: (builder) => {
|
||||||
builder.addCase(getMyAccountAsync.pending, (state) => {
|
builder.addCase(getMyAccountAsync.pending, (state) => {
|
||||||
@ -104,14 +135,27 @@ export const partnerLicenseSlice = createSlice({
|
|||||||
builder.addCase(getPartnerLicenseAsync.rejected, (state) => {
|
builder.addCase(getPartnerLicenseAsync.rejected, (state) => {
|
||||||
state.apps.isLoading = false;
|
state.apps.isLoading = false;
|
||||||
});
|
});
|
||||||
|
builder.addCase(switchParentAsync.pending, (state) => {
|
||||||
|
state.apps.isLoading = true;
|
||||||
|
});
|
||||||
|
builder.addCase(switchParentAsync.fulfilled, (state) => {
|
||||||
|
state.apps.isLoading = false;
|
||||||
|
});
|
||||||
|
builder.addCase(switchParentAsync.rejected, (state) => {
|
||||||
|
state.apps.isLoading = false;
|
||||||
|
});
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
export const {
|
export const {
|
||||||
pushHierarchicalElement,
|
pushHierarchicalElement,
|
||||||
popHierarchicalElement,
|
popHierarchicalElement,
|
||||||
spliceHierarchicalElement,
|
spliceHierarchicalElement,
|
||||||
|
clearHierarchicalElement,
|
||||||
changeSelectedRow,
|
changeSelectedRow,
|
||||||
savePageInfo,
|
savePageInfo,
|
||||||
|
setIsLicenseOrderHistoryOpen,
|
||||||
|
setIsViewDetailsOpen,
|
||||||
|
setIsSearchPopupOpen,
|
||||||
} = partnerLicenseSlice.actions;
|
} = partnerLicenseSlice.actions;
|
||||||
|
|
||||||
export default partnerLicenseSlice.reducer;
|
export default partnerLicenseSlice.reducer;
|
||||||
|
|||||||
@ -30,3 +30,10 @@ export const selectCurrentPage = (state: RootState) => {
|
|||||||
};
|
};
|
||||||
export const selectSelectedRow = (state: RootState) =>
|
export const selectSelectedRow = (state: RootState) =>
|
||||||
state.partnerLicense.apps.selectedRow;
|
state.partnerLicense.apps.selectedRow;
|
||||||
|
|
||||||
|
export const selectIsLicenseOrderHistoryOpen = (state: RootState) =>
|
||||||
|
state.partnerLicense.apps.isLicenseOrderHistoryOpen;
|
||||||
|
export const selectIsViewDetailsOpen = (state: RootState) =>
|
||||||
|
state.partnerLicense.apps.isViewDetailsOpen;
|
||||||
|
export const selectIsSearchPopupOpen = (state: RootState) =>
|
||||||
|
state.partnerLicense.apps.isSearchPopupOpen;
|
||||||
|
|||||||
@ -20,6 +20,9 @@ export interface Apps {
|
|||||||
hierarchicalElements: HierarchicalElement[];
|
hierarchicalElements: HierarchicalElement[];
|
||||||
isLoading: boolean;
|
isLoading: boolean;
|
||||||
selectedRow?: PartnerLicenseInfo;
|
selectedRow?: PartnerLicenseInfo;
|
||||||
|
isLicenseOrderHistoryOpen: boolean;
|
||||||
|
isViewDetailsOpen: boolean;
|
||||||
|
isSearchPopupOpen: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface HierarchicalElement {
|
export interface HierarchicalElement {
|
||||||
|
|||||||
@ -0,0 +1,4 @@
|
|||||||
|
export * from "./state";
|
||||||
|
export * from "./operations";
|
||||||
|
export * from "./selectors";
|
||||||
|
export * from "./searchPartnerSlice";
|
||||||
@ -0,0 +1,96 @@
|
|||||||
|
import { createAsyncThunk } from "@reduxjs/toolkit";
|
||||||
|
import { getAccessToken } from "features/auth";
|
||||||
|
import type { RootState } from "../../../app/store";
|
||||||
|
import { getTranslationID } from "../../../translation";
|
||||||
|
import { openSnackbar } from "../../ui/uiSlice";
|
||||||
|
import { AccountsApi, SearchPartner, PartnerHierarchy } from "../../../api/api";
|
||||||
|
import { Configuration } from "../../../api/configuration";
|
||||||
|
import { ErrorObject, createErrorObject } from "../../../common/errors";
|
||||||
|
|
||||||
|
export const searchPartnersAsync = createAsyncThunk<
|
||||||
|
// 正常時の戻り値の型
|
||||||
|
SearchPartner[],
|
||||||
|
// 引数
|
||||||
|
{
|
||||||
|
companyName?: string;
|
||||||
|
accountId?: number;
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// rejectした時の返却値の型
|
||||||
|
rejectValue: {
|
||||||
|
error: ErrorObject;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
>("licenses/searchPartners", async (args, thunkApi) => {
|
||||||
|
// apiのConfigurationを取得する
|
||||||
|
const { companyName, accountId } = args;
|
||||||
|
const { getState } = thunkApi;
|
||||||
|
const state = getState() as RootState;
|
||||||
|
const { configuration } = state.auth;
|
||||||
|
const accessToken = getAccessToken(state.auth);
|
||||||
|
const config = new Configuration(configuration);
|
||||||
|
const accountsApi = new AccountsApi(config);
|
||||||
|
try {
|
||||||
|
const searchPartnerResponse = await accountsApi.searchPartners(
|
||||||
|
companyName,
|
||||||
|
accountId,
|
||||||
|
{
|
||||||
|
headers: { authorization: `Bearer ${accessToken}` },
|
||||||
|
}
|
||||||
|
);
|
||||||
|
return searchPartnerResponse.data.searchResult;
|
||||||
|
} catch (e) {
|
||||||
|
// e ⇒ errorObjectに変換"
|
||||||
|
const error = createErrorObject(e);
|
||||||
|
thunkApi.dispatch(
|
||||||
|
openSnackbar({
|
||||||
|
level: "error",
|
||||||
|
message: getTranslationID("common.message.internalServerError"),
|
||||||
|
})
|
||||||
|
);
|
||||||
|
return thunkApi.rejectWithValue({ error });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export const getPartnerHierarchy = createAsyncThunk<
|
||||||
|
// 正常時の戻り値の型
|
||||||
|
PartnerHierarchy[],
|
||||||
|
// 引数
|
||||||
|
{
|
||||||
|
accountId: number;
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// rejectした時の返却値の型
|
||||||
|
rejectValue: {
|
||||||
|
error: ErrorObject;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
>("licenses/getPartnerHierarchy", async (args, thunkApi) => {
|
||||||
|
// apiのConfigurationを取得する
|
||||||
|
const { accountId } = args;
|
||||||
|
const { getState } = thunkApi;
|
||||||
|
const state = getState() as RootState;
|
||||||
|
const { configuration } = state.auth;
|
||||||
|
const accessToken = getAccessToken(state.auth);
|
||||||
|
const config = new Configuration(configuration);
|
||||||
|
const accountsApi = new AccountsApi(config);
|
||||||
|
try {
|
||||||
|
const partnerHierarchyResponse = await accountsApi.getPartnerHierarchy(
|
||||||
|
accountId,
|
||||||
|
{
|
||||||
|
headers: { authorization: `Bearer ${accessToken}` },
|
||||||
|
}
|
||||||
|
);
|
||||||
|
return partnerHierarchyResponse.data.accountHierarchy;
|
||||||
|
} catch (e) {
|
||||||
|
// e ⇒ errorObjectに変換"
|
||||||
|
const error = createErrorObject(e);
|
||||||
|
thunkApi.dispatch(
|
||||||
|
openSnackbar({
|
||||||
|
level: "error",
|
||||||
|
message: getTranslationID("common.message.internalServerError"),
|
||||||
|
})
|
||||||
|
);
|
||||||
|
return thunkApi.rejectWithValue({ error });
|
||||||
|
}
|
||||||
|
});
|
||||||
@ -0,0 +1,80 @@
|
|||||||
|
import { PayloadAction, createSlice } from "@reduxjs/toolkit";
|
||||||
|
import { SearchPartner } from "../../../api";
|
||||||
|
import { SearchPartnerState } from "./state";
|
||||||
|
import { searchPartnersAsync, getPartnerHierarchy } from "./operations";
|
||||||
|
|
||||||
|
const initialState: SearchPartnerState = {
|
||||||
|
domain: {
|
||||||
|
searchResult: [],
|
||||||
|
partnerHierarchy: [],
|
||||||
|
},
|
||||||
|
apps: {
|
||||||
|
isLoading: false,
|
||||||
|
selectedRow: undefined,
|
||||||
|
isLicenseOrderHistoryOpen: false,
|
||||||
|
isViewDetailsOpen: false,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const searchPartnersSlice = createSlice({
|
||||||
|
name: "searchPartners",
|
||||||
|
initialState,
|
||||||
|
reducers: {
|
||||||
|
changeSelectedRow: (
|
||||||
|
state,
|
||||||
|
action: PayloadAction<{ value?: SearchPartner }>
|
||||||
|
) => {
|
||||||
|
const { value } = action.payload;
|
||||||
|
state.apps.selectedRow = value;
|
||||||
|
},
|
||||||
|
setIsLicenseOrderHistoryOpen: (
|
||||||
|
state,
|
||||||
|
action: PayloadAction<{ value: boolean }>
|
||||||
|
) => {
|
||||||
|
state.apps.isLicenseOrderHistoryOpen = action.payload.value;
|
||||||
|
},
|
||||||
|
setIsViewDetailsOpen: (
|
||||||
|
state,
|
||||||
|
action: PayloadAction<{ value: boolean }>
|
||||||
|
) => {
|
||||||
|
state.apps.isViewDetailsOpen = action.payload.value;
|
||||||
|
},
|
||||||
|
cleanupSearchResult: (state) => {
|
||||||
|
state.domain.searchResult = initialState.domain.searchResult;
|
||||||
|
},
|
||||||
|
cleanupPartnerHierarchy: (state) => {
|
||||||
|
state.domain.partnerHierarchy = initialState.domain.partnerHierarchy;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
extraReducers: (builder) => {
|
||||||
|
builder.addCase(searchPartnersAsync.pending, (state) => {
|
||||||
|
state.apps.isLoading = true;
|
||||||
|
});
|
||||||
|
builder.addCase(searchPartnersAsync.fulfilled, (state, action) => {
|
||||||
|
state.domain.searchResult = action.payload;
|
||||||
|
state.apps.isLoading = false;
|
||||||
|
});
|
||||||
|
builder.addCase(searchPartnersAsync.rejected, (state) => {
|
||||||
|
state.apps.isLoading = false;
|
||||||
|
});
|
||||||
|
builder.addCase(getPartnerHierarchy.pending, (state) => {
|
||||||
|
state.apps.isLoading = true;
|
||||||
|
});
|
||||||
|
builder.addCase(getPartnerHierarchy.fulfilled, (state, action) => {
|
||||||
|
state.domain.partnerHierarchy = action.payload;
|
||||||
|
state.apps.isLoading = false;
|
||||||
|
});
|
||||||
|
builder.addCase(getPartnerHierarchy.rejected, (state) => {
|
||||||
|
state.apps.isLoading = false;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
export const {
|
||||||
|
changeSelectedRow,
|
||||||
|
setIsLicenseOrderHistoryOpen,
|
||||||
|
setIsViewDetailsOpen,
|
||||||
|
cleanupSearchResult,
|
||||||
|
cleanupPartnerHierarchy,
|
||||||
|
} = searchPartnersSlice.actions;
|
||||||
|
|
||||||
|
export default searchPartnersSlice.reducer;
|
||||||
@ -0,0 +1,14 @@
|
|||||||
|
import { RootState } from "../../../app/store";
|
||||||
|
|
||||||
|
export const selectSearchResult = (state: RootState) =>
|
||||||
|
state.searchPartners.domain.searchResult;
|
||||||
|
export const selectPartnerHierarchy = (state: RootState) =>
|
||||||
|
state.searchPartners.domain.partnerHierarchy;
|
||||||
|
export const selectIsLoading = (state: RootState) =>
|
||||||
|
state.searchPartners.apps.isLoading;
|
||||||
|
export const selectSelectedRow = (state: RootState) =>
|
||||||
|
state.searchPartners.apps.selectedRow;
|
||||||
|
export const selectIsLicenseOrderHistoryOpen = (state: RootState) =>
|
||||||
|
state.searchPartners.apps.isLicenseOrderHistoryOpen;
|
||||||
|
export const selectIsViewDetailsOpen = (state: RootState) =>
|
||||||
|
state.searchPartners.apps.isViewDetailsOpen;
|
||||||
18
dictation_client/src/features/license/searchPartner/state.ts
Normal file
18
dictation_client/src/features/license/searchPartner/state.ts
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
import { SearchPartner, PartnerHierarchy } from "../../../api/api";
|
||||||
|
|
||||||
|
export interface SearchPartnerState {
|
||||||
|
domain: Domain;
|
||||||
|
apps: Apps;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Domain {
|
||||||
|
searchResult: SearchPartner[];
|
||||||
|
partnerHierarchy: PartnerHierarchy[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Apps {
|
||||||
|
isLoading: boolean;
|
||||||
|
selectedRow?: SearchPartner;
|
||||||
|
isLicenseOrderHistoryOpen: boolean;
|
||||||
|
isViewDetailsOpen: boolean;
|
||||||
|
}
|
||||||
@ -8,6 +8,8 @@ import {
|
|||||||
AccountsApi,
|
AccountsApi,
|
||||||
CreatePartnerAccountRequest,
|
CreatePartnerAccountRequest,
|
||||||
GetPartnersResponse,
|
GetPartnersResponse,
|
||||||
|
DeletePartnerAccountRequest,
|
||||||
|
GetPartnerUsersResponse,
|
||||||
} from "../../api/api";
|
} from "../../api/api";
|
||||||
import { Configuration } from "../../api/configuration";
|
import { Configuration } from "../../api/configuration";
|
||||||
|
|
||||||
@ -116,3 +118,170 @@ export const getPartnerInfoAsync = createAsyncThunk<
|
|||||||
return thunkApi.rejectWithValue({ error });
|
return thunkApi.rejectWithValue({ error });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// パートナーアカウント削除
|
||||||
|
export const deletePartnerAccountAsync = createAsyncThunk<
|
||||||
|
{
|
||||||
|
/* Empty Object */
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// パラメータ
|
||||||
|
accountId: number;
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// rejectした時の返却値の型
|
||||||
|
rejectValue: {
|
||||||
|
error: ErrorObject;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
>("partner/deletePartnerAccountAsync", async (args, thunkApi) => {
|
||||||
|
const { accountId } = args;
|
||||||
|
// apiのConfigurationを取得する
|
||||||
|
const { getState } = thunkApi;
|
||||||
|
const state = getState() as RootState;
|
||||||
|
const { configuration } = state.auth;
|
||||||
|
const accessToken = getAccessToken(state.auth);
|
||||||
|
const config = new Configuration(configuration);
|
||||||
|
const accountApi = new AccountsApi(config);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const deletePartnerAccountRequest: DeletePartnerAccountRequest = {
|
||||||
|
targetAccountId: accountId,
|
||||||
|
};
|
||||||
|
await accountApi.deletePartnerAccount(deletePartnerAccountRequest, {
|
||||||
|
headers: { authorization: `Bearer ${accessToken}` },
|
||||||
|
});
|
||||||
|
thunkApi.dispatch(
|
||||||
|
openSnackbar({
|
||||||
|
level: "info",
|
||||||
|
message: getTranslationID("common.message.success"),
|
||||||
|
})
|
||||||
|
);
|
||||||
|
return {};
|
||||||
|
} catch (e) {
|
||||||
|
const error = createErrorObject(e);
|
||||||
|
|
||||||
|
let errorMessage = getTranslationID("common.message.internalServerError");
|
||||||
|
if (error.code === "E018001") {
|
||||||
|
errorMessage = getTranslationID(
|
||||||
|
"partnerPage.message.partnerDeleteFailedError"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
thunkApi.dispatch(
|
||||||
|
openSnackbar({
|
||||||
|
level: "error",
|
||||||
|
message: errorMessage,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
return thunkApi.rejectWithValue({ error });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// パートナーアカウントユーザー取得
|
||||||
|
export const getPartnerUsersAsync = createAsyncThunk<
|
||||||
|
GetPartnerUsersResponse,
|
||||||
|
{
|
||||||
|
// パラメータ
|
||||||
|
accountId: number;
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// rejectした時の返却値の型
|
||||||
|
rejectValue: {
|
||||||
|
error: ErrorObject;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
>("partner/getPartnerUsersAsync", async (args, thunkApi) => {
|
||||||
|
const { accountId } = args;
|
||||||
|
// apiのConfigurationを取得する
|
||||||
|
const { getState } = thunkApi;
|
||||||
|
const state = getState() as RootState;
|
||||||
|
const { configuration } = state.auth;
|
||||||
|
const accessToken = getAccessToken(state.auth);
|
||||||
|
const config = new Configuration(configuration);
|
||||||
|
const accountApi = new AccountsApi(config);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const res = await accountApi.getPartnerUsers(
|
||||||
|
{ targetAccountId: accountId },
|
||||||
|
{
|
||||||
|
headers: { authorization: `Bearer ${accessToken}` },
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
return res.data;
|
||||||
|
} catch (e) {
|
||||||
|
const error = createErrorObject(e);
|
||||||
|
thunkApi.dispatch(
|
||||||
|
openSnackbar({
|
||||||
|
level: "error",
|
||||||
|
message: getTranslationID("common.message.internalServerError"),
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
return thunkApi.rejectWithValue({ error });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// パートナーアカウントユーザー編集
|
||||||
|
export const editPartnerInfoAsync = createAsyncThunk<
|
||||||
|
{
|
||||||
|
/* Empty Object */
|
||||||
|
},
|
||||||
|
void,
|
||||||
|
{
|
||||||
|
// rejectした時の返却値の型
|
||||||
|
rejectValue: {
|
||||||
|
error: ErrorObject;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
>("partner/editPartnerInfoAsync", async (args, thunkApi) => {
|
||||||
|
// apiのConfigurationを取得する
|
||||||
|
const { getState } = thunkApi;
|
||||||
|
const state = getState() as RootState;
|
||||||
|
const { configuration } = state.auth;
|
||||||
|
const accessToken = getAccessToken(state.auth);
|
||||||
|
const config = new Configuration(configuration);
|
||||||
|
const accountApi = new AccountsApi(config);
|
||||||
|
|
||||||
|
const { id, companyName, selectedAdminId } = state.partner.apps.editPartner;
|
||||||
|
|
||||||
|
try {
|
||||||
|
await accountApi.updatePartnerInfo(
|
||||||
|
{
|
||||||
|
targetAccountId: id,
|
||||||
|
primaryAdminUserId: selectedAdminId,
|
||||||
|
companyName,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
headers: { authorization: `Bearer ${accessToken}` },
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
thunkApi.dispatch(
|
||||||
|
openSnackbar({
|
||||||
|
level: "info",
|
||||||
|
message: getTranslationID("common.message.success"),
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
return {};
|
||||||
|
} catch (e) {
|
||||||
|
const error = createErrorObject(e);
|
||||||
|
|
||||||
|
let errorMessage = getTranslationID("common.message.internalServerError");
|
||||||
|
|
||||||
|
if (error.code === "E010502" || error.code === "E020001") {
|
||||||
|
errorMessage = getTranslationID("partnerPage.message.editFailedError");
|
||||||
|
}
|
||||||
|
|
||||||
|
thunkApi.dispatch(
|
||||||
|
openSnackbar({
|
||||||
|
level: "error",
|
||||||
|
message: errorMessage,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
return thunkApi.rejectWithValue({ error });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|||||||
@ -1,6 +1,12 @@
|
|||||||
import { createSlice, PayloadAction } from "@reduxjs/toolkit";
|
import { createSlice, PayloadAction } from "@reduxjs/toolkit";
|
||||||
import { PartnerState } from "./state";
|
import { PartnerState } from "./state";
|
||||||
import { createPartnerAccountAsync, getPartnerInfoAsync } from "./operations";
|
import {
|
||||||
|
createPartnerAccountAsync,
|
||||||
|
getPartnerInfoAsync,
|
||||||
|
deletePartnerAccountAsync,
|
||||||
|
getPartnerUsersAsync,
|
||||||
|
editPartnerInfoAsync,
|
||||||
|
} from "./operations";
|
||||||
import { LIMIT_PARTNER_VIEW_NUM } from "./constants";
|
import { LIMIT_PARTNER_VIEW_NUM } from "./constants";
|
||||||
|
|
||||||
const initialState: PartnerState = {
|
const initialState: PartnerState = {
|
||||||
@ -17,6 +23,13 @@ const initialState: PartnerState = {
|
|||||||
adminName: "",
|
adminName: "",
|
||||||
email: "",
|
email: "",
|
||||||
},
|
},
|
||||||
|
editPartner: {
|
||||||
|
users: [],
|
||||||
|
id: 0,
|
||||||
|
companyName: "",
|
||||||
|
country: "",
|
||||||
|
selectedAdminId: 0,
|
||||||
|
},
|
||||||
limit: LIMIT_PARTNER_VIEW_NUM,
|
limit: LIMIT_PARTNER_VIEW_NUM,
|
||||||
offset: 0,
|
offset: 0,
|
||||||
isLoading: false,
|
isLoading: false,
|
||||||
@ -75,6 +88,37 @@ export const partnerSlice = createSlice({
|
|||||||
state.apps.delegatedAccountId = undefined;
|
state.apps.delegatedAccountId = undefined;
|
||||||
state.apps.delegatedCompanyName = undefined;
|
state.apps.delegatedCompanyName = undefined;
|
||||||
},
|
},
|
||||||
|
changeEditPartner: (
|
||||||
|
state,
|
||||||
|
action: PayloadAction<{
|
||||||
|
id: number;
|
||||||
|
companyName: string;
|
||||||
|
country: string;
|
||||||
|
}>
|
||||||
|
) => {
|
||||||
|
const { id, companyName, country } = action.payload;
|
||||||
|
|
||||||
|
state.apps.editPartner.id = id;
|
||||||
|
state.apps.editPartner.companyName = companyName;
|
||||||
|
state.apps.editPartner.country = country;
|
||||||
|
},
|
||||||
|
changeEditCompanyName: (
|
||||||
|
state,
|
||||||
|
action: PayloadAction<{ companyName: string }>
|
||||||
|
) => {
|
||||||
|
const { companyName } = action.payload;
|
||||||
|
state.apps.editPartner.companyName = companyName;
|
||||||
|
},
|
||||||
|
changeSelectedAdminId: (
|
||||||
|
state,
|
||||||
|
action: PayloadAction<{ adminId: number }>
|
||||||
|
) => {
|
||||||
|
const { adminId } = action.payload;
|
||||||
|
state.apps.editPartner.selectedAdminId = adminId;
|
||||||
|
},
|
||||||
|
cleanupPartnerAccount: (state) => {
|
||||||
|
state.apps.editPartner = initialState.apps.editPartner;
|
||||||
|
},
|
||||||
},
|
},
|
||||||
extraReducers: (builder) => {
|
extraReducers: (builder) => {
|
||||||
builder.addCase(createPartnerAccountAsync.pending, (state) => {
|
builder.addCase(createPartnerAccountAsync.pending, (state) => {
|
||||||
@ -97,6 +141,37 @@ export const partnerSlice = createSlice({
|
|||||||
builder.addCase(getPartnerInfoAsync.rejected, (state) => {
|
builder.addCase(getPartnerInfoAsync.rejected, (state) => {
|
||||||
state.apps.isLoading = false;
|
state.apps.isLoading = false;
|
||||||
});
|
});
|
||||||
|
builder.addCase(deletePartnerAccountAsync.pending, (state) => {
|
||||||
|
state.apps.isLoading = true;
|
||||||
|
});
|
||||||
|
builder.addCase(deletePartnerAccountAsync.fulfilled, (state) => {
|
||||||
|
state.apps.isLoading = false;
|
||||||
|
});
|
||||||
|
builder.addCase(deletePartnerAccountAsync.rejected, (state) => {
|
||||||
|
state.apps.isLoading = false;
|
||||||
|
});
|
||||||
|
builder.addCase(getPartnerUsersAsync.pending, (state) => {
|
||||||
|
state.apps.isLoading = true;
|
||||||
|
});
|
||||||
|
builder.addCase(getPartnerUsersAsync.fulfilled, (state, action) => {
|
||||||
|
const { users } = action.payload;
|
||||||
|
state.apps.editPartner.users = users;
|
||||||
|
state.apps.editPartner.selectedAdminId =
|
||||||
|
users.find((user) => user.isPrimaryAdmin)?.id ?? 0;
|
||||||
|
state.apps.isLoading = false;
|
||||||
|
});
|
||||||
|
builder.addCase(getPartnerUsersAsync.rejected, (state) => {
|
||||||
|
state.apps.isLoading = false;
|
||||||
|
});
|
||||||
|
builder.addCase(editPartnerInfoAsync.pending, (state) => {
|
||||||
|
state.apps.isLoading = true;
|
||||||
|
});
|
||||||
|
builder.addCase(editPartnerInfoAsync.fulfilled, (state) => {
|
||||||
|
state.apps.isLoading = false;
|
||||||
|
});
|
||||||
|
builder.addCase(editPartnerInfoAsync.rejected, (state) => {
|
||||||
|
state.apps.isLoading = false;
|
||||||
|
});
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
export const {
|
export const {
|
||||||
@ -108,5 +183,9 @@ export const {
|
|||||||
savePageInfo,
|
savePageInfo,
|
||||||
changeDelegateAccount,
|
changeDelegateAccount,
|
||||||
cleanupDelegateAccount,
|
cleanupDelegateAccount,
|
||||||
|
changeEditPartner,
|
||||||
|
changeEditCompanyName,
|
||||||
|
changeSelectedAdminId,
|
||||||
|
cleanupPartnerAccount,
|
||||||
} = partnerSlice.actions;
|
} = partnerSlice.actions;
|
||||||
export default partnerSlice.reducer;
|
export default partnerSlice.reducer;
|
||||||
|
|||||||
@ -62,3 +62,17 @@ export const selectDelegatedAccountId = (state: RootState) =>
|
|||||||
state.partner.apps.delegatedAccountId;
|
state.partner.apps.delegatedAccountId;
|
||||||
export const selectDelegatedCompanyName = (state: RootState) =>
|
export const selectDelegatedCompanyName = (state: RootState) =>
|
||||||
state.partner.apps.delegatedCompanyName;
|
state.partner.apps.delegatedCompanyName;
|
||||||
|
|
||||||
|
// edit
|
||||||
|
export const selectEditPartnerId = (state: RootState) =>
|
||||||
|
state.partner.apps.editPartner.id;
|
||||||
|
export const selectEditPartnerCompanyName = (state: RootState) =>
|
||||||
|
state.partner.apps.editPartner.companyName;
|
||||||
|
export const selectEditPartnerCountry = (state: RootState) =>
|
||||||
|
state.partner.apps.editPartner.country;
|
||||||
|
|
||||||
|
export const selectEditPartnerUsers = (state: RootState) =>
|
||||||
|
state.partner.apps.editPartner.users;
|
||||||
|
|
||||||
|
export const selectSelectedAdminId = (state: RootState) =>
|
||||||
|
state.partner.apps.editPartner.selectedAdminId;
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
import {
|
import {
|
||||||
CreatePartnerAccountRequest,
|
CreatePartnerAccountRequest,
|
||||||
GetPartnersResponse,
|
GetPartnersResponse,
|
||||||
|
PartnerUser,
|
||||||
} from "../../api/api";
|
} from "../../api/api";
|
||||||
|
|
||||||
export interface PartnerState {
|
export interface PartnerState {
|
||||||
@ -19,4 +20,11 @@ export interface Apps {
|
|||||||
isLoading: boolean;
|
isLoading: boolean;
|
||||||
delegatedAccountId?: number;
|
delegatedAccountId?: number;
|
||||||
delegatedCompanyName?: string;
|
delegatedCompanyName?: string;
|
||||||
|
editPartner: {
|
||||||
|
users: PartnerUser[];
|
||||||
|
id: number;
|
||||||
|
companyName: string;
|
||||||
|
country: string;
|
||||||
|
selectedAdminId: number;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@ -9,6 +9,7 @@ import {
|
|||||||
UsersApi,
|
UsersApi,
|
||||||
LicensesApi,
|
LicensesApi,
|
||||||
GetAllocatableLicensesResponse,
|
GetAllocatableLicensesResponse,
|
||||||
|
MultipleImportUser,
|
||||||
} from "../../api/api";
|
} from "../../api/api";
|
||||||
import { Configuration } from "../../api/configuration";
|
import { Configuration } from "../../api/configuration";
|
||||||
import { ErrorObject, createErrorObject } from "../../common/errors";
|
import { ErrorObject, createErrorObject } from "../../common/errors";
|
||||||
@ -17,7 +18,7 @@ export const listUsersAsync = createAsyncThunk<
|
|||||||
// 正常時の戻り値の型
|
// 正常時の戻り値の型
|
||||||
GetUsersResponse,
|
GetUsersResponse,
|
||||||
// 引数
|
// 引数
|
||||||
void,
|
undefined | { userInputUserName?: string; userInputEmail?: string },
|
||||||
{
|
{
|
||||||
// rejectした時の返却値の型
|
// rejectした時の返却値の型
|
||||||
rejectValue: {
|
rejectValue: {
|
||||||
@ -32,9 +33,11 @@ export const listUsersAsync = createAsyncThunk<
|
|||||||
const accessToken = getAccessToken(state.auth);
|
const accessToken = getAccessToken(state.auth);
|
||||||
const config = new Configuration(configuration);
|
const config = new Configuration(configuration);
|
||||||
const usersApi = new UsersApi(config);
|
const usersApi = new UsersApi(config);
|
||||||
|
const userInputUserName = args?.userInputUserName;
|
||||||
|
const userInputEmail = args?.userInputEmail;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const res = await usersApi.getUsers({
|
const res = await usersApi.getUsers(userInputUserName, userInputEmail, {
|
||||||
headers: { authorization: `Bearer ${accessToken}` },
|
headers: { authorization: `Bearer ${accessToken}` },
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -383,3 +386,255 @@ export const deallocateLicenseAsync = createAsyncThunk<
|
|||||||
return thunkApi.rejectWithValue({ error });
|
return thunkApi.rejectWithValue({ error });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const deleteUserAsync = createAsyncThunk<
|
||||||
|
// 正常時の戻り値の型
|
||||||
|
{
|
||||||
|
/* Empty Object */
|
||||||
|
},
|
||||||
|
// 引数
|
||||||
|
{
|
||||||
|
userId: number;
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// rejectした時の返却値の型
|
||||||
|
rejectValue: {
|
||||||
|
error: ErrorObject;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
>("users/deleteUserAsync", async (args, thunkApi) => {
|
||||||
|
const { userId } = args;
|
||||||
|
|
||||||
|
// apiのConfigurationを取得する
|
||||||
|
const { getState } = thunkApi;
|
||||||
|
const state = getState() as RootState;
|
||||||
|
const { configuration } = state.auth;
|
||||||
|
const accessToken = getAccessToken(state.auth);
|
||||||
|
const config = new Configuration(configuration);
|
||||||
|
const usersApi = new UsersApi(config);
|
||||||
|
|
||||||
|
try {
|
||||||
|
await usersApi.deleteUser(
|
||||||
|
{
|
||||||
|
userId,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
headers: { authorization: `Bearer ${accessToken}` },
|
||||||
|
}
|
||||||
|
);
|
||||||
|
thunkApi.dispatch(
|
||||||
|
openSnackbar({
|
||||||
|
level: "info",
|
||||||
|
message: getTranslationID("common.message.success"),
|
||||||
|
})
|
||||||
|
);
|
||||||
|
return {};
|
||||||
|
} catch (e) {
|
||||||
|
// e ⇒ errorObjectに変換
|
||||||
|
const error = createErrorObject(e);
|
||||||
|
|
||||||
|
let errorMessage = getTranslationID("common.message.internalServerError");
|
||||||
|
|
||||||
|
if (error.statusCode === 400) {
|
||||||
|
if (error.code === "E014001") {
|
||||||
|
// ユーザーが削除済みのため成功
|
||||||
|
thunkApi.dispatch(
|
||||||
|
openSnackbar({
|
||||||
|
level: "info",
|
||||||
|
message: getTranslationID("common.message.success"),
|
||||||
|
})
|
||||||
|
);
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ユーザーに有効なライセンスが割り当たっているため削除不可
|
||||||
|
if (error.code === "E014007") {
|
||||||
|
errorMessage = getTranslationID(
|
||||||
|
"userListPage.message.userDeletionLicenseActiveError"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
// 管理者ユーザーため削除不可
|
||||||
|
if (error.code === "E014002") {
|
||||||
|
errorMessage = getTranslationID(
|
||||||
|
"userListPage.message.adminUserDeletionError"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
// タイピストユーザーで担当タスクがあるため削除不可
|
||||||
|
if (error.code === "E014009") {
|
||||||
|
errorMessage = getTranslationID(
|
||||||
|
"userListPage.message.typistUserDeletionTranscriptionTaskError"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
// タイピストユーザーでルーティングルールに設定されているため削除不可
|
||||||
|
if (error.code === "E014004") {
|
||||||
|
errorMessage = getTranslationID(
|
||||||
|
"userListPage.message.typistDeletionRoutingRuleError"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
// タイピストユーザーでTranscriptionistGroupに所属しているため削除不可
|
||||||
|
if (error.code === "E014005") {
|
||||||
|
errorMessage = getTranslationID(
|
||||||
|
"userListPage.message.typistUserDeletionTranscriptionistGroupError"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
// Authorユーザーで同一AuthorIDのタスクがあるため削除不可
|
||||||
|
if (error.code === "E014006") {
|
||||||
|
errorMessage = getTranslationID(
|
||||||
|
"userListPage.message.authorUserDeletionTranscriptionTaskError"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
// Authorユーザーで同一AuthorIDがルーティングルールに設定されているため削除不可
|
||||||
|
if (error.code === "E014003") {
|
||||||
|
errorMessage = getTranslationID(
|
||||||
|
"userListPage.message.authorDeletionRoutingRuleError"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
thunkApi.dispatch(
|
||||||
|
openSnackbar({
|
||||||
|
level: "error",
|
||||||
|
message: errorMessage,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
return thunkApi.rejectWithValue({ error });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export const confirmUserForceAsync = createAsyncThunk<
|
||||||
|
// 正常時の戻り値の型
|
||||||
|
{
|
||||||
|
/* Empty Object */
|
||||||
|
},
|
||||||
|
// 引数
|
||||||
|
{
|
||||||
|
userId: number;
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// rejectした時の返却値の型
|
||||||
|
rejectValue: {
|
||||||
|
error: ErrorObject;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
>("users/confirmUserForceAsync", async (args, thunkApi) => {
|
||||||
|
const { userId } = args;
|
||||||
|
|
||||||
|
// apiのConfigurationを取得する
|
||||||
|
const { getState } = thunkApi;
|
||||||
|
const state = getState() as RootState;
|
||||||
|
const { configuration } = state.auth;
|
||||||
|
const accessToken = getAccessToken(state.auth);
|
||||||
|
const config = new Configuration(configuration);
|
||||||
|
const usersApi = new UsersApi(config);
|
||||||
|
|
||||||
|
try {
|
||||||
|
await usersApi.confirmUserForce(
|
||||||
|
{
|
||||||
|
userId,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
headers: { authorization: `Bearer ${accessToken}` },
|
||||||
|
}
|
||||||
|
);
|
||||||
|
thunkApi.dispatch(
|
||||||
|
openSnackbar({
|
||||||
|
level: "info",
|
||||||
|
message: getTranslationID("common.message.success"),
|
||||||
|
})
|
||||||
|
);
|
||||||
|
return {};
|
||||||
|
} catch (e) {
|
||||||
|
// e ⇒ errorObjectに変換
|
||||||
|
const error = createErrorObject(e);
|
||||||
|
|
||||||
|
let errorMessage = getTranslationID("common.message.internalServerError");
|
||||||
|
|
||||||
|
// ユーザーが既に認証済みのため、強制認証不可
|
||||||
|
if (error.code === "E010202") {
|
||||||
|
errorMessage = getTranslationID(
|
||||||
|
"userListPage.message.alreadyEmailVerifiedError"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
thunkApi.dispatch(
|
||||||
|
openSnackbar({
|
||||||
|
level: "error",
|
||||||
|
message: errorMessage,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
return thunkApi.rejectWithValue({ error });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export const importUsersAsync = createAsyncThunk<
|
||||||
|
// 正常時の戻り値の型
|
||||||
|
{
|
||||||
|
/* Empty Object */
|
||||||
|
},
|
||||||
|
// 引数
|
||||||
|
void,
|
||||||
|
{
|
||||||
|
// rejectした時の返却値の型
|
||||||
|
rejectValue: {
|
||||||
|
error: ErrorObject;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
>("users/importUsersAsync", async (args, thunkApi) => {
|
||||||
|
// apiのConfigurationを取得する
|
||||||
|
const { getState } = thunkApi;
|
||||||
|
const state = getState() as RootState;
|
||||||
|
const { configuration } = state.auth;
|
||||||
|
const { importFileName, importUsers } = state.user.apps;
|
||||||
|
const accessToken = getAccessToken(state.auth);
|
||||||
|
const config = new Configuration(configuration);
|
||||||
|
const usersApi = new UsersApi(config);
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (importFileName === undefined) {
|
||||||
|
throw new Error("importFileName is undefined");
|
||||||
|
}
|
||||||
|
|
||||||
|
// CSVデータをAPIに送信するためのデータに変換
|
||||||
|
const users: MultipleImportUser[] = importUsers.map((user) => ({
|
||||||
|
name: user.name ?? "",
|
||||||
|
email: user.email ?? "",
|
||||||
|
role: user.role ?? 0,
|
||||||
|
authorId: user.author_id ?? undefined,
|
||||||
|
autoRenew: user.auto_assign ?? 0,
|
||||||
|
notification: user.notification ?? 0,
|
||||||
|
encryption: user.encryption ?? undefined,
|
||||||
|
encryptionPassword: user.encryption_password ?? undefined,
|
||||||
|
prompt: user.prompt ?? undefined,
|
||||||
|
}));
|
||||||
|
|
||||||
|
await usersApi.multipleImports(
|
||||||
|
{
|
||||||
|
filename: importFileName,
|
||||||
|
users,
|
||||||
|
},
|
||||||
|
{ headers: { authorization: `Bearer ${accessToken}` } }
|
||||||
|
);
|
||||||
|
|
||||||
|
thunkApi.dispatch(
|
||||||
|
openSnackbar({
|
||||||
|
level: "info",
|
||||||
|
message: getTranslationID("userListPage.message.importSuccess"),
|
||||||
|
})
|
||||||
|
);
|
||||||
|
return {};
|
||||||
|
} catch (e) {
|
||||||
|
// e ⇒ errorObjectに変換
|
||||||
|
const error = createErrorObject(e);
|
||||||
|
|
||||||
|
thunkApi.dispatch(
|
||||||
|
openSnackbar({
|
||||||
|
level: "error",
|
||||||
|
message: getTranslationID("common.message.internalServerError"),
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
return thunkApi.rejectWithValue({ error });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|||||||
@ -382,3 +382,142 @@ const convertValueBasedOnLicenseStatus = (
|
|||||||
remaining: undefined,
|
remaining: undefined,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const selectImportFileName = (state: RootState) =>
|
||||||
|
state.user.apps.importFileName;
|
||||||
|
|
||||||
|
export const selectImportValidationErrors = (state: RootState) => {
|
||||||
|
const csvUsers = state.user.apps.importUsers;
|
||||||
|
|
||||||
|
let rowNumber = 1;
|
||||||
|
const invalidInput: number[] = [];
|
||||||
|
|
||||||
|
const duplicatedEmailsMap = new Map<string, number>();
|
||||||
|
const duplicatedAuthorIdsMap = new Map<string, number>();
|
||||||
|
const overMaxRow = csvUsers.length > 100;
|
||||||
|
|
||||||
|
// eslint-disable-next-line no-restricted-syntax
|
||||||
|
for (const csvUser of csvUsers) {
|
||||||
|
rowNumber += 1;
|
||||||
|
|
||||||
|
// メールアドレスの重複がある場合、エラーとしてその行番号を追加する
|
||||||
|
const duplicatedEmailUser = csvUsers.filter(
|
||||||
|
(x) => x.email === csvUser.email
|
||||||
|
);
|
||||||
|
if (duplicatedEmailUser.length > 1) {
|
||||||
|
if (csvUser.email !== null && !duplicatedEmailsMap.has(csvUser.email)) {
|
||||||
|
duplicatedEmailsMap.set(csvUser.email, rowNumber);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// AuthorIDの重複がある場合、エラーとしてその行番号を追加する
|
||||||
|
const duplicatedAuthorIdUser = csvUsers.filter(
|
||||||
|
(x) => x.author_id === csvUser.author_id
|
||||||
|
);
|
||||||
|
if (duplicatedAuthorIdUser.length > 1) {
|
||||||
|
if (
|
||||||
|
csvUser.author_id !== null &&
|
||||||
|
!duplicatedAuthorIdsMap.has(csvUser.author_id)
|
||||||
|
) {
|
||||||
|
duplicatedAuthorIdsMap.set(csvUser.author_id, rowNumber);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// name
|
||||||
|
if (csvUser.name === null || csvUser.name.length > 225) {
|
||||||
|
invalidInput.push(rowNumber);
|
||||||
|
// eslint-disable-next-line no-continue
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// email
|
||||||
|
const emailPattern =
|
||||||
|
/^[a-zA-Z0-9!#$%&'_`/=~+\-?^{|}.]+@[a-zA-Z0-9!#$%&'_`/=~+\-?^{|}.]*\.[a-zA-Z0-9!#$%&'_`/=~+\-?^{|}.]*[a-zA-Z]$/;
|
||||||
|
if (
|
||||||
|
csvUser.name === null ||
|
||||||
|
csvUser.name.length > 225 ||
|
||||||
|
!emailPattern.test(csvUser.email ?? "")
|
||||||
|
) {
|
||||||
|
invalidInput.push(rowNumber);
|
||||||
|
// eslint-disable-next-line no-continue
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// role
|
||||||
|
if (csvUser.role === null || ![0, 1, 2].includes(csvUser.role)) {
|
||||||
|
invalidInput.push(rowNumber);
|
||||||
|
// eslint-disable-next-line no-continue
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// role=1(Author)
|
||||||
|
if (csvUser.role === 1) {
|
||||||
|
// author_id
|
||||||
|
if (csvUser.author_id === null || csvUser.author_id.length > 16) {
|
||||||
|
invalidInput.push(rowNumber);
|
||||||
|
// eslint-disable-next-line no-continue
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// 半角英数字と_の組み合わせで16文字まで
|
||||||
|
const charaTypePattern = /^[A-Z0-9_]{1,16}$/;
|
||||||
|
const charaType = new RegExp(charaTypePattern).test(csvUser.author_id);
|
||||||
|
if (!charaType) {
|
||||||
|
invalidInput.push(rowNumber);
|
||||||
|
// eslint-disable-next-line no-continue
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// encryption
|
||||||
|
if (csvUser.encryption === null || ![0, 1].includes(csvUser.encryption)) {
|
||||||
|
invalidInput.push(rowNumber);
|
||||||
|
// eslint-disable-next-line no-continue
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (csvUser.encryption === 1) {
|
||||||
|
// encryption_password
|
||||||
|
if (csvUser.encryption === 1) {
|
||||||
|
const regex = /^[!-~]{4,16}$/;
|
||||||
|
if (!regex.test(csvUser.encryption_password ?? "")) {
|
||||||
|
invalidInput.push(rowNumber);
|
||||||
|
// eslint-disable-next-line no-continue
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// prompt
|
||||||
|
if (csvUser.prompt === null || ![0, 1].includes(csvUser.prompt)) {
|
||||||
|
invalidInput.push(rowNumber);
|
||||||
|
// eslint-disable-next-line no-continue
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// auto_assign
|
||||||
|
if (csvUser.auto_assign === null || ![0, 1].includes(csvUser.auto_assign)) {
|
||||||
|
invalidInput.push(rowNumber);
|
||||||
|
// eslint-disable-next-line no-continue
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// notification
|
||||||
|
if (
|
||||||
|
csvUser.notification === null ||
|
||||||
|
![0, 1].includes(csvUser.notification)
|
||||||
|
) {
|
||||||
|
invalidInput.push(rowNumber);
|
||||||
|
// eslint-disable-next-line no-continue
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const duplicatedEmails = Array.from(duplicatedEmailsMap.values());
|
||||||
|
const duplicatedAuthorIds = Array.from(duplicatedAuthorIdsMap.values());
|
||||||
|
|
||||||
|
return {
|
||||||
|
invalidInput,
|
||||||
|
duplicatedEmails,
|
||||||
|
duplicatedAuthorIds,
|
||||||
|
overMaxRow,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
import { CSVType } from "common/parser";
|
||||||
import { User, AllocatableLicenseInfo } from "../../api/api";
|
import { User, AllocatableLicenseInfo } from "../../api/api";
|
||||||
import { AddUser, UpdateUser, LicenseAllocateUser } from "./types";
|
import { AddUser, UpdateUser, LicenseAllocateUser } from "./types";
|
||||||
|
|
||||||
@ -19,4 +20,6 @@ export interface Apps {
|
|||||||
selectedlicenseId: number;
|
selectedlicenseId: number;
|
||||||
hasPasswordMask: boolean;
|
hasPasswordMask: boolean;
|
||||||
isLoading: boolean;
|
isLoading: boolean;
|
||||||
|
importFileName: string | undefined;
|
||||||
|
importUsers: CSVType[];
|
||||||
}
|
}
|
||||||
|
|||||||
@ -54,14 +54,14 @@ export interface LicenseAllocateUser {
|
|||||||
remaining?: number;
|
remaining?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type RoleType = typeof USER_ROLES[keyof typeof USER_ROLES];
|
export type RoleType = (typeof USER_ROLES)[keyof typeof USER_ROLES];
|
||||||
|
|
||||||
// 受け取った値がUSER_ROLESの型であるかどうかを判定する
|
// 受け取った値がUSER_ROLESの型であるかどうかを判定する
|
||||||
export const isRoleType = (role: string): role is RoleType =>
|
export const isRoleType = (role: string): role is RoleType =>
|
||||||
Object.values(USER_ROLES).includes(role as RoleType);
|
Object.values(USER_ROLES).includes(role as RoleType);
|
||||||
|
|
||||||
export type LicenseStatusType =
|
export type LicenseStatusType =
|
||||||
typeof LICENSE_STATUS[keyof typeof LICENSE_STATUS];
|
(typeof LICENSE_STATUS)[keyof typeof LICENSE_STATUS];
|
||||||
|
|
||||||
// 受け取った値がLicenseStatusTypeの型であるかどうかを判定する
|
// 受け取った値がLicenseStatusTypeの型であるかどうかを判定する
|
||||||
export const isLicenseStatusType = (
|
export const isLicenseStatusType = (
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
import { PayloadAction, createSlice } from "@reduxjs/toolkit";
|
import { PayloadAction, createSlice } from "@reduxjs/toolkit";
|
||||||
import { USER_ROLES } from "components/auth/constants";
|
import { USER_ROLES } from "components/auth/constants";
|
||||||
|
import { CSVType } from "common/parser";
|
||||||
import { UsersState } from "./state";
|
import { UsersState } from "./state";
|
||||||
import {
|
import {
|
||||||
addUserAsync,
|
addUserAsync,
|
||||||
@ -7,6 +8,8 @@ import {
|
|||||||
updateUserAsync,
|
updateUserAsync,
|
||||||
getAllocatableLicensesAsync,
|
getAllocatableLicensesAsync,
|
||||||
deallocateLicenseAsync,
|
deallocateLicenseAsync,
|
||||||
|
deleteUserAsync,
|
||||||
|
importUsersAsync,
|
||||||
} from "./operations";
|
} from "./operations";
|
||||||
import { RoleType, UserView } from "./types";
|
import { RoleType, UserView } from "./types";
|
||||||
|
|
||||||
@ -60,6 +63,8 @@ const initialState: UsersState = {
|
|||||||
selectedlicenseId: 0,
|
selectedlicenseId: 0,
|
||||||
hasPasswordMask: false,
|
hasPasswordMask: false,
|
||||||
isLoading: false,
|
isLoading: false,
|
||||||
|
importFileName: undefined,
|
||||||
|
importUsers: [],
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -241,6 +246,21 @@ export const userSlice = createSlice({
|
|||||||
state.apps.licenseAllocateUser = initialState.apps.licenseAllocateUser;
|
state.apps.licenseAllocateUser = initialState.apps.licenseAllocateUser;
|
||||||
state.apps.selectedlicenseId = initialState.apps.selectedlicenseId;
|
state.apps.selectedlicenseId = initialState.apps.selectedlicenseId;
|
||||||
},
|
},
|
||||||
|
changeImportFileName: (
|
||||||
|
state,
|
||||||
|
action: PayloadAction<{ fileName: string }>
|
||||||
|
) => {
|
||||||
|
const { fileName } = action.payload;
|
||||||
|
state.apps.importFileName = fileName;
|
||||||
|
},
|
||||||
|
changeImportCsv: (state, action: PayloadAction<{ users: CSVType[] }>) => {
|
||||||
|
const { users } = action.payload;
|
||||||
|
state.apps.importUsers = users;
|
||||||
|
},
|
||||||
|
cleanupImportUsers: (state) => {
|
||||||
|
state.apps.importFileName = initialState.apps.importFileName;
|
||||||
|
state.apps.importUsers = initialState.apps.importUsers;
|
||||||
|
},
|
||||||
},
|
},
|
||||||
extraReducers: (builder) => {
|
extraReducers: (builder) => {
|
||||||
builder.addCase(listUsersAsync.pending, (state) => {
|
builder.addCase(listUsersAsync.pending, (state) => {
|
||||||
@ -290,6 +310,24 @@ export const userSlice = createSlice({
|
|||||||
builder.addCase(deallocateLicenseAsync.rejected, (state) => {
|
builder.addCase(deallocateLicenseAsync.rejected, (state) => {
|
||||||
state.apps.isLoading = false;
|
state.apps.isLoading = false;
|
||||||
});
|
});
|
||||||
|
builder.addCase(deleteUserAsync.pending, (state) => {
|
||||||
|
state.apps.isLoading = true;
|
||||||
|
});
|
||||||
|
builder.addCase(deleteUserAsync.fulfilled, (state) => {
|
||||||
|
state.apps.isLoading = false;
|
||||||
|
});
|
||||||
|
builder.addCase(deleteUserAsync.rejected, (state) => {
|
||||||
|
state.apps.isLoading = false;
|
||||||
|
});
|
||||||
|
builder.addCase(importUsersAsync.pending, (state) => {
|
||||||
|
state.apps.isLoading = true;
|
||||||
|
});
|
||||||
|
builder.addCase(importUsersAsync.fulfilled, (state) => {
|
||||||
|
state.apps.isLoading = false;
|
||||||
|
});
|
||||||
|
builder.addCase(importUsersAsync.rejected, (state) => {
|
||||||
|
state.apps.isLoading = false;
|
||||||
|
});
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -317,6 +355,9 @@ export const {
|
|||||||
changeLicenseAllocateUser,
|
changeLicenseAllocateUser,
|
||||||
changeSelectedlicenseId,
|
changeSelectedlicenseId,
|
||||||
cleanupLicenseAllocateInfo,
|
cleanupLicenseAllocateInfo,
|
||||||
|
changeImportFileName,
|
||||||
|
changeImportCsv,
|
||||||
|
cleanupImportUsers,
|
||||||
} = userSlice.actions;
|
} = userSlice.actions;
|
||||||
|
|
||||||
export default userSlice.reducer;
|
export default userSlice.reducer;
|
||||||
|
|||||||
@ -115,3 +115,78 @@ export const uploadTemplateAsync = createAsyncThunk<
|
|||||||
return thunkApi.rejectWithValue({ error });
|
return thunkApi.rejectWithValue({ error });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const deleteTemplateAsync = createAsyncThunk<
|
||||||
|
{
|
||||||
|
/* Empty Object */
|
||||||
|
},
|
||||||
|
{ templateFileId: number },
|
||||||
|
{
|
||||||
|
// rejectした時の返却値の型
|
||||||
|
rejectValue: {
|
||||||
|
error: ErrorObject;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
>("workflow/deleteTemplateAsync", async (args, thunkApi) => {
|
||||||
|
const { templateFileId } = args;
|
||||||
|
// apiのConfigurationを取得する
|
||||||
|
const { getState } = thunkApi;
|
||||||
|
const state = getState() as RootState;
|
||||||
|
const { configuration } = state.auth;
|
||||||
|
const accessToken = getAccessToken(state.auth);
|
||||||
|
const config = new Configuration(configuration);
|
||||||
|
const templateApi = new TemplatesApi(config);
|
||||||
|
|
||||||
|
try {
|
||||||
|
// ファイルを削除する
|
||||||
|
await templateApi.deleteTemplateFile(templateFileId, {
|
||||||
|
headers: { authorization: `Bearer ${accessToken}` },
|
||||||
|
});
|
||||||
|
|
||||||
|
thunkApi.dispatch(
|
||||||
|
openSnackbar({
|
||||||
|
level: "info",
|
||||||
|
message: getTranslationID("common.message.success"),
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
return {};
|
||||||
|
} catch (e) {
|
||||||
|
// e ⇒ errorObjectに変換"
|
||||||
|
const error = createErrorObject(e);
|
||||||
|
|
||||||
|
if (error.code === "E016001") {
|
||||||
|
// テンプレートファイルが削除済みの場合は成功扱いとする
|
||||||
|
thunkApi.dispatch(
|
||||||
|
openSnackbar({
|
||||||
|
level: "info",
|
||||||
|
message: getTranslationID("common.message.success"),
|
||||||
|
})
|
||||||
|
);
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
let message = getTranslationID("common.message.internalServerError");
|
||||||
|
|
||||||
|
// テンプレートファイルがルーティングルールに紐づく場合はエラー
|
||||||
|
if (error.code === "E016002") {
|
||||||
|
message = getTranslationID(
|
||||||
|
"templateFilePage.message.deleteFailedWorkflowAssigned"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
// テンプレートファイルが未完了のタスクに紐づく場合はエラー
|
||||||
|
if (error.code === "E016003") {
|
||||||
|
message = getTranslationID(
|
||||||
|
"templateFilePage.message.deleteFailedTaskAssigned"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
thunkApi.dispatch(
|
||||||
|
openSnackbar({
|
||||||
|
level: "error",
|
||||||
|
message,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
return thunkApi.rejectWithValue({ error });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|||||||
@ -1,6 +1,10 @@
|
|||||||
import { PayloadAction, createSlice } from "@reduxjs/toolkit";
|
import { PayloadAction, createSlice } from "@reduxjs/toolkit";
|
||||||
import { TemplateState } from "./state";
|
import { TemplateState } from "./state";
|
||||||
import { listTemplateAsync, uploadTemplateAsync } from "./operations";
|
import {
|
||||||
|
deleteTemplateAsync,
|
||||||
|
listTemplateAsync,
|
||||||
|
uploadTemplateAsync,
|
||||||
|
} from "./operations";
|
||||||
|
|
||||||
const initialState: TemplateState = {
|
const initialState: TemplateState = {
|
||||||
apps: {
|
apps: {
|
||||||
@ -45,6 +49,15 @@ export const templateSlice = createSlice({
|
|||||||
builder.addCase(uploadTemplateAsync.rejected, (state) => {
|
builder.addCase(uploadTemplateAsync.rejected, (state) => {
|
||||||
state.apps.isUploading = false;
|
state.apps.isUploading = false;
|
||||||
});
|
});
|
||||||
|
builder.addCase(deleteTemplateAsync.pending, (state) => {
|
||||||
|
state.apps.isLoading = true;
|
||||||
|
});
|
||||||
|
builder.addCase(deleteTemplateAsync.fulfilled, (state) => {
|
||||||
|
state.apps.isLoading = false;
|
||||||
|
});
|
||||||
|
builder.addCase(deleteTemplateAsync.rejected, (state) => {
|
||||||
|
state.apps.isLoading = false;
|
||||||
|
});
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -269,3 +269,70 @@ export const updateTypistGroupAsync = createAsyncThunk<
|
|||||||
return thunkApi.rejectWithValue({ error });
|
return thunkApi.rejectWithValue({ error });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const deleteTypistGroupAsync = createAsyncThunk<
|
||||||
|
{
|
||||||
|
/* Empty Object */
|
||||||
|
},
|
||||||
|
{
|
||||||
|
typistGroupId: number;
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// rejectした時の返却値の型
|
||||||
|
rejectValue: {
|
||||||
|
error: ErrorObject;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
>("workflow/deleteTypistGroupAsync", async (args, thunkApi) => {
|
||||||
|
const { typistGroupId } = args;
|
||||||
|
|
||||||
|
// apiのConfigurationを取得する
|
||||||
|
const { getState } = thunkApi;
|
||||||
|
const state = getState() as RootState;
|
||||||
|
const { configuration } = state.auth;
|
||||||
|
const accessToken = getAccessToken(state.auth);
|
||||||
|
const config = new Configuration(configuration);
|
||||||
|
const accountsApi = new AccountsApi(config);
|
||||||
|
|
||||||
|
try {
|
||||||
|
await accountsApi.deleteTypistGroup(typistGroupId, {
|
||||||
|
headers: { authorization: `Bearer ${accessToken}` },
|
||||||
|
});
|
||||||
|
|
||||||
|
thunkApi.dispatch(
|
||||||
|
openSnackbar({
|
||||||
|
level: "info",
|
||||||
|
message: getTranslationID("common.message.success"),
|
||||||
|
})
|
||||||
|
);
|
||||||
|
return {};
|
||||||
|
} catch (e) {
|
||||||
|
// e ⇒ errorObjectに変換"
|
||||||
|
const error = createErrorObject(e);
|
||||||
|
|
||||||
|
// すでに削除されていた場合は成功扱いする
|
||||||
|
if (error.code === "E015001") {
|
||||||
|
thunkApi.dispatch(
|
||||||
|
openSnackbar({
|
||||||
|
level: "info",
|
||||||
|
message: getTranslationID("common.message.success"),
|
||||||
|
})
|
||||||
|
);
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
// 以下は実際の削除失敗
|
||||||
|
let message = getTranslationID("common.message.internalServerError");
|
||||||
|
if (error.code === "E015002")
|
||||||
|
message = getTranslationID(
|
||||||
|
"typistGroupSetting.message.deleteFailedWorkflowAssigned"
|
||||||
|
);
|
||||||
|
if (error.code === "E015003")
|
||||||
|
message = getTranslationID(
|
||||||
|
"typistGroupSetting.message.deleteFailedCheckoutPermissionExisted"
|
||||||
|
);
|
||||||
|
|
||||||
|
thunkApi.dispatch(openSnackbar({ level: "error", message }));
|
||||||
|
return thunkApi.rejectWithValue({ error });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|||||||
@ -6,6 +6,7 @@ import {
|
|||||||
listTypistGroupsAsync,
|
listTypistGroupsAsync,
|
||||||
listTypistsAsync,
|
listTypistsAsync,
|
||||||
updateTypistGroupAsync,
|
updateTypistGroupAsync,
|
||||||
|
deleteTypistGroupAsync,
|
||||||
} from "./operations";
|
} from "./operations";
|
||||||
|
|
||||||
const initialState: TypistGroupState = {
|
const initialState: TypistGroupState = {
|
||||||
@ -106,6 +107,15 @@ export const typistGroupSlice = createSlice({
|
|||||||
builder.addCase(updateTypistGroupAsync.rejected, (state) => {
|
builder.addCase(updateTypistGroupAsync.rejected, (state) => {
|
||||||
state.apps.isLoading = false;
|
state.apps.isLoading = false;
|
||||||
});
|
});
|
||||||
|
builder.addCase(deleteTypistGroupAsync.pending, (state) => {
|
||||||
|
state.apps.isLoading = true;
|
||||||
|
});
|
||||||
|
builder.addCase(deleteTypistGroupAsync.fulfilled, (state) => {
|
||||||
|
state.apps.isLoading = false;
|
||||||
|
});
|
||||||
|
builder.addCase(deleteTypistGroupAsync.rejected, (state) => {
|
||||||
|
state.apps.isLoading = false;
|
||||||
|
});
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -2,7 +2,7 @@ import { OPTION_ITEMS_DEFAULT_VALUE_TYPE } from "./constants";
|
|||||||
|
|
||||||
// OPTION_ITEMS_DEFAULT_VALUE_TYPEからOptionItemDefaultValueTypeを作成する
|
// OPTION_ITEMS_DEFAULT_VALUE_TYPEからOptionItemDefaultValueTypeを作成する
|
||||||
export type OptionItemsDefaultValueType =
|
export type OptionItemsDefaultValueType =
|
||||||
typeof OPTION_ITEMS_DEFAULT_VALUE_TYPE[keyof typeof OPTION_ITEMS_DEFAULT_VALUE_TYPE];
|
(typeof OPTION_ITEMS_DEFAULT_VALUE_TYPE)[keyof typeof OPTION_ITEMS_DEFAULT_VALUE_TYPE];
|
||||||
|
|
||||||
// 受け取った値がOptionItemDefaultValueType型かどうかを判定する
|
// 受け取った値がOptionItemDefaultValueType型かどうかを判定する
|
||||||
export const isOptionItemDefaultValueType = (
|
export const isOptionItemDefaultValueType = (
|
||||||
|
|||||||
@ -0,0 +1,169 @@
|
|||||||
|
import React, { useCallback, useState } from "react";
|
||||||
|
import { useDispatch, useSelector } from "react-redux";
|
||||||
|
import {
|
||||||
|
selectInputValidationErrors,
|
||||||
|
selectFileDeleteSetting,
|
||||||
|
updateFileDeleteSettingAsync,
|
||||||
|
selectIsLoading,
|
||||||
|
getAccountRelationsAsync,
|
||||||
|
} from "features/account";
|
||||||
|
import { AppDispatch } from "app/store";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import styles from "../../styles/app.module.scss";
|
||||||
|
import { getTranslationID } from "../../translation";
|
||||||
|
import close from "../../assets/images/close.svg";
|
||||||
|
import {
|
||||||
|
changeAutoFileDelete,
|
||||||
|
changeFileRetentionDays,
|
||||||
|
} from "../../features/account/accountSlice";
|
||||||
|
import progress_activit from "../../assets/images/progress_activit.svg";
|
||||||
|
|
||||||
|
interface FileDeleteSettingPopupProps {
|
||||||
|
// eslint-disable-next-line react/no-unused-prop-types
|
||||||
|
onClose: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const FileDeleteSettingPopup: React.FC<FileDeleteSettingPopupProps> = (
|
||||||
|
props
|
||||||
|
) => {
|
||||||
|
const { onClose } = props;
|
||||||
|
|
||||||
|
const dispatch: AppDispatch = useDispatch();
|
||||||
|
const [t] = useTranslation();
|
||||||
|
|
||||||
|
const isLoading = useSelector(selectIsLoading);
|
||||||
|
const fileDeleteSetting = useSelector(selectFileDeleteSetting);
|
||||||
|
const { hasFileRetentionDaysError } = useSelector(
|
||||||
|
selectInputValidationErrors
|
||||||
|
);
|
||||||
|
|
||||||
|
const closePopup = useCallback(() => {
|
||||||
|
if (isLoading) return;
|
||||||
|
onClose();
|
||||||
|
}, [isLoading, onClose]);
|
||||||
|
|
||||||
|
const [isPushSubmitButton, setIsPushSubmitButton] = useState<boolean>(false);
|
||||||
|
|
||||||
|
const onUpdateFileDeleteSetting = useCallback(async () => {
|
||||||
|
if (isLoading) return;
|
||||||
|
setIsPushSubmitButton(true);
|
||||||
|
if (hasFileRetentionDaysError) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { meta } = await dispatch(
|
||||||
|
updateFileDeleteSettingAsync({
|
||||||
|
autoFileDelete: fileDeleteSetting.autoFileDelete,
|
||||||
|
fileRetentionDays: fileDeleteSetting.fileRetentionDays,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
setIsPushSubmitButton(false);
|
||||||
|
if (meta.requestStatus === "fulfilled") {
|
||||||
|
closePopup();
|
||||||
|
dispatch(getAccountRelationsAsync());
|
||||||
|
}
|
||||||
|
}, [
|
||||||
|
closePopup,
|
||||||
|
dispatch,
|
||||||
|
fileDeleteSetting.autoFileDelete,
|
||||||
|
fileDeleteSetting.fileRetentionDays,
|
||||||
|
hasFileRetentionDaysError,
|
||||||
|
isLoading,
|
||||||
|
]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={`${styles.modal} ${styles.isShow}`}>
|
||||||
|
<div className={styles.modalBox}>
|
||||||
|
<p className={styles.modalTitle}>
|
||||||
|
{t(getTranslationID("fileDeleteSettingPopup.label.title"))}
|
||||||
|
<button type="button" onClick={closePopup}>
|
||||||
|
<img src={close} className={styles.modalTitleIcon} alt="close" />
|
||||||
|
</button>
|
||||||
|
</p>
|
||||||
|
<form className={styles.form}>
|
||||||
|
<dl className={`${styles.formList} ${styles.hasbg}`}>
|
||||||
|
<dt className={styles.formTitle} />
|
||||||
|
<dt>
|
||||||
|
{t(
|
||||||
|
getTranslationID(
|
||||||
|
"fileDeleteSettingPopup.label.autoFileDeleteCheck"
|
||||||
|
)
|
||||||
|
)}
|
||||||
|
</dt>
|
||||||
|
<dd className={styles.last}>
|
||||||
|
<p>
|
||||||
|
{/* eslint-disable-next-line jsx-a11y/label-has-associated-control */}
|
||||||
|
<label>
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
className={styles.formCheck}
|
||||||
|
checked={fileDeleteSetting.autoFileDelete}
|
||||||
|
onChange={(e) => {
|
||||||
|
dispatch(
|
||||||
|
changeAutoFileDelete({
|
||||||
|
autoFileDelete: e.target.checked,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
</p>
|
||||||
|
<p className={styles.txWsline}>
|
||||||
|
{t(
|
||||||
|
getTranslationID(
|
||||||
|
"fileDeleteSettingPopup.label.daysAnnotation"
|
||||||
|
)
|
||||||
|
)}
|
||||||
|
</p>
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
min={1}
|
||||||
|
max={999}
|
||||||
|
value={fileDeleteSetting.fileRetentionDays}
|
||||||
|
className={`${styles.formInput} ${styles.short}`}
|
||||||
|
disabled={!fileDeleteSetting.autoFileDelete}
|
||||||
|
onChange={(e) => {
|
||||||
|
dispatch(
|
||||||
|
changeFileRetentionDays({
|
||||||
|
fileRetentionDays: Number(e.target.value),
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>{" "}
|
||||||
|
{t(getTranslationID("fileDeleteSettingPopup.label.days"))}
|
||||||
|
{isPushSubmitButton && hasFileRetentionDaysError && (
|
||||||
|
<span className={styles.formError}>
|
||||||
|
{t(
|
||||||
|
getTranslationID(
|
||||||
|
"fileDeleteSettingPopup.label.daysValidationError"
|
||||||
|
)
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</dd>
|
||||||
|
<dd className={`${styles.full} ${styles.alignCenter}`}>
|
||||||
|
<input
|
||||||
|
type="button"
|
||||||
|
name="submit"
|
||||||
|
value={t(
|
||||||
|
getTranslationID("fileDeleteSettingPopup.label.saveButton")
|
||||||
|
)}
|
||||||
|
className={`${styles.formSubmit} ${styles.marginBtm1} ${
|
||||||
|
!isLoading ? styles.isActive : ""
|
||||||
|
}`}
|
||||||
|
onClick={onUpdateFileDeleteSetting}
|
||||||
|
disabled={isLoading}
|
||||||
|
/>
|
||||||
|
</dd>
|
||||||
|
<img
|
||||||
|
style={{ display: isLoading ? "inline" : "none" }}
|
||||||
|
src={progress_activit}
|
||||||
|
className={styles.icLoading}
|
||||||
|
alt="Loading"
|
||||||
|
/>
|
||||||
|
</dl>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
@ -1,5 +1,4 @@
|
|||||||
import { AppDispatch } from "app/store";
|
import { AppDispatch } from "app/store";
|
||||||
import { UpdateTokenTimer } from "components/auth/updateTokenTimer";
|
|
||||||
import Footer from "components/footer";
|
import Footer from "components/footer";
|
||||||
import Header from "components/header";
|
import Header from "components/header";
|
||||||
import React, { useCallback, useEffect, useState } from "react";
|
import React, { useCallback, useEffect, useState } from "react";
|
||||||
@ -23,6 +22,7 @@ import { getTranslationID } from "translation";
|
|||||||
import { TIERS } from "components/auth/constants";
|
import { TIERS } from "components/auth/constants";
|
||||||
import { isApproveTier } from "features/auth";
|
import { isApproveTier } from "features/auth";
|
||||||
import { DeleteAccountPopup } from "./deleteAccountPopup";
|
import { DeleteAccountPopup } from "./deleteAccountPopup";
|
||||||
|
import { FileDeleteSettingPopup } from "./fileDeleteSettingPopup";
|
||||||
import progress_activit from "../../assets/images/progress_activit.svg";
|
import progress_activit from "../../assets/images/progress_activit.svg";
|
||||||
|
|
||||||
const AccountPage: React.FC = (): JSX.Element => {
|
const AccountPage: React.FC = (): JSX.Element => {
|
||||||
@ -40,10 +40,17 @@ const AccountPage: React.FC = (): JSX.Element => {
|
|||||||
const [isDeleteAccountPopupOpen, setIsDeleteAccountPopupOpen] =
|
const [isDeleteAccountPopupOpen, setIsDeleteAccountPopupOpen] =
|
||||||
useState(false);
|
useState(false);
|
||||||
|
|
||||||
|
const [isFileDeleteSettingPopupOpen, setIsFileDeleteSettingPopupOpen] =
|
||||||
|
useState(false);
|
||||||
|
|
||||||
const onDeleteAccountOpen = useCallback(() => {
|
const onDeleteAccountOpen = useCallback(() => {
|
||||||
setIsDeleteAccountPopupOpen(true);
|
setIsDeleteAccountPopupOpen(true);
|
||||||
}, [setIsDeleteAccountPopupOpen]);
|
}, [setIsDeleteAccountPopupOpen]);
|
||||||
|
|
||||||
|
const onDeleteFileDeleteSettingOpen = useCallback(() => {
|
||||||
|
setIsFileDeleteSettingPopupOpen(true);
|
||||||
|
}, [setIsFileDeleteSettingPopupOpen]);
|
||||||
|
|
||||||
// 階層表示用
|
// 階層表示用
|
||||||
const tierNames: { [key: number]: string } = {
|
const tierNames: { [key: number]: string } = {
|
||||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||||
@ -89,9 +96,15 @@ const AccountPage: React.FC = (): JSX.Element => {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
{isFileDeleteSettingPopupOpen && (
|
||||||
|
<FileDeleteSettingPopup
|
||||||
|
onClose={() => {
|
||||||
|
setIsFileDeleteSettingPopupOpen(false);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
<div className={styles.wrap}>
|
<div className={styles.wrap}>
|
||||||
<Header />
|
<Header />
|
||||||
<UpdateTokenTimer />
|
|
||||||
<main className={styles.main}>
|
<main className={styles.main}>
|
||||||
<div>
|
<div>
|
||||||
<div className={styles.pageHeader}>
|
<div className={styles.pageHeader}>
|
||||||
@ -102,12 +115,13 @@ const AccountPage: React.FC = (): JSX.Element => {
|
|||||||
|
|
||||||
<section className={styles.account}>
|
<section className={styles.account}>
|
||||||
<div className={styles.boxFlex}>
|
<div className={styles.boxFlex}>
|
||||||
{/* File Delete Setting は現状不要のため非表示
|
|
||||||
<ul className={`${styles.menuAction} ${styles.box100}`}>
|
<ul className={`${styles.menuAction} ${styles.box100}`}>
|
||||||
<li>
|
<li>
|
||||||
|
{/* eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions */}
|
||||||
<a
|
<a
|
||||||
href="account_setting.html"
|
|
||||||
className={`${styles.menuLink} ${styles.isActive}`}
|
className={`${styles.menuLink} ${styles.isActive}`}
|
||||||
|
onClick={onDeleteFileDeleteSettingOpen}
|
||||||
|
data-tag="open-file-delete-setting-popup"
|
||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
src="images/file_delete.svg"
|
src="images/file_delete.svg"
|
||||||
@ -120,7 +134,6 @@ const AccountPage: React.FC = (): JSX.Element => {
|
|||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
*/}
|
|
||||||
|
|
||||||
<div className={styles.marginRgt3}>
|
<div className={styles.marginRgt3}>
|
||||||
<dl className={styles.listVertical}>
|
<dl className={styles.listVertical}>
|
||||||
@ -158,7 +171,7 @@ const AccountPage: React.FC = (): JSX.Element => {
|
|||||||
changeDealer({
|
changeDealer({
|
||||||
parentAccountId:
|
parentAccountId:
|
||||||
dealers.find(
|
dealers.find(
|
||||||
(x) => x.name === event.target.value
|
(x) => x.id === Number(event.target.value)
|
||||||
)?.id || undefined,
|
)?.id || undefined,
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
@ -175,7 +188,7 @@ const AccountPage: React.FC = (): JSX.Element => {
|
|||||||
)} --`}
|
)} --`}
|
||||||
</option>
|
</option>
|
||||||
{dealers.map((x) => (
|
{dealers.map((x) => (
|
||||||
<option key={x.name} value={x.name}>
|
<option key={x.id} value={x.id}>
|
||||||
{x.name}
|
{x.name}
|
||||||
</option>
|
</option>
|
||||||
))}
|
))}
|
||||||
@ -216,9 +229,23 @@ const AccountPage: React.FC = (): JSX.Element => {
|
|||||||
)
|
)
|
||||||
)}
|
)}
|
||||||
</dd>
|
</dd>
|
||||||
|
<dd
|
||||||
|
style={{ paddingBottom: 0 }}
|
||||||
|
className={`${styles.full}`}
|
||||||
|
/>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
{!isTier5 && <dd>-</dd>}
|
{!isTier5 && <dd>-</dd>}
|
||||||
|
<dt>
|
||||||
|
{t(
|
||||||
|
getTranslationID("accountPage.label.fileRetentionDays")
|
||||||
|
)}
|
||||||
|
</dt>
|
||||||
|
<dd>
|
||||||
|
{viewInfo.account.autoFileDelete
|
||||||
|
? viewInfo.account.fileRetentionDays
|
||||||
|
: "-"}
|
||||||
|
</dd>
|
||||||
</dl>
|
</dl>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@ -1,13 +1,15 @@
|
|||||||
import React, { useCallback } from "react";
|
import React, { useCallback, useEffect, useState } from "react";
|
||||||
import styles from "styles/app.module.scss";
|
import styles from "styles/app.module.scss";
|
||||||
import { useSelector } from "react-redux";
|
import { useDispatch, useSelector } from "react-redux";
|
||||||
import {
|
import {
|
||||||
selectSelectedFileTask,
|
selectSelectedFileTask,
|
||||||
selectIsLoading,
|
selectIsLoading,
|
||||||
PRIORITY,
|
PRIORITY,
|
||||||
|
renameFileAsync,
|
||||||
} from "features/dictation";
|
} from "features/dictation";
|
||||||
import { getTranslationID } from "translation";
|
import { getTranslationID } from "translation";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { AppDispatch } from "app/store";
|
||||||
import close from "../../assets/images/close.svg";
|
import close from "../../assets/images/close.svg";
|
||||||
import lock from "../../assets/images/lock.svg";
|
import lock from "../../assets/images/lock.svg";
|
||||||
|
|
||||||
@ -19,14 +21,55 @@ interface FilePropertyPopupProps {
|
|||||||
export const FilePropertyPopup: React.FC<FilePropertyPopupProps> = (props) => {
|
export const FilePropertyPopup: React.FC<FilePropertyPopupProps> = (props) => {
|
||||||
const { onClose, isOpen } = props;
|
const { onClose, isOpen } = props;
|
||||||
const [t] = useTranslation();
|
const [t] = useTranslation();
|
||||||
|
const dispatch: AppDispatch = useDispatch();
|
||||||
const isLoading = useSelector(selectIsLoading);
|
const isLoading = useSelector(selectIsLoading);
|
||||||
|
|
||||||
|
const [isPushSaveButton, setIsPushSaveButton] = useState<boolean>(false);
|
||||||
|
|
||||||
// ポップアップを閉じる処理
|
// ポップアップを閉じる処理
|
||||||
const closePopup = useCallback(() => {
|
const closePopup = useCallback(() => {
|
||||||
|
setIsPushSaveButton(false);
|
||||||
onClose(false);
|
onClose(false);
|
||||||
}, [onClose]);
|
}, [onClose]);
|
||||||
const selectedFileTask = useSelector(selectSelectedFileTask);
|
const selectedFileTask = useSelector(selectSelectedFileTask);
|
||||||
|
|
||||||
|
const [fileName, setFileName] = useState<string>("");
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (isOpen) {
|
||||||
|
setFileName(selectedFileTask?.fileName ?? "");
|
||||||
|
}
|
||||||
|
}, [selectedFileTask, isOpen]);
|
||||||
|
|
||||||
|
// ファイル名の保存処理
|
||||||
|
const saveFileName = useCallback(async () => {
|
||||||
|
setIsPushSaveButton(true);
|
||||||
|
if (fileName.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ダイアログ確認
|
||||||
|
if (
|
||||||
|
/* eslint-disable-next-line no-alert */
|
||||||
|
!window.confirm(t(getTranslationID("common.message.dialogConfirm")))
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { meta } = await dispatch(
|
||||||
|
renameFileAsync({
|
||||||
|
audioFileId: selectedFileTask?.audioFileId ?? 0,
|
||||||
|
fileName,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
setIsPushSaveButton(false);
|
||||||
|
|
||||||
|
if (meta.requestStatus === "fulfilled") {
|
||||||
|
onClose(true);
|
||||||
|
}
|
||||||
|
}, [t, dispatch, onClose, fileName, selectedFileTask]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={`${styles.modal} ${isOpen ? styles.isShow : ""}`}>
|
<div className={`${styles.modal} ${isOpen ? styles.isShow : ""}`}>
|
||||||
<div className={styles.modalBox}>
|
<div className={styles.modalBox}>
|
||||||
@ -45,7 +88,41 @@ export const FilePropertyPopup: React.FC<FilePropertyPopupProps> = (props) => {
|
|||||||
{t(getTranslationID("filePropertyPopup.label.general"))}
|
{t(getTranslationID("filePropertyPopup.label.general"))}
|
||||||
</dt>
|
</dt>
|
||||||
<dt>{t(getTranslationID("dictationPage.label.fileName"))}</dt>
|
<dt>{t(getTranslationID("dictationPage.label.fileName"))}</dt>
|
||||||
<dd>{selectedFileTask?.fileName.replace(".zip", "") ?? ""}</dd>
|
<dd className={styles.hasInput}>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
size={40}
|
||||||
|
maxLength={64}
|
||||||
|
value={fileName}
|
||||||
|
className={`${styles.formInput} ${styles.short} ${
|
||||||
|
isPushSaveButton && fileName.length === 0 && styles.isError
|
||||||
|
}`}
|
||||||
|
onChange={(e) => setFileName(e.target.value)}
|
||||||
|
/>
|
||||||
|
<input
|
||||||
|
type="button"
|
||||||
|
name="submit"
|
||||||
|
value={t(getTranslationID("dictationPage.label.fileNameSave"))}
|
||||||
|
className={`${styles.formSubmit} ${styles.isActive}`}
|
||||||
|
style={{
|
||||||
|
position: "relative",
|
||||||
|
marginTop: "0.2rem",
|
||||||
|
right: "auto",
|
||||||
|
maxWidth: "18rem",
|
||||||
|
whiteSpace: "normal",
|
||||||
|
overflowWrap: "break-word",
|
||||||
|
fontSize: "small",
|
||||||
|
}}
|
||||||
|
onClick={saveFileName}
|
||||||
|
/>
|
||||||
|
{isPushSaveButton && fileName.length === 0 && (
|
||||||
|
<span className={styles.formError}>
|
||||||
|
{t(getTranslationID("common.message.inputEmptyError"))}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</dd>
|
||||||
|
<dt>{t(getTranslationID("dictationPage.label.rawFileName"))}</dt>
|
||||||
|
<dd>{selectedFileTask?.rawFileName ?? ""}</dd>
|
||||||
<dt>{t(getTranslationID("dictationPage.label.fileSize"))}</dt>
|
<dt>{t(getTranslationID("dictationPage.label.fileSize"))}</dt>
|
||||||
<dd>{selectedFileTask?.fileSize ?? ""}</dd>
|
<dd>{selectedFileTask?.fileSize ?? ""}</dd>
|
||||||
<dt>{t(getTranslationID("dictationPage.label.fileLength"))}</dt>
|
<dt>{t(getTranslationID("dictationPage.label.fileLength"))}</dt>
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@ -2,6 +2,10 @@ import React, { useState, useCallback, useEffect } from "react";
|
|||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { AppDispatch } from "app/store";
|
import { AppDispatch } from "app/store";
|
||||||
import { useDispatch, useSelector } from "react-redux";
|
import { useDispatch, useSelector } from "react-redux";
|
||||||
|
import {
|
||||||
|
clearHierarchicalElement,
|
||||||
|
getMyAccountAsync,
|
||||||
|
} from "features/license/partnerLicense";
|
||||||
import styles from "../../styles/app.module.scss";
|
import styles from "../../styles/app.module.scss";
|
||||||
import { getTranslationID } from "../../translation";
|
import { getTranslationID } from "../../translation";
|
||||||
import close from "../../assets/images/close.svg";
|
import close from "../../assets/images/close.svg";
|
||||||
@ -92,6 +96,8 @@ export const CardLicenseIssuePopup: React.FC<CardLicenseIssuePopupProps> = (
|
|||||||
setIsPushCreateButton(false);
|
setIsPushCreateButton(false);
|
||||||
|
|
||||||
if (meta.requestStatus === "fulfilled") {
|
if (meta.requestStatus === "fulfilled") {
|
||||||
|
dispatch(getMyAccountAsync());
|
||||||
|
dispatch(clearHierarchicalElement());
|
||||||
closePopup();
|
closePopup();
|
||||||
}
|
}
|
||||||
}, [
|
}, [
|
||||||
|
|||||||
203
dictation_client/src/pages/LicensePage/changeOwnerPopup.tsx
Normal file
203
dictation_client/src/pages/LicensePage/changeOwnerPopup.tsx
Normal file
@ -0,0 +1,203 @@
|
|||||||
|
import React, { useState, useCallback } from "react";
|
||||||
|
import { useDispatch, useSelector } from "react-redux";
|
||||||
|
import {
|
||||||
|
selectChildrenPartnerLicenses,
|
||||||
|
selectIsLoading,
|
||||||
|
selectOwnPartnerLicense,
|
||||||
|
} from "features/license/partnerLicense/selectors";
|
||||||
|
import {
|
||||||
|
getMyAccountAsync,
|
||||||
|
switchParentAsync,
|
||||||
|
} from "features/license/partnerLicense/operations";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { getTranslationID } from "translation";
|
||||||
|
import { AppDispatch } from "app/store";
|
||||||
|
import { clearHierarchicalElement } from "features/license/partnerLicense";
|
||||||
|
import styles from "../../styles/app.module.scss";
|
||||||
|
import close from "../../assets/images/close.svg";
|
||||||
|
import shuffle from "../../assets/images/shuffle.svg";
|
||||||
|
import progress_activit from "../../assets/images/progress_activit.svg";
|
||||||
|
|
||||||
|
interface ChangeOwnerPopupProps {
|
||||||
|
onClose: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ChangeOwnerPopup: React.FC<ChangeOwnerPopupProps> = (props) => {
|
||||||
|
const dispatch: AppDispatch = useDispatch();
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
const [selectedChildId, setSelectedChildId] = useState<number | null>(null);
|
||||||
|
const [selectedChildName, setSelectedChildName] = useState<string>("");
|
||||||
|
const [destinationParentId, setDestinationParentId] = useState<string>("");
|
||||||
|
const [error, setError] = useState<string>("");
|
||||||
|
|
||||||
|
const originParentLicenseInfo = useSelector(selectOwnPartnerLicense);
|
||||||
|
const childrenLicenseInfos = useSelector(selectChildrenPartnerLicenses);
|
||||||
|
const isLoading = useSelector(selectIsLoading);
|
||||||
|
|
||||||
|
const { onClose } = props;
|
||||||
|
const closePopup = useCallback(() => {
|
||||||
|
if (isLoading) return;
|
||||||
|
onClose();
|
||||||
|
}, [isLoading, onClose]);
|
||||||
|
|
||||||
|
const bulkDisplayName = "-- Bulk --";
|
||||||
|
const bulkValue = "bulk";
|
||||||
|
|
||||||
|
const onBulkChange = useCallback(
|
||||||
|
(e: React.ChangeEvent<HTMLSelectElement>) => {
|
||||||
|
const { value } = e.target;
|
||||||
|
const childId = value === bulkValue ? null : Number(value);
|
||||||
|
setSelectedChildId(childId);
|
||||||
|
|
||||||
|
// 一括追加のときは子アカウント名を表示しない
|
||||||
|
let childName = "";
|
||||||
|
if (childId) {
|
||||||
|
const child = childrenLicenseInfos.find((c) => c.accountId === childId);
|
||||||
|
// childがundefinedになることはないが、コード解析対応のためのチェック
|
||||||
|
if (child) {
|
||||||
|
childName = child.companyName;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setSelectedChildName(childName);
|
||||||
|
},
|
||||||
|
[childrenLicenseInfos]
|
||||||
|
);
|
||||||
|
|
||||||
|
const onSaveClick = useCallback(async () => {
|
||||||
|
const destinationParentIdNum = Number(destinationParentId);
|
||||||
|
if (
|
||||||
|
Number.isNaN(destinationParentIdNum) || // 数値でない場合
|
||||||
|
destinationParentIdNum <= 0 || // IDにならない数値の場合
|
||||||
|
destinationParentId.length > 7 // 8桁以上の場合(本システムの特徴として8桁以上になることはあり得ない)
|
||||||
|
) {
|
||||||
|
setError(t(getTranslationID("changeOwnerPopup.label.invalidInputError")));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setError("");
|
||||||
|
if (
|
||||||
|
// eslint-disable-next-line no-alert
|
||||||
|
!window.confirm(t(getTranslationID("common.message.dialogConfirm")))
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const children = selectedChildId
|
||||||
|
? [selectedChildId]
|
||||||
|
: childrenLicenseInfos.map((child) => child.accountId);
|
||||||
|
const { meta } = await dispatch(
|
||||||
|
switchParentAsync({ to: Number(destinationParentId), children })
|
||||||
|
);
|
||||||
|
if (meta.requestStatus === "fulfilled") {
|
||||||
|
dispatch(getMyAccountAsync());
|
||||||
|
dispatch(clearHierarchicalElement());
|
||||||
|
closePopup();
|
||||||
|
}
|
||||||
|
}, [
|
||||||
|
childrenLicenseInfos,
|
||||||
|
closePopup,
|
||||||
|
destinationParentId,
|
||||||
|
dispatch,
|
||||||
|
selectedChildId,
|
||||||
|
t,
|
||||||
|
]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={`${styles.modal} ${styles.isShow}`}>
|
||||||
|
<div className={styles.modalBox}>
|
||||||
|
<p className={styles.modalTitle}>
|
||||||
|
{t(getTranslationID("changeOwnerPopup.label.title"))}
|
||||||
|
{/* eslint-disable-next-line jsx-a11y/click-events-have-key-events,jsx-a11y/no-noninteractive-element-interactions */}
|
||||||
|
<img
|
||||||
|
src={close}
|
||||||
|
className={styles.modalTitleIcon}
|
||||||
|
alt="close"
|
||||||
|
onClick={closePopup}
|
||||||
|
/>
|
||||||
|
</p>
|
||||||
|
<form action="" name="" method="" className={styles.form}>
|
||||||
|
<dl className={`${styles.formList} ${styles.hasbg}`}>
|
||||||
|
<dt className={styles.formTitle} />
|
||||||
|
<dt>
|
||||||
|
{t(getTranslationID("changeOwnerPopup.label.upperLayerId"))}
|
||||||
|
</dt>
|
||||||
|
<dd className={styles.ownerChange}>
|
||||||
|
<p className={styles.Owner}>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
size={40}
|
||||||
|
name=""
|
||||||
|
value={originParentLicenseInfo.accountId}
|
||||||
|
readOnly
|
||||||
|
className={`${styles.formInput} ${styles.short}`}
|
||||||
|
/>
|
||||||
|
<span className={styles.txName}>
|
||||||
|
{originParentLicenseInfo.companyName}
|
||||||
|
</span>
|
||||||
|
</p>
|
||||||
|
<p className={styles.arrowR} />
|
||||||
|
<p className={styles.newOwner}>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
size={40}
|
||||||
|
name=""
|
||||||
|
value={destinationParentId}
|
||||||
|
placeholder=" "
|
||||||
|
className={`${styles.formInput} ${styles.short}`}
|
||||||
|
onChange={(e) => setDestinationParentId(e.target.value)}
|
||||||
|
/>
|
||||||
|
<span className={styles.formError}>{error}</span>
|
||||||
|
</p>
|
||||||
|
</dd>
|
||||||
|
<dd className={styles.full}>
|
||||||
|
<img src={shuffle} className={styles.transOwner} alt="" />
|
||||||
|
</dd>
|
||||||
|
<dt>
|
||||||
|
{t(getTranslationID("changeOwnerPopup.label.lowerLayerId"))}
|
||||||
|
</dt>
|
||||||
|
<dd className={styles.lowerTrans}>
|
||||||
|
<select
|
||||||
|
name=""
|
||||||
|
className={`${styles.formInput} ${styles.short}`}
|
||||||
|
value={selectedChildId ?? bulkDisplayName}
|
||||||
|
onChange={onBulkChange}
|
||||||
|
>
|
||||||
|
<option value={bulkValue}>{bulkDisplayName}</option>
|
||||||
|
{childrenLicenseInfos.map((child) => (
|
||||||
|
<option key={child.accountId} value={child.accountId}>
|
||||||
|
{child.accountId}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
<span className={styles.txName}>{selectedChildName}</span>
|
||||||
|
</dd>
|
||||||
|
<dd className={`${styles.full} ${styles.alignCenter}`}>
|
||||||
|
{/* 処理中や子アカウントが1件も存在しない場合、Saveボタンを押せないようにする */}
|
||||||
|
<input
|
||||||
|
type="button"
|
||||||
|
name="submit"
|
||||||
|
value={t(getTranslationID("common.label.save"))}
|
||||||
|
className={`${styles.formSubmit} ${styles.marginBtm1} ${
|
||||||
|
!isLoading && childrenLicenseInfos.length > 0
|
||||||
|
? styles.isActive
|
||||||
|
: ""
|
||||||
|
}`}
|
||||||
|
onClick={onSaveClick}
|
||||||
|
disabled={isLoading || childrenLicenseInfos.length <= 0}
|
||||||
|
/>
|
||||||
|
</dd>
|
||||||
|
|
||||||
|
<img
|
||||||
|
style={{ display: isLoading ? "inline" : "none" }}
|
||||||
|
src={progress_activit}
|
||||||
|
className={styles.icLoading}
|
||||||
|
alt="Loading"
|
||||||
|
/>
|
||||||
|
</dl>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ChangeOwnerPopup;
|
||||||
@ -1,6 +1,5 @@
|
|||||||
/* eslint-disable jsx-a11y/control-has-associated-label */
|
/* eslint-disable jsx-a11y/control-has-associated-label */
|
||||||
import React, { useCallback, useEffect } from "react";
|
import React, { useCallback, useEffect } from "react";
|
||||||
import { UpdateTokenTimer } from "components/auth/updateTokenTimer";
|
|
||||||
import { isApproveTier } from "features/auth";
|
import { isApproveTier } from "features/auth";
|
||||||
import { TIERS } from "components/auth/constants";
|
import { TIERS } from "components/auth/constants";
|
||||||
import Footer from "components/footer";
|
import Footer from "components/footer";
|
||||||
@ -13,6 +12,7 @@ import { useDispatch, useSelector } from "react-redux";
|
|||||||
import {
|
import {
|
||||||
LIMIT_ORDER_HISORY_NUM,
|
LIMIT_ORDER_HISORY_NUM,
|
||||||
STATUS,
|
STATUS,
|
||||||
|
LICENSE_TYPE,
|
||||||
getLicenseOrderHistoriesAsync,
|
getLicenseOrderHistoriesAsync,
|
||||||
selectCurrentPage,
|
selectCurrentPage,
|
||||||
selectIsLoading,
|
selectIsLoading,
|
||||||
@ -26,20 +26,21 @@ import {
|
|||||||
selectCompanyName,
|
selectCompanyName,
|
||||||
cancelIssueAsync,
|
cancelIssueAsync,
|
||||||
} from "features/license/licenseOrderHistory";
|
} from "features/license/licenseOrderHistory";
|
||||||
import { selectSelectedRow } from "features/license/partnerLicense";
|
|
||||||
import { selectDelegationAccessToken } from "features/auth/selectors";
|
import { selectDelegationAccessToken } from "features/auth/selectors";
|
||||||
import { DelegationBar } from "components/delegate";
|
import { DelegationBar } from "components/delegate";
|
||||||
|
import { LicenseOrder, SearchPartner, PartnerLicenseInfo } from "api/api";
|
||||||
import undo from "../../assets/images/undo.svg";
|
import undo from "../../assets/images/undo.svg";
|
||||||
import history from "../../assets/images/history.svg";
|
import history from "../../assets/images/history.svg";
|
||||||
import progress_activit from "../../assets/images/progress_activit.svg";
|
import progress_activit from "../../assets/images/progress_activit.svg";
|
||||||
|
|
||||||
interface LicenseOrderHistoryProps {
|
interface LicenseOrderHistoryProps {
|
||||||
onReturn: () => void;
|
onReturn: () => void;
|
||||||
|
selectedRow?: PartnerLicenseInfo | SearchPartner;
|
||||||
}
|
}
|
||||||
export const LicenseOrderHistory: React.FC<LicenseOrderHistoryProps> = (
|
export const LicenseOrderHistory: React.FC<LicenseOrderHistoryProps> = (
|
||||||
props
|
props
|
||||||
): JSX.Element => {
|
): JSX.Element => {
|
||||||
const { onReturn } = props;
|
const { onReturn, selectedRow } = props;
|
||||||
const dispatch: AppDispatch = useDispatch();
|
const dispatch: AppDispatch = useDispatch();
|
||||||
const [t] = useTranslation();
|
const [t] = useTranslation();
|
||||||
const total = useSelector(selectTotal);
|
const total = useSelector(selectTotal);
|
||||||
@ -47,7 +48,6 @@ export const LicenseOrderHistory: React.FC<LicenseOrderHistoryProps> = (
|
|||||||
const offset = useSelector(selectOffset);
|
const offset = useSelector(selectOffset);
|
||||||
const currentPage = useSelector(selectCurrentPage);
|
const currentPage = useSelector(selectCurrentPage);
|
||||||
const isLoading = useSelector(selectIsLoading);
|
const isLoading = useSelector(selectIsLoading);
|
||||||
const selectedRow = useSelector(selectSelectedRow);
|
|
||||||
// 代行操作用のトークンを取得する
|
// 代行操作用のトークンを取得する
|
||||||
const delegationAccessToken = useSelector(selectDelegationAccessToken);
|
const delegationAccessToken = useSelector(selectDelegationAccessToken);
|
||||||
|
|
||||||
@ -65,6 +65,7 @@ export const LicenseOrderHistory: React.FC<LicenseOrderHistoryProps> = (
|
|||||||
getLicenseOrderHistoriesAsync({
|
getLicenseOrderHistoriesAsync({
|
||||||
limit: LIMIT_ORDER_HISORY_NUM,
|
limit: LIMIT_ORDER_HISORY_NUM,
|
||||||
offset,
|
offset,
|
||||||
|
selectedRow,
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@ -152,11 +153,15 @@ export const LicenseOrderHistory: React.FC<LicenseOrderHistoryProps> = (
|
|||||||
getLicenseOrderHistoriesAsync({
|
getLicenseOrderHistoriesAsync({
|
||||||
limit: LIMIT_ORDER_HISORY_NUM,
|
limit: LIMIT_ORDER_HISORY_NUM,
|
||||||
offset,
|
offset,
|
||||||
|
selectedRow,
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [dispatch, currentPage]);
|
}, [dispatch, currentPage]);
|
||||||
|
|
||||||
|
const isNotTrialLicense = (license: LicenseOrder) =>
|
||||||
|
license.type !== LICENSE_TYPE.TRIAL;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={`${styles.wrap} ${delegationAccessToken ? styles.manage : ""}`}
|
className={`${styles.wrap} ${delegationAccessToken ? styles.manage : ""}`}
|
||||||
@ -166,7 +171,6 @@ export const LicenseOrderHistory: React.FC<LicenseOrderHistoryProps> = (
|
|||||||
delegationAccessToken && <DelegationBar />
|
delegationAccessToken && <DelegationBar />
|
||||||
}
|
}
|
||||||
<Header />
|
<Header />
|
||||||
<UpdateTokenTimer />
|
|
||||||
<main className={styles.main}>
|
<main className={styles.main}>
|
||||||
<div>
|
<div>
|
||||||
<div className={styles.pageHeader}>
|
<div className={styles.pageHeader}>
|
||||||
@ -210,6 +214,11 @@ export const LicenseOrderHistory: React.FC<LicenseOrderHistoryProps> = (
|
|||||||
getTranslationID("orderHistoriesPage.label.issueDate")
|
getTranslationID("orderHistoriesPage.label.issueDate")
|
||||||
)}
|
)}
|
||||||
</th>
|
</th>
|
||||||
|
<th>
|
||||||
|
{t(
|
||||||
|
getTranslationID("orderHistoriesPage.label.licenseType")
|
||||||
|
)}
|
||||||
|
</th>
|
||||||
<th>
|
<th>
|
||||||
{t(
|
{t(
|
||||||
getTranslationID(
|
getTranslationID(
|
||||||
@ -231,9 +240,10 @@ export const LicenseOrderHistory: React.FC<LicenseOrderHistoryProps> = (
|
|||||||
// eslint-disable-next-line react/jsx-key
|
// eslint-disable-next-line react/jsx-key
|
||||||
<tr>
|
<tr>
|
||||||
<td>{x.orderDate}</td>
|
<td>{x.orderDate}</td>
|
||||||
<td>{x.issueDate ? x.issueDate : "-"}</td>
|
<td>{x.issueDate ?? "-"}</td>
|
||||||
|
<td>{x.type}</td>
|
||||||
<td>{x.numberOfOrder}</td>
|
<td>{x.numberOfOrder}</td>
|
||||||
<td>{x.poNumber}</td>
|
<td>{x.poNumber ?? "-"}</td>
|
||||||
<td>
|
<td>
|
||||||
{(() => {
|
{(() => {
|
||||||
switch (x.status) {
|
switch (x.status) {
|
||||||
@ -261,7 +271,7 @@ export const LicenseOrderHistory: React.FC<LicenseOrderHistoryProps> = (
|
|||||||
})()}
|
})()}
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
{!selectedRow && (
|
{!selectedRow && isNotTrialLicense(x) && (
|
||||||
<ul
|
<ul
|
||||||
className={`${styles.menuAction} ${styles.inTable}`}
|
className={`${styles.menuAction} ${styles.inTable}`}
|
||||||
>
|
>
|
||||||
@ -286,7 +296,7 @@ export const LicenseOrderHistory: React.FC<LicenseOrderHistoryProps> = (
|
|||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
)}
|
)}
|
||||||
{selectedRow && (
|
{selectedRow && isNotTrialLicense(x) && (
|
||||||
<ul
|
<ul
|
||||||
className={`${styles.menuAction} ${styles.inTable}`}
|
className={`${styles.menuAction} ${styles.inTable}`}
|
||||||
>
|
>
|
||||||
|
|||||||
@ -14,6 +14,10 @@ import {
|
|||||||
cleanupApps,
|
cleanupApps,
|
||||||
selectIsLoading,
|
selectIsLoading,
|
||||||
} from "features/license/licenseOrder";
|
} from "features/license/licenseOrder";
|
||||||
|
import {
|
||||||
|
clearHierarchicalElement,
|
||||||
|
getMyAccountAsync,
|
||||||
|
} from "features/license/partnerLicense";
|
||||||
import close from "../../assets/images/close.svg";
|
import close from "../../assets/images/close.svg";
|
||||||
import progress_activit from "../../assets/images/progress_activit.svg";
|
import progress_activit from "../../assets/images/progress_activit.svg";
|
||||||
|
|
||||||
@ -41,9 +45,10 @@ export const LicenseOrderPopup: React.FC<LicenseOrderPopupProps> = (props) => {
|
|||||||
|
|
||||||
// ポップアップを閉じる処理
|
// ポップアップを閉じる処理
|
||||||
const closePopup = useCallback(() => {
|
const closePopup = useCallback(() => {
|
||||||
|
if (isLoading) return;
|
||||||
setIsPushOrderButton(false);
|
setIsPushOrderButton(false);
|
||||||
onClose();
|
onClose();
|
||||||
}, [onClose]);
|
}, [isLoading, onClose]);
|
||||||
|
|
||||||
// 画面からのパラメータ
|
// 画面からのパラメータ
|
||||||
const poNumber = useSelector(selectPoNumber);
|
const poNumber = useSelector(selectPoNumber);
|
||||||
@ -90,6 +95,8 @@ export const LicenseOrderPopup: React.FC<LicenseOrderPopupProps> = (props) => {
|
|||||||
setIsPushOrderButton(false);
|
setIsPushOrderButton(false);
|
||||||
|
|
||||||
if (meta.requestStatus === "fulfilled") {
|
if (meta.requestStatus === "fulfilled") {
|
||||||
|
dispatch(getMyAccountAsync());
|
||||||
|
dispatch(clearHierarchicalElement());
|
||||||
closePopup();
|
closePopup();
|
||||||
}
|
}
|
||||||
}, [
|
}, [
|
||||||
|
|||||||
@ -1,5 +1,4 @@
|
|||||||
import React, { useCallback, useEffect, useState } from "react";
|
import React, { useCallback, useEffect, useState } from "react";
|
||||||
import { UpdateTokenTimer } from "components/auth/updateTokenTimer";
|
|
||||||
import Footer from "components/footer";
|
import Footer from "components/footer";
|
||||||
import Header from "components/header";
|
import Header from "components/header";
|
||||||
import styles from "styles/app.module.scss";
|
import styles from "styles/app.module.scss";
|
||||||
@ -10,12 +9,16 @@ import { useDispatch, useSelector } from "react-redux";
|
|||||||
import {
|
import {
|
||||||
getCompanyNameAsync,
|
getCompanyNameAsync,
|
||||||
getLicenseSummaryAsync,
|
getLicenseSummaryAsync,
|
||||||
selecLicenseSummaryInfo,
|
selectLicenseSummaryInfo,
|
||||||
selectCompanyName,
|
selectCompanyName,
|
||||||
|
selectIsLoading,
|
||||||
|
updateRestrictionStatusAsync,
|
||||||
} from "features/license/licenseSummary";
|
} from "features/license/licenseSummary";
|
||||||
import { selectSelectedRow } from "features/license/partnerLicense";
|
|
||||||
import { selectDelegationAccessToken } from "features/auth/selectors";
|
import { selectDelegationAccessToken } from "features/auth/selectors";
|
||||||
import { DelegationBar } from "components/delegate";
|
import { DelegationBar } from "components/delegate";
|
||||||
|
import { TIERS } from "components/auth/constants";
|
||||||
|
import { isAdminUser, isApproveTier } from "features/auth/utils";
|
||||||
|
import { PartnerLicenseInfo, SearchPartner } from "../../api";
|
||||||
import postAdd from "../../assets/images/post_add.svg";
|
import postAdd from "../../assets/images/post_add.svg";
|
||||||
import history from "../../assets/images/history.svg";
|
import history from "../../assets/images/history.svg";
|
||||||
import key from "../../assets/images/key.svg";
|
import key from "../../assets/images/key.svg";
|
||||||
@ -24,26 +27,31 @@ import circle from "../../assets/images/circle.svg";
|
|||||||
import returnLabel from "../../assets/images/undo.svg";
|
import returnLabel from "../../assets/images/undo.svg";
|
||||||
import { LicenseOrderPopup } from "./licenseOrderPopup";
|
import { LicenseOrderPopup } from "./licenseOrderPopup";
|
||||||
import { CardLicenseActivatePopup } from "./cardLicenseActivatePopup";
|
import { CardLicenseActivatePopup } from "./cardLicenseActivatePopup";
|
||||||
|
import { TrialLicenseIssuePopup } from "./trialLicenseIssuePopup";
|
||||||
// eslint-disable-next-line import/no-named-as-default
|
// eslint-disable-next-line import/no-named-as-default
|
||||||
import LicenseOrderHistory from "./licenseOrderHistory";
|
import LicenseOrderHistory from "./licenseOrderHistory";
|
||||||
|
|
||||||
interface LicenseSummaryProps {
|
interface LicenseSummaryProps {
|
||||||
onReturn?: () => void;
|
onReturn?: () => void;
|
||||||
|
selectedRow?: PartnerLicenseInfo | SearchPartner;
|
||||||
}
|
}
|
||||||
export const LicenseSummary: React.FC<LicenseSummaryProps> = (
|
export const LicenseSummary: React.FC<LicenseSummaryProps> = (
|
||||||
props
|
props
|
||||||
): JSX.Element => {
|
): JSX.Element => {
|
||||||
const { onReturn } = props;
|
const { onReturn, selectedRow } = props;
|
||||||
const dispatch: AppDispatch = useDispatch();
|
const dispatch: AppDispatch = useDispatch();
|
||||||
const [t] = useTranslation();
|
const [t] = useTranslation();
|
||||||
const selectedRow = useSelector(selectSelectedRow);
|
|
||||||
// 代行操作用のトークンを取得する
|
// 代行操作用のトークンを取得する
|
||||||
const delegationAccessToken = useSelector(selectDelegationAccessToken);
|
const delegationAccessToken = useSelector(selectDelegationAccessToken);
|
||||||
|
|
||||||
|
const isLoading = useSelector(selectIsLoading);
|
||||||
|
|
||||||
// popup制御関係
|
// popup制御関係
|
||||||
const [islicenseOrderPopupOpen, setIslicenseOrderPopupOpen] = useState(false);
|
const [islicenseOrderPopupOpen, setIslicenseOrderPopupOpen] = useState(false);
|
||||||
const [isCardLicenseActivatePopupOpen, setIsCardLicenseActivatePopupOpen] =
|
const [isCardLicenseActivatePopupOpen, setIsCardLicenseActivatePopupOpen] =
|
||||||
useState(false);
|
useState(false);
|
||||||
|
const [isTrialLicenseIssuePopupOpen, setIsTrialLicenseIssuePopupOpen] =
|
||||||
|
useState(false);
|
||||||
|
|
||||||
const onlicenseOrderOpen = useCallback(() => {
|
const onlicenseOrderOpen = useCallback(() => {
|
||||||
setIslicenseOrderPopupOpen(true);
|
setIslicenseOrderPopupOpen(true);
|
||||||
@ -53,6 +61,10 @@ export const LicenseSummary: React.FC<LicenseSummaryProps> = (
|
|||||||
setIsCardLicenseActivatePopupOpen(true);
|
setIsCardLicenseActivatePopupOpen(true);
|
||||||
}, [setIsCardLicenseActivatePopupOpen]);
|
}, [setIsCardLicenseActivatePopupOpen]);
|
||||||
|
|
||||||
|
const onTrialLicenseIssueOpen = useCallback(() => {
|
||||||
|
setIsTrialLicenseIssuePopupOpen(true);
|
||||||
|
}, [setIsTrialLicenseIssuePopupOpen]);
|
||||||
|
|
||||||
// 呼び出し画面制御関係
|
// 呼び出し画面制御関係
|
||||||
const [islicenseOrderHistoryOpen, setIsLicenseOrderHistoryOpen] =
|
const [islicenseOrderHistoryOpen, setIsLicenseOrderHistoryOpen] =
|
||||||
useState(false);
|
useState(false);
|
||||||
@ -62,9 +74,13 @@ export const LicenseSummary: React.FC<LicenseSummaryProps> = (
|
|||||||
}, [setIsLicenseOrderHistoryOpen]);
|
}, [setIsLicenseOrderHistoryOpen]);
|
||||||
|
|
||||||
// apiからの値取得関係
|
// apiからの値取得関係
|
||||||
const licenseSummaryInfo = useSelector(selecLicenseSummaryInfo);
|
const licenseSummaryInfo = useSelector(selectLicenseSummaryInfo);
|
||||||
const companyName = useSelector(selectCompanyName);
|
const companyName = useSelector(selectCompanyName);
|
||||||
|
|
||||||
|
const isTier1 = isApproveTier([TIERS.TIER1]);
|
||||||
|
const isTier2 = isApproveTier([TIERS.TIER2]);
|
||||||
|
const isAdmin = isAdminUser();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
dispatch(getLicenseSummaryAsync({ selectedRow }));
|
dispatch(getLicenseSummaryAsync({ selectedRow }));
|
||||||
dispatch(getCompanyNameAsync({ selectedRow }));
|
dispatch(getCompanyNameAsync({ selectedRow }));
|
||||||
@ -78,6 +94,35 @@ export const LicenseSummary: React.FC<LicenseSummaryProps> = (
|
|||||||
}
|
}
|
||||||
}, [onReturn]);
|
}, [onReturn]);
|
||||||
|
|
||||||
|
const onStorageAvailableChange = useCallback(
|
||||||
|
async (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
if (
|
||||||
|
/* eslint-disable-next-line no-alert */
|
||||||
|
!window.confirm(
|
||||||
|
t(
|
||||||
|
getTranslationID(
|
||||||
|
"LicenseSummaryPage.message.storageUnavalableSwitchingConfirm"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const restricted = e.target.checked;
|
||||||
|
const accountId = selectedRow?.accountId;
|
||||||
|
// 本関数が実行されるときはselectedRowが存在する前提のため、accountIdが存在しない場合の処理は不要
|
||||||
|
if (!accountId) return;
|
||||||
|
const { meta } = await dispatch(
|
||||||
|
updateRestrictionStatusAsync({ accountId, restricted })
|
||||||
|
);
|
||||||
|
if (meta.requestStatus === "fulfilled") {
|
||||||
|
dispatch(getLicenseSummaryAsync({ selectedRow }));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[dispatch, selectedRow, t]
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{/* isPopupOpenがfalseの場合はポップアップのhtmlを生成しないように対応。これによりポップアップは都度生成されて初期化の考慮が減る */}
|
{/* isPopupOpenがfalseの場合はポップアップのhtmlを生成しないように対応。これによりポップアップは都度生成されて初期化の考慮が減る */}
|
||||||
@ -96,11 +141,21 @@ export const LicenseSummary: React.FC<LicenseSummaryProps> = (
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
{isTrialLicenseIssuePopupOpen && (
|
||||||
|
<TrialLicenseIssuePopup
|
||||||
|
onClose={() => {
|
||||||
|
setIsTrialLicenseIssuePopupOpen(false);
|
||||||
|
dispatch(getLicenseSummaryAsync({ selectedRow }));
|
||||||
|
}}
|
||||||
|
selectedRow={selectedRow}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
{islicenseOrderHistoryOpen && (
|
{islicenseOrderHistoryOpen && (
|
||||||
<LicenseOrderHistory
|
<LicenseOrderHistory
|
||||||
onReturn={() => {
|
onReturn={() => {
|
||||||
setIsLicenseOrderHistoryOpen(false);
|
setIsLicenseOrderHistoryOpen(false);
|
||||||
}}
|
}}
|
||||||
|
selectedRow={selectedRow}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{!islicenseOrderHistoryOpen && (
|
{!islicenseOrderHistoryOpen && (
|
||||||
@ -111,8 +166,6 @@ export const LicenseSummary: React.FC<LicenseSummaryProps> = (
|
|||||||
>
|
>
|
||||||
{delegationAccessToken && <DelegationBar />}
|
{delegationAccessToken && <DelegationBar />}
|
||||||
<Header />
|
<Header />
|
||||||
|
|
||||||
<UpdateTokenTimer />
|
|
||||||
<main className={styles.main}>
|
<main className={styles.main}>
|
||||||
<div className="">
|
<div className="">
|
||||||
<div className={styles.pageHeader}>
|
<div className={styles.pageHeader}>
|
||||||
@ -193,6 +246,30 @@ export const LicenseSummary: React.FC<LicenseSummaryProps> = (
|
|||||||
</a>
|
</a>
|
||||||
)}
|
)}
|
||||||
</li>
|
</li>
|
||||||
|
<li>
|
||||||
|
{/* 第一階層、第二階層の管理者が第五階層アカウントのライセンス情報を見ている場合は、トライアルライセンス注文ボタンを表示 */}
|
||||||
|
{selectedRow &&
|
||||||
|
isAdmin &&
|
||||||
|
selectedRow.tier.toString() === TIERS.TIER5 &&
|
||||||
|
(isTier1 || isTier2) && (
|
||||||
|
// eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions
|
||||||
|
<a
|
||||||
|
className={`${styles.menuLink} ${styles.isActive}`}
|
||||||
|
onClick={onTrialLicenseIssueOpen}
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
src={postAdd}
|
||||||
|
alt=""
|
||||||
|
className={styles.menuIcon}
|
||||||
|
/>
|
||||||
|
{t(
|
||||||
|
getTranslationID(
|
||||||
|
"LicenseSummaryPage.label.issueTrialLicense"
|
||||||
|
)
|
||||||
|
)}
|
||||||
|
</a>
|
||||||
|
)}
|
||||||
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
<div className={styles.marginRgt3}>
|
<div className={styles.marginRgt3}>
|
||||||
<dl
|
<dl
|
||||||
@ -272,6 +349,27 @@ export const LicenseSummary: React.FC<LicenseSummaryProps> = (
|
|||||||
</dl>
|
</dl>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
|
{isTier1 && isAdmin && (
|
||||||
|
<p
|
||||||
|
className={`${styles.checkAvail} ${styles.alignRight}`}
|
||||||
|
>
|
||||||
|
{/* eslint-disable-next-line jsx-a11y/label-has-associated-control */}
|
||||||
|
<label>
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
className={styles.formCheck}
|
||||||
|
checked={licenseSummaryInfo.isStorageAvailable}
|
||||||
|
disabled={isLoading}
|
||||||
|
onChange={onStorageAvailableChange}
|
||||||
|
/>
|
||||||
|
{t(
|
||||||
|
getTranslationID(
|
||||||
|
"LicenseSummaryPage.label.storageUnavailableCheckbox"
|
||||||
|
)
|
||||||
|
)}
|
||||||
|
</label>
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
<dl
|
<dl
|
||||||
className={`${styles.listVertical} ${styles.marginBtm3}`}
|
className={`${styles.listVertical} ${styles.marginBtm3}`}
|
||||||
>
|
>
|
||||||
@ -289,17 +387,31 @@ export const LicenseSummary: React.FC<LicenseSummaryProps> = (
|
|||||||
)
|
)
|
||||||
)}
|
)}
|
||||||
</dt>
|
</dt>
|
||||||
{/* Storage Usedの値表示をハイフンに置き換え */}
|
<dd>
|
||||||
{/* <dd>{licenseSummaryInfo.storageSize}GB</dd> */}
|
{/** Byte単位で受け取った値をGB単位で表示するため1000^3で割っている(小数点以下第三位まで表示で第四位で四捨五入) */}
|
||||||
<dd>-</dd>
|
{(
|
||||||
|
licenseSummaryInfo.storageSize /
|
||||||
|
1000 /
|
||||||
|
1000 /
|
||||||
|
1000
|
||||||
|
).toFixed(3)}
|
||||||
|
GB
|
||||||
|
</dd>
|
||||||
<dt>
|
<dt>
|
||||||
{t(
|
{t(
|
||||||
getTranslationID("LicenseSummaryPage.label.usedSize")
|
getTranslationID("LicenseSummaryPage.label.usedSize")
|
||||||
)}
|
)}
|
||||||
</dt>
|
</dt>
|
||||||
{/* Storage Usedの値表示をハイフンに置き換え */}
|
<dd>
|
||||||
{/* <dd>{licenseSummaryInfo.usedSize}GB</dd> */}
|
{/** Byte単位で受け取った値をGB単位で表示するため1000^3で割っている(小数点以下第三位まで表示で第四位で四捨五入) */}
|
||||||
<dd>-</dd>
|
{(
|
||||||
|
licenseSummaryInfo.usedSize /
|
||||||
|
1000 /
|
||||||
|
1000 /
|
||||||
|
1000
|
||||||
|
).toFixed(3)}
|
||||||
|
GB
|
||||||
|
</dd>
|
||||||
<dt className={styles.overLine}>
|
<dt className={styles.overLine}>
|
||||||
{t(
|
{t(
|
||||||
getTranslationID(
|
getTranslationID(
|
||||||
|
|||||||
@ -1,42 +1,57 @@
|
|||||||
import React, { useCallback, useState, useEffect } from "react";
|
import { PartnerLicenseInfo } from "api";
|
||||||
|
import { AppDispatch } from "app/store";
|
||||||
import Footer from "components/footer";
|
import Footer from "components/footer";
|
||||||
import Header from "components/header";
|
import Header from "components/header";
|
||||||
import styles from "styles/app.module.scss";
|
import React, { useCallback, useEffect, useMemo, useState } from "react";
|
||||||
import { UpdateTokenTimer } from "components/auth/updateTokenTimer";
|
|
||||||
import { useDispatch, useSelector } from "react-redux";
|
|
||||||
import { AppDispatch } from "app/store";
|
|
||||||
import { getTranslationID } from "translation";
|
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { PartnerLicenseInfo } from "api";
|
import { useDispatch, useSelector } from "react-redux";
|
||||||
import { CardLicenseIssuePopup } from "./cardLicenseIssuePopup";
|
import styles from "styles/app.module.scss";
|
||||||
import postAdd from "../../assets/images/post_add.svg";
|
import { getTranslationID } from "translation";
|
||||||
|
import changeOwnerIcon from "../../assets/images/change_circle.svg";
|
||||||
import history from "../../assets/images/history.svg";
|
import history from "../../assets/images/history.svg";
|
||||||
|
import postAdd from "../../assets/images/post_add.svg";
|
||||||
|
import progress_activit from "../../assets/images/progress_activit.svg";
|
||||||
import returnLabel from "../../assets/images/undo.svg";
|
import returnLabel from "../../assets/images/undo.svg";
|
||||||
import { isApproveTier } from "../../features/auth/utils";
|
import searchIcon from "../../assets/images/search.svg";
|
||||||
import { TIERS } from "../../components/auth/constants";
|
import { TIERS } from "../../components/auth/constants";
|
||||||
|
import { isApproveTier } from "../../features/auth/utils";
|
||||||
import {
|
import {
|
||||||
getPartnerLicenseAsync,
|
|
||||||
ACCOUNTS_VIEW_LIMIT,
|
ACCOUNTS_VIEW_LIMIT,
|
||||||
selectMyAccountInfo,
|
changeSelectedRow,
|
||||||
selectTotal,
|
getMyAccountAsync,
|
||||||
selectOwnPartnerLicense,
|
getPartnerLicenseAsync,
|
||||||
selectChildrenPartnerLicenses,
|
|
||||||
selectHierarchicalElements,
|
|
||||||
selectTotalPage,
|
|
||||||
selectIsLoading,
|
|
||||||
selectOffset,
|
|
||||||
selectCurrentPage,
|
|
||||||
pushHierarchicalElement,
|
|
||||||
popHierarchicalElement,
|
popHierarchicalElement,
|
||||||
|
pushHierarchicalElement,
|
||||||
spliceHierarchicalElement,
|
spliceHierarchicalElement,
|
||||||
savePageInfo,
|
savePageInfo,
|
||||||
getMyAccountAsync,
|
setIsLicenseOrderHistoryOpen,
|
||||||
changeSelectedRow,
|
setIsViewDetailsOpen,
|
||||||
|
selectChildrenPartnerLicenses,
|
||||||
|
selectCurrentPage,
|
||||||
|
selectHierarchicalElements,
|
||||||
|
selectIsLoading,
|
||||||
|
selectMyAccountInfo,
|
||||||
|
selectOffset,
|
||||||
|
selectOwnPartnerLicense,
|
||||||
|
selectTotal,
|
||||||
|
selectTotalPage,
|
||||||
|
selectSelectedRow,
|
||||||
|
selectIsLicenseOrderHistoryOpen,
|
||||||
|
selectIsViewDetailsOpen,
|
||||||
|
setIsSearchPopupOpen,
|
||||||
|
selectIsSearchPopupOpen,
|
||||||
} from "../../features/license/partnerLicense";
|
} from "../../features/license/partnerLicense";
|
||||||
import { LicenseOrderPopup } from "./licenseOrderPopup";
|
|
||||||
|
import {
|
||||||
|
selectIsViewDetailsOpen as selectIsViewDetailsInSearchOpen,
|
||||||
|
selectIsLicenseOrderHistoryOpen as selectIsLicenseOrderHistoryInSearchOpen,
|
||||||
|
} from "../../features/license/searchPartner";
|
||||||
|
import { CardLicenseIssuePopup } from "./cardLicenseIssuePopup";
|
||||||
|
import ChangeOwnerPopup from "./changeOwnerPopup";
|
||||||
import { LicenseOrderHistory } from "./licenseOrderHistory";
|
import { LicenseOrderHistory } from "./licenseOrderHistory";
|
||||||
|
import { LicenseOrderPopup } from "./licenseOrderPopup";
|
||||||
import { LicenseSummary } from "./licenseSummary";
|
import { LicenseSummary } from "./licenseSummary";
|
||||||
import progress_activit from "../../assets/images/progress_activit.svg";
|
import { SearchPartnerPopup } from "./searchPartnerAccountPopup";
|
||||||
|
|
||||||
const PartnerLicense: React.FC = (): JSX.Element => {
|
const PartnerLicense: React.FC = (): JSX.Element => {
|
||||||
const dispatch: AppDispatch = useDispatch();
|
const dispatch: AppDispatch = useDispatch();
|
||||||
@ -46,9 +61,22 @@ const PartnerLicense: React.FC = (): JSX.Element => {
|
|||||||
const [isCardLicenseIssuePopupOpen, setIsCardLicenseIssuePopupOpen] =
|
const [isCardLicenseIssuePopupOpen, setIsCardLicenseIssuePopupOpen] =
|
||||||
useState(false);
|
useState(false);
|
||||||
const [islicenseOrderPopupOpen, setIslicenseOrderPopupOpen] = useState(false);
|
const [islicenseOrderPopupOpen, setIslicenseOrderPopupOpen] = useState(false);
|
||||||
const [islicenseOrderHistoryOpen, setIslicenseOrderHistoryOpen] =
|
|
||||||
useState(false);
|
// パートナーライセンス画面のOrderHistory, ViewDetailsの表示制御
|
||||||
const [isViewDetailsOpen, setIsViewDetailsOpen] = useState(false);
|
const isLicenseOrderHistoryOpen = useSelector(
|
||||||
|
selectIsLicenseOrderHistoryOpen
|
||||||
|
);
|
||||||
|
const isViewDetailsOpen = useSelector(selectIsViewDetailsOpen);
|
||||||
|
|
||||||
|
// パートナー検索ポップアップのOrderHistory, ViewDetailsの表示制御
|
||||||
|
const isLicenseOrderHistoryInSearchOpen = useSelector(
|
||||||
|
selectIsLicenseOrderHistoryInSearchOpen
|
||||||
|
);
|
||||||
|
const isViewDetailsInSearchOpen = useSelector(
|
||||||
|
selectIsViewDetailsInSearchOpen
|
||||||
|
);
|
||||||
|
const isSearchPopupOpen = useSelector(selectIsSearchPopupOpen);
|
||||||
|
const [isChangeOwnerPopupOpen, setIsChangeOwnerPopupOpen] = useState(false);
|
||||||
|
|
||||||
// 階層表示用
|
// 階層表示用
|
||||||
const tierNames: { [key: number]: string } = {
|
const tierNames: { [key: number]: string } = {
|
||||||
@ -82,6 +110,7 @@ const PartnerLicense: React.FC = (): JSX.Element => {
|
|||||||
);
|
);
|
||||||
const hierarchicalElements = useSelector(selectHierarchicalElements);
|
const hierarchicalElements = useSelector(selectHierarchicalElements);
|
||||||
const isLoading = useSelector(selectIsLoading);
|
const isLoading = useSelector(selectIsLoading);
|
||||||
|
const selectedRow = useSelector(selectSelectedRow) as PartnerLicenseInfo;
|
||||||
|
|
||||||
// ページネーション制御用
|
// ページネーション制御用
|
||||||
const currentPage = useSelector(selectCurrentPage);
|
const currentPage = useSelector(selectCurrentPage);
|
||||||
@ -134,20 +163,29 @@ const PartnerLicense: React.FC = (): JSX.Element => {
|
|||||||
const onClickViewDetails = useCallback(
|
const onClickViewDetails = useCallback(
|
||||||
(value?: PartnerLicenseInfo) => {
|
(value?: PartnerLicenseInfo) => {
|
||||||
dispatch(changeSelectedRow({ value }));
|
dispatch(changeSelectedRow({ value }));
|
||||||
setIsViewDetailsOpen(true);
|
dispatch(setIsViewDetailsOpen({ value: true }));
|
||||||
},
|
},
|
||||||
[dispatch, setIsViewDetailsOpen]
|
[dispatch]
|
||||||
);
|
);
|
||||||
|
|
||||||
// orderHistoryボタン押下時
|
// orderHistoryボタン押下時
|
||||||
const onClickOrderHistory = useCallback(
|
const onClickOrderHistory = useCallback(
|
||||||
(value?: PartnerLicenseInfo) => {
|
(value?: PartnerLicenseInfo) => {
|
||||||
dispatch(changeSelectedRow({ value }));
|
dispatch(changeSelectedRow({ value }));
|
||||||
setIslicenseOrderHistoryOpen(true);
|
dispatch(setIsLicenseOrderHistoryOpen({ value: true }));
|
||||||
},
|
},
|
||||||
[dispatch, setIslicenseOrderHistoryOpen]
|
[dispatch]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// changeOwnerボタン押下時
|
||||||
|
const onClickChangeOwner = useCallback(() => {
|
||||||
|
setIsChangeOwnerPopupOpen(true);
|
||||||
|
}, [setIsChangeOwnerPopupOpen]);
|
||||||
|
|
||||||
|
const onOpenSearchPopup = useCallback(() => {
|
||||||
|
dispatch(setIsSearchPopupOpen({ value: true }));
|
||||||
|
}, [dispatch]);
|
||||||
|
|
||||||
// マウント時のみ実行
|
// マウント時のみ実行
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
dispatch(getMyAccountAsync());
|
dispatch(getMyAccountAsync());
|
||||||
@ -169,7 +207,8 @@ const PartnerLicense: React.FC = (): JSX.Element => {
|
|||||||
}, [myAccountInfo]);
|
}, [myAccountInfo]);
|
||||||
|
|
||||||
// 現在の表示階層に合わせたボタン制御用
|
// 現在の表示階層に合わせたボタン制御用
|
||||||
const [buttonLabel, setButtonLabel] = useState("");
|
const [showOrderHistoryButton, setShowOrderHistoryButton] = useState(false);
|
||||||
|
const [showViewDetailsButton, setShowViewDetailsButton] = useState(false);
|
||||||
|
|
||||||
// パンくずリスト用stateに自アカウントを追加
|
// パンくずリスト用stateに自アカウントを追加
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -187,15 +226,17 @@ const PartnerLicense: React.FC = (): JSX.Element => {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
// 表内のボタン表示判定
|
// 表内のボタン表示判定
|
||||||
if (hierarchicalElements.length === 1 && ownPartnerLicenseInfo.tier !== 4) {
|
if (ownPartnerLicenseInfo.tier !== 4) {
|
||||||
setButtonLabel(
|
setShowOrderHistoryButton(true);
|
||||||
t(getTranslationID("partnerLicense.label.orderHistoryButton"))
|
setShowViewDetailsButton(false);
|
||||||
);
|
|
||||||
} else if (ownPartnerLicenseInfo.tier === 4) {
|
} else if (ownPartnerLicenseInfo.tier === 4) {
|
||||||
setButtonLabel(t(getTranslationID("partnerLicense.label.viewDetails")));
|
setShowOrderHistoryButton(true);
|
||||||
|
setShowViewDetailsButton(true);
|
||||||
} else {
|
} else {
|
||||||
setButtonLabel("");
|
setShowOrderHistoryButton(false);
|
||||||
|
setShowViewDetailsButton(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [ownPartnerLicenseInfo]);
|
}, [ownPartnerLicenseInfo]);
|
||||||
|
|
||||||
@ -214,15 +255,28 @@ const PartnerLicense: React.FC = (): JSX.Element => {
|
|||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [hierarchicalElements, currentPage]);
|
}, [hierarchicalElements, currentPage]);
|
||||||
|
|
||||||
|
// パートナーライセンス画面からも検索ポップアップからもOrder History/View Detailsが表示されていない時に表示
|
||||||
|
const isVisiblePartnerLicensePage = useMemo(
|
||||||
|
() =>
|
||||||
|
!isLicenseOrderHistoryInSearchOpen &&
|
||||||
|
!isViewDetailsInSearchOpen &&
|
||||||
|
!isLicenseOrderHistoryOpen &&
|
||||||
|
!isViewDetailsOpen,
|
||||||
|
[
|
||||||
|
isLicenseOrderHistoryInSearchOpen,
|
||||||
|
isViewDetailsInSearchOpen,
|
||||||
|
isLicenseOrderHistoryOpen,
|
||||||
|
isViewDetailsOpen,
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{/* 表示確認用の仮画面 */}
|
|
||||||
{/* isPopupOpenがfalseの場合はポップアップのhtmlを生成しないように対応。これによりポップアップは都度生成されて初期化の考慮が減る */}
|
{/* isPopupOpenがfalseの場合はポップアップのhtmlを生成しないように対応。これによりポップアップは都度生成されて初期化の考慮が減る */}
|
||||||
{isCardLicenseIssuePopupOpen && (
|
{isCardLicenseIssuePopupOpen && (
|
||||||
<CardLicenseIssuePopup
|
<CardLicenseIssuePopup
|
||||||
onClose={() => {
|
onClose={() => {
|
||||||
setIsCardLicenseIssuePopupOpen(false);
|
setIsCardLicenseIssuePopupOpen(false);
|
||||||
dispatch(getMyAccountAsync());
|
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
@ -230,28 +284,40 @@ const PartnerLicense: React.FC = (): JSX.Element => {
|
|||||||
<LicenseOrderPopup
|
<LicenseOrderPopup
|
||||||
onClose={() => {
|
onClose={() => {
|
||||||
setIslicenseOrderPopupOpen(false);
|
setIslicenseOrderPopupOpen(false);
|
||||||
dispatch(getMyAccountAsync());
|
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{islicenseOrderHistoryOpen && (
|
{isLicenseOrderHistoryOpen && (
|
||||||
<LicenseOrderHistory
|
<LicenseOrderHistory
|
||||||
onReturn={() => {
|
onReturn={() => {
|
||||||
setIslicenseOrderHistoryOpen(false);
|
dispatch(setIsLicenseOrderHistoryOpen({ value: false }));
|
||||||
}}
|
}}
|
||||||
|
selectedRow={selectedRow}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{isViewDetailsOpen && (
|
{isViewDetailsOpen && (
|
||||||
<LicenseSummary
|
<LicenseSummary
|
||||||
onReturn={() => {
|
onReturn={() => {
|
||||||
setIsViewDetailsOpen(false);
|
dispatch(setIsViewDetailsOpen({ value: false }));
|
||||||
|
}}
|
||||||
|
selectedRow={selectedRow}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{isChangeOwnerPopupOpen && (
|
||||||
|
<ChangeOwnerPopup
|
||||||
|
onClose={() => {
|
||||||
|
setIsChangeOwnerPopupOpen(false);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{!islicenseOrderHistoryOpen && !isViewDetailsOpen && (
|
{isVisiblePartnerSearch() && isSearchPopupOpen && (
|
||||||
|
<SearchPartnerPopup
|
||||||
|
onClose={() => dispatch(setIsSearchPopupOpen({ value: true }))}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{isVisiblePartnerLicensePage && (
|
||||||
<div className={styles.wrap}>
|
<div className={styles.wrap}>
|
||||||
<Header />
|
<Header />
|
||||||
<UpdateTokenTimer />
|
|
||||||
<main className={styles.main}>
|
<main className={styles.main}>
|
||||||
<div className="">
|
<div className="">
|
||||||
<div className={styles.pageHeader}>
|
<div className={styles.pageHeader}>
|
||||||
@ -332,6 +398,42 @@ const PartnerLicense: React.FC = (): JSX.Element => {
|
|||||||
</a>
|
</a>
|
||||||
)}
|
)}
|
||||||
</li>
|
</li>
|
||||||
|
<li>
|
||||||
|
{isVisibleChangeOwner(ownPartnerLicenseInfo.tier) && (
|
||||||
|
// eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions
|
||||||
|
<a
|
||||||
|
className={`${styles.menuLink} ${styles.isActive}`}
|
||||||
|
onClick={onClickChangeOwner}
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
src={changeOwnerIcon}
|
||||||
|
alt=""
|
||||||
|
className={styles.menuIcon}
|
||||||
|
/>
|
||||||
|
{t(
|
||||||
|
getTranslationID(
|
||||||
|
"partnerLicense.label.changeOwnerButton"
|
||||||
|
)
|
||||||
|
)}
|
||||||
|
</a>
|
||||||
|
)}
|
||||||
|
</li>
|
||||||
|
<li className={styles.floatRight}>
|
||||||
|
{isVisiblePartnerSearch() && (
|
||||||
|
// eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions
|
||||||
|
<a
|
||||||
|
className={`${styles.menuLink} ${styles.isActive} ${styles.alignRight}`}
|
||||||
|
onClick={onOpenSearchPopup}
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
src={searchIcon}
|
||||||
|
alt="search"
|
||||||
|
className={styles.menuIcon}
|
||||||
|
/>
|
||||||
|
{t(getTranslationID("partnerLicense.label.search"))}
|
||||||
|
</a>
|
||||||
|
)}
|
||||||
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
<ul className={styles.brCrumbLicense}>
|
<ul className={styles.brCrumbLicense}>
|
||||||
{hierarchicalElements.map((value) => (
|
{hierarchicalElements.map((value) => (
|
||||||
@ -358,6 +460,13 @@ const PartnerLicense: React.FC = (): JSX.Element => {
|
|||||||
<th>
|
<th>
|
||||||
{t(getTranslationID("partnerLicense.label.stockLicense"))}
|
{t(getTranslationID("partnerLicense.label.stockLicense"))}
|
||||||
</th>
|
</th>
|
||||||
|
<th>
|
||||||
|
{t(
|
||||||
|
getTranslationID(
|
||||||
|
"partnerLicense.label.allocatedLicense"
|
||||||
|
)
|
||||||
|
)}
|
||||||
|
</th>
|
||||||
<th>
|
<th>
|
||||||
{t(
|
{t(
|
||||||
getTranslationID("partnerLicense.label.issueRequested")
|
getTranslationID("partnerLicense.label.issueRequested")
|
||||||
@ -383,12 +492,13 @@ const PartnerLicense: React.FC = (): JSX.Element => {
|
|||||||
? ownPartnerLicenseInfo.stockLicense
|
? ownPartnerLicenseInfo.stockLicense
|
||||||
: "-"}
|
: "-"}
|
||||||
</td>
|
</td>
|
||||||
|
<td>-</td>
|
||||||
<td>{ownPartnerLicenseInfo.issuedRequested}</td>
|
<td>{ownPartnerLicenseInfo.issuedRequested}</td>
|
||||||
<td>
|
<td>
|
||||||
<span
|
<span
|
||||||
className={
|
className={
|
||||||
ownPartnerLicenseInfo.shortage > 0 &&
|
ownPartnerLicenseInfo.shortage > 0 &&
|
||||||
ownPartnerLicenseInfo.tier !== 1
|
ownPartnerLicenseInfo.tier !== 1
|
||||||
? styles.isAlert
|
? styles.isAlert
|
||||||
: ""
|
: ""
|
||||||
}
|
}
|
||||||
@ -426,6 +536,7 @@ const PartnerLicense: React.FC = (): JSX.Element => {
|
|||||||
<td>{tierNames[value.tier]}</td>
|
<td>{tierNames[value.tier]}</td>
|
||||||
<td>{value.accountId}</td>
|
<td>{value.accountId}</td>
|
||||||
<td>{value.stockLicense}</td>
|
<td>{value.stockLicense}</td>
|
||||||
|
<td>{value.tier === 5 ? value.allocatedLicense : "-"}</td>
|
||||||
<td>{value.issuedRequested}</td>
|
<td>{value.issuedRequested}</td>
|
||||||
<td>
|
<td>
|
||||||
<span
|
<span
|
||||||
@ -442,20 +553,38 @@ const PartnerLicense: React.FC = (): JSX.Element => {
|
|||||||
<li>
|
<li>
|
||||||
{/* eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions */}
|
{/* eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions */}
|
||||||
<a
|
<a
|
||||||
className={`${styles.menuLink} ${
|
className={`${styles.menuLink} ${showOrderHistoryButton ? styles.isActive : ""
|
||||||
buttonLabel ? styles.isActive : ""
|
}`}
|
||||||
}`}
|
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (ownPartnerLicenseInfo.tier === 4) {
|
onClickOrderHistory(value);
|
||||||
onClickViewDetails(value);
|
|
||||||
} else {
|
|
||||||
onClickOrderHistory(value);
|
|
||||||
}
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{buttonLabel}
|
{t(
|
||||||
|
getTranslationID(
|
||||||
|
"partnerLicense.label.orderHistoryButton"
|
||||||
|
)
|
||||||
|
)}
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
<li>
|
||||||
|
{/* Second button (only if tier is 4) */}
|
||||||
|
{showViewDetailsButton && (
|
||||||
|
// eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions
|
||||||
|
<a
|
||||||
|
className={`${styles.menuLink} ${showViewDetailsButton ? styles.isActive : ""
|
||||||
|
}`}
|
||||||
|
onClick={() => {
|
||||||
|
onClickViewDetails(value);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{t(
|
||||||
|
getTranslationID(
|
||||||
|
"partnerLicense.label.viewDetails"
|
||||||
|
)
|
||||||
|
)}
|
||||||
|
</a>
|
||||||
|
)}
|
||||||
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
@ -483,9 +612,8 @@ const PartnerLicense: React.FC = (): JSX.Element => {
|
|||||||
onClick={() => {
|
onClick={() => {
|
||||||
movePage(0);
|
movePage(0);
|
||||||
}}
|
}}
|
||||||
className={` ${
|
className={` ${!isLoading && currentPage !== 1 ? styles.isActive : ""
|
||||||
!isLoading && currentPage !== 1 ? styles.isActive : ""
|
}`}
|
||||||
}`}
|
|
||||||
>
|
>
|
||||||
«
|
«
|
||||||
</a>
|
</a>
|
||||||
@ -494,25 +622,22 @@ const PartnerLicense: React.FC = (): JSX.Element => {
|
|||||||
onClick={() => {
|
onClick={() => {
|
||||||
movePage((currentPage - 2) * ACCOUNTS_VIEW_LIMIT);
|
movePage((currentPage - 2) * ACCOUNTS_VIEW_LIMIT);
|
||||||
}}
|
}}
|
||||||
className={`${
|
className={`${!isLoading && currentPage !== 1 ? styles.isActive : ""
|
||||||
!isLoading && currentPage !== 1 ? styles.isActive : ""
|
}`}
|
||||||
}`}
|
|
||||||
>
|
>
|
||||||
‹
|
‹
|
||||||
</a>
|
</a>
|
||||||
{` ${total !== 0 ? currentPage : 0} of ${
|
{` ${total !== 0 ? currentPage : 0} of ${total !== 0 ? totalPage : 0
|
||||||
total !== 0 ? totalPage : 0
|
} `}
|
||||||
} `}
|
|
||||||
{/* eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions */}
|
{/* eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions */}
|
||||||
<a
|
<a
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
movePage(currentPage * ACCOUNTS_VIEW_LIMIT);
|
movePage(currentPage * ACCOUNTS_VIEW_LIMIT);
|
||||||
}}
|
}}
|
||||||
className={`${
|
className={`${!isLoading && currentPage < totalPage
|
||||||
!isLoading && currentPage < totalPage
|
|
||||||
? styles.isActive
|
? styles.isActive
|
||||||
: ""
|
: ""
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
›
|
›
|
||||||
</a>
|
</a>
|
||||||
@ -521,11 +646,10 @@ const PartnerLicense: React.FC = (): JSX.Element => {
|
|||||||
onClick={() => {
|
onClick={() => {
|
||||||
movePage((totalPage - 1) * ACCOUNTS_VIEW_LIMIT);
|
movePage((totalPage - 1) * ACCOUNTS_VIEW_LIMIT);
|
||||||
}}
|
}}
|
||||||
className={` ${
|
className={` ${!isLoading && currentPage < totalPage
|
||||||
!isLoading && currentPage < totalPage
|
|
||||||
? styles.isActive
|
? styles.isActive
|
||||||
: ""
|
: ""
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
»
|
»
|
||||||
</a>
|
</a>
|
||||||
@ -548,4 +672,14 @@ const PartnerLicense: React.FC = (): JSX.Element => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const isVisibleChangeOwner = (partnerTier: number) =>
|
||||||
|
// 自身が第一階層または第二階層で、表示しているパートナーが第三階層または第四階層の場合のみ表示
|
||||||
|
isApproveTier([TIERS.TIER1, TIERS.TIER2]) &&
|
||||||
|
(partnerTier.toString() === TIERS.TIER3 ||
|
||||||
|
partnerTier.toString() === TIERS.TIER4);
|
||||||
|
|
||||||
|
const isVisiblePartnerSearch = () =>
|
||||||
|
// 自身が第一階層〜第四階層の場合のみ表示
|
||||||
|
isApproveTier([TIERS.TIER1, TIERS.TIER2, TIERS.TIER3, TIERS.TIER4]);
|
||||||
|
|
||||||
export default PartnerLicense;
|
export default PartnerLicense;
|
||||||
|
|||||||
@ -0,0 +1,354 @@
|
|||||||
|
import { SearchPartner } from "api";
|
||||||
|
import React, {
|
||||||
|
useState,
|
||||||
|
useEffect,
|
||||||
|
useRef,
|
||||||
|
useCallback,
|
||||||
|
useMemo,
|
||||||
|
} from "react";
|
||||||
|
import { AppDispatch } from "app/store";
|
||||||
|
import styles from "styles/app.module.scss";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { getTranslationID } from "translation";
|
||||||
|
import { useSelector, useDispatch } from "react-redux";
|
||||||
|
import {
|
||||||
|
changeSelectedRow,
|
||||||
|
cleanupSearchResult,
|
||||||
|
cleanupPartnerHierarchy,
|
||||||
|
setIsLicenseOrderHistoryOpen,
|
||||||
|
setIsViewDetailsOpen,
|
||||||
|
searchPartnersAsync,
|
||||||
|
selectIsLoading,
|
||||||
|
selectSelectedRow,
|
||||||
|
selectSearchResult,
|
||||||
|
selectPartnerHierarchy,
|
||||||
|
selectIsLicenseOrderHistoryOpen,
|
||||||
|
selectIsViewDetailsOpen,
|
||||||
|
getPartnerHierarchy,
|
||||||
|
} from "features/license/searchPartner";
|
||||||
|
import { setIsSearchPopupOpen } from "features/license/partnerLicense";
|
||||||
|
import { LicenseSummary } from "./licenseSummary";
|
||||||
|
import { LicenseOrderHistory } from "./licenseOrderHistory";
|
||||||
|
import close from "../../assets/images/close.svg";
|
||||||
|
import searchIcon from "../../assets/images/search.svg";
|
||||||
|
import progress_activit from "../../assets/images/progress_activit.svg";
|
||||||
|
|
||||||
|
interface SearchPopupProps {
|
||||||
|
onClose: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const SearchPartnerPopup: React.FC<SearchPopupProps> = (props) => {
|
||||||
|
const dispatch: AppDispatch = useDispatch();
|
||||||
|
const { onClose } = props;
|
||||||
|
const [t] = useTranslation();
|
||||||
|
const isLoading = useSelector(selectIsLoading);
|
||||||
|
const selectedRow = useSelector(selectSelectedRow) as SearchPartner;
|
||||||
|
const searchResult = useSelector(selectSearchResult);
|
||||||
|
const partnerHierarchy = useSelector(selectPartnerHierarchy);
|
||||||
|
const [accountId, setAccountId] = useState("");
|
||||||
|
const [companyName, setCompanyName] = useState("");
|
||||||
|
const [isBreadcrumbOpen, setIsBreadcrumbOpen] = useState(false);
|
||||||
|
const isViewDetailsOpen = useSelector(selectIsViewDetailsOpen);
|
||||||
|
const isLicenseOrderHistoryOpen = useSelector(
|
||||||
|
selectIsLicenseOrderHistoryOpen
|
||||||
|
);
|
||||||
|
const [popupPosition, setPopupPosition] = useState<{ x: number; y: number }>({
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
});
|
||||||
|
const breadcrumbRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
|
// フォームの入力チェック
|
||||||
|
const searchButtonEnabled = useMemo(() => {
|
||||||
|
// 両方入力がない場合はボタンを活性化しない。
|
||||||
|
if (!companyName && !accountId) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// Company Nameは3文字以上入力がない場合はボタンを活性化しない。
|
||||||
|
// サロゲートペアを考慮して、スプレッド構文でリスト化してから文字数をカウントする
|
||||||
|
// 絵文字が入力された場合は救えないが、そもそも入力されても検索できないので考慮しない。
|
||||||
|
if (companyName && [...companyName].length <= 2) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// Account IDは数字ではないまたは0以下の場合はボタンを活性化しない。
|
||||||
|
if (
|
||||||
|
accountId &&
|
||||||
|
(Number.isNaN(Number(accountId)) || Number(accountId) <= 0)
|
||||||
|
) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}, [companyName, accountId]);
|
||||||
|
|
||||||
|
const requestSearch = useCallback(
|
||||||
|
async (e: React.FormEvent<HTMLFormElement>) => {
|
||||||
|
e.preventDefault();
|
||||||
|
if (!searchButtonEnabled) return;
|
||||||
|
dispatch(
|
||||||
|
searchPartnersAsync({
|
||||||
|
companyName,
|
||||||
|
accountId: Number(accountId),
|
||||||
|
})
|
||||||
|
);
|
||||||
|
},
|
||||||
|
[dispatch, companyName, accountId, searchButtonEnabled]
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleAccountNameClick = useCallback(
|
||||||
|
async (clickAccountId: number, event: React.MouseEvent) => {
|
||||||
|
// ロード中はなにもしない。
|
||||||
|
if (isLoading) return;
|
||||||
|
event.stopPropagation();
|
||||||
|
// アカウントの階層を取得
|
||||||
|
await dispatch(getPartnerHierarchy({ accountId: clickAccountId }));
|
||||||
|
setPopupPosition({ x: event.clientX, y: event.clientY });
|
||||||
|
setIsBreadcrumbOpen(true);
|
||||||
|
},
|
||||||
|
[dispatch, setPopupPosition, setIsBreadcrumbOpen, isLoading]
|
||||||
|
);
|
||||||
|
|
||||||
|
const closeBreadcrumbPopup = () => {
|
||||||
|
setIsBreadcrumbOpen(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
// ポップアップ外のクリックポップアップ非表示するイベント
|
||||||
|
useEffect(() => {
|
||||||
|
const handleClickOutside = (event: MouseEvent) => {
|
||||||
|
if (
|
||||||
|
breadcrumbRef.current &&
|
||||||
|
!breadcrumbRef.current.contains(event.target as Node)
|
||||||
|
) {
|
||||||
|
closeBreadcrumbPopup();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
if (isBreadcrumbOpen) {
|
||||||
|
document.addEventListener("mousedown", handleClickOutside);
|
||||||
|
} else {
|
||||||
|
document.removeEventListener("mousedown", handleClickOutside);
|
||||||
|
}
|
||||||
|
return () => {
|
||||||
|
document.removeEventListener("mousedown", handleClickOutside);
|
||||||
|
};
|
||||||
|
}, [isBreadcrumbOpen]);
|
||||||
|
|
||||||
|
const tierNames: { [key: number]: string } = {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||||
|
1: t(getTranslationID("common.label.tier1")),
|
||||||
|
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||||
|
2: t(getTranslationID("common.label.tier2")),
|
||||||
|
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||||
|
3: t(getTranslationID("common.label.tier3")),
|
||||||
|
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||||
|
4: t(getTranslationID("common.label.tier4")),
|
||||||
|
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||||
|
5: t(getTranslationID("common.label.tier5")),
|
||||||
|
};
|
||||||
|
|
||||||
|
const openViewDetails = useCallback(
|
||||||
|
(value: SearchPartner) => {
|
||||||
|
dispatch(changeSelectedRow({ value }));
|
||||||
|
dispatch(setIsViewDetailsOpen({ value: true }));
|
||||||
|
},
|
||||||
|
[dispatch]
|
||||||
|
);
|
||||||
|
|
||||||
|
const openOrderHistory = useCallback(
|
||||||
|
(value?: SearchPartner) => {
|
||||||
|
dispatch(changeSelectedRow({ value }));
|
||||||
|
dispatch(setIsLicenseOrderHistoryOpen({ value: true }));
|
||||||
|
},
|
||||||
|
[dispatch]
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleModalClose = useCallback(() => {
|
||||||
|
// ポップアップ閉じたら、検索結果をクリアする
|
||||||
|
dispatch(cleanupSearchResult());
|
||||||
|
dispatch(cleanupPartnerHierarchy());
|
||||||
|
onClose();
|
||||||
|
dispatch(setIsSearchPopupOpen({ value: false }));
|
||||||
|
}, [dispatch, onClose]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
// eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions
|
||||||
|
<div>
|
||||||
|
{isViewDetailsOpen && (
|
||||||
|
<div>
|
||||||
|
<LicenseSummary
|
||||||
|
onReturn={() => dispatch(setIsViewDetailsOpen({ value: false }))}
|
||||||
|
selectedRow={selectedRow}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{isLicenseOrderHistoryOpen && (
|
||||||
|
<div>
|
||||||
|
<LicenseOrderHistory
|
||||||
|
onReturn={() =>
|
||||||
|
dispatch(setIsLicenseOrderHistoryOpen({ value: false }))
|
||||||
|
}
|
||||||
|
selectedRow={selectedRow}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<div className={`${styles.modal} ${styles.isShow}`}>
|
||||||
|
<div className={styles.searchModalBox}>
|
||||||
|
<div className={styles.headerContainer}>
|
||||||
|
<p className={styles.modalTitle}>
|
||||||
|
{t(getTranslationID("searchPartnerAccountPopupPage.label.title"))}
|
||||||
|
</p>
|
||||||
|
<form
|
||||||
|
className={styles.searchBar}
|
||||||
|
onSubmit={(e) => requestSearch(e)}
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
placeholder={t(getTranslationID("partnerLicense.label.name"))}
|
||||||
|
value={companyName}
|
||||||
|
onChange={(e) => setCompanyName(e.target.value.trimStart())}
|
||||||
|
className={styles.searchInput}
|
||||||
|
/>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
placeholder={t(
|
||||||
|
getTranslationID("partnerLicense.label.accountId")
|
||||||
|
)}
|
||||||
|
value={accountId}
|
||||||
|
onChange={(e) => setAccountId(e.target.value.trimStart())}
|
||||||
|
className={styles.searchInput}
|
||||||
|
/>
|
||||||
|
{/* eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions */}
|
||||||
|
<button
|
||||||
|
className={`${styles.menuLink} ${!isLoading && searchButtonEnabled ? styles.isActive : ""
|
||||||
|
}`}
|
||||||
|
type="submit"
|
||||||
|
disabled={!searchButtonEnabled || isLoading}
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
src={searchIcon}
|
||||||
|
alt="search"
|
||||||
|
className={styles.menuIcon}
|
||||||
|
/>
|
||||||
|
{t(getTranslationID("partnerLicense.label.search"))}
|
||||||
|
</button>
|
||||||
|
<button type="button" onClick={handleModalClose}>
|
||||||
|
<img
|
||||||
|
src={close}
|
||||||
|
className={styles.modalTitleIcon}
|
||||||
|
alt="close"
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<table
|
||||||
|
className={`${styles.table} ${styles.partner} ${styles.marginBtm3}`}
|
||||||
|
>
|
||||||
|
<tr className={styles.tableHeader}>
|
||||||
|
<th>
|
||||||
|
<a>{t(getTranslationID("partnerPage.label.name"))}</a>
|
||||||
|
</th>
|
||||||
|
<th>
|
||||||
|
<a>{t(getTranslationID("partnerPage.label.category"))}</a>
|
||||||
|
</th>
|
||||||
|
<th>
|
||||||
|
<a>{t(getTranslationID("partnerPage.label.accountId"))}</a>
|
||||||
|
</th>
|
||||||
|
<th>
|
||||||
|
<a>{t(getTranslationID("partnerPage.label.country"))}</a>
|
||||||
|
</th>
|
||||||
|
<th>
|
||||||
|
<a>{t(getTranslationID("partnerPage.label.primaryAdmin"))}</a>
|
||||||
|
</th>
|
||||||
|
<th>
|
||||||
|
<a>{t(getTranslationID("partnerPage.label.email"))}</a>
|
||||||
|
</th>
|
||||||
|
<th>
|
||||||
|
<a>{"" /** Order History、View Details用の空カラム */}</a>
|
||||||
|
</th>
|
||||||
|
</tr>
|
||||||
|
{searchResult.map((result) => (
|
||||||
|
<tr key={result.accountId}>
|
||||||
|
<td
|
||||||
|
onClick={(event) =>
|
||||||
|
handleAccountNameClick(result.accountId, event)
|
||||||
|
}
|
||||||
|
className={styles.hoverBlue}
|
||||||
|
>
|
||||||
|
{result.name}
|
||||||
|
</td>
|
||||||
|
<td>{tierNames[result.tier]}</td>
|
||||||
|
<td>{result.accountId}</td>
|
||||||
|
<td>{result.country}</td>
|
||||||
|
<td>{result.primaryAdmin}</td>
|
||||||
|
<td>{result.email ?? "-"}</td>
|
||||||
|
<td>
|
||||||
|
<ul className={`${styles.menuAction} ${styles.inTable}`}>
|
||||||
|
<li>
|
||||||
|
{/* eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions */}
|
||||||
|
<a
|
||||||
|
className={`${styles.menuLink} ${styles.isActive}`}
|
||||||
|
onClick={() => {
|
||||||
|
openOrderHistory(result);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{t(
|
||||||
|
getTranslationID(
|
||||||
|
"partnerLicense.label.orderHistoryButton"
|
||||||
|
)
|
||||||
|
)}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
{result.tier === 5 && (
|
||||||
|
<li>
|
||||||
|
{/* eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions */}
|
||||||
|
<a
|
||||||
|
className={`${styles.menuLink} ${styles.isActive}`}
|
||||||
|
onClick={() => {
|
||||||
|
openViewDetails(result);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{t(
|
||||||
|
getTranslationID("partnerLicense.label.viewDetails")
|
||||||
|
)}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
)}
|
||||||
|
</ul>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
))}
|
||||||
|
</table>
|
||||||
|
{searchResult.length === 0 && (
|
||||||
|
<p style={{ margin: "10px", textAlign: "center" }}>
|
||||||
|
{t(getTranslationID("common.message.listEmpty"))}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
{/* ローディングオーバーレイ */}
|
||||||
|
<div
|
||||||
|
style={{ display: isLoading ? "inline" : "none" }}
|
||||||
|
className={styles.overlay}
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
src={progress_activit}
|
||||||
|
className={`${styles.icLoading} ${styles.alignCenter}`}
|
||||||
|
alt="Loading"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{isBreadcrumbOpen && (
|
||||||
|
<div
|
||||||
|
ref={breadcrumbRef}
|
||||||
|
className={styles.breadcrumbPopup}
|
||||||
|
style={{ top: popupPosition.y, left: popupPosition.x }}
|
||||||
|
>
|
||||||
|
<ul className={styles.brCrumbPartner}>
|
||||||
|
{partnerHierarchy.map((parent) => (
|
||||||
|
<li key={parent.tier}>
|
||||||
|
<span>{parent.name}</span>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
@ -0,0 +1,123 @@
|
|||||||
|
import React, { useCallback, useEffect } from "react";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { AppDispatch } from "app/store";
|
||||||
|
import { useDispatch, useSelector } from "react-redux";
|
||||||
|
import {
|
||||||
|
issueTrialLicenseAsync,
|
||||||
|
cleanupApps,
|
||||||
|
selectIsLoading,
|
||||||
|
selectNumberOfLicenses,
|
||||||
|
selectExpirationDate,
|
||||||
|
setExpirationDate,
|
||||||
|
} from "features/license/licenseTrialIssue";
|
||||||
|
import styles from "../../styles/app.module.scss";
|
||||||
|
import { getTranslationID } from "../../translation";
|
||||||
|
import close from "../../assets/images/close.svg";
|
||||||
|
import progress_activit from "../../assets/images/progress_activit.svg";
|
||||||
|
import { SearchPartner, PartnerLicenseInfo } from "../../api";
|
||||||
|
|
||||||
|
interface TrialLicenseIssuePopupProps {
|
||||||
|
onClose: () => void;
|
||||||
|
selectedRow?: PartnerLicenseInfo | SearchPartner;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const TrialLicenseIssuePopup: React.FC<TrialLicenseIssuePopupProps> = (
|
||||||
|
props
|
||||||
|
) => {
|
||||||
|
const { onClose, selectedRow } = props;
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const dispatch: AppDispatch = useDispatch();
|
||||||
|
const isLoading = useSelector(selectIsLoading);
|
||||||
|
|
||||||
|
const numberOfLicenses = useSelector(selectNumberOfLicenses);
|
||||||
|
const expirationDate = useSelector(selectExpirationDate);
|
||||||
|
|
||||||
|
useEffect(
|
||||||
|
() => () => {
|
||||||
|
// useEffectのreturnとしてcleanupAppsを実行することで、ポップアップのアンマウント時に初期化を行う
|
||||||
|
dispatch(cleanupApps());
|
||||||
|
},
|
||||||
|
[dispatch]
|
||||||
|
);
|
||||||
|
|
||||||
|
// ポップアップ表示時
|
||||||
|
useEffect(() => {
|
||||||
|
// トライアルライセンスの有効期限を設定。
|
||||||
|
dispatch(setExpirationDate());
|
||||||
|
}, [dispatch]);
|
||||||
|
|
||||||
|
// ポップアップを閉じる処理
|
||||||
|
const closePopup = useCallback(() => {
|
||||||
|
if (isLoading) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
onClose();
|
||||||
|
}, [isLoading, onClose]);
|
||||||
|
|
||||||
|
// 発行ボタン押下時
|
||||||
|
const onIssueTrialLicense = useCallback(async () => {
|
||||||
|
// トライアルライセンス発行APIの呼び出し
|
||||||
|
const { meta } = await dispatch(issueTrialLicenseAsync({ selectedRow }));
|
||||||
|
if (meta.requestStatus === "fulfilled") {
|
||||||
|
closePopup();
|
||||||
|
}
|
||||||
|
}, [dispatch, closePopup, selectedRow]);
|
||||||
|
|
||||||
|
// HTML
|
||||||
|
return (
|
||||||
|
<div className={`${styles.modal} ${styles.isShow}`}>
|
||||||
|
<div className={styles.modalBox}>
|
||||||
|
<p className={styles.modalTitle}>
|
||||||
|
{t(getTranslationID("trialLicenseIssuePopupPage.label.title"))}
|
||||||
|
<button type="button" onClick={closePopup}>
|
||||||
|
<img src={close} className={styles.modalTitleIcon} alt="close" />
|
||||||
|
</button>
|
||||||
|
</p>
|
||||||
|
<form className={styles.form}>
|
||||||
|
<dl className={`${styles.formList} ${styles.hasbg}`}>
|
||||||
|
<dt className={styles.formTitle}>
|
||||||
|
{t(getTranslationID("trialLicenseIssuePopupPage.label.subTitle"))}
|
||||||
|
</dt>
|
||||||
|
<dt className={styles.overLine}>
|
||||||
|
{t(
|
||||||
|
getTranslationID(
|
||||||
|
"trialLicenseIssuePopupPage.label.numberOfLicenses"
|
||||||
|
)
|
||||||
|
)}
|
||||||
|
</dt>
|
||||||
|
<dd>{numberOfLicenses}</dd>
|
||||||
|
<dt>
|
||||||
|
{t(
|
||||||
|
getTranslationID(
|
||||||
|
"trialLicenseIssuePopupPage.label.expirationDate"
|
||||||
|
)
|
||||||
|
)}
|
||||||
|
</dt>
|
||||||
|
<dd>{expirationDate}</dd>
|
||||||
|
<dd className={`${styles.full} ${styles.alignCenter}`}>
|
||||||
|
<input
|
||||||
|
type="button"
|
||||||
|
name="submit"
|
||||||
|
value={t(
|
||||||
|
getTranslationID(
|
||||||
|
"trialLicenseIssuePopupPage.label.issueButton"
|
||||||
|
)
|
||||||
|
)}
|
||||||
|
className={`${styles.formSubmit} ${styles.marginBtm1} ${
|
||||||
|
!isLoading ? styles.isActive : ""
|
||||||
|
}`}
|
||||||
|
onClick={onIssueTrialLicense}
|
||||||
|
/>
|
||||||
|
<img
|
||||||
|
style={{ display: isLoading ? "inline" : "none" }}
|
||||||
|
src={progress_activit}
|
||||||
|
className={styles.icLoading}
|
||||||
|
alt="Loading"
|
||||||
|
/>
|
||||||
|
</dd>
|
||||||
|
</dl>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
@ -110,6 +110,7 @@ export const AddPartnerAccountPopup: React.FC<AddPartnerAccountPopup> = (
|
|||||||
country,
|
country,
|
||||||
adminName,
|
adminName,
|
||||||
email,
|
email,
|
||||||
|
offset,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@ -0,0 +1,178 @@
|
|||||||
|
import { AppDispatch } from "app/store";
|
||||||
|
import React, { useCallback, useEffect } from "react";
|
||||||
|
import styles from "styles/app.module.scss";
|
||||||
|
import { useDispatch, useSelector } from "react-redux";
|
||||||
|
import { getTranslationID } from "translation";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import {
|
||||||
|
changeEditCompanyName,
|
||||||
|
changeSelectedAdminId,
|
||||||
|
cleanupPartnerAccount,
|
||||||
|
getPartnerUsersAsync,
|
||||||
|
editPartnerInfoAsync,
|
||||||
|
selectEditPartnerCompanyName,
|
||||||
|
selectEditPartnerCountry,
|
||||||
|
selectEditPartnerId,
|
||||||
|
selectEditPartnerUsers,
|
||||||
|
selectIsLoading,
|
||||||
|
selectSelectedAdminId,
|
||||||
|
selectOffset,
|
||||||
|
getPartnerInfoAsync,
|
||||||
|
LIMIT_PARTNER_VIEW_NUM,
|
||||||
|
} from "features/partner";
|
||||||
|
import close from "../../assets/images/close.svg";
|
||||||
|
import progress_activit from "../../assets/images/progress_activit.svg";
|
||||||
|
import { COUNTRY_LIST } from "../SignupPage/constants";
|
||||||
|
|
||||||
|
interface EditPartnerAccountPopup {
|
||||||
|
isOpen: boolean;
|
||||||
|
onClose: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const EditPartnerAccountPopup: React.FC<EditPartnerAccountPopup> = (
|
||||||
|
props
|
||||||
|
) => {
|
||||||
|
const { isOpen, onClose } = props;
|
||||||
|
const dispatch: AppDispatch = useDispatch();
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const isLoading = useSelector(selectIsLoading);
|
||||||
|
const offset = useSelector(selectOffset);
|
||||||
|
|
||||||
|
const partnerId = useSelector(selectEditPartnerId);
|
||||||
|
const companyName = useSelector(selectEditPartnerCompanyName);
|
||||||
|
const country = useSelector(selectEditPartnerCountry);
|
||||||
|
|
||||||
|
const users = useSelector(selectEditPartnerUsers);
|
||||||
|
const adminUser = users.find((user) => user.isPrimaryAdmin);
|
||||||
|
|
||||||
|
const selectedAdminId = useSelector(selectSelectedAdminId);
|
||||||
|
|
||||||
|
// ポップアップを閉じる処理
|
||||||
|
const closePopup = useCallback(() => {
|
||||||
|
if (isLoading) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
dispatch(cleanupPartnerAccount());
|
||||||
|
onClose();
|
||||||
|
}, [isLoading, onClose, dispatch]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (isOpen) {
|
||||||
|
dispatch(getPartnerUsersAsync({ accountId: partnerId }));
|
||||||
|
}
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [isOpen]);
|
||||||
|
|
||||||
|
const onEditPartner = useCallback(async () => {
|
||||||
|
// eslint-disable-next-line no-alert
|
||||||
|
if (!window.confirm(t(getTranslationID("common.message.dialogConfirm")))) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { meta } = await dispatch(editPartnerInfoAsync());
|
||||||
|
if (meta.requestStatus === "fulfilled") {
|
||||||
|
dispatch(
|
||||||
|
getPartnerInfoAsync({
|
||||||
|
limit: LIMIT_PARTNER_VIEW_NUM,
|
||||||
|
offset,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
closePopup();
|
||||||
|
}
|
||||||
|
}, [dispatch, closePopup, t, offset]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={`${styles.modal} ${isOpen ? styles.isShow : ""}`}>
|
||||||
|
<div className={styles.modalBox}>
|
||||||
|
<p className={styles.modalTitle}>
|
||||||
|
{t(getTranslationID("partnerPage.label.editAccount"))}
|
||||||
|
<button type="button" onClick={closePopup}>
|
||||||
|
<img src={close} className={styles.modalTitleIcon} alt="close" />
|
||||||
|
</button>
|
||||||
|
</p>
|
||||||
|
<form className={styles.form}>
|
||||||
|
<dl className={`${styles.formList} ${styles.hasbg}`}>
|
||||||
|
<dt className={styles.formTitle}>
|
||||||
|
{t(getTranslationID("partnerPage.label.accountInformation"))}
|
||||||
|
</dt>
|
||||||
|
<dt>{t(getTranslationID("partnerPage.label.name"))}</dt>
|
||||||
|
<dd>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
size={40}
|
||||||
|
maxLength={255}
|
||||||
|
value={companyName}
|
||||||
|
className={styles.formInput}
|
||||||
|
onChange={(e) => {
|
||||||
|
dispatch(
|
||||||
|
changeEditCompanyName({ companyName: e.target.value })
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</dd>
|
||||||
|
<dt>{t(getTranslationID("partnerPage.label.country"))}</dt>
|
||||||
|
<dd className={styles.last}>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
size={40}
|
||||||
|
value={COUNTRY_LIST.find((c) => c.value === country)?.label}
|
||||||
|
className={styles.formInput}
|
||||||
|
readOnly
|
||||||
|
/>
|
||||||
|
</dd>
|
||||||
|
<dt className={styles.formTitle}>
|
||||||
|
{t(getTranslationID("partnerPage.label.primaryAdminInfo"))}
|
||||||
|
</dt>
|
||||||
|
<dt>{t(getTranslationID("partnerPage.label.adminName"))}</dt>
|
||||||
|
<dd>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
size={40}
|
||||||
|
value={adminUser?.name}
|
||||||
|
className={styles.formInput}
|
||||||
|
readOnly
|
||||||
|
/>
|
||||||
|
</dd>
|
||||||
|
<dt>{t(getTranslationID("partnerPage.label.email"))}</dt>
|
||||||
|
<dd className={styles.last}>
|
||||||
|
<select
|
||||||
|
className={styles.formInput}
|
||||||
|
onChange={(event) => {
|
||||||
|
dispatch(
|
||||||
|
changeSelectedAdminId({
|
||||||
|
adminId: Number(event.target.value),
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
value={selectedAdminId}
|
||||||
|
>
|
||||||
|
{users.map((user) => (
|
||||||
|
<option key={user.id} value={user.id}>
|
||||||
|
{user.email}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
</dd>
|
||||||
|
<dd className={`${styles.full} ${styles.alignCenter}`}>
|
||||||
|
<input
|
||||||
|
type="button"
|
||||||
|
name="submit"
|
||||||
|
value={t(getTranslationID("partnerPage.label.saveChanges"))}
|
||||||
|
className={`${styles.formSubmit} ${styles.marginBtm1} ${
|
||||||
|
!isLoading && companyName.length !== 0 ? styles.isActive : ""
|
||||||
|
}`}
|
||||||
|
onClick={onEditPartner}
|
||||||
|
/>
|
||||||
|
<img
|
||||||
|
style={{ display: isLoading ? "inline" : "none" }}
|
||||||
|
src={progress_activit}
|
||||||
|
className={styles.icLoading}
|
||||||
|
alt="Loading"
|
||||||
|
/>
|
||||||
|
</dd>
|
||||||
|
</dl>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
@ -1,6 +1,5 @@
|
|||||||
/* eslint-disable jsx-a11y/control-has-associated-label */
|
/* eslint-disable jsx-a11y/control-has-associated-label */
|
||||||
import { AppDispatch } from "app/store";
|
import { AppDispatch } from "app/store";
|
||||||
import { UpdateTokenTimer } from "components/auth/updateTokenTimer";
|
|
||||||
import Footer from "components/footer";
|
import Footer from "components/footer";
|
||||||
import Header from "components/header";
|
import Header from "components/header";
|
||||||
import React, { useCallback, useEffect, useState } from "react";
|
import React, { useCallback, useEffect, useState } from "react";
|
||||||
@ -16,23 +15,28 @@ import {
|
|||||||
selectTotalPage,
|
selectTotalPage,
|
||||||
getPartnerInfoAsync,
|
getPartnerInfoAsync,
|
||||||
selectPartnersInfo,
|
selectPartnersInfo,
|
||||||
|
deletePartnerAccountAsync,
|
||||||
} from "features/partner/index";
|
} from "features/partner/index";
|
||||||
import {
|
import {
|
||||||
changeDelegateAccount,
|
changeDelegateAccount,
|
||||||
|
changeEditPartner,
|
||||||
savePageInfo,
|
savePageInfo,
|
||||||
} from "features/partner/partnerSlice";
|
} from "features/partner/partnerSlice";
|
||||||
import { getTranslationID } from "translation";
|
import { getTranslationID } from "translation";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { getDelegationTokenAsync } from "features/auth/operations";
|
import { getDelegationTokenAsync } from "features/auth/operations";
|
||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
|
import { Partner } from "api";
|
||||||
import personAdd from "../../assets/images/person_add.svg";
|
import personAdd from "../../assets/images/person_add.svg";
|
||||||
import { TIERS } from "../../components/auth/constants";
|
import { TIERS } from "../../components/auth/constants";
|
||||||
import { AddPartnerAccountPopup } from "./addPartnerAccountPopup";
|
import { AddPartnerAccountPopup } from "./addPartnerAccountPopup";
|
||||||
|
import { EditPartnerAccountPopup } from "./editPartnerAccountPopup";
|
||||||
import checkFill from "../../assets/images/check_fill.svg";
|
import checkFill from "../../assets/images/check_fill.svg";
|
||||||
|
|
||||||
const PartnerPage: React.FC = (): JSX.Element => {
|
const PartnerPage: React.FC = (): JSX.Element => {
|
||||||
const dispatch: AppDispatch = useDispatch();
|
const dispatch: AppDispatch = useDispatch();
|
||||||
const [isPopupOpen, setIsPopupOpen] = useState(false);
|
const [isPopupOpen, setIsPopupOpen] = useState(false);
|
||||||
|
const [isEditPopupOpen, setIsEditPopupOpen] = useState(false);
|
||||||
const [t] = useTranslation();
|
const [t] = useTranslation();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const total = useSelector(selectTotal);
|
const total = useSelector(selectTotal);
|
||||||
@ -71,6 +75,19 @@ const PartnerPage: React.FC = (): JSX.Element => {
|
|||||||
const onOpen = useCallback(() => {
|
const onOpen = useCallback(() => {
|
||||||
setIsPopupOpen(true);
|
setIsPopupOpen(true);
|
||||||
}, [setIsPopupOpen]);
|
}, [setIsPopupOpen]);
|
||||||
|
const onOpenEditPopup = useCallback(
|
||||||
|
(editPartner: Partner) => {
|
||||||
|
dispatch(
|
||||||
|
changeEditPartner({
|
||||||
|
id: editPartner.accountId,
|
||||||
|
companyName: editPartner.name,
|
||||||
|
country: editPartner.country,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
setIsEditPopupOpen(true);
|
||||||
|
},
|
||||||
|
[setIsEditPopupOpen, dispatch]
|
||||||
|
);
|
||||||
|
|
||||||
// パートナー取得APIを呼び出す
|
// パートナー取得APIを呼び出す
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -109,6 +126,31 @@ const PartnerPage: React.FC = (): JSX.Element => {
|
|||||||
[dispatch, navigate, t]
|
[dispatch, navigate, t]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// delete account押下時処理
|
||||||
|
const onDeleteAccount = useCallback(
|
||||||
|
async (accountId: number, companyName: string) => {
|
||||||
|
// ダイアログ確認
|
||||||
|
if (
|
||||||
|
/* eslint-disable-next-line no-alert */
|
||||||
|
!window.confirm(
|
||||||
|
`${t(
|
||||||
|
getTranslationID("partnerPage.message.partnerDeleteConfirm")
|
||||||
|
)} ${companyName}`
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { meta } = await dispatch(deletePartnerAccountAsync({ accountId }));
|
||||||
|
if (meta.requestStatus === "fulfilled") {
|
||||||
|
dispatch(
|
||||||
|
getPartnerInfoAsync({ limit: LIMIT_PARTNER_VIEW_NUM, offset })
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[dispatch, t, offset]
|
||||||
|
);
|
||||||
|
|
||||||
// HTML
|
// HTML
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@ -118,9 +160,14 @@ const PartnerPage: React.FC = (): JSX.Element => {
|
|||||||
setIsPopupOpen(false);
|
setIsPopupOpen(false);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
<EditPartnerAccountPopup
|
||||||
|
isOpen={isEditPopupOpen}
|
||||||
|
onClose={() => {
|
||||||
|
setIsEditPopupOpen(false);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
<div className={styles.wrap}>
|
<div className={styles.wrap}>
|
||||||
<Header />
|
<Header />
|
||||||
<UpdateTokenTimer />
|
|
||||||
<main className={styles.main}>
|
<main className={styles.main}>
|
||||||
<div className={styles.pageHeader}>
|
<div className={styles.pageHeader}>
|
||||||
<h1 className={styles.pageTitle}>
|
<h1 className={styles.pageTitle}>
|
||||||
@ -185,10 +232,30 @@ const PartnerPage: React.FC = (): JSX.Element => {
|
|||||||
<tr>
|
<tr>
|
||||||
<td className={styles.clm0}>
|
<td className={styles.clm0}>
|
||||||
<ul className={styles.menuInTable}>
|
<ul className={styles.menuInTable}>
|
||||||
{/* パートナーアカウント削除はCCB後回し分なので非表示
|
|
||||||
{isVisibleButton && (
|
{isVisibleButton && (
|
||||||
<li>
|
<li>
|
||||||
<a>
|
{/* eslint-disable-next-line jsx-a11y/click-events-have-key-events,jsx-a11y/no-static-element-interactions */}
|
||||||
|
<a
|
||||||
|
onClick={() => {
|
||||||
|
onOpenEditPopup(x);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{t(
|
||||||
|
getTranslationID(
|
||||||
|
"partnerPage.label.editAccount"
|
||||||
|
)
|
||||||
|
)}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
)}
|
||||||
|
{isVisibleButton && (
|
||||||
|
<li>
|
||||||
|
{/* eslint-disable-next-line jsx-a11y/click-events-have-key-events,jsx-a11y/no-static-element-interactions */}
|
||||||
|
<a
|
||||||
|
onClick={() => {
|
||||||
|
onDeleteAccount(x.accountId, x.name);
|
||||||
|
}}
|
||||||
|
>
|
||||||
{t(
|
{t(
|
||||||
getTranslationID(
|
getTranslationID(
|
||||||
"partnerPage.label.deleteAccount"
|
"partnerPage.label.deleteAccount"
|
||||||
@ -197,7 +264,6 @@ const PartnerPage: React.FC = (): JSX.Element => {
|
|||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
)}
|
)}
|
||||||
*/}
|
|
||||||
{isVisibleDealerManagement && (
|
{isVisibleDealerManagement && (
|
||||||
<li>
|
<li>
|
||||||
{/* eslint-disable-next-line jsx-a11y/click-events-have-key-events,jsx-a11y/no-static-element-interactions */}
|
{/* eslint-disable-next-line jsx-a11y/click-events-have-key-events,jsx-a11y/no-static-element-interactions */}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user