Merged PR 386: ワークタイプ編集ポップアップ実装

## 概要
[Task2570: ワークタイプ編集ポップアップ実装](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/2570)

- ワークタイプ編集ポップアップを実装しました。
  - ローディング中処理をタスク追加にも追加しています。

## レビューポイント
- 画面の表示は想定通りか
- ローディング中処理の追加に問題はないか

## UIの変更
- [Task2570](https://ndstokyo.sharepoint.com/:f:/r/sites/Piranha/Shared%20Documents/General/OMDS/%E3%82%B9%E3%82%AF%E3%83%AA%E3%83%BC%E3%83%B3%E3%82%B7%E3%83%A7%E3%83%83%E3%83%88/Task2570?csf=1&web=1&e=bcRjsJ)

## 動作確認状況
- ローカルで確認
This commit is contained in:
makabe.t 2023-09-07 08:58:36 +00:00
parent 6cba76fa61
commit f14c086980
12 changed files with 596 additions and 25 deletions

View File

@ -757,6 +757,25 @@ export interface GetPartnerLicensesResponse {
*/ */
'childrenPartnerLicenses': Array<PartnerLicenseInfo>; 'childrenPartnerLicenses': Array<PartnerLicenseInfo>;
} }
/**
*
* @export
* @interface GetPartnersResponse
*/
export interface GetPartnersResponse {
/**
*
* @type {number}
* @memberof GetPartnersResponse
*/
'total': number;
/**
*
* @type {Array<Partner>}
* @memberof GetPartnersResponse
*/
'partners': Array<Partner>;
}
/** /**
* *
* @export * @export
@ -1028,6 +1047,55 @@ export interface OptionItemList {
*/ */
'optionItemList': Array<OptionItem>; 'optionItemList': Array<OptionItem>;
} }
/**
*
* @export
* @interface Partner
*/
export interface Partner {
/**
*
* @type {string}
* @memberof Partner
*/
'name': string;
/**
*
* @type {number}
* @memberof Partner
*/
'tier': number;
/**
* ID
* @type {number}
* @memberof Partner
*/
'accountId': number;
/**
*
* @type {string}
* @memberof Partner
*/
'country': string;
/**
*
* @type {string}
* @memberof Partner
*/
'primaryAdmin': string;
/**
*
* @type {string}
* @memberof Partner
*/
'email': string;
/**
*
* @type {boolean}
* @memberof Partner
*/
'dealerManagement': boolean;
}
/** /**
* *
* @export * @export
@ -1528,6 +1596,25 @@ export interface UpdateTypistGroupRequest {
*/ */
'typistIds': Array<number>; 'typistIds': Array<number>;
} }
/**
*
* @export
* @interface UpdateWorktypesRequest
*/
export interface UpdateWorktypesRequest {
/**
* WorktypeID
* @type {string}
* @memberof UpdateWorktypesRequest
*/
'worktypeId': string;
/**
* Worktypeの説明
* @type {string}
* @memberof UpdateWorktypesRequest
*/
'description'?: string;
}
/** /**
* *
* @export * @export
@ -2037,6 +2124,54 @@ export const AccountsApiAxiosParamCreator = function (configuration?: Configurat
options: localVarRequestOptions, options: localVarRequestOptions,
}; };
}, },
/**
*
* @summary
* @param {number} limit
* @param {number} offset
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
getPartners: async (limit: number, offset: number, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
// verify required parameter 'limit' is not null or undefined
assertParamExists('getPartners', 'limit', limit)
// verify required parameter 'offset' is not null or undefined
assertParamExists('getPartners', 'offset', offset)
const localVarPath = `/accounts/partners`;
// 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: 'GET', ...baseOptions, ...options};
const localVarHeaderParameter = {} as any;
const localVarQueryParameter = {} as any;
// authentication bearer required
// http bearer authentication required
await setBearerAuthToObject(localVarHeaderParameter, configuration)
if (limit !== undefined) {
localVarQueryParameter['limit'] = limit;
}
if (offset !== undefined) {
localVarQueryParameter['offset'] = offset;
}
setSearchParams(localVarUrlObj, localVarQueryParameter);
let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
return {
url: toPathString(localVarUrlObj),
options: localVarRequestOptions,
};
},
/** /**
* IDで指定されたタイピストグループを取得します * IDで指定されたタイピストグループを取得します
* @summary * @summary
@ -2256,6 +2391,50 @@ export const AccountsApiAxiosParamCreator = function (configuration?: Configurat
localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
localVarRequestOptions.data = serializeDataIfNeeded(updateTypistGroupRequest, localVarRequestOptions, configuration) localVarRequestOptions.data = serializeDataIfNeeded(updateTypistGroupRequest, localVarRequestOptions, configuration)
return {
url: toPathString(localVarUrlObj),
options: localVarRequestOptions,
};
},
/**
*
* @summary
* @param {number} id Worktypeの内部ID
* @param {UpdateWorktypesRequest} updateWorktypesRequest
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
updateWorktype: async (id: number, updateWorktypesRequest: UpdateWorktypesRequest, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
// verify required parameter 'id' is not null or undefined
assertParamExists('updateWorktype', 'id', id)
// verify required parameter 'updateWorktypesRequest' is not null or undefined
assertParamExists('updateWorktype', 'updateWorktypesRequest', updateWorktypesRequest)
const localVarPath = `/accounts/worktypes/{id}`
.replace(`{${"id"}}`, encodeURIComponent(String(id)));
// 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(updateWorktypesRequest, localVarRequestOptions, configuration)
return { return {
url: toPathString(localVarUrlObj), url: toPathString(localVarUrlObj),
options: localVarRequestOptions, options: localVarRequestOptions,
@ -2379,6 +2558,18 @@ export const AccountsApiFp = function(configuration?: Configuration) {
const localVarAxiosArgs = await localVarAxiosParamCreator.getPartnerLicenses(getPartnerLicensesRequest, options); const localVarAxiosArgs = await localVarAxiosParamCreator.getPartnerLicenses(getPartnerLicensesRequest, options);
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
}, },
/**
*
* @summary
* @param {number} limit
* @param {number} offset
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
async getPartners(limit: number, offset: number, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<GetPartnersResponse>> {
const localVarAxiosArgs = await localVarAxiosParamCreator.getPartners(limit, offset, options);
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
},
/** /**
* IDで指定されたタイピストグループを取得します * IDで指定されたタイピストグループを取得します
* @summary * @summary
@ -2443,6 +2634,18 @@ export const AccountsApiFp = function(configuration?: Configuration) {
const localVarAxiosArgs = await localVarAxiosParamCreator.updateTypistGroup(typistGroupId, updateTypistGroupRequest, options); const localVarAxiosArgs = await localVarAxiosParamCreator.updateTypistGroup(typistGroupId, updateTypistGroupRequest, options);
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
}, },
/**
*
* @summary
* @param {number} id Worktypeの内部ID
* @param {UpdateWorktypesRequest} updateWorktypesRequest
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
async updateWorktype(id: number, updateWorktypesRequest: UpdateWorktypesRequest, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<object>> {
const localVarAxiosArgs = await localVarAxiosParamCreator.updateWorktype(id, updateWorktypesRequest, options);
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
},
} }
}; };
@ -2551,6 +2754,17 @@ export const AccountsApiFactory = function (configuration?: Configuration, baseP
getPartnerLicenses(getPartnerLicensesRequest: GetPartnerLicensesRequest, options?: any): AxiosPromise<GetPartnerLicensesResponse> { getPartnerLicenses(getPartnerLicensesRequest: GetPartnerLicensesRequest, options?: any): AxiosPromise<GetPartnerLicensesResponse> {
return localVarFp.getPartnerLicenses(getPartnerLicensesRequest, options).then((request) => request(axios, basePath)); return localVarFp.getPartnerLicenses(getPartnerLicensesRequest, options).then((request) => request(axios, basePath));
}, },
/**
*
* @summary
* @param {number} limit
* @param {number} offset
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
getPartners(limit: number, offset: number, options?: any): AxiosPromise<GetPartnersResponse> {
return localVarFp.getPartners(limit, offset, options).then((request) => request(axios, basePath));
},
/** /**
* IDで指定されたタイピストグループを取得します * IDで指定されたタイピストグループを取得します
* @summary * @summary
@ -2609,6 +2823,17 @@ export const AccountsApiFactory = function (configuration?: Configuration, baseP
updateTypistGroup(typistGroupId: number, updateTypistGroupRequest: UpdateTypistGroupRequest, options?: any): AxiosPromise<object> { updateTypistGroup(typistGroupId: number, updateTypistGroupRequest: UpdateTypistGroupRequest, options?: any): AxiosPromise<object> {
return localVarFp.updateTypistGroup(typistGroupId, updateTypistGroupRequest, options).then((request) => request(axios, basePath)); return localVarFp.updateTypistGroup(typistGroupId, updateTypistGroupRequest, options).then((request) => request(axios, basePath));
}, },
/**
*
* @summary
* @param {number} id Worktypeの内部ID
* @param {UpdateWorktypesRequest} updateWorktypesRequest
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
updateWorktype(id: number, updateWorktypesRequest: UpdateWorktypesRequest, options?: any): AxiosPromise<object> {
return localVarFp.updateWorktype(id, updateWorktypesRequest, options).then((request) => request(axios, basePath));
},
}; };
}; };
@ -2737,6 +2962,19 @@ export class AccountsApi extends BaseAPI {
return AccountsApiFp(this.configuration).getPartnerLicenses(getPartnerLicensesRequest, options).then((request) => request(this.axios, this.basePath)); return AccountsApiFp(this.configuration).getPartnerLicenses(getPartnerLicensesRequest, options).then((request) => request(this.axios, this.basePath));
} }
/**
*
* @summary
* @param {number} limit
* @param {number} offset
* @param {*} [options] Override http request option.
* @throws {RequiredError}
* @memberof AccountsApi
*/
public getPartners(limit: number, offset: number, options?: AxiosRequestConfig) {
return AccountsApiFp(this.configuration).getPartners(limit, offset, options).then((request) => request(this.axios, this.basePath));
}
/** /**
* IDで指定されたタイピストグループを取得します * IDで指定されたタイピストグループを取得します
* @summary * @summary
@ -2806,6 +3044,19 @@ export class AccountsApi extends BaseAPI {
public updateTypistGroup(typistGroupId: number, updateTypistGroupRequest: UpdateTypistGroupRequest, options?: AxiosRequestConfig) { public updateTypistGroup(typistGroupId: number, updateTypistGroupRequest: UpdateTypistGroupRequest, options?: AxiosRequestConfig) {
return AccountsApiFp(this.configuration).updateTypistGroup(typistGroupId, updateTypistGroupRequest, options).then((request) => request(this.axios, this.basePath)); return AccountsApiFp(this.configuration).updateTypistGroup(typistGroupId, updateTypistGroupRequest, options).then((request) => request(this.axios, this.basePath));
} }
/**
*
* @summary
* @param {number} id Worktypeの内部ID
* @param {UpdateWorktypesRequest} updateWorktypesRequest
* @param {*} [options] Override http request option.
* @throws {RequiredError}
* @memberof AccountsApi
*/
public updateWorktype(id: number, updateWorktypesRequest: UpdateWorktypesRequest, options?: AxiosRequestConfig) {
return AccountsApiFp(this.configuration).updateWorktype(id, updateWorktypesRequest, options).then((request) => request(this.axios, this.basePath));
}
} }

