diff --git a/dictation_client/src/api/api.ts b/dictation_client/src/api/api.ts index ea4d026..ae9c5a5 100644 --- a/dictation_client/src/api/api.ts +++ b/dictation_client/src/api/api.ts @@ -446,6 +446,19 @@ export interface Dealer { */ country: string; } +/** + * + * @export + * @interface DeallocateLicenseRequest + */ +export interface DeallocateLicenseRequest { + /** + * ユーザーID + * @type {number} + * @memberof DeallocateLicenseRequest + */ + userId: number; +} /** * * @export @@ -3601,8 +3614,6 @@ export const LicensesApiAxiosParamCreator = function ( // http bearer authentication required await setBearerAuthToObject(localVarHeaderParameter, configuration); - localVarHeaderParameter["Content-Type"] = "application/json"; - setSearchParams(localVarUrlObj, localVarQueryParameter); let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; @@ -3611,10 +3622,6 @@ export const LicensesApiAxiosParamCreator = function ( ...headersFromBaseOptions, ...options.headers, }; - localVarRequestOptions.data = serializeDataIfNeeded( - localVarRequestOptions, - configuration - ); return { url: toPathString(localVarUrlObj), @@ -5302,6 +5309,64 @@ export const UsersApiAxiosParamCreator = function ( options: localVarRequestOptions, }; }, + /** + * ライセンス割り当てを解除します + * @summary + * @param {DeallocateLicenseRequest} deallocateLicenseRequest + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + deallocateLicense: async ( + deallocateLicenseRequest: DeallocateLicenseRequest, + options: AxiosRequestConfig = {} + ): Promise => { + // verify required parameter 'deallocateLicenseRequest' is not null or undefined + assertParamExists( + "deallocateLicense", + "deallocateLicenseRequest", + deallocateLicenseRequest + ); + const localVarPath = `/users/license/deallocate`; + // use dummy base URL string because the URL constructor only accepts absolute URLs. + const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); + let baseOptions; + if (configuration) { + baseOptions = configuration.baseOptions; + } + + const localVarRequestOptions = { + method: "POST", + ...baseOptions, + ...options, + }; + const localVarHeaderParameter = {} as any; + const localVarQueryParameter = {} as any; + + // authentication bearer required + // http bearer authentication required + await setBearerAuthToObject(localVarHeaderParameter, configuration); + + localVarHeaderParameter["Content-Type"] = "application/json"; + + setSearchParams(localVarUrlObj, localVarQueryParameter); + let headersFromBaseOptions = + baseOptions && baseOptions.headers ? baseOptions.headers : {}; + localVarRequestOptions.headers = { + ...localVarHeaderParameter, + ...headersFromBaseOptions, + ...options.headers, + }; + localVarRequestOptions.data = serializeDataIfNeeded( + deallocateLicenseRequest, + localVarRequestOptions, + configuration + ); + + return { + url: toPathString(localVarUrlObj), + options: localVarRequestOptions, + }; + }, /** * ログインしているユーザーに関連する各種情報を取得します * @summary @@ -5684,6 +5749,31 @@ export const UsersApiFp = function (configuration?: Configuration) { configuration ); }, + /** + * ライセンス割り当てを解除します + * @summary + * @param {DeallocateLicenseRequest} deallocateLicenseRequest + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + async deallocateLicense( + deallocateLicenseRequest: DeallocateLicenseRequest, + options?: AxiosRequestConfig + ): Promise< + (axios?: AxiosInstance, basePath?: string) => AxiosPromise + > { + const localVarAxiosArgs = + await localVarAxiosParamCreator.deallocateLicense( + deallocateLicenseRequest, + options + ); + return createRequestFunction( + localVarAxiosArgs, + globalAxios, + BASE_PATH, + configuration + ); + }, /** * ログインしているユーザーに関連する各種情報を取得します * @summary @@ -5888,6 +5978,21 @@ export const UsersApiFactory = function ( .confirmUserAndInitPassword(confirmRequest, options) .then((request) => request(axios, basePath)); }, + /** + * ライセンス割り当てを解除します + * @summary + * @param {DeallocateLicenseRequest} deallocateLicenseRequest + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + deallocateLicense( + deallocateLicenseRequest: DeallocateLicenseRequest, + options?: any + ): AxiosPromise { + return localVarFp + .deallocateLicense(deallocateLicenseRequest, options) + .then((request) => request(axios, basePath)); + }, /** * ログインしているユーザーに関連する各種情報を取得します * @summary @@ -6024,6 +6129,23 @@ export class UsersApi extends BaseAPI { .then((request) => request(this.axios, this.basePath)); } + /** + * ライセンス割り当てを解除します + * @summary + * @param {DeallocateLicenseRequest} deallocateLicenseRequest + * @param {*} [options] Override http request option. + * @throws {RequiredError} + * @memberof UsersApi + */ + public deallocateLicense( + deallocateLicenseRequest: DeallocateLicenseRequest, + options?: AxiosRequestConfig + ) { + return UsersApiFp(this.configuration) + .deallocateLicense(deallocateLicenseRequest, options) + .then((request) => request(this.axios, this.basePath)); + } + /** * ログインしているユーザーに関連する各種情報を取得します * @summary diff --git a/dictation_client/src/common/errors/code.ts b/dictation_client/src/common/errors/code.ts index d2de6df..036a049 100644 --- a/dictation_client/src/common/errors/code.ts +++ b/dictation_client/src/common/errors/code.ts @@ -46,4 +46,5 @@ export const errorCodes = [ "E010804", // ライセンス数不足エラー "E010805", // ライセンス有効期限切れエラー "E010806", // ライセンス割り当て不可エラー + "E010807", // ライセンス割り当て解除不可エラー ] as const; diff --git a/dictation_client/src/features/user/operations.ts b/dictation_client/src/features/user/operations.ts index 1cd1819..225fbfe 100644 --- a/dictation_client/src/features/user/operations.ts +++ b/dictation_client/src/features/user/operations.ts @@ -306,3 +306,67 @@ export const allocateLicenseAsync = createAsyncThunk< return thunkApi.rejectWithValue({ error }); } }); + +export const deallocateLicenseAsync = createAsyncThunk< + // 正常時の戻り値の型 + { + /* Empty Object */ + }, + // 引数 + { + userId: number; + }, + { + // rejectした時の返却値の型 + rejectValue: { + error: ErrorObject; + }; + } +>("users/deallocateLicenseAsync", async (args, thunkApi) => { + const { userId } = args; + + // apiのConfigurationを取得する + const { getState } = thunkApi; + const state = getState() as RootState; + const { configuration, accessToken } = state.auth; + const config = new Configuration(configuration); + const usersApi = new UsersApi(config); + + try { + await usersApi.deallocateLicense( + { + 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 === "E010807") { + errorMessage = getTranslationID( + "userListPage.message.alreadyLicenseDeallocatedError" + ); + } + + thunkApi.dispatch( + openSnackbar({ + level: "error", + message: errorMessage, + }) + ); + + return thunkApi.rejectWithValue({ error }); + } +}); diff --git a/dictation_client/src/features/user/userSlice.ts b/dictation_client/src/features/user/userSlice.ts index a3270ca..6d64137 100644 --- a/dictation_client/src/features/user/userSlice.ts +++ b/dictation_client/src/features/user/userSlice.ts @@ -6,6 +6,7 @@ import { listUsersAsync, updateUserAsync, getAllocatableLicensesAsync, + deallocateLicenseAsync, } from "./operations"; import { RoleType, UserView } from "./types"; @@ -298,6 +299,15 @@ export const userSlice = createSlice({ builder.addCase(getAllocatableLicensesAsync.rejected, (state) => { state.apps.isLoading = false; }); + builder.addCase(deallocateLicenseAsync.pending, (state) => { + state.apps.isLoading = true; + }); + builder.addCase(deallocateLicenseAsync.fulfilled, (state) => { + state.apps.isLoading = false; + }); + builder.addCase(deallocateLicenseAsync.rejected, (state) => { + state.apps.isLoading = false; + }); }, }); diff --git a/dictation_client/src/pages/UserListPage/index.tsx b/dictation_client/src/pages/UserListPage/index.tsx index 9a04273..873f47d 100644 --- a/dictation_client/src/pages/UserListPage/index.tsx +++ b/dictation_client/src/pages/UserListPage/index.tsx @@ -9,6 +9,7 @@ import { listUsersAsync, selectUserViews, selectIsLoading, + deallocateLicenseAsync, } from "features/user"; import { useTranslation } from "react-i18next"; import { getTranslationID } from "translation"; @@ -57,6 +58,24 @@ const UserListPage: React.FC = (): JSX.Element => { [setIsAllocateLicensePopupOpen, dispatch] ); + const onLicenseDeallocation = useCallback( + async (userId: number) => { + // ダイアログ確認 + if ( + /* eslint-disable-next-line no-alert */ + !window.confirm(t(getTranslationID("common.message.dialogConfirm"))) + ) { + return; + } + + const { meta } = await dispatch(deallocateLicenseAsync({ userId })); + if (meta.requestStatus === "fulfilled") { + dispatch(listUsersAsync()); + } + }, + [dispatch, t] + ); + useEffect(() => { // ユーザ一覧取得処理を呼び出す dispatch(listUsersAsync()); @@ -196,7 +215,18 @@ const UserListPage: React.FC = (): JSX.Element => {
  • - + {/* eslint-disable-next-line jsx-a11y/click-events-have-key-events,jsx-a11y/no-static-element-interactions */} + { + onLicenseDeallocation(user.id); + }} + > {t( getTranslationID( "userListPage.label.licenseDeallocation" diff --git a/dictation_client/src/translation/de.json b/dictation_client/src/translation/de.json index a8407fd..6c8a7af 100644 --- a/dictation_client/src/translation/de.json +++ b/dictation_client/src/translation/de.json @@ -113,7 +113,8 @@ "authorIdConflictError": "(de)このAuthor IDは既に登録されています。他のAuthor IDで登録してください。", "authorIdIncorrectError": "(de)Author IDの形式が不正です。Author IDは半角英数字(大文字)と\"_\"のみ入力可能です。", "roleChangeError": "(de)Roleの変更に失敗しました。画面を更新して再度ユーザー情報を取得してください。", - "encryptionPasswordCorrectError": "(de)EncryptionPasswordがルールを満たしていません。" + "encryptionPasswordCorrectError": "(de)EncryptionPasswordがルールを満たしていません。", + "alreadyLicenseDeallocatedError": "(de)すでにライセンス割り当てが解除されています。画面を更新して再度ご確認下さい。" }, "label": { "title": "(de)User", diff --git a/dictation_client/src/translation/en.json b/dictation_client/src/translation/en.json index 723c04f..397ffe2 100644 --- a/dictation_client/src/translation/en.json +++ b/dictation_client/src/translation/en.json @@ -113,7 +113,8 @@ "authorIdConflictError": "このAuthor IDは既に登録されています。他のAuthor IDで登録してください。", "authorIdIncorrectError": "Author IDの形式が不正です。Author IDは半角英数字(大文字)と\"_\"のみ入力可能です。", "roleChangeError": "Roleの変更に失敗しました。画面を更新して再度ユーザー情報を取得してください。", - "encryptionPasswordCorrectError": "EncryptionPasswordがルールを満たしていません。" + "encryptionPasswordCorrectError": "EncryptionPasswordがルールを満たしていません。", + "alreadyLicenseDeallocatedError": "すでにライセンス割り当てが解除されています。画面を更新して再度ご確認下さい。" }, "label": { "title": "User", diff --git a/dictation_client/src/translation/es.json b/dictation_client/src/translation/es.json index 6affad2..c94d2ac 100644 --- a/dictation_client/src/translation/es.json +++ b/dictation_client/src/translation/es.json @@ -113,7 +113,8 @@ "authorIdConflictError": "(es)このAuthor IDは既に登録されています。他のAuthor IDで登録してください。", "authorIdIncorrectError": "(es)Author IDの形式が不正です。Author IDは半角英数字(大文字)と\"_\"のみ入力可能です。", "roleChangeError": "(es)Roleの変更に失敗しました。画面を更新して再度ユーザー情報を取得してください。", - "encryptionPasswordCorrectError": "(es)EncryptionPasswordがルールを満たしていません。" + "encryptionPasswordCorrectError": "(es)EncryptionPasswordがルールを満たしていません。", + "alreadyLicenseDeallocatedError": "(es)すでにライセンス割り当てが解除されています。画面を更新して再度ご確認下さい。" }, "label": { "title": "(es)User", diff --git a/dictation_client/src/translation/fr.json b/dictation_client/src/translation/fr.json index ade9c18..5c5bdd4 100644 --- a/dictation_client/src/translation/fr.json +++ b/dictation_client/src/translation/fr.json @@ -113,7 +113,8 @@ "authorIdConflictError": "(fr)このAuthor IDは既に登録されています。他のAuthor IDで登録してください。", "authorIdIncorrectError": "(fr)Author IDの形式が不正です。Author IDは半角英数字(大文字)と\"_\"のみ入力可能です。", "roleChangeError": "(fr)Roleの変更に失敗しました。画面を更新して再度ユーザー情報を取得してください。", - "encryptionPasswordCorrectError": "(fr)EncryptionPasswordがルールを満たしていません。" + "encryptionPasswordCorrectError": "(fr)EncryptionPasswordがルールを満たしていません。", + "alreadyLicenseDeallocatedError": "(fr)すでにライセンス割り当てが解除されています。画面を更新して再度ご確認下さい。" }, "label": { "title": "(fr)User",