diff --git a/dictation_client/openapitools.json b/dictation_client/openapitools.json index 3015568..4053ae8 100644 --- a/dictation_client/openapitools.json +++ b/dictation_client/openapitools.json @@ -2,6 +2,6 @@ "$schema": "./node_modules/@openapitools/openapi-generator-cli/config.schema.json", "spaces": 2, "generator-cli": { - "version": "7.0.0" + "version": "7.0.1" } } diff --git a/dictation_client/src/App.tsx b/dictation_client/src/App.tsx index 676df3d..39bfec6 100644 --- a/dictation_client/src/App.tsx +++ b/dictation_client/src/App.tsx @@ -24,7 +24,7 @@ const App = (): JSX.Element => { (e: AxiosError) => { if (e?.response?.status === 401) { dispatch(clearToken()); - instance.logout({ + instance.logoutRedirect({ postLogoutRedirectUri: "/?logout=true", }); } diff --git a/dictation_client/src/AppRouter.tsx b/dictation_client/src/AppRouter.tsx index 248c80b..1cb6298 100644 --- a/dictation_client/src/AppRouter.tsx +++ b/dictation_client/src/AppRouter.tsx @@ -19,6 +19,7 @@ import PartnerPage from "pages/PartnerPage"; import WorkflowPage from "pages/WorkflowPage"; import TypistGroupSettingPage from "pages/TypistGroupSettingPage"; import WorktypeIdSettingPage from "pages/WorkTypeIdSettingPage"; +import AccountPage from "pages/AccountPage"; const AppRouter: React.FC = () => ( @@ -53,7 +54,7 @@ const AppRouter: React.FC = () => ( {/* XXX ヘッダーの挙動確認のため仮のページを作成 */} } />} + element={} />} /> { if (!isAuth || isExpired) { dispatch(clearToken()); // B2Cからもログアウトする - instance.logout({ + instance.logoutRedirect({ postLogoutRedirectUri: "/?logout=true", }); } diff --git a/dictation_client/src/features/account/accountSlice.ts b/dictation_client/src/features/account/accountSlice.ts new file mode 100644 index 0000000..f886a08 --- /dev/null +++ b/dictation_client/src/features/account/accountSlice.ts @@ -0,0 +1,107 @@ +import { createSlice, PayloadAction } from "@reduxjs/toolkit"; +import { AccountState } from "./state"; +import { updateAccountInfoAsync, getAccountRelationsAsync } from "./operations"; + +const initialState: AccountState = { + domain: { + getAccountInfo: { + account: { + accountId: 0, + companyName: "", + tier: 0, + country: "", + delegationPermission: false, + }, + }, + dealers: [], + users: [], + }, + + apps: { + updateAccountInfo: { + parentAccountId: undefined, + delegationPermission: false, + primaryAdminUserId: 0, + secondryAdminUserId: undefined, + }, + isLoading: false, + }, +}; + +export const accountSlice = createSlice({ + name: "account", + initialState, + reducers: { + changeDealer: ( + state, + action: PayloadAction<{ parentAccountId: number | undefined }> + ) => { + const { parentAccountId } = action.payload; + state.apps.updateAccountInfo.parentAccountId = parentAccountId; + }, + changeDealerPermission: ( + state, + action: PayloadAction<{ delegationPermission: boolean }> + ) => { + const { delegationPermission } = action.payload; + state.apps.updateAccountInfo.delegationPermission = delegationPermission; + }, + changePrimaryAdministrator: ( + state, + action: PayloadAction<{ primaryAdminUserId: number }> + ) => { + const { primaryAdminUserId } = action.payload; + state.apps.updateAccountInfo.primaryAdminUserId = primaryAdminUserId; + }, + changeSecondryAdministrator: ( + state, + action: PayloadAction<{ secondryAdminUserId: number | undefined }> + ) => { + const { secondryAdminUserId } = action.payload; + state.apps.updateAccountInfo.secondryAdminUserId = secondryAdminUserId; + }, + cleanupApps: (state) => { + state.domain = initialState.domain; + }, + }, + extraReducers: (builder) => { + builder.addCase(getAccountRelationsAsync.pending, (state) => { + state.apps.isLoading = true; + }); + builder.addCase(getAccountRelationsAsync.fulfilled, (state, action) => { + state.domain.getAccountInfo = action.payload.accountInfo; + state.domain.dealers = action.payload.dealers.dealers; + state.domain.users = action.payload.users.users; + state.apps.updateAccountInfo.parentAccountId = + action.payload.accountInfo.account.parentAccountId; + state.apps.updateAccountInfo.delegationPermission = + action.payload.accountInfo.account.delegationPermission; + if (action.payload.accountInfo.account.primaryAdminUserId) + state.apps.updateAccountInfo.primaryAdminUserId = + action.payload.accountInfo.account.primaryAdminUserId; + state.apps.updateAccountInfo.secondryAdminUserId = + action.payload.accountInfo.account.secondryAdminUserId; + state.apps.isLoading = false; + }); + builder.addCase(getAccountRelationsAsync.rejected, (state) => { + state.apps.isLoading = false; + }); + builder.addCase(updateAccountInfoAsync.pending, (state) => { + state.apps.isLoading = true; + }); + builder.addCase(updateAccountInfoAsync.fulfilled, (state) => { + state.apps.isLoading = false; + }); + builder.addCase(updateAccountInfoAsync.rejected, (state) => { + state.apps.isLoading = false; + }); + }, +}); +export const { + changeDealer, + changeDealerPermission, + changePrimaryAdministrator, + changeSecondryAdministrator, + cleanupApps, +} = accountSlice.actions; +export default accountSlice.reducer; diff --git a/dictation_client/src/features/account/index.ts b/dictation_client/src/features/account/index.ts new file mode 100644 index 0000000..30af2f3 --- /dev/null +++ b/dictation_client/src/features/account/index.ts @@ -0,0 +1,5 @@ +export * from "./state"; +export * from "./operations"; +export * from "./accountSlice"; +export * from "./selectors"; +export * from "./types"; diff --git a/dictation_client/src/features/account/operations.ts b/dictation_client/src/features/account/operations.ts new file mode 100644 index 0000000..93b092b --- /dev/null +++ b/dictation_client/src/features/account/operations.ts @@ -0,0 +1,105 @@ +import { createAsyncThunk } from "@reduxjs/toolkit"; +import type { RootState } from "app/store"; +import { ErrorObject, createErrorObject } from "common/errors"; +import { getTranslationID } from "translation"; +import { openSnackbar } from "features/ui/uiSlice"; +import { AccountsApi, UpdateAccountInfoRequest, UsersApi } from "../../api/api"; +import { Configuration } from "../../api/configuration"; +import { ViewAccountRelationsInfo } from "./types"; + +export const getAccountRelationsAsync = createAsyncThunk< + // 正常時の戻り値の型 + ViewAccountRelationsInfo, + void, + { + // rejectした時の返却値の型 + rejectValue: { + error: ErrorObject; + }; + } +>("accounts/getAccountRelationsAsync", 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); + const usersApi = new UsersApi(config); + + try { + const accountInfo = await accountsApi.getMyAccount({ + headers: { authorization: `Bearer ${accessToken}` }, + }); + const dealers = await accountsApi.getDealers(); + const users = await usersApi.getUsers({ + headers: { authorization: `Bearer ${accessToken}` }, + }); + return { + accountInfo: accountInfo.data, + dealers: dealers.data, + users: users.data, + }; + } catch (e) { + // e ⇒ errorObjectに変換" + const error = createErrorObject(e); + thunkApi.dispatch( + openSnackbar({ + level: "error", + message: getTranslationID("common.message.internalServerError"), + }) + ); + return thunkApi.rejectWithValue({ error }); + } +}); + +export const updateAccountInfoAsync = createAsyncThunk< + { + /* Empty Object */ + }, + UpdateAccountInfoRequest, + { + // rejectした時の返却値の型 + rejectValue: { + error: ErrorObject; + }; + } +>("accounts/updateAccountInfoAsync", async (args, thunkApi) => { + // apiのConfigurationを取得する + const { getState } = thunkApi; + const state = getState() as RootState; + const { configuration, accessToken } = state.auth; + const config = new Configuration(configuration); + const accountApi = new AccountsApi(config); + + try { + await accountApi.me(args, { + headers: { authorization: `Bearer ${accessToken}` }, + }); + thunkApi.dispatch( + openSnackbar({ + level: "info", + message: getTranslationID("common.message.success"), + }) + ); + return {}; + } catch (e) { + const error = createErrorObject(e); + + let errorMessage = getTranslationID("common.message.internalServerError"); + + if (error.code === "E010502") { + errorMessage = getTranslationID( + "accountPage.message.updateAccountFailedError" + ); + } + + thunkApi.dispatch( + openSnackbar({ + level: "error", + message: errorMessage, + }) + ); + + return thunkApi.rejectWithValue({ error }); + } +}); diff --git a/dictation_client/src/features/account/selectors.ts b/dictation_client/src/features/account/selectors.ts new file mode 100644 index 0000000..23a0bfe --- /dev/null +++ b/dictation_client/src/features/account/selectors.ts @@ -0,0 +1,17 @@ +import { Dealer } from "api/api"; +import { RootState } from "app/store"; + +export const selectAccountInfo = (state: RootState) => + state.account.domain.getAccountInfo; +export const selectAllDealers = (state: RootState) => + state.account.domain.dealers; +export const selectDealers = (state: RootState) => { + const { dealers } = state.account.domain; + const { country } = state.account.domain.getAccountInfo.account; + return dealers.filter((x: Dealer) => x.country === country); +}; +export const selectUsers = (state: RootState) => state.account.domain.users; +export const selectIsLoading = (state: RootState) => + state.account.apps.isLoading; +export const selectUpdateAccountInfo = (state: RootState) => + state.account.apps.updateAccountInfo; diff --git a/dictation_client/src/features/account/state.ts b/dictation_client/src/features/account/state.ts new file mode 100644 index 0000000..6d3cbe7 --- /dev/null +++ b/dictation_client/src/features/account/state.ts @@ -0,0 +1,22 @@ +import { + UpdateAccountInfoRequest, + GetMyAccountResponse, + Dealer, + User, +} from "../../api/api"; + +export interface AccountState { + domain: Domain; + apps: Apps; +} + +export interface Domain { + getAccountInfo: GetMyAccountResponse; + dealers: Dealer[]; + users: User[]; +} + +export interface Apps { + updateAccountInfo: UpdateAccountInfoRequest; + isLoading: boolean; +} diff --git a/dictation_client/src/features/account/types.ts b/dictation_client/src/features/account/types.ts new file mode 100644 index 0000000..c88875f --- /dev/null +++ b/dictation_client/src/features/account/types.ts @@ -0,0 +1,11 @@ +import { + GetMyAccountResponse, + GetDealersResponse, + GetUsersResponse, +} from "../../api/api"; + +export interface ViewAccountRelationsInfo { + accountInfo: GetMyAccountResponse; + dealers: GetDealersResponse; + users: GetUsersResponse; +} diff --git a/dictation_client/src/features/license/partnerLicense/partnerLicenseSlice.ts b/dictation_client/src/features/license/partnerLicense/partnerLicenseSlice.ts index 259eb02..8fd549a 100644 --- a/dictation_client/src/features/license/partnerLicense/partnerLicenseSlice.ts +++ b/dictation_client/src/features/license/partnerLicense/partnerLicenseSlice.ts @@ -6,7 +6,13 @@ import { ACCOUNTS_VIEW_LIMIT } from "./constants"; const initialState: PartnerLicensesState = { domain: { - myAccountInfo: { accountId: 0, companyName: "" }, + myAccountInfo: { + accountId: 0, + companyName: "", + tier: 0, + country: "", + delegationPermission: false, + }, total: 0, ownPartnerLicense: { accountId: 0, diff --git a/dictation_client/src/features/workflow/worktype/operations.ts b/dictation_client/src/features/workflow/worktype/operations.ts index 1875d48..0e45d33 100644 --- a/dictation_client/src/features/workflow/worktype/operations.ts +++ b/dictation_client/src/features/workflow/worktype/operations.ts @@ -284,3 +284,61 @@ export const editOptionItemsAsync = createAsyncThunk< return thunkApi.rejectWithValue({ error }); } }); + +export const updateActiveWorktypeAsync = createAsyncThunk< + { + // return empty + }, + { id?: number | undefined }, + { + // rejectした時の返却値の型 + rejectValue: { + error: ErrorObject; + }; + } +>("workflow/updateActiveWorktypeAsync", 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); + const { id } = args; + + try { + await accountsApi.activeWorktype( + { id }, + { + 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"); + + // ActiveWorktypeの保存に失敗した場合 + if (error.code === "E011003") { + errorMessage = getTranslationID( + "worktypeIdSetting.message.updateActiveWorktypeFailedError" + ); + } + + 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 1f4aabc..bdd6bc1 100644 --- a/dictation_client/src/features/workflow/worktype/selectors.ts +++ b/dictation_client/src/features/workflow/worktype/selectors.ts @@ -70,3 +70,6 @@ export const selectHasErrorOptionItems = (state: RootState) => { // isOptionItemsLoadingを取得する export const selectIsOptionItemsLoading = (state: RootState) => state.worktype.apps.isOptionItemsLoading; + +export const selectActiveWorktypeId = (state: RootState) => + state.worktype.apps.activeWorktypeId; diff --git a/dictation_client/src/features/workflow/worktype/state.ts b/dictation_client/src/features/workflow/worktype/state.ts index 0475ab5..3acbae2 100644 --- a/dictation_client/src/features/workflow/worktype/state.ts +++ b/dictation_client/src/features/workflow/worktype/state.ts @@ -15,6 +15,7 @@ export interface Apps { worktypeId: string; description?: string; optionItems?: OptionItem[]; + activeWorktypeId?: number | undefined; } export interface Domain { diff --git a/dictation_client/src/features/workflow/worktype/worktypeSlice.ts b/dictation_client/src/features/workflow/worktype/worktypeSlice.ts index 833aa27..224e7e3 100644 --- a/dictation_client/src/features/workflow/worktype/worktypeSlice.ts +++ b/dictation_client/src/features/workflow/worktype/worktypeSlice.ts @@ -6,6 +6,7 @@ import { editWorktypeAsync, getOptionItemsAsync, listWorktypesAsync, + updateActiveWorktypeAsync, } from "./operations"; import { OptionItem, isOptionItemDefaultValueType } from "./types"; import { OPTION_ITEMS_DEFAULT_VALUE_TYPE } from "./constants"; @@ -20,6 +21,7 @@ const initialState: WorktypeState = { worktypeId: "", description: undefined, optionItems: undefined, + activeWorktypeId: undefined, }, domain: {}, }; @@ -82,9 +84,10 @@ export const worktypeSlice = createSlice({ state.apps.isLoading = true; }); builder.addCase(listWorktypesAsync.fulfilled, (state, action) => { - // TODO:Active WorktypeIDも取得する - const { worktypes } = action.payload; + const { worktypes, active } = action.payload; + state.domain.worktypes = worktypes; + state.apps.activeWorktypeId = active; state.apps.isLoading = false; }); builder.addCase(listWorktypesAsync.rejected, (state) => { @@ -137,6 +140,15 @@ export const worktypeSlice = createSlice({ builder.addCase(editOptionItemsAsync.rejected, (state) => { state.apps.isOptionItemsLoading = false; }); + builder.addCase(updateActiveWorktypeAsync.pending, (state) => { + state.apps.isLoading = true; + }); + builder.addCase(updateActiveWorktypeAsync.fulfilled, (state) => { + state.apps.isLoading = false; + }); + builder.addCase(updateActiveWorktypeAsync.rejected, (state) => { + state.apps.isLoading = false; + }); }, }); export const { diff --git a/dictation_client/src/pages/AccountPage/index.tsx b/dictation_client/src/pages/AccountPage/index.tsx new file mode 100644 index 0000000..402e044 --- /dev/null +++ b/dictation_client/src/pages/AccountPage/index.tsx @@ -0,0 +1,350 @@ +import { AppDispatch } from "app/store"; +import { UpdateTokenTimer } from "components/auth/updateTokenTimer"; +import Footer from "components/footer"; +import Header from "components/header"; +import React, { useCallback, useEffect, useState } from "react"; +import { useDispatch, useSelector } from "react-redux"; +import styles from "styles/app.module.scss"; +import { + changeDealer, + changeDealerPermission, + changePrimaryAdministrator, + changeSecondryAdministrator, + getAccountRelationsAsync, + selectAccountInfo, + selectDealers, + selectIsLoading, + selectUpdateAccountInfo, + selectUsers, + updateAccountInfoAsync, +} from "features/account/index"; +import { useTranslation } from "react-i18next"; +import { getTranslationID } from "translation"; +import { TIERS } from "components/auth/constants"; +import { isApproveTier } from "features/auth/utils"; +import progress_activit from "../../assets/images/progress_activit.svg"; + +const AccountPage: React.FC = (): JSX.Element => { + const dispatch: AppDispatch = useDispatch(); + const [t] = useTranslation(); + const viewInfo = useSelector(selectAccountInfo); + const dealers = useSelector(selectDealers); + const users = useSelector(selectUsers); + const isLoading = useSelector(selectIsLoading); + const updateAccountInfo = useSelector(selectUpdateAccountInfo); + + // ユーザーが第5階層であるかどうかを判定する + const isTier5 = isApproveTier([TIERS.TIER5]); + + // 階層表示用 + const tierNames: { [key: number]: string } = { + // eslint-disable-next-line @typescript-eslint/naming-convention + 1: t(getTranslationID("common.label.tier1")), + // eslint-disable-next-line @typescript-eslint/naming-convention + 2: t(getTranslationID("common.label.tier2")), + // eslint-disable-next-line @typescript-eslint/naming-convention + 3: t(getTranslationID("common.label.tier3")), + // eslint-disable-next-line @typescript-eslint/naming-convention + 4: t(getTranslationID("common.label.tier4")), + // eslint-disable-next-line @typescript-eslint/naming-convention + 5: t(getTranslationID("common.label.tier5")), + }; + + // 画面起動時 + useEffect(() => { + dispatch(getAccountRelationsAsync()); + }, [dispatch]); + + const [isPushSaveChangesButton, setIsPushSaveChangesButton] = + useState(false); + const [isEmptyPrimaryAdmin, setIsEmptyPrimaryAdmin] = + useState(false); + // SaveChanges押下時 + const onSaveChangesButton = useCallback(async () => { + setIsPushSaveChangesButton(true); + if (!updateAccountInfo.primaryAdminUserId) { + setIsEmptyPrimaryAdmin(true); + return; + } + await dispatch(updateAccountInfoAsync(updateAccountInfo)); + dispatch(getAccountRelationsAsync()); + setIsEmptyPrimaryAdmin(false); + setIsPushSaveChangesButton(false); + }, [dispatch, updateAccountInfo]); + + return ( +
+
+ +
+
+
+

+ {t(getTranslationID("accountPage.label.title"))} +

+
+ +
+
+ + +
+
+

+ {t( + getTranslationID("accountPage.label.accountInformation") + )} +

+
+ {t(getTranslationID("accountPage.label.companyName"))} +
+
{viewInfo.account.companyName}
+
{t(getTranslationID("accountPage.label.accountID"))}
+
{viewInfo.account.accountId}
+
+ {t(getTranslationID("accountPage.label.yourCategory"))} +
+
{tierNames[viewInfo.account.tier]}
+
+ {t(getTranslationID("accountPage.label.yourCountry"))} +
+
{viewInfo.account.country}
+
{t(getTranslationID("accountPage.label.yourDealer"))}
+ {isTier5 && !viewInfo.account.parentAccountName && ( +
+ +
+ )} + {(!isTier5 || viewInfo.account.parentAccountName) && ( +
{viewInfo.account.parentAccountName ?? "-"}
+ )} +
+ {t(getTranslationID("accountPage.label.dealerManagement"))} +
+ {isTier5 && ( +
+ {/* eslint-disable-next-line jsx-a11y/label-has-associated-control */} + +
+ )} + {!isTier5 &&
-
} +
+
+ +
+
+

+ {t( + getTranslationID( + "accountPage.label.administratorInformation" + ) + )} +

+
+ {t( + getTranslationID("accountPage.label.primaryAdministrator") + )} +
+
+ { + users.find( + (x) => x.id === viewInfo.account.primaryAdminUserId + )?.name + } +
+
+ {t(getTranslationID("accountPage.label.emailAddress"))} +
+
+ + {isPushSaveChangesButton && isEmptyPrimaryAdmin && ( + + {" "} + {t( + getTranslationID("signupPage.message.inputEmptyError") + )} + + )} +
+
+ {t( + getTranslationID( + "accountPage.label.secondaryAdministrator" + ) + )} +
+
+ { + users.find( + (x) => x.id === viewInfo.account.secondryAdminUserId + )?.name + } +
+
+ {t(getTranslationID("accountPage.label.emailAddress"))} +
+
+ +
+
+
+
+ + Loading +
+
+ + +
+
+
+
+
+ ); +}; + +export default AccountPage; diff --git a/dictation_client/src/pages/LicensePage/index.tsx b/dictation_client/src/pages/LicensePage/index.tsx index 26d2ab3..95f65f3 100644 --- a/dictation_client/src/pages/LicensePage/index.tsx +++ b/dictation_client/src/pages/LicensePage/index.tsx @@ -16,7 +16,7 @@ const LicensePage: React.FC = (): JSX.Element => { const redirectToTopPage = useCallback(() => { dispatch(clearToken()); - instance.logout({ + instance.logoutRedirect({ postLogoutRedirectUri: "/", }); }, [dispatch, instance]); diff --git a/dictation_client/src/pages/LoginPage/index.tsx b/dictation_client/src/pages/LoginPage/index.tsx index 002ee2f..ba7f6b0 100644 --- a/dictation_client/src/pages/LoginPage/index.tsx +++ b/dictation_client/src/pages/LoginPage/index.tsx @@ -25,7 +25,7 @@ const LoginPage: React.FC = (): JSX.Element => { // ログイン失敗した場合、B2Cをログアウトしてからエラーページに遷移する if (meta.requestStatus === "rejected") { - instance.logout({ + instance.logoutRedirect({ postLogoutRedirectUri: "/AuthError", }); } diff --git a/dictation_client/src/pages/PartnerPage/index.tsx b/dictation_client/src/pages/PartnerPage/index.tsx index 617015b..38adaca 100644 --- a/dictation_client/src/pages/PartnerPage/index.tsx +++ b/dictation_client/src/pages/PartnerPage/index.tsx @@ -24,7 +24,6 @@ import personAdd from "../../assets/images/person_add.svg"; import { TIERS } from "../../components/auth/constants"; import { AddPartnerAccountPopup } from "./addPartnerAccountPopup"; import checkFill from "../../assets/images/check_fill.svg"; -import checkOutline from "../../assets/images/check_outline.svg"; const PartnerPage: React.FC = (): JSX.Element => { const dispatch: AppDispatch = useDispatch(); diff --git a/dictation_client/src/pages/SamplePage/index.tsx b/dictation_client/src/pages/SamplePage/index.tsx index 1d62cfa..ee3d56b 100644 --- a/dictation_client/src/pages/SamplePage/index.tsx +++ b/dictation_client/src/pages/SamplePage/index.tsx @@ -20,7 +20,7 @@ const SamplePage: React.FC = (): JSX.Element => { type="button" className={styles.buttonText} onClick={() => { - instance.logout({ postLogoutRedirectUri: "/" }); + instance.logoutRedirect({ postLogoutRedirectUri: "/" }); dispatch(clearToken()); }} > diff --git a/dictation_client/src/pages/WorkTypeIdSettingPage/index.tsx b/dictation_client/src/pages/WorkTypeIdSettingPage/index.tsx index d33fa1b..e176adf 100644 --- a/dictation_client/src/pages/WorkTypeIdSettingPage/index.tsx +++ b/dictation_client/src/pages/WorkTypeIdSettingPage/index.tsx @@ -14,8 +14,10 @@ import { changeWorktypeId, changeDescription, listWorktypesAsync, + updateActiveWorktypeAsync, selectIsLoading, selectWorktypes, + selectActiveWorktypeId, } from "features/workflow/worktype"; import { AppDispatch } from "app/store"; import { AddWorktypeIdPopup } from "./addWorktypeIdPopup"; @@ -27,6 +29,8 @@ const WorktypeIdSettingPage: React.FC = (): JSX.Element => { const [t] = useTranslation(); const isLoading = useSelector(selectIsLoading); const worktypes = useSelector(selectWorktypes); + const activeWorktypeId = useSelector(selectActiveWorktypeId); + const [selectedRow, setSelectedRow] = useState(NaN); // 追加Popupの表示制御 const [isShowAddPopup, setIsShowAddPopup] = useState(false); @@ -34,10 +38,53 @@ const WorktypeIdSettingPage: React.FC = (): JSX.Element => { const [isShowEditPopup, setIsShowEditPopup] = useState(false); const [isShowEditOptionItemPopup, setIsShowEditOptionItemPopup] = useState(false); + + // ActiveWorktypeIDのセレクトボックス表示の値 + const [selectedActiveWorktypeId, setSelectedActiveWorktypeId] = useState< + number | undefined + >(undefined); + + // 初期表示 useEffect(() => { dispatch(listWorktypesAsync()); }, [dispatch]); + // APIから取得したactiveWorktypeIdを画面表示に反映 + useEffect(() => { + setSelectedActiveWorktypeId(activeWorktypeId); + }, [activeWorktypeId]); + + // ActiveWorktypeIDのセレクトボックス変更時(セレクトボックスに表示するActiveWorktypeIDの変更時) + useEffect(() => { + // 画面表示の変更後にダイアログを表示するため、setTimeoutを使用 + const timeout = setTimeout(async () => { + if (selectedActiveWorktypeId === activeWorktypeId) { + return; + } + // ダイアログ確認 + if ( + // eslint-disable-next-line no-alert + !window.confirm(t(getTranslationID("common.message.dialogConfirm"))) + ) { + setSelectedActiveWorktypeId(activeWorktypeId); + return; + } + const { meta } = await dispatch( + updateActiveWorktypeAsync({ id: selectedActiveWorktypeId }) + ); + + if (meta.requestStatus === "fulfilled") { + dispatch(listWorktypesAsync()); + } else { + setSelectedActiveWorktypeId(activeWorktypeId); + } + }, 0); + return () => { + clearTimeout(timeout); + }; + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [selectedActiveWorktypeId]); + return ( <> { "worktypeIdSetting.label.activeWorktypeId" ) )}:`} - { + const { value } = e.target; + const active = value === "" ? undefined : Number(value); + setSelectedActiveWorktypeId(active); + }} + > + {/* eslint-disable-next-line jsx-a11y/control-has-associated-label */} +