View File

@ -110,3 +110,66 @@ export const addWorktypeAsync = createAsyncThunk<
return thunkApi.rejectWithValue({ error }); return thunkApi.rejectWithValue({ error });
} }
}); });
export const editWorktypeAsync = createAsyncThunk<
{
// return empty
},
void,
{
// rejectした時の返却値の型
rejectValue: {
error: ErrorObject;
};
}
>("workflow/editWorktypeAsync", async (args, thunkApi) => {
// apiのConfigurationを取得する
const { getState } = thunkApi;
const state = getState() as RootState;
const { configuration, accessToken } = state.auth;
const config = new Configuration(configuration);
const accountsApi = new AccountsApi(config);
// stateからworktypeIdとdescriptionを取得する
const { selectedId, worktypeId, description } = state.worktype.apps;
try {
await accountsApi.updateWorktype(
selectedId,
{
worktypeId,
description,
},
{
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");
// 既に同じworktypeIdが存在する場合
if (error.code === "E011001") {
errorMessage = getTranslationID(
"worktypeIdSetting.message.alreadyWorktypeIdExistError"
);
}
thunkApi.dispatch(
openSnackbar({
level: "error",
message: errorMessage,
})
);
return thunkApi.rejectWithValue({ error });
}
});

View File

@ -2,9 +2,19 @@ import { RootState } from "app/store";
export const selectWorktypes = (state: RootState) => export const selectWorktypes = (state: RootState) =>
state.worktype.domain.worktypes; state.worktype.domain.worktypes;
export const selectIsLoading = (state: RootState) => export const selectIsLoading = (state: RootState) =>
state.worktype.apps.isLoading; state.worktype.apps.isLoading;
export const selectIsAddLoading = (state: RootState) =>
state.worktype.apps.isAddLoading;
export const selectIsEditLoading = (state: RootState) =>
state.worktype.apps.isEditLoading;
export const selectSelectedId = (state: RootState) =>
state.worktype.apps.selectedId;
export const selectWorktypeId = (state: RootState) => export const selectWorktypeId = (state: RootState) =>
state.worktype.apps.worktypeId; state.worktype.apps.worktypeId;

View File

@ -7,6 +7,9 @@ export interface WorktypeState {
export interface Apps { export interface Apps {
isLoading: boolean; isLoading: boolean;
isAddLoading: boolean;
isEditLoading: boolean;
selectedId: number;
worktypeId: string; worktypeId: string;
description?: string; description?: string;
} }

View File

@ -1,11 +1,19 @@
import { PayloadAction, createSlice } from "@reduxjs/toolkit"; import { PayloadAction, createSlice } from "@reduxjs/toolkit";
import { WorktypeState } from "./state"; import { WorktypeState } from "./state";
import { addWorktypeAsync, listWorktypesAsync } from "./operations"; import {
addWorktypeAsync,
editWorktypeAsync,
listWorktypesAsync,
} from "./operations";
const initialState: WorktypeState = { const initialState: WorktypeState = {
apps: { apps: {
isLoading: false, isLoading: false,
isAddLoading: false,
isEditLoading: false,
selectedId: NaN,
worktypeId: "", worktypeId: "",
description: undefined,
}, },
domain: {}, domain: {},
}; };
@ -15,8 +23,13 @@ export const worktypeSlice = createSlice({
initialState, initialState,
reducers: { reducers: {
cleanupWorktype: (state) => { cleanupWorktype: (state) => {
state.apps.selectedId = initialState.apps.selectedId;
state.apps.worktypeId = initialState.apps.worktypeId; state.apps.worktypeId = initialState.apps.worktypeId;
state.apps.description = undefined; state.apps.description = initialState.apps.description;
},
changeSelectedId: (state, action: PayloadAction<{ id: number }>) => {
const { id } = action.payload;
state.apps.selectedId = id;
}, },
changeWorktypeId: ( changeWorktypeId: (
state, state,
@ -46,13 +59,29 @@ export const worktypeSlice = createSlice({
state.apps.isLoading = false; state.apps.isLoading = false;
}); });
builder.addCase(addWorktypeAsync.pending, (state) => { builder.addCase(addWorktypeAsync.pending, (state) => {
state.apps.isLoading = true; state.apps.isAddLoading = true;
}); });
builder.addCase(addWorktypeAsync.rejected, (state) => { builder.addCase(addWorktypeAsync.rejected, (state) => {
state.apps.isLoading = false; state.apps.isAddLoading = false;
});
builder.addCase(addWorktypeAsync.fulfilled, (state) => {
state.apps.isAddLoading = false;
});
builder.addCase(editWorktypeAsync.pending, (state) => {
state.apps.isEditLoading = true;
});
builder.addCase(editWorktypeAsync.rejected, (state) => {
state.apps.isEditLoading = false;
});
builder.addCase(editWorktypeAsync.fulfilled, (state) => {
state.apps.isEditLoading = false;
}); });
}, },
}); });
export const { changeDescription, changeWorktypeId, cleanupWorktype } = export const {
worktypeSlice.actions; changeDescription,
changeWorktypeId,
changeSelectedId,
cleanupWorktype,
} = worktypeSlice.actions;
export default worktypeSlice.reducer; export default worktypeSlice.reducer;

View File

@ -10,11 +10,13 @@ import {
listWorktypesAsync, listWorktypesAsync,
selectDescription, selectDescription,
selectHasErrorWorktypeId, selectHasErrorWorktypeId,
selectIsAddLoading,
selectWorktypeId, selectWorktypeId,
} from "features/workflow/worktype"; } from "features/workflow/worktype";
import { AppDispatch } from "app/store"; import { AppDispatch } from "app/store";
import { getTranslationID } from "translation"; import { getTranslationID } from "translation";
import close from "../../assets/images/close.svg"; import close from "../../assets/images/close.svg";
import progress_activit from "../../assets/images/progress_activit.svg";
// popupのpropsの型定義 // popupのpropsの型定義
interface AddWorktypeIdPopupProps { interface AddWorktypeIdPopupProps {
@ -28,6 +30,8 @@ export const AddWorktypeIdPopup: React.FC<AddWorktypeIdPopupProps> = (
const { onClose, isOpen } = props; const { onClose, isOpen } = props;
const [t] = useTranslation(); const [t] = useTranslation();
const dispatch: AppDispatch = useDispatch(); const dispatch: AppDispatch = useDispatch();
const isAddLoading = useSelector(selectIsAddLoading);
const worktypeId = useSelector(selectWorktypeId); const worktypeId = useSelector(selectWorktypeId);
const description = useSelector(selectDescription); const description = useSelector(selectDescription);
// 追加ボタンを押したかどうか // 追加ボタンを押したかどうか
@ -39,10 +43,13 @@ export const AddWorktypeIdPopup: React.FC<AddWorktypeIdPopupProps> = (
// ×ボタンを押した時の処理 // ×ボタンを押した時の処理
const closePopup = useCallback(() => { const closePopup = useCallback(() => {
if (isAddLoading) {
return;
}
dispatch(cleanupWorktype()); dispatch(cleanupWorktype());
setIsPushAddButton(false); setIsPushAddButton(false);
onClose(); onClose();
}, [onClose, dispatch]); }, [onClose, dispatch, isAddLoading]);
// 追加ボタンを押した時の処理 // 追加ボタンを押した時の処理
const addWorktypeId = useCallback(async () => { const addWorktypeId = useCallback(async () => {
@ -70,7 +77,7 @@ export const AddWorktypeIdPopup: React.FC<AddWorktypeIdPopupProps> = (
onClick={closePopup} onClick={closePopup}
/> />
</p> </p>
<form action="" name="" method="" className={styles.form}> <form className={styles.form}>
<dl className={`${styles.formList} ${styles.hasbg}`}> <dl className={`${styles.formList} ${styles.hasbg}`}>
<dt className={styles.formTitle} /> <dt className={styles.formTitle} />
<dt>{t(getTranslationID("worktypeIdSetting.label.worktypeId"))}</dt> <dt>{t(getTranslationID("worktypeIdSetting.label.worktypeId"))}</dt>
@ -119,9 +126,12 @@ export const AddWorktypeIdPopup: React.FC<AddWorktypeIdPopupProps> = (
value={description ?? ""} value={description ?? ""}
className={styles.formInput} className={styles.formInput}
onChange={(e) => { onChange={(e) => {
const description = dispatch(
e.target.value === "" ? undefined : e.target.value; changeDescription({
dispatch(changeDescription({ description })); description:
e.target.value === "" ? undefined : e.target.value,
})
);
}} }}
/> />
</dd> </dd>
@ -132,9 +142,18 @@ export const AddWorktypeIdPopup: React.FC<AddWorktypeIdPopupProps> = (
value={t( value={t(
getTranslationID("worktypeIdSetting.label.addWorktype") getTranslationID("worktypeIdSetting.label.addWorktype")
)} )}
className={`${styles.formSubmit} ${styles.marginBtm1} ${styles.isActive}`} className={`${styles.formSubmit} ${styles.marginBtm1} ${
!isAddLoading ? styles.isActive : ""
}`}
onClick={addWorktypeId} onClick={addWorktypeId}
/> />
{isAddLoading && (
<img
src={progress_activit}
className={styles.icLoading}
alt="Loading"
/>
)}
</dd> </dd>
</dl> </dl>
</form> </form>

View File

@ -0,0 +1,162 @@
import React, { useCallback, useState } from "react";
import { useTranslation } from "react-i18next";
import styles from "styles/app.module.scss";
import { useDispatch, useSelector } from "react-redux";
import {
editWorktypeAsync,
changeDescription,
changeWorktypeId,
cleanupWorktype,
listWorktypesAsync,
selectDescription,
selectHasErrorWorktypeId,
selectIsEditLoading,
selectWorktypeId,
} from "features/workflow/worktype";
import { AppDispatch } from "app/store";
import { getTranslationID } from "translation";
import close from "../../assets/images/close.svg";
import progress_activit from "../../assets/images/progress_activit.svg";
// popupのpropsの型定義
interface EditWorktypeIdPopupProps {
onClose: () => void;
isOpen: boolean;
}
export const EditWorktypeIdPopup: React.FC<EditWorktypeIdPopupProps> = (
props: EditWorktypeIdPopupProps
): JSX.Element => {
const { onClose, isOpen } = props;
const [t] = useTranslation();
const dispatch: AppDispatch = useDispatch();
const isEditLoading = useSelector(selectIsEditLoading);
const worktypeId = useSelector(selectWorktypeId);
const description = useSelector(selectDescription);
// 保存ボタンを押したかどうか
const [isPushSaveButton, setIsPushSaveButton] = useState<boolean>(false);
// WorktypeIdのバリデーションチェック
const { hasIncorrectPatternWorktypeId, isEmptyWorktypeId } = useSelector(
selectHasErrorWorktypeId
);
// ×ボタンを押した時の処理
const closePopup = useCallback(() => {
if (isEditLoading) {
return;
}
dispatch(cleanupWorktype());
setIsPushSaveButton(false);
onClose();
}, [onClose, dispatch, isEditLoading]);
// 保存ボタンを押した時の処理
const saveWorktypeId = useCallback(async () => {
setIsPushSaveButton(true);
if (isEmptyWorktypeId || hasIncorrectPatternWorktypeId) {
return;
}
const { meta } = await dispatch(editWorktypeAsync());
if (meta.requestStatus === "fulfilled") {
dispatch(listWorktypesAsync());
closePopup();
}
}, [closePopup, dispatch, hasIncorrectPatternWorktypeId, isEmptyWorktypeId]);
return (
<div className={`${styles.modal} ${isOpen ? styles.isShow : ""}`}>
<div className={styles.modalBox}>
<p className={styles.modalTitle}>
{t(getTranslationID("worktypeIdSetting.label.editWorktypeId"))}
{/* 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 className={styles.form}>
<dl className={`${styles.formList} ${styles.hasbg}`}>
<dt className={styles.formTitle} />
<dt>{t(getTranslationID("worktypeIdSetting.label.worktypeId"))}</dt>
<dd>
<input
type="text"
size={40}
maxLength={255}
value={worktypeId ?? ""}
className={styles.formInput}
onChange={(e) => {
dispatch(changeWorktypeId({ worktypeId: e.target.value }));
}}
/>
{isPushSaveButton && isEmptyWorktypeId && (
<span className={styles.formError}>
{t(getTranslationID("common.message.inputEmptyError"))}
</span>
)}
{isPushSaveButton && hasIncorrectPatternWorktypeId && (
<span className={styles.formError}>
{t(
getTranslationID(
"worktypeIdSetting.message.worktypeIdIncorrectError"
)
)}
</span>
)}
<span
style={{ whiteSpace: "pre-line" }}
className={styles.formComment}
>
{t(getTranslationID("worktypeIdSetting.label.worktypeIdTerms"))}
</span>
</dd>
<dt className={styles.overLine}>
{t(
getTranslationID("worktypeIdSetting.label.descriptionOptional")
)}
</dt>
<dd className={styles.last}>
<input
type="text"
size={40}
maxLength={255}
value={description ?? ""}
className={styles.formInput}
onChange={(e) => {
dispatch(
changeDescription({
description:
e.target.value === "" ? undefined : e.target.value,
})
);
}}
/>
</dd>
<dd className={`${styles.full} ${styles.alignCenter}`}>
<input
type="button"
name="Save Changes"
value={t(
getTranslationID("worktypeIdSetting.label.saveChange")
)}
className={`${styles.formSubmit} ${styles.marginBtm1} ${
!isEditLoading ? styles.isActive : ""
}`}
onClick={saveWorktypeId}
/>
{isEditLoading && (
<img
src={progress_activit}
className={styles.icLoading}
alt="Loading"
/>
)}
</dd>
</dl>
</form>
</div>
</div>
);
};

View File

@ -10,12 +10,16 @@ import progress_activit from "assets/images/progress_activit.svg";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { useDispatch, useSelector } from "react-redux"; import { useDispatch, useSelector } from "react-redux";
import { import {
changeSelectedId,
changeWorktypeId,
changeDescription,
listWorktypesAsync, listWorktypesAsync,
selectIsLoading, selectIsLoading,
selectWorktypes, selectWorktypes,
} from "features/workflow/worktype"; } from "features/workflow/worktype";
import { AppDispatch } from "app/store"; import { AppDispatch } from "app/store";
import { AddWorktypeIdPopup } from "./addWorktypeIdPopup"; import { AddWorktypeIdPopup } from "./addWorktypeIdPopup";
import { EditWorktypeIdPopup } from "./editWorktypeIdPopup";
const WorktypeIdSettingPage: React.FC = (): JSX.Element => { const WorktypeIdSettingPage: React.FC = (): JSX.Element => {
const dispatch: AppDispatch = useDispatch(); const dispatch: AppDispatch = useDispatch();
@ -25,6 +29,8 @@ const WorktypeIdSettingPage: React.FC = (): JSX.Element => {
const [selectedRow, setSelectedRow] = useState<number>(NaN); const [selectedRow, setSelectedRow] = useState<number>(NaN);
// 追加Popupの表示制御 // 追加Popupの表示制御
const [isShowAddPopup, setIsShowAddPopup] = useState<boolean>(false); const [isShowAddPopup, setIsShowAddPopup] = useState<boolean>(false);
// 編集Popupの表示制御
const [isShowEditPopup, setIsShowEditPopup] = useState<boolean>(false);
useEffect(() => { useEffect(() => {
dispatch(listWorktypesAsync()); dispatch(listWorktypesAsync());
}, [dispatch]); }, [dispatch]);
@ -37,6 +43,12 @@ const WorktypeIdSettingPage: React.FC = (): JSX.Element => {
}} }}
isOpen={isShowAddPopup} isOpen={isShowAddPopup}
/> />
<EditWorktypeIdPopup
onClose={() => {
setIsShowEditPopup(false);
}}
isOpen={isShowEditPopup}
/>
<div className={styles.wrap}> <div className={styles.wrap}>
<Header userName="XXXXXXX" /> <Header userName="XXXXXXX" />
<UpdateTokenTimer /> <UpdateTokenTimer />
@ -131,9 +143,23 @@ const WorktypeIdSettingPage: React.FC = (): JSX.Element => {
className={`${styles.menuAction} ${styles.inTable}`} className={`${styles.menuAction} ${styles.inTable}`}
> >
<li> <li>
{/* eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions */}
<a <a
className={`${styles.menuLink} ${styles.isActive}`} className={`${styles.menuLink} ${styles.isActive}`}
// onClick={} onClick={() => {
dispatch(changeSelectedId({ id: worktype.id }));
dispatch(
changeWorktypeId({
worktypeId: worktype.worktypeId,
})
);
dispatch(
changeDescription({
description: worktype.description,
})
);
setIsShowEditPopup(true);
}}
> >
{t(getTranslationID("common.label.edit"))} {t(getTranslationID("common.label.edit"))}
</a> </a>

View File

@ -390,8 +390,10 @@
"description": "(de)Description", "description": "(de)Description",
"descriptionOptional": "(de)Description (Optional)", "descriptionOptional": "(de)Description (Optional)",
"optionItem": "(de)Option Item", "optionItem": "(de)Option Item",
"worktypeIdTerms": "(de)WorktypeID should be alphanumeric and symbols,\n but not include: \\ / : * ? “ < > | .", "worktypeIdTerms": "(de)WorktypeID should be alphanumeric and symbols,\nbut not include: \\ / : * ? “ < > | .",
"addWorktype": "(de)Add Worktype" "addWorktype": "(de)Add Worktype",
"editWorktypeId": "(de)Edit Worktype ID",
"saveChange": "(de)Save Changes"
}, },
"message": { "message": {
"worktypeIdIncorrectError": "(de)入力されたWorktypeIDがルールを満たしていません。下記のルールを満たすWorktypeIDを入力してください", "worktypeIdIncorrectError": "(de)入力されたWorktypeIDがルールを満たしていません。下記のルールを満たすWorktypeIDを入力してください",
@ -414,4 +416,4 @@
"deleteAccount": "(de)Delete Account" "deleteAccount": "(de)Delete Account"
} }
} }
} }

View File

@ -390,8 +390,10 @@
"description": "Description", "description": "Description",
"descriptionOptional": "Description (Optional)", "descriptionOptional": "Description (Optional)",
"optionItem": "Option Item", "optionItem": "Option Item",
"worktypeIdTerms": "WorktypeID should be alphanumeric and symbols,\n but not include: \\ / : * ? “ < > | .", "worktypeIdTerms": "WorktypeID should be alphanumeric and symbols,\nbut not include: \\ / : * ? “ < > | .",
"addWorktype": "Add Worktype" "addWorktype": "Add Worktype",
"editWorktypeId": "Edit Worktype ID",
"saveChange": "Save Changes"
}, },
"message": { "message": {
"worktypeIdIncorrectError": "入力されたWorktypeIDがルールを満たしていません。下記のルールを満たすWorktypeIDを入力してください", "worktypeIdIncorrectError": "入力されたWorktypeIDがルールを満たしていません。下記のルールを満たすWorktypeIDを入力してください",
@ -414,4 +416,4 @@
"deleteAccount": "Delete Account" "deleteAccount": "Delete Account"
} }
} }
} }

