Merged PR 394: 画面実装(オプションアイテム編集Popup)

## 概要
[Task2594: 画面実装(オプションアイテム編集Popup)](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/2594)

- オプションアイテム編集Popupを実装
  - Popup表示時にWorktypeIDに紐づくOptionItemを取得
  - 入力値のバリデーション

## レビューポイント
- エラーの表示の仕方を変えてみたが、問題ないか
  -「 タスク 2630: 入力のエラーラベルの表示が不自然なので対応」の参考となる実装として
- 保存可能な文字種ルールの位置
- エラーラベルの位置

## UIの変更
- Before/Afterのスクショなど
- 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/Task2594?csf=1&web=1&e=9Ygf6d

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

## 補足
- オプションアイテム更新の確認は未実施
This commit is contained in:
saito.k 2023-09-13 06:39:55 +00:00
parent 95b48a766a
commit bdbb0dfe5d
15 changed files with 691 additions and 17 deletions

View File

@ -826,7 +826,7 @@ export interface GetRelationsResponse {
*/
'encryptionPassword': string | null;
/**
* WorkTypeIDWorkTypeIDから一つ指定
* WorkTypeIDWorkTypeIDから一つ指定activeWorktypeがなければ空文字を返却する
* @type {string}
* @memberof GetRelationsResponse
*/
@ -1307,6 +1307,12 @@ export interface PostUpdateUserRequest {
* @interface PostWorktypeOptionItem
*/
export interface PostWorktypeOptionItem {
/**
*
* @type {number}
* @memberof PostWorktypeOptionItem
*/
'id': number;
/**
*
* @type {string}
@ -1665,6 +1671,37 @@ export interface TypistGroup {
*/
'name': string;
}
/**
*
* @export
* @interface UpdateAccountInfoRequest
*/
export interface UpdateAccountInfoRequest {
/**
* ID
* @type {number}
* @memberof UpdateAccountInfoRequest
*/
'parentAccountId': number;
/**
*
* @type {boolean}
* @memberof UpdateAccountInfoRequest
*/
'delegationPermission': boolean;
/**
* ID
* @type {number}
* @memberof UpdateAccountInfoRequest
*/
'primaryAdminUserId': number;
/**
* ID
* @type {number}
* @memberof UpdateAccountInfoRequest
*/
'secondryAdminUserId': number;
}
/**
*
* @export
@ -2531,6 +2568,46 @@ export const AccountsApiAxiosParamCreator = function (configuration?: Configurat
options: localVarRequestOptions,
};
},
/**
*
* @summary
* @param {UpdateAccountInfoRequest} updateAccountInfoRequest
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
me: async (updateAccountInfoRequest: UpdateAccountInfoRequest, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
// verify required parameter 'updateAccountInfoRequest' is not null or undefined
assertParamExists('me', 'updateAccountInfoRequest', updateAccountInfoRequest)
const localVarPath = `/accounts/me`;
// 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(updateAccountInfoRequest, localVarRequestOptions, configuration)
return {
url: toPathString(localVarUrlObj),
options: localVarRequestOptions,
};
},
/**
*
* @summary
@ -2867,6 +2944,17 @@ export const AccountsApiFp = function(configuration?: Configuration) {
const localVarAxiosArgs = await localVarAxiosParamCreator.issueLicense(issueLicenseRequest, options);
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
},
/**
*
* @summary
* @param {UpdateAccountInfoRequest} updateAccountInfoRequest
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
async me(updateAccountInfoRequest: UpdateAccountInfoRequest, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<object>> {
const localVarAxiosArgs = await localVarAxiosParamCreator.me(updateAccountInfoRequest, options);
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
},
/**
*
* @summary
@ -3089,6 +3177,16 @@ export const AccountsApiFactory = function (configuration?: Configuration, baseP
issueLicense(issueLicenseRequest: IssueLicenseRequest, options?: any): AxiosPromise<object> {
return localVarFp.issueLicense(issueLicenseRequest, options).then((request) => request(axios, basePath));
},
/**
*
* @summary
* @param {UpdateAccountInfoRequest} updateAccountInfoRequest
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
me(updateAccountInfoRequest: UpdateAccountInfoRequest, options?: any): AxiosPromise<object> {
return localVarFp.me(updateAccountInfoRequest, options).then((request) => request(axios, basePath));
},
/**
*
* @summary
@ -3344,6 +3442,18 @@ export class AccountsApi extends BaseAPI {
return AccountsApiFp(this.configuration).issueLicense(issueLicenseRequest, options).then((request) => request(this.axios, this.basePath));
}
/**
*
* @summary
* @param {UpdateAccountInfoRequest} updateAccountInfoRequest
* @param {*} [options] Override http request option.
* @throws {RequiredError}
* @memberof AccountsApi
*/
public me(updateAccountInfoRequest: UpdateAccountInfoRequest, options?: AxiosRequestConfig) {
return AccountsApiFp(this.configuration).me(updateAccountInfoRequest, options).then((request) => request(this.axios, this.basePath));
}
/**
*
* @summary

View File

@ -53,4 +53,5 @@ export const errorCodes = [
"E010811", // ライセンス発行キャンセル不可エラー(発行したライセンスが割り当てされている場合)
"E011001", // ワークタイプ重複エラー
"E011002", // ワークタイプ登録上限超過エラー
"E011003", // ワークタイプ不在エラー
] as const;

View File

@ -0,0 +1,6 @@
export const OPTION_ITEMS_DEFAULT_VALUE_TYPE = {
DEFAULT: "Default",
BLANK: "Blank",
// eslint-disable-next-line @typescript-eslint/naming-convention
LAST_INPUT: "LastInput",
} as const;

View File

@ -2,7 +2,11 @@ import { createAsyncThunk } from "@reduxjs/toolkit";
import type { RootState } from "app/store";
import { openSnackbar } from "features/ui/uiSlice";
import { getTranslationID } from "translation";
import { AccountsApi, GetWorktypesResponse } from "../../../api/api";
import {
AccountsApi,
GetOptionItemsResponse,
GetWorktypesResponse,
} from "../../../api/api";
import { Configuration } from "../../../api/configuration";
import { ErrorObject, createErrorObject } from "../../../common/errors";
@ -129,7 +133,7 @@ export const editWorktypeAsync = createAsyncThunk<
const { configuration, accessToken } = state.auth;
const config = new Configuration(configuration);
const accountsApi = new AccountsApi(config);
// stateからworktypeIdとdescriptionを取得する
// stateからselectedId,worktypeId,descriptionを取得する
const { selectedId, worktypeId, description } = state.worktype.apps;
try {
@ -173,3 +177,110 @@ export const editWorktypeAsync = createAsyncThunk<
return thunkApi.rejectWithValue({ error });
}
});
export const getOptionItemsAsync = createAsyncThunk<
GetOptionItemsResponse,
void,
{
// rejectした時の返却値の型
rejectValue: {
error: ErrorObject;
};
}
>("workflow/getOptionItemsAsync", 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からselectedIdを取得する
const { selectedId } = state.worktype.apps;
try {
const optionItems = (
await accountsApi.getOptionItems(selectedId, {
headers: { authorization: `Bearer ${accessToken}` },
})
).data;
return optionItems;
} catch (e) {
// e ⇒ errorObjectに変換"
const error = createErrorObject(e);
thunkApi.dispatch(
openSnackbar({
level: "error",
message: getTranslationID("common.message.internalServerError"),
})
);
return thunkApi.rejectWithValue({ error });
}
});
export const editOptionItemsAsync = createAsyncThunk<
{
// return empty
},
void,
{
// rejectした時の返却値の型
rejectValue: {
error: ErrorObject;
};
}
>("workflow/editOptionItemsAsync", 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からselectedId,optionItemsを取得する
const { selectedId, optionItems } = state.worktype.apps;
if (!optionItems) {
throw new Error("optionItems is undefined");
}
try {
await accountsApi.updateOptionItems(
selectedId,
{
optionItems,
},
{
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");
// OptionItemの保存に失敗した場合
if (error.code === "E011003") {
errorMessage = getTranslationID(
"worktypeIdSetting.message.optionItemSaveFailedError"
);
}
thunkApi.dispatch(
openSnackbar({
level: "error",
message: errorMessage,
})
);
return thunkApi.rejectWithValue({ error });
}
});

View File

@ -33,3 +33,40 @@ export const selectHasErrorWorktypeId = (state: RootState) => {
return { isEmptyWorktypeId, hasIncorrectPatternWorktypeId };
};
export const selectOptionItems = (state: RootState) =>
state.worktype.apps.optionItems;
// OptionItemsの値をチェックする
export const selectHasErrorOptionItems = (state: RootState) => {
const { optionItems } = state.worktype.apps;
if (!optionItems) {
return {
hasInvalidOptionItems: false,
hasIncorrectPatternOptionItems: false,
};
}
// optionItemsに以下の状態の要素が含まれている場合はエラー
// itemLabelに値が入っている
// defaultValueTypeがDefault
// initialValueに値が入っていない
const hasInvalidOptionItems = optionItems.some(
(item) =>
item.itemLabel !== "" &&
item.defaultValueType === "Default" &&
item.initialValue === ""
);
// optionItemsに以下の状態の要素が含まれている場合はエラー
// itemLabelとinitialValueのどちらかに\/ : * ? “< > | .が含まれている
const incorrectPattern = /[\\/:*?"<>|.]|[^ -~]/;
const hasIncorrectPatternOptionItems = optionItems.some(
(item) =>
incorrectPattern.test(item.itemLabel) ||
incorrectPattern.test(item.initialValue)
);
return { hasInvalidOptionItems, hasIncorrectPatternOptionItems };
};
// isOptionItemsLoadingを取得する
export const selectIsOptionItemsLoading = (state: RootState) =>
state.worktype.apps.isOptionItemsLoading;

View File

@ -1,4 +1,5 @@
import { Worktype } from "api";
import { OptionItem } from "./types";
export interface WorktypeState {
apps: Apps;
@ -9,9 +10,11 @@ export interface Apps {
isLoading: boolean;
isAddLoading: boolean;
isEditLoading: boolean;
isOptionItemsLoading: boolean;
selectedId: number;
worktypeId: string;
description?: string;
optionItems?: OptionItem[];
}
export interface Domain {

View File

@ -0,0 +1,20 @@
import { OPTION_ITEMS_DEFAULT_VALUE_TYPE } from "./constants";
// OPTION_ITEMS_DEFAULT_VALUE_TYPEからOptionItemDefaultValueTypeを作成する
export type OptionItemsDefaultValueType =
typeof OPTION_ITEMS_DEFAULT_VALUE_TYPE[keyof typeof OPTION_ITEMS_DEFAULT_VALUE_TYPE];
// 受け取った値がOptionItemDefaultValueType型かどうかを判定する
export const isOptionItemDefaultValueType = (
value: string
): value is OptionItemsDefaultValueType =>
value === OPTION_ITEMS_DEFAULT_VALUE_TYPE.DEFAULT ||
value === OPTION_ITEMS_DEFAULT_VALUE_TYPE.BLANK ||
value === OPTION_ITEMS_DEFAULT_VALUE_TYPE.LAST_INPUT;
export interface OptionItem {
id: number;
defaultValueType: OptionItemsDefaultValueType;
itemLabel: string;
initialValue: string;
}

View File

@ -2,18 +2,24 @@ import { PayloadAction, createSlice } from "@reduxjs/toolkit";
import { WorktypeState } from "./state";
import {
addWorktypeAsync,
editOptionItemsAsync,
editWorktypeAsync,
getOptionItemsAsync,
listWorktypesAsync,
} from "./operations";
import { OptionItem, isOptionItemDefaultValueType } from "./types";
import { OPTION_ITEMS_DEFAULT_VALUE_TYPE } from "./constants";
const initialState: WorktypeState = {
apps: {
isLoading: false,
isAddLoading: false,
isEditLoading: false,
isOptionItemsLoading: false,
selectedId: NaN,
worktypeId: "",
description: undefined,
optionItems: undefined,
},
domain: {},
};
@ -26,6 +32,7 @@ export const worktypeSlice = createSlice({
state.apps.selectedId = initialState.apps.selectedId;
state.apps.worktypeId = initialState.apps.worktypeId;
state.apps.description = initialState.apps.description;
state.apps.optionItems = initialState.apps.optionItems;
},
changeSelectedId: (state, action: PayloadAction<{ id: number }>) => {
const { id } = action.payload;
@ -45,6 +52,30 @@ export const worktypeSlice = createSlice({
const { description } = action.payload;
state.apps.description = description;
},
changeOptionItems: (
state,
action: PayloadAction<{ optionItem: OptionItem }>
) => {
const { optionItem } = action.payload;
// defaultValueTypeがDefault以外の場合はinitialValueを空文字にする
if (
optionItem.defaultValueType !== OPTION_ITEMS_DEFAULT_VALUE_TYPE.DEFAULT
) {
optionItem.initialValue = "";
}
// idが一致するoptionItemを削除して、新しいoptionItemを追加する。一致するidがない場合は何もしない
const optionItems = state.apps.optionItems?.filter(
(item) => item.id !== optionItem.id
);
if (optionItems) {
optionItems.push(optionItem);
// optionItemsをidで昇順にソートする
optionItems.sort((a, b) => a.id - b.id);
state.apps.optionItems = optionItems;
}
},
},
extraReducers: (builder) => {
builder.addCase(listWorktypesAsync.pending, (state) => {
@ -77,6 +108,35 @@ export const worktypeSlice = createSlice({
builder.addCase(editWorktypeAsync.fulfilled, (state) => {
state.apps.isEditLoading = false;
});
builder.addCase(getOptionItemsAsync.pending, (state) => {
state.apps.isOptionItemsLoading = true;
});
builder.addCase(getOptionItemsAsync.fulfilled, (state, action) => {
state.apps.isOptionItemsLoading = false;
const optionItems: OptionItem[] = action.payload.optionItems.map(
(item) => ({
id: item.id,
itemLabel: item.itemLabel,
defaultValueType: isOptionItemDefaultValueType(item.defaultValueType)
? item.defaultValueType
: OPTION_ITEMS_DEFAULT_VALUE_TYPE.DEFAULT,
initialValue: item.initialValue,
})
);
state.apps.optionItems = optionItems;
});
builder.addCase(getOptionItemsAsync.rejected, (state) => {
state.apps.isOptionItemsLoading = false;
});
builder.addCase(editOptionItemsAsync.pending, (state) => {
state.apps.isOptionItemsLoading = true;
});
builder.addCase(editOptionItemsAsync.fulfilled, (state) => {
state.apps.isOptionItemsLoading = false;
});
builder.addCase(editOptionItemsAsync.rejected, (state) => {
state.apps.isOptionItemsLoading = false;
});
},
});
export const {
@ -84,5 +144,6 @@ export const {
changeWorktypeId,
changeSelectedId,
cleanupWorktype,
changeOptionItems,
} = worktypeSlice.actions;
export default worktypeSlice.reducer;

View File

@ -0,0 +1,267 @@
import React, { useCallback, useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import styles from "styles/app.module.scss";
import { useDispatch, useSelector } from "react-redux";
import { AppDispatch } from "app/store";
import { getTranslationID } from "translation";
import {
changeOptionItems,
cleanupWorktype,
editOptionItemsAsync,
getOptionItemsAsync,
selectHasErrorOptionItems,
selectIsOptionItemsLoading,
selectOptionItems,
} from "features/workflow/worktype";
import { OPTION_ITEMS_DEFAULT_VALUE_TYPE } from "features/workflow/worktype/constants";
import { isOptionItemDefaultValueType } from "features/workflow/worktype/types";
import close from "../../assets/images/close.svg";
import progress_activit from "../../assets/images/progress_activit.svg";
// popupのpropsの型定義
interface EditOptionItemsPopupProps {
onClose: () => void;
isOpen: boolean;
}
export const EditOptionItemsPopup: React.FC<EditOptionItemsPopupProps> = (
props: EditOptionItemsPopupProps
): JSX.Element => {
const { onClose, isOpen } = props;
const dispatch: AppDispatch = useDispatch();
// optionItemsを取得
const optionItems = useSelector(selectOptionItems);
const isLoading = useSelector(selectIsOptionItemsLoading);
const [t] = useTranslation();
const closePopup = useCallback(() => {
onClose();
dispatch(cleanupWorktype());
// エラーをリセット
setOptionItemsErrors({
hasInvalidOptionItems: false,
hasIncorrectPatternOptionItems: false,
});
}, [onClose, dispatch]);
// 今の入力状態のエラー有無
const currentErrors = useSelector(selectHasErrorOptionItems);
// saveボタンを押したタイミングのエラー有無
const [optionItemsErrors, setOptionItemsErrors] = useState<{
hasInvalidOptionItems: boolean;
hasIncorrectPatternOptionItems: boolean;
}>({ hasInvalidOptionItems: false, hasIncorrectPatternOptionItems: false });
useEffect(() => {
// optionItems取得
if (isOpen) {
dispatch(getOptionItemsAsync());
}
}, [dispatch, isOpen]);
const onChangeOptionItem = useCallback(
(optionItem: {
defaultValueType: string;
id: number;
itemLabel: string;
initialValue: string;
}) => {
const { defaultValueType } = optionItem;
if (isOptionItemDefaultValueType(defaultValueType)) {
dispatch(
changeOptionItems({ optionItem: { ...optionItem, defaultValueType } })
);
}
},
[dispatch]
);
// optionItemsの更新
const onSubmit = useCallback(async () => {
// optionItemsのバリデーションチェック
setOptionItemsErrors(currentErrors);
const { hasInvalidOptionItems, hasIncorrectPatternOptionItems } =
currentErrors;
// エラーがある場合は実行しない
if (hasInvalidOptionItems || hasIncorrectPatternOptionItems) {
return;
}
// optionItemsの更新API呼び出し
const { meta } = await dispatch(editOptionItemsAsync());
if (meta.requestStatus === "fulfilled") {
closePopup();
}
}, [closePopup, dispatch, currentErrors]);
return (
<div className={`${styles.modal} ${isOpen ? styles.isShow : ""}`}>
<div className={styles.modalBox}>
<p className={styles.modalTitle}>
{t(getTranslationID("worktypeIdSetting.label.optionItem"))}
{/* 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}`}>
<dd className={styles.full}>
<div className={styles.tableWrap}>
<table className={styles.table}>
<tr className={styles.tableHeader}>
<th className={styles.noLine}>
{t(getTranslationID("worktypeIdSetting.label.itemLabel"))}
</th>
<th className={styles.noLine}>
{t(
getTranslationID("worktypeIdSetting.label.defaultValue")
)}
</th>
<th>
{t(
getTranslationID("worktypeIdSetting.label.initialValue")
)}
</th>
</tr>
{optionItems?.map((item) => (
<tr key={`optionItem_${item.id}`}>
<td>
<input
type="text"
maxLength={16}
name="itemLabel"
value={item.itemLabel}
className={styles.formInput}
onChange={(e) => {
const { value } = e.target;
// optionItemsの更新
const newOptionItem = {
...item,
itemLabel: value,
};
onChangeOptionItem(newOptionItem);
}}
/>
</td>
<td>
<select
name="defaultValueType"
className={styles.formInput}
value={item.defaultValueType}
onChange={(e) => {
const { value } = e.target;
// optionItemsの更新
const newOptionItem = {
...item,
defaultValueType: value,
};
onChangeOptionItem(newOptionItem);
}}
>
<option
value={OPTION_ITEMS_DEFAULT_VALUE_TYPE.DEFAULT}
>
{t(
getTranslationID(
"worktypeIdSetting.label.default"
)
)}
</option>
<option value={OPTION_ITEMS_DEFAULT_VALUE_TYPE.BLANK}>
{t(
getTranslationID("worktypeIdSetting.label.blank")
)}
</option>
<option
value={OPTION_ITEMS_DEFAULT_VALUE_TYPE.LAST_INPUT}
>
{t(
getTranslationID(
"worktypeIdSetting.label.lastInput"
)
)}
</option>
</select>
</td>
<td>
{item.defaultValueType ===
OPTION_ITEMS_DEFAULT_VALUE_TYPE.DEFAULT ? (
<input
type="text"
maxLength={20}
name="initialValue"
value={item.initialValue}
className={styles.formInput}
onChange={(e) => {
const { value } = e.target;
// optionItemsの更新
const newOptionItem = {
...item,
initialValue: value,
};
onChangeOptionItem(newOptionItem);
}}
/>
) : (
"-"
)}
</td>
</tr>
))}
</table>
{optionItemsErrors.hasInvalidOptionItems && (
<span className={`${styles.formError} ${styles.alignCenter}`}>
{t(
getTranslationID(
"worktypeIdSetting.message.optionItemInvalidError"
)
)}
</span>
)}
{optionItemsErrors.hasIncorrectPatternOptionItems && (
<span className={`${styles.formError} ${styles.alignCenter}`}>
{t(
getTranslationID(
"worktypeIdSetting.message.optionItemIncorrectError"
)
)}
</span>
)}
<span
style={{ display: "block" }}
className={`${styles.formComment} ${styles.alignCenter}`}
>
{t(
getTranslationID("worktypeIdSetting.label.optionItemTerms")
)}
</span>
</div>
</dd>
<dd className={`${styles.full} ${styles.alignCenter}`}>
<input
type="button"
name="submit"
value={t(getTranslationID("common.label.save"))}
className={`${styles.formSubmit} ${styles.marginBtm1} ${styles.isActive}`}
onClick={onSubmit}
/>
{isLoading && (
<img
src={progress_activit}
className={styles.icLoading}
alt="Loading"
/>
)}
</dd>
</dl>
</form>
</div>
</div>
);
};

View File

@ -20,6 +20,7 @@ import {
import { AppDispatch } from "app/store";
import { AddWorktypeIdPopup } from "./addWorktypeIdPopup";
import { EditWorktypeIdPopup } from "./editWorktypeIdPopup";
import { EditOptionItemsPopup } from "./editOptionItemsPopup";
const WorktypeIdSettingPage: React.FC = (): JSX.Element => {
const dispatch: AppDispatch = useDispatch();
@ -31,6 +32,8 @@ const WorktypeIdSettingPage: React.FC = (): JSX.Element => {
const [isShowAddPopup, setIsShowAddPopup] = useState<boolean>(false);
// 編集Popupの表示制御
const [isShowEditPopup, setIsShowEditPopup] = useState<boolean>(false);
const [isShowEditOptionItemPopup, setIsShowEditOptionItemPopup] =
useState<boolean>(false);
useEffect(() => {
dispatch(listWorktypesAsync());
}, [dispatch]);
@ -49,6 +52,12 @@ const WorktypeIdSettingPage: React.FC = (): JSX.Element => {
}}
isOpen={isShowEditPopup}
/>
<EditOptionItemsPopup
onClose={() => {
setIsShowEditOptionItemPopup(false);
}}
isOpen={isShowEditOptionItemPopup}
/>
<div className={styles.wrap}>
<Header userName="XXXXXXX" />
<UpdateTokenTimer />
@ -165,9 +174,13 @@ const WorktypeIdSettingPage: React.FC = (): JSX.Element => {
</a>
</li>
<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={}
onClick={() => {
dispatch(changeSelectedId({ id: worktype.id }));
setIsShowEditOptionItemPopup(true);
}}
>
{t(
getTranslationID(

View File

@ -393,12 +393,23 @@
"worktypeIdTerms": "(de)WorktypeID should be alphanumeric and symbols,\nbut not include: \\ / : * ? “ < > | .",
"addWorktype": "(de)Add Worktype",
"editWorktypeId": "(de)Edit Worktype ID",
"saveChange": "(de)Save Changes"
"saveChange": "(de)Save Changes",
"editOptionItems": "(de)Option Item",
"itemLabel": "(de)Item label",
"defaultValue": "(de)Default value",
"initialValue": "(de)Initial value",
"default": "(de)Default",
"blank": "(de)Blank",
"lastInput": "(de)Last Input",
"optionItemTerms": "(de)The Item label and Initial value should be alphanumeric and symbols, but not include: \\ / : * ? “ < > | ."
},
"message": {
"worktypeIdIncorrectError": "(de)入力されたWorktypeIDがルールを満たしていません。下記のルールを満たすWorktypeIDを入力してください",
"alreadyWorktypeIdExistError": "(de)このWorktype IDは既に登録されています。他のWorktype IDで登録してください。",
"worktypeIDLimitError": "(de)Worktype IDが登録件数の上限に達しているため追加できません。"
"worktypeIDLimitError": "(de)Worktype IDが登録件数の上限に達しているため追加できません。",
"optionItemInvalidError": "(de)Default valueがDefaultに設定されている場合、Initial valueは入力が必須です。",
"optionItemSaveFailedError": "(de)オプションアイテムの保存に失敗しました。画面を更新し、再度実行してください",
"optionItemIncorrectError": "(de)入力されたItem labelまたはInitial valueがルールを満たしていません。下記のルールを満たす値を入力してください"
}
},
"partnerPage": {
@ -416,4 +427,4 @@
"deleteAccount": "(de)Delete Account"
}
}
}
}

View File

@ -393,12 +393,23 @@
"worktypeIdTerms": "WorktypeID should be alphanumeric and symbols,\nbut not include: \\ / : * ? “ < > | .",
"addWorktype": "Add Worktype",
"editWorktypeId": "Edit Worktype ID",
"saveChange": "Save Changes"
"saveChange": "Save Changes",
"editOptionItems": "Option Item",
"itemLabel": "Item label",
"defaultValue": "Default value",
"initialValue": "Initial value",
"default": "Default",
"blank": "Blank",
"lastInput": "Last Input",
"optionItemTerms": "The Item label and Initial value should be alphanumeric and symbols, but not include: \\ / : * ? “ < > | ."
},
"message": {
"worktypeIdIncorrectError": "入力されたWorktypeIDがルールを満たしていません。下記のルールを満たすWorktypeIDを入力してください",
"alreadyWorktypeIdExistError": "このWorktype IDは既に登録されています。他のWorktype IDで登録してください。",
"worktypeIDLimitError": "Worktype IDが登録件数の上限に達しているため追加できません。"
"worktypeIDLimitError": "Worktype IDが登録件数の上限に達しているため追加できません。",
"optionItemInvalidError": "Default valueがDefaultに設定されている場合、Initial valueは入力が必須です。",
"optionItemSaveFailedError": "オプションアイテムの保存に失敗しました。画面を更新し、再度実行してください",
"optionItemIncorrectError": "入力されたItem labelまたはInitial valueがルールを満たしていません。下記のルールを満たす値を入力してください"
}
},
"partnerPage": {
@ -416,4 +427,4 @@
"deleteAccount": "Delete Account"
}
}
}
}

View File

@ -393,12 +393,23 @@
"worktypeIdTerms": "(es)WorktypeID should be alphanumeric and symbols,\nbut not include: \\ / : * ? “ < > | .",
"addWorktype": "(es)Add Worktype",
"editWorktypeId": "(es)Edit Worktype ID",
"saveChange": "(es)Save Changes"
"saveChange": "(es)Save Changes",
"editOptionItems": "(es)Option Item",
"itemLabel": "(es)Item label",
"defaultValue": "(es)Default value",
"initialValue": "(es)Initial value",
"default": "(es)Default",
"blank": "(es)Blank",
"lastInput": "(es)Last Input",
"optionItemTerms": "(es)The Item label and Initial value should be alphanumeric and symbols, but not include: \\ / : * ? “ < > | ."
},
"message": {
"worktypeIdIncorrectError": "(es)入力されたWorktypeIDがルールを満たしていません。下記のルールを満たすWorktypeIDを入力してください",
"alreadyWorktypeIdExistError": "(es)このWorktype IDは既に登録されています。他のWorktype IDで登録してください。",
"worktypeIDLimitError": "(es)Worktype IDが登録件数の上限に達しているため追加できません。"
"worktypeIDLimitError": "(es)Worktype IDが登録件数の上限に達しているため追加できません。",
"optionItemInvalidError": "(es)Default valueがDefaultに設定されている場合、Initial valueは入力が必須です。",
"optionItemSaveFailedError": "(es)オプションアイテムの保存に失敗しました。画面を更新し、再度実行してください",
"optionItemIncorrectError": "(es)入力されたItem labelまたはInitial valueがルールを満たしていません。下記のルールを満たす値を入力してください"
}
},
"partnerPage": {
@ -416,4 +427,4 @@
"deleteAccount": "(es)Delete Account"
}
}
}
}

View File

@ -393,12 +393,23 @@
"worktypeIdTerms": "(fr)WorktypeID should be alphanumeric and symbols,\nbut not include: \\ / : * ? “ < > | .",
"addWorktype": "(fr)Add Worktype",
"editWorktypeId": "(fr)Edit Worktype ID",
"saveChange": "(fr)Save Changes"
"saveChange": "(fr)Save Changes",
"editOptionItems": "(fr)Option Item",
"itemLabel": "(fr)Item label",
"defaultValue": "(fr)Default value",
"initialValue": "(fr)Initial value",
"default": "(fr)Default",
"blank": "(fr)Blank",
"lastInput": "(fr)Last Input",
"optionItemTerms": "(fr)The Item label and Initial value should be alphanumeric and symbols, but not include: \\ / : * ? “ < > | ."
},
"message": {
"worktypeIdIncorrectError": "(fr)入力されたWorktypeIDがルールを満たしていません。下記のルールを満たすWorktypeIDを入力してください",
"alreadyWorktypeIdExistError": "(fr)このWorktype IDは既に登録されています。他のWorktype IDで登録してください。",
"worktypeIDLimitError": "(fr)Worktype IDが登録件数の上限に達しているため追加できません。"
"worktypeIDLimitError": "(fr)Worktype IDが登録件数の上限に達しているため追加できません。",
"optionItemInvalidError": "(fr)Default valueがDefaultに設定されている場合、Initial valueは入力が必須です。",
"optionItemSaveFailedError": "(fr)オプションアイテムの保存に失敗しました。画面を更新し、再度実行してください",
"optionItemIncorrectError": "(fr)入力されたItem labelまたはInitial valueがルールを満たしていません。下記のルールを満たす値を入力してください"
}
},
"partnerPage": {
@ -416,4 +427,4 @@
"deleteAccount": "(fr)Delete Account"
}
}
}
}

View File

@ -3156,6 +3156,7 @@
"PostWorktypeOptionItem": {
"type": "object",
"properties": {
"id": { "type": "number" },
"itemLabel": { "type": "string", "maxLength": 16 },
"defaultValueType": {
"type": "string",
@ -3164,7 +3165,7 @@
},
"initialValue": { "type": "string", "maxLength": 20 }
},
"required": ["itemLabel", "defaultValueType", "initialValue"]
"required": ["id", "itemLabel", "defaultValueType", "initialValue"]
},
"UpdateOptionItemsRequest": {
"type": "object",