From bdbb0dfe5dc2d88934679c85fadc18b87dad5966 Mon Sep 17 00:00:00 2001 From: "saito.k" Date: Wed, 13 Sep 2023 06:39:55 +0000 Subject: [PATCH] =?UTF-8?q?Merged=20PR=20394:=20=E7=94=BB=E9=9D=A2?= =?UTF-8?q?=E5=AE=9F=E8=A3=85=EF=BC=88=E3=82=AA=E3=83=97=E3=82=B7=E3=83=A7?= =?UTF-8?q?=E3=83=B3=E3=82=A2=E3=82=A4=E3=83=86=E3=83=A0=E7=B7=A8=E9=9B=86?= =?UTF-8?q?Popup=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 概要 [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 ## 動作確認状況 - ローカルで確認 ## 補足 - オプションアイテム更新の確認は未実施 --- dictation_client/src/api/api.ts | 112 +++++++- dictation_client/src/common/errors/code.ts | 1 + .../features/workflow/worktype/constants.ts | 6 + .../features/workflow/worktype/operations.ts | 115 +++++++- .../features/workflow/worktype/selectors.ts | 37 +++ .../src/features/workflow/worktype/state.ts | 3 + .../src/features/workflow/worktype/types.ts | 20 ++ .../workflow/worktype/worktypeSlice.ts | 61 ++++ .../editOptionItemsPopup.tsx | 267 ++++++++++++++++++ .../src/pages/WorkTypeIdSettingPage/index.tsx | 15 +- dictation_client/src/translation/de.json | 17 +- dictation_client/src/translation/en.json | 17 +- dictation_client/src/translation/es.json | 17 +- dictation_client/src/translation/fr.json | 17 +- dictation_server/src/api/odms/openapi.json | 3 +- 15 files changed, 691 insertions(+), 17 deletions(-) create mode 100644 dictation_client/src/features/workflow/worktype/constants.ts create mode 100644 dictation_client/src/features/workflow/worktype/types.ts create mode 100644 dictation_client/src/pages/WorkTypeIdSettingPage/editOptionItemsPopup.tsx diff --git a/dictation_client/src/api/api.ts b/dictation_client/src/api/api.ts index d8fa420..f5d0b72 100644 --- a/dictation_client/src/api/api.ts +++ b/dictation_client/src/api/api.ts @@ -826,7 +826,7 @@ export interface GetRelationsResponse { */ 'encryptionPassword': string | null; /** - * アカウントがデフォルトで利用するWorkTypeID(アカウントに紐づくWorkTypeIDから一つ指定) + * アカウントがデフォルトで利用するWorkTypeID(アカウントに紐づくWorkTypeIDから一つ指定。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 => { + // 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> { + 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 { 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 { + 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 diff --git a/dictation_client/src/common/errors/code.ts b/dictation_client/src/common/errors/code.ts index d8eb794..2dc6bdc 100644 --- a/dictation_client/src/common/errors/code.ts +++ b/dictation_client/src/common/errors/code.ts @@ -53,4 +53,5 @@ export const errorCodes = [ "E010811", // ライセンス発行キャンセル不可エラー(発行したライセンスが割り当てされている場合) "E011001", // ワークタイプ重複エラー "E011002", // ワークタイプ登録上限超過エラー + "E011003", // ワークタイプ不在エラー ] as const; diff --git a/dictation_client/src/features/workflow/worktype/constants.ts b/dictation_client/src/features/workflow/worktype/constants.ts new file mode 100644 index 0000000..ba53d9d --- /dev/null +++ b/dictation_client/src/features/workflow/worktype/constants.ts @@ -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; diff --git a/dictation_client/src/features/workflow/worktype/operations.ts b/dictation_client/src/features/workflow/worktype/operations.ts index 4945831..1875d48 100644 --- a/dictation_client/src/features/workflow/worktype/operations.ts +++ b/dictation_client/src/features/workflow/worktype/operations.ts @@ -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 }); + } +}); diff --git a/dictation_client/src/features/workflow/worktype/selectors.ts b/dictation_client/src/features/workflow/worktype/selectors.ts index 41d3a75..1f4aabc 100644 --- a/dictation_client/src/features/workflow/worktype/selectors.ts +++ b/dictation_client/src/features/workflow/worktype/selectors.ts @@ -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; diff --git a/dictation_client/src/features/workflow/worktype/state.ts b/dictation_client/src/features/workflow/worktype/state.ts index d2006e2..0475ab5 100644 --- a/dictation_client/src/features/workflow/worktype/state.ts +++ b/dictation_client/src/features/workflow/worktype/state.ts @@ -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 { diff --git a/dictation_client/src/features/workflow/worktype/types.ts b/dictation_client/src/features/workflow/worktype/types.ts new file mode 100644 index 0000000..239cb26 --- /dev/null +++ b/dictation_client/src/features/workflow/worktype/types.ts @@ -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; +} diff --git a/dictation_client/src/features/workflow/worktype/worktypeSlice.ts b/dictation_client/src/features/workflow/worktype/worktypeSlice.ts index 353d342..833aa27 100644 --- a/dictation_client/src/features/workflow/worktype/worktypeSlice.ts +++ b/dictation_client/src/features/workflow/worktype/worktypeSlice.ts @@ -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; diff --git a/dictation_client/src/pages/WorkTypeIdSettingPage/editOptionItemsPopup.tsx b/dictation_client/src/pages/WorkTypeIdSettingPage/editOptionItemsPopup.tsx new file mode 100644 index 0000000..f41f8d6 --- /dev/null +++ b/dictation_client/src/pages/WorkTypeIdSettingPage/editOptionItemsPopup.tsx @@ -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 = ( + 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 ( +
+
+

+ {t(getTranslationID("worktypeIdSetting.label.optionItem"))} + {/* eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-noninteractive-element-interactions */} + close +

+
+
+
+
+ + + + + + + {optionItems?.map((item) => ( + + + + + + ))} +
+ {t(getTranslationID("worktypeIdSetting.label.itemLabel"))} + + {t( + getTranslationID("worktypeIdSetting.label.defaultValue") + )} + + {t( + getTranslationID("worktypeIdSetting.label.initialValue") + )} +
+ { + const { value } = e.target; + // optionItemsの更新 + const newOptionItem = { + ...item, + itemLabel: value, + }; + onChangeOptionItem(newOptionItem); + }} + /> + + + + {item.defaultValueType === + OPTION_ITEMS_DEFAULT_VALUE_TYPE.DEFAULT ? ( + { + const { value } = e.target; + // optionItemsの更新 + const newOptionItem = { + ...item, + initialValue: value, + }; + onChangeOptionItem(newOptionItem); + }} + /> + ) : ( + "-" + )} +
+ {optionItemsErrors.hasInvalidOptionItems && ( + + {t( + getTranslationID( + "worktypeIdSetting.message.optionItemInvalidError" + ) + )} + + )} + {optionItemsErrors.hasIncorrectPatternOptionItems && ( + + {t( + getTranslationID( + "worktypeIdSetting.message.optionItemIncorrectError" + ) + )} + + )} + + {t( + getTranslationID("worktypeIdSetting.label.optionItemTerms") + )} + +
+
+
+ + {isLoading && ( + Loading + )} +
+
+
+
+
+ ); +}; diff --git a/dictation_client/src/pages/WorkTypeIdSettingPage/index.tsx b/dictation_client/src/pages/WorkTypeIdSettingPage/index.tsx index 6d3e7cf..d33fa1b 100644 --- a/dictation_client/src/pages/WorkTypeIdSettingPage/index.tsx +++ b/dictation_client/src/pages/WorkTypeIdSettingPage/index.tsx @@ -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(false); // 編集Popupの表示制御 const [isShowEditPopup, setIsShowEditPopup] = useState(false); + const [isShowEditOptionItemPopup, setIsShowEditOptionItemPopup] = + useState(false); useEffect(() => { dispatch(listWorktypesAsync()); }, [dispatch]); @@ -49,6 +52,12 @@ const WorktypeIdSettingPage: React.FC = (): JSX.Element => { }} isOpen={isShowEditPopup} /> + { + setIsShowEditOptionItemPopup(false); + }} + isOpen={isShowEditOptionItemPopup} + />
@@ -165,9 +174,13 @@ const WorktypeIdSettingPage: React.FC = (): JSX.Element => {
  • + {/* eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions */} { + dispatch(changeSelectedId({ id: worktype.id })); + setIsShowEditOptionItemPopup(true); + }} > {t( getTranslationID( diff --git a/dictation_client/src/translation/de.json b/dictation_client/src/translation/de.json index 5a7bd67..7131bc7 100644 --- a/dictation_client/src/translation/de.json +++ b/dictation_client/src/translation/de.json @@ -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" } } -} \ No newline at end of file +} diff --git a/dictation_client/src/translation/en.json b/dictation_client/src/translation/en.json index 63ce4e3..8d5c740 100644 --- a/dictation_client/src/translation/en.json +++ b/dictation_client/src/translation/en.json @@ -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" } } -} \ No newline at end of file +} diff --git a/dictation_client/src/translation/es.json b/dictation_client/src/translation/es.json index 3edb794..59b9d93 100644 --- a/dictation_client/src/translation/es.json +++ b/dictation_client/src/translation/es.json @@ -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" } } -} \ No newline at end of file +} diff --git a/dictation_client/src/translation/fr.json b/dictation_client/src/translation/fr.json index 5ccc10f..781dc35 100644 --- a/dictation_client/src/translation/fr.json +++ b/dictation_client/src/translation/fr.json @@ -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" } } -} \ No newline at end of file +} diff --git a/dictation_server/src/api/odms/openapi.json b/dictation_server/src/api/odms/openapi.json index 0e6a814..03b186e 100644 --- a/dictation_server/src/api/odms/openapi.json +++ b/dictation_server/src/api/odms/openapi.json @@ -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",