View File

@ -390,8 +390,10 @@
"description": "(es)Description", "description": "(es)Description",
"descriptionOptional": "(es)Description (Optional)", "descriptionOptional": "(es)Description (Optional)",
"optionItem": "(es)Option Item", "optionItem": "(es)Option Item",
"worktypeIdTerms": "(es)WorktypeID should be alphanumeric and symbols,\n but not include: \\ / : * ? “ < > | .", "worktypeIdTerms": "(es)WorktypeID should be alphanumeric and symbols,\nbut not include: \\ / : * ? “ < > | .",
"addWorktype": "(es)Add Worktype" "addWorktype": "(es)Add Worktype",
"editWorktypeId": "(es)Edit Worktype ID",
"saveChange": "(es)Save Changes"
}, },
"message": { "message": {
"worktypeIdIncorrectError": "(es)入力されたWorktypeIDがルールを満たしていません。下記のルールを満たすWorktypeIDを入力してください", "worktypeIdIncorrectError": "(es)入力されたWorktypeIDがルールを満たしていません。下記のルールを満たすWorktypeIDを入力してください",
@ -414,4 +416,4 @@
"deleteAccount": "(es)Delete Account" "deleteAccount": "(es)Delete Account"
} }
} }
} }

View File

@ -390,8 +390,10 @@
"description": "(fr)Description", "description": "(fr)Description",
"descriptionOptional": "(fr)Description (Optional)", "descriptionOptional": "(fr)Description (Optional)",
"optionItem": "(fr)Option Item", "optionItem": "(fr)Option Item",
"worktypeIdTerms": "(fr)WorktypeID should be alphanumeric and symbols,\n but not include: \\ / : * ? “ < > | .", "worktypeIdTerms": "(fr)WorktypeID should be alphanumeric and symbols,\nbut not include: \\ / : * ? “ < > | .",
"addWorktype": "(fr)Add Worktype" "addWorktype": "(fr)Add Worktype",
"editWorktypeId": "(fr)Edit Worktype ID",
"saveChange": "(fr)Save Changes"
}, },
"message": { "message": {
"worktypeIdIncorrectError": "(fr)入力されたWorktypeIDがルールを満たしていません。下記のルールを満たすWorktypeIDを入力してください", "worktypeIdIncorrectError": "(fr)入力されたWorktypeIDがルールを満たしていません。下記のルールを満たすWorktypeIDを入力してください",
@ -414,4 +416,4 @@
"deleteAccount": "(fr)Delete Account" "deleteAccount": "(fr)Delete Account"
} }
} }
} }