Merged PR 344: 画面実装(ユーザー一覧画面修正)

## 概要
[Task2449: 画面実装(ユーザー一覧画面修正)](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/2449)

- 元PBI or タスクへのリンク(内容・目的などはそちらにあるはず)
- 何をどう変更したか、追加したライブラリなど
  - ユーザー一覧画面を修正し、License Deallocation押下時にライセンス割り当て解除APIを呼び出す処理を追加しました
- このPull Requestでの対象/対象外
  - すべて対象になります
- 影響範囲(他の機能にも影響があるか)
  - 無し

## レビューポイント
- 特にレビューしてほしい箇所
- 軽微なものや自明なものは記載不要
- 修正範囲が大きい場合などに記載
- 全体的にや仕様を満たしているか等は本当に必要な時のみ記載
  - 特筆すべきところはありません。

## UIの変更
- 変更なし

## 動作確認状況
- ローカルで確認

## 補足
- 特になし
This commit is contained in:
masaaki 2023-08-24 07:19:08 +00:00
parent c42ba4d3db
commit 177c8beb41
9 changed files with 242 additions and 11 deletions

View File

@ -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<RequestArgs> => {
// 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<object>
> {
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<object> {
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

View File

@ -46,4 +46,5 @@ export const errorCodes = [
"E010804", // ライセンス数不足エラー
"E010805", // ライセンス有効期限切れエラー
"E010806", // ライセンス割り当て不可エラー
"E010807", // ライセンス割り当て解除不可エラー
] as const;

View File

@ -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 });
}
});

View File

@ -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;
});
},
});

View File

@ -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 => {
</a>
</li>
<li>
<a href="">
{/* eslint-disable-next-line jsx-a11y/click-events-have-key-events,jsx-a11y/no-static-element-interactions */}
<a
className={
user.licenseStatus !==
LICENSE_STATUS.NOLICENSE
? styles.isDisable
: ""
}
onClick={() => {
onLicenseDeallocation(user.id);
}}
>
{t(
getTranslationID(
"userListPage.label.licenseDeallocation"

View File

@ -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",

View File

@ -113,7 +113,8 @@
"authorIdConflictError": "このAuthor IDは既に登録されています。他のAuthor IDで登録してください。",
"authorIdIncorrectError": "Author IDの形式が不正です。Author IDは半角英数字(大文字)と\"_\"のみ入力可能です。",
"roleChangeError": "Roleの変更に失敗しました。画面を更新して再度ユーザー情報を取得してください。",
"encryptionPasswordCorrectError": "EncryptionPasswordがルールを満たしていません。"
"encryptionPasswordCorrectError": "EncryptionPasswordがルールを満たしていません。",
"alreadyLicenseDeallocatedError": "すでにライセンス割り当てが解除されています。画面を更新して再度ご確認下さい。"
},
"label": {
"title": "User",

View File

@ -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",

View File

@ -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",