Merged PR 710: 画面実装(削除操作)

## 概要
[Task3488: 画面実装(削除操作)](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/3488)

- ユーザー削除の画面実装
  - 確認ダイアログ
  - 削除API呼び出し
  - エラーハンドリング
  - 成功時のメッセージ
  - 成功時のユーザー一覧更新

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

## UIの変更
- Before/Afterのスクショなど
- スクショ置き場

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

## 補足
- API呼び出しのエラーハンドリング部分はエラーコードが採番されたら追従します
This commit is contained in:
saito.k 2024-02-05 00:39:53 +00:00
parent 44759b1aac
commit 4be13e002d
8 changed files with 244 additions and 14 deletions

View File

@ -6982,6 +6982,46 @@ export const UsersApiAxiosParamCreator = function (configuration?: Configuration
options: localVarRequestOptions,
};
},
/**
*
* @summary
* @param {PostDeleteUserRequest} postDeleteUserRequest
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
deleteUser: async (postDeleteUserRequest: PostDeleteUserRequest, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
// verify required parameter 'postDeleteUserRequest' is not null or undefined
assertParamExists('deleteUser', 'postDeleteUserRequest', postDeleteUserRequest)
const localVarPath = `/users/delete`;
// 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(postDeleteUserRequest, localVarRequestOptions, configuration)
return {
url: toPathString(localVarUrlObj),
options: localVarRequestOptions,
};
},
/**
*
* @summary
@ -7376,6 +7416,19 @@ export const UsersApiFp = function(configuration?: Configuration) {
const operationBasePath = operationServerMap['UsersApi.deallocateLicense']?.[index]?.url;
return (axios, basePath) => createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration)(axios, operationBasePath || basePath);
},
/**
*
* @summary
* @param {PostDeleteUserRequest} postDeleteUserRequest
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
async deleteUser(postDeleteUserRequest: PostDeleteUserRequest, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<object>> {
const localVarAxiosArgs = await localVarAxiosParamCreator.deleteUser(postDeleteUserRequest, options);
const index = configuration?.serverIndex ?? 0;
const operationBasePath = operationServerMap['UsersApi.deleteUser']?.[index]?.url;
return (axios, basePath) => createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration)(axios, operationBasePath || basePath);
},
/**
*
* @summary
@ -7539,6 +7592,16 @@ export const UsersApiFactory = function (configuration?: Configuration, basePath
deallocateLicense(deallocateLicenseRequest: DeallocateLicenseRequest, options?: any): AxiosPromise<object> {
return localVarFp.deallocateLicense(deallocateLicenseRequest, options).then((request) => request(axios, basePath));
},
/**
*
* @summary
* @param {PostDeleteUserRequest} postDeleteUserRequest
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
deleteUser(postDeleteUserRequest: PostDeleteUserRequest, options?: any): AxiosPromise<object> {
return localVarFp.deleteUser(postDeleteUserRequest, options).then((request) => request(axios, basePath));
},
/**
*
* @summary
@ -7683,6 +7746,18 @@ export class UsersApi extends BaseAPI {
return UsersApiFp(this.configuration).deallocateLicense(deallocateLicenseRequest, options).then((request) => request(this.axios, this.basePath));
}
/**
*
* @summary
* @param {PostDeleteUserRequest} postDeleteUserRequest
* @param {*} [options] Override http request option.
* @throws {RequiredError}
* @memberof UsersApi
*/
public deleteUser(postDeleteUserRequest: PostDeleteUserRequest, options?: AxiosRequestConfig) {
return UsersApiFp(this.configuration).deleteUser(postDeleteUserRequest, options).then((request) => request(this.axios, this.basePath));
}
/**
*
* @summary

View File

@ -60,6 +60,14 @@ export const errorCodes = [
"E011004", // ワークタイプ使用中エラー
"E013001", // ワークフローのAuthorIDとWorktypeIDのペア重複エラー
"E013002", // ワークフロー不在エラー
"E014001", // ユーザー削除エラー(削除しようとしたユーザーがすでに削除済みだった)
"E014002", // ユーザー削除エラー(削除しようとしたユーザーが管理者だった)
"E014003", // ユーザー削除エラー削除しようとしたAuthorのAuthorIDがWorkflowに指定されていた
"E014004", // ユーザー削除エラー削除しようとしたTypistがWorkflowのTypist候補として指定されていた
"E014005", // ユーザー削除エラー削除しようとしたTypistがUserGroupに所属していた
"E014006", // ユーザー削除エラー(削除しようとしたユーザが所有者の未完了のタスクが残っている)
"E014007", // ユーザー削除エラー(削除しようとしたユーザーが有効なライセンスを持っていた)
"E014009", // ユーザー削除エラー削除しようとしたTypistが未完了のタスクのルーティングに設定されている
"E015001", // タイピストグループ削除済みエラー
"E015002", // タイピストグループがワークフローに紐づいているエラー
"E015003", // タイピストグループがルーティングされているエラー

View File

@ -610,7 +610,7 @@ export const deleteTaskAsync = createAsyncThunk<
// e ⇒ errorObjectに変換"
const error = createErrorObject(e);
let message = getTranslationID("dictationPage.message.backupFailedError");
let message = getTranslationID("common.message.internalServerError");
if (error.statusCode === 400) {
if (error.code === "E010603") {

View File

@ -383,3 +383,118 @@ export const deallocateLicenseAsync = createAsyncThunk<
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 });
}
});

View File

@ -7,6 +7,7 @@ import {
updateUserAsync,
getAllocatableLicensesAsync,
deallocateLicenseAsync,
deleteUserAsync,
} from "./operations";
import { RoleType, UserView } from "./types";
@ -290,6 +291,15 @@ export const userSlice = createSlice({
builder.addCase(deallocateLicenseAsync.rejected, (state) => {
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;
});
},
});

View File

@ -10,6 +10,7 @@ import {
selectUserViews,
selectIsLoading,
deallocateLicenseAsync,
deleteUserAsync,
} from "features/user";
import { useTranslation } from "react-i18next";
import { getTranslationID } from "translation";
@ -84,6 +85,24 @@ const UserListPage: React.FC = (): JSX.Element => {
[dispatch, t]
);
const onDeleteUser = useCallback(
async (userId: number) => {
// ダイアログ確認
if (
/* eslint-disable-next-line no-alert */
!window.confirm(t(getTranslationID("common.message.dialogConfirm")))
) {
return;
}
const { meta } = await dispatch(deleteUserAsync({ userId }));
if (meta.requestStatus === "fulfilled") {
dispatch(listUsersAsync());
}
},
[dispatch, t]
);
useEffect(() => {
// ユーザ一覧取得処理を呼び出す
dispatch(listUsersAsync());
@ -244,17 +263,20 @@ const UserListPage: React.FC = (): JSX.Element => {
</li>
</>
)}
{/* CCB
<li>
<a href="">
{t(
getTranslationID(
"userListPage.label.deleteUser"
)
)}
</a>
</li>
*/}
<li>
{/* eslint-disable-next-line jsx-a11y/click-events-have-key-events,jsx-a11y/no-static-element-interactions */}
<a
onClick={() => {
onDeleteUser(user.id);
}}
>
{t(
getTranslationID(
"userListPage.label.deleteUser"
)
)}
</a>
</li>
</ul>
</td>
<td> {user.name}</td>

View File

@ -2160,7 +2160,7 @@
},
"/users/delete": {
"post": {
"operationId": "updeateUser",
"operationId": "deleteUser",
"summary": "",
"description": "ユーザーを削除します",
"parameters": [],

View File

@ -940,7 +940,7 @@ export class UsersController {
type: ErrorResponse,
})
@ApiOperation({
operationId: 'updeateUser',
operationId: 'deleteUser',
description: 'ユーザーを削除します',
})
@ApiBearerAuth()