diff --git a/dictation_client/src/api/api.ts b/dictation_client/src/api/api.ts index dcd3d1d..180551e 100644 --- a/dictation_client/src/api/api.ts +++ b/dictation_client/src/api/api.ts @@ -48,6 +48,12 @@ export interface Account { * @memberof Account */ 'accountId': number; + /** + * + * @type {string} + * @memberof Account + */ + 'companyName': string; } /** * @@ -513,19 +519,19 @@ export interface GetOrderHistoriesRequest { /** * * @export - * @interface GetOrderHistoriesResponce + * @interface GetOrderHistoriesResponse */ -export interface GetOrderHistoriesResponce { +export interface GetOrderHistoriesResponse { /** * 合計件数 * @type {number} - * @memberof GetOrderHistoriesResponce + * @memberof GetOrderHistoriesResponse */ 'total': number; /** * * @type {Array} - * @memberof GetOrderHistoriesResponce + * @memberof GetOrderHistoriesResponse */ 'orderHistories': Array; } @@ -718,6 +724,25 @@ export interface IssueCardLicensesResponse { */ 'cardLicenseKeys': Array; } +/** + * + * @export + * @interface IssueLicenseRequest + */ +export interface IssueLicenseRequest { + /** + * 注文元アカウントID + * @type {number} + * @memberof IssueLicenseRequest + */ + 'orderedAccountId': number; + /** + * POナンバー + * @type {string} + * @memberof IssueLicenseRequest + */ + 'poNumber': string; +} /** * * @export @@ -880,6 +905,67 @@ export interface PostSortCriteriaRequest { */ 'paramName': string; } +/** + * + * @export + * @interface PostUpdateUserRequest + */ +export interface PostUpdateUserRequest { + /** + * + * @type {number} + * @memberof PostUpdateUserRequest + */ + 'id': number; + /** + * none/author/typist + * @type {string} + * @memberof PostUpdateUserRequest + */ + 'role': string; + /** + * + * @type {string} + * @memberof PostUpdateUserRequest + */ + 'authorId'?: string; + /** + * + * @type {boolean} + * @memberof PostUpdateUserRequest + */ + 'autoRenew': boolean; + /** + * + * @type {boolean} + * @memberof PostUpdateUserRequest + */ + 'licenseAlart': boolean; + /** + * + * @type {boolean} + * @memberof PostUpdateUserRequest + */ + 'notification': boolean; + /** + * + * @type {boolean} + * @memberof PostUpdateUserRequest + */ + 'encryption'?: boolean; + /** + * + * @type {string} + * @memberof PostUpdateUserRequest + */ + 'encryptionPassword'?: string; + /** + * + * @type {boolean} + * @memberof PostUpdateUserRequest + */ + 'prompt'?: boolean; +} /** * * @export @@ -923,12 +1009,6 @@ export interface SignupRequest { * @memberof SignupRequest */ 'authorId'?: string; - /** - * - * @type {number} - * @memberof SignupRequest - */ - 'typistGroupId'?: number; /** * * @type {string} @@ -953,6 +1033,24 @@ export interface SignupRequest { * @memberof SignupRequest */ 'notification': boolean; + /** + * + * @type {boolean} + * @memberof SignupRequest + */ + 'encryption'?: boolean; + /** + * + * @type {string} + * @memberof SignupRequest + */ + 'encryptionPassword'?: string; + /** + * + * @type {boolean} + * @memberof SignupRequest + */ + 'prompt'?: boolean; } /** * @@ -1623,6 +1721,46 @@ export const AccountsApiAxiosParamCreator = function (configuration?: Configurat let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; + return { + url: toPathString(localVarUrlObj), + options: localVarRequestOptions, + }; + }, + /** + * + * @summary + * @param {IssueLicenseRequest} issueLicenseRequest + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + issueLicense: async (issueLicenseRequest: IssueLicenseRequest, options: AxiosRequestConfig = {}): Promise => { + // verify required parameter 'issueLicenseRequest' is not null or undefined + assertParamExists('issueLicense', 'issueLicenseRequest', issueLicenseRequest) + const localVarPath = `/accounts/licenses/issue`; + // 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(issueLicenseRequest, localVarRequestOptions, configuration) + return { url: toPathString(localVarUrlObj), options: localVarRequestOptions, @@ -1688,7 +1826,7 @@ export const AccountsApiFp = function(configuration?: Configuration) { * @param {*} [options] Override http request option. * @throws {RequiredError} */ - async getOrderHistories(getOrderHistoriesRequest: GetOrderHistoriesRequest, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { + async getOrderHistories(getOrderHistoriesRequest: GetOrderHistoriesRequest, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { const localVarAxiosArgs = await localVarAxiosParamCreator.getOrderHistories(getOrderHistoriesRequest, options); return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); }, @@ -1723,6 +1861,17 @@ export const AccountsApiFp = function(configuration?: Configuration) { const localVarAxiosArgs = await localVarAxiosParamCreator.getTypists(options); return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); }, + /** + * + * @summary + * @param {IssueLicenseRequest} issueLicenseRequest + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + async issueLicense(issueLicenseRequest: IssueLicenseRequest, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { + const localVarAxiosArgs = await localVarAxiosParamCreator.issueLicense(issueLicenseRequest, options); + return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); + }, } }; @@ -1779,7 +1928,7 @@ export const AccountsApiFactory = function (configuration?: Configuration, baseP * @param {*} [options] Override http request option. * @throws {RequiredError} */ - getOrderHistories(getOrderHistoriesRequest: GetOrderHistoriesRequest, options?: any): AxiosPromise { + getOrderHistories(getOrderHistoriesRequest: GetOrderHistoriesRequest, options?: any): AxiosPromise { return localVarFp.getOrderHistories(getOrderHistoriesRequest, options).then((request) => request(axios, basePath)); }, /** @@ -1810,6 +1959,16 @@ export const AccountsApiFactory = function (configuration?: Configuration, baseP getTypists(options?: any): AxiosPromise { return localVarFp.getTypists(options).then((request) => request(axios, basePath)); }, + /** + * + * @summary + * @param {IssueLicenseRequest} issueLicenseRequest + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + issueLicense(issueLicenseRequest: IssueLicenseRequest, options?: any): AxiosPromise { + return localVarFp.issueLicense(issueLicenseRequest, options).then((request) => request(axios, basePath)); + }, }; }; @@ -1912,6 +2071,18 @@ export class AccountsApi extends BaseAPI { public getTypists(options?: AxiosRequestConfig) { return AccountsApiFp(this.configuration).getTypists(options).then((request) => request(this.axios, this.basePath)); } + + /** + * + * @summary + * @param {IssueLicenseRequest} issueLicenseRequest + * @param {*} [options] Override http request option. + * @throws {RequiredError} + * @memberof AccountsApi + */ + public issueLicense(issueLicenseRequest: IssueLicenseRequest, options?: AxiosRequestConfig) { + return AccountsApiFp(this.configuration).issueLicense(issueLicenseRequest, options).then((request) => request(this.axios, this.basePath)); + } } @@ -3852,6 +4023,46 @@ export const UsersApiAxiosParamCreator = function (configuration?: Configuration localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; localVarRequestOptions.data = serializeDataIfNeeded(postSortCriteriaRequest, localVarRequestOptions, configuration) + return { + url: toPathString(localVarUrlObj), + options: localVarRequestOptions, + }; + }, + /** + * ユーザーの情報を更新します + * @summary + * @param {PostUpdateUserRequest} postUpdateUserRequest + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + updateUser: async (postUpdateUserRequest: PostUpdateUserRequest, options: AxiosRequestConfig = {}): Promise => { + // verify required parameter 'postUpdateUserRequest' is not null or undefined + assertParamExists('updateUser', 'postUpdateUserRequest', postUpdateUserRequest) + const localVarPath = `/users/update`; + // 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(postUpdateUserRequest, localVarRequestOptions, configuration) + return { url: toPathString(localVarUrlObj), options: localVarRequestOptions, @@ -3941,6 +4152,17 @@ export const UsersApiFp = function(configuration?: Configuration) { const localVarAxiosArgs = await localVarAxiosParamCreator.updateSortCriteria(postSortCriteriaRequest, options); return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); }, + /** + * ユーザーの情報を更新します + * @summary + * @param {PostUpdateUserRequest} postUpdateUserRequest + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + async updateUser(postUpdateUserRequest: PostUpdateUserRequest, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { + const localVarAxiosArgs = await localVarAxiosParamCreator.updateUser(postUpdateUserRequest, options); + return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); + }, } }; @@ -4018,6 +4240,16 @@ export const UsersApiFactory = function (configuration?: Configuration, basePath updateSortCriteria(postSortCriteriaRequest: PostSortCriteriaRequest, options?: any): AxiosPromise { return localVarFp.updateSortCriteria(postSortCriteriaRequest, options).then((request) => request(axios, basePath)); }, + /** + * ユーザーの情報を更新します + * @summary + * @param {PostUpdateUserRequest} postUpdateUserRequest + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + updateUser(postUpdateUserRequest: PostUpdateUserRequest, options?: any): AxiosPromise { + return localVarFp.updateUser(postUpdateUserRequest, options).then((request) => request(axios, basePath)); + }, }; }; @@ -4108,6 +4340,18 @@ export class UsersApi extends BaseAPI { public updateSortCriteria(postSortCriteriaRequest: PostSortCriteriaRequest, options?: AxiosRequestConfig) { return UsersApiFp(this.configuration).updateSortCriteria(postSortCriteriaRequest, options).then((request) => request(this.axios, this.basePath)); } + + /** + * ユーザーの情報を更新します + * @summary + * @param {PostUpdateUserRequest} postUpdateUserRequest + * @param {*} [options] Override http request option. + * @throws {RequiredError} + * @memberof UsersApi + */ + public updateUser(postUpdateUserRequest: PostUpdateUserRequest, options?: AxiosRequestConfig) { + return UsersApiFp(this.configuration).updateUser(postUpdateUserRequest, options).then((request) => request(this.axios, this.basePath)); + } } diff --git a/dictation_client/src/common/errors/code.ts b/dictation_client/src/common/errors/code.ts index 30131c1..9d3f7d3 100644 --- a/dictation_client/src/common/errors/code.ts +++ b/dictation_client/src/common/errors/code.ts @@ -20,13 +20,26 @@ export const errorCodes = [ "E000104", // トークン署名エラー "E000105", // トークン発行元エラー "E000106", // トークンアルゴリズムエラー + "E000107", // トークン不足エラー + "E000108", // トークン権限エラー + "E000301", // ADB2Cへのリクエスト上限超過エラー + "E010001", // パラメータ形式不正エラー "E010201", // 未認証ユーザエラー "E010202", // 認証済ユーザエラー + "E010203", // 管理ユーザ権限エラー + "E010204", // ユーザ不在エラー + "E010205", // DBのRoleが想定外の値エラー + "E010206", // DBのTierが想定外の値エラー + "E010207", // ユーザーのRole変更不可エラー + "E010208", // ユーザーの暗号化パスワード不足エラー "E010301", // メールアドレス登録済みエラー "E010302", // authorId重複エラー "E010401", // PONumber重複エラー + "E010501", // アカウント不在エラー "E010601", // タスク変更不可エラー(タスクが変更できる状態でない、またはタスクが存在しない) "E010602", // タスク変更権限不足エラー + "E010603", // タスク不在エラー + "E010701", // Blobファイル不在エラー "E010801", // ライセンス不在エラー "E010802", // ライセンス取り込み済みエラー ] as const; diff --git a/dictation_client/src/features/license/licenseOrderHistory/licenseOrderHistorySlice.ts b/dictation_client/src/features/license/licenseOrderHistory/licenseOrderHistorySlice.ts index 5e1f0f6..d86f33c 100644 --- a/dictation_client/src/features/license/licenseOrderHistory/licenseOrderHistorySlice.ts +++ b/dictation_client/src/features/license/licenseOrderHistory/licenseOrderHistorySlice.ts @@ -1,4 +1,4 @@ -import { createSlice } from "@reduxjs/toolkit"; +import { PayloadAction, createSlice } from "@reduxjs/toolkit"; import { LicenseOrderHistoryState } from "./state"; import { getLicenseOrderHistoriesAsync } from "./operations"; import { LIMIT_ORDER_HISORY_NUM } from "./constants"; @@ -7,6 +7,7 @@ const initialState: LicenseOrderHistoryState = { domain: { total: 0, orderHistories: [], + companyName: "", }, apps: { limit: LIMIT_ORDER_HISORY_NUM, @@ -23,6 +24,17 @@ export const licenseOrderHistorySlice = createSlice({ cleanupApps: (state) => { state.domain = initialState.domain; }, + savePageInfo: ( + state, + action: PayloadAction<{ + limit: number; + offset: number; + }> + ) => { + const { limit, offset } = action.payload; + state.apps.limit = limit; + state.apps.offset = offset; + }, }, extraReducers: (builder) => { builder.addCase(getLicenseOrderHistoriesAsync.pending, (state) => { @@ -31,8 +43,9 @@ export const licenseOrderHistorySlice = createSlice({ builder.addCase( getLicenseOrderHistoriesAsync.fulfilled, (state, action) => { - state.domain.total = action.payload.total; - state.domain.orderHistories = action.payload.orderHistories; + state.domain.total = action.payload.histories.total; + state.domain.orderHistories = action.payload.histories.orderHistories; + state.domain.companyName = action.payload.companyName; state.apps.isLoading = false; } ); @@ -42,6 +55,6 @@ export const licenseOrderHistorySlice = createSlice({ }, }); -export const { cleanupApps } = licenseOrderHistorySlice.actions; +export const { cleanupApps, savePageInfo } = licenseOrderHistorySlice.actions; export default licenseOrderHistorySlice.reducer; diff --git a/dictation_client/src/features/license/licenseOrderHistory/operations.ts b/dictation_client/src/features/license/licenseOrderHistory/operations.ts index 307662c..75632d5 100644 --- a/dictation_client/src/features/license/licenseOrderHistory/operations.ts +++ b/dictation_client/src/features/license/licenseOrderHistory/operations.ts @@ -2,13 +2,14 @@ import { createAsyncThunk } from "@reduxjs/toolkit"; import type { RootState } from "app/store"; import { getTranslationID } from "translation"; import { openSnackbar } from "features/ui/uiSlice"; -import { AccountsApi, GetOrderHistoriesResponce } from "../../../api/api"; +import { AccountsApi } from "../../../api/api"; import { Configuration } from "../../../api/configuration"; import { ErrorObject, createErrorObject } from "../../../common/errors"; +import { OrderHistoryView } from "./types"; export const getLicenseOrderHistoriesAsync = createAsyncThunk< // 正常時の戻り値の型 - GetOrderHistoriesResponce, + OrderHistoryView, { // パラメータ limit: number; @@ -32,15 +33,18 @@ export const getLicenseOrderHistoriesAsync = createAsyncThunk< try { const { selectedRow } = state.partnerLicense.apps; let accountId = 0; + let companyName = ""; // 他の画面から指定されていない場合はログインアカウントのidを取得する if (!selectedRow) { const getMyAccountResponse = await accountsApi.getMyAccount({ headers: { authorization: `Bearer ${accessToken}` }, }); - // accountIDを返す + // アカウントID,アカウント名を返す accountId = getMyAccountResponse.data.account.accountId; + companyName = getMyAccountResponse.data.account.companyName; } else { accountId = selectedRow.accountId; + companyName = selectedRow.companyName; } const res = await accountsApi.getOrderHistories( @@ -53,7 +57,11 @@ export const getLicenseOrderHistoriesAsync = createAsyncThunk< headers: { authorization: `Bearer ${accessToken}` }, } ); - return res.data; + const ret = { + histories: res.data, + companyName, + }; + return ret; } catch (e) { // e ⇒ errorObjectに変換" const error = createErrorObject(e); diff --git a/dictation_client/src/features/license/licenseOrderHistory/selectors.ts b/dictation_client/src/features/license/licenseOrderHistory/selectors.ts index 7edf09a..e3e4ceb 100644 --- a/dictation_client/src/features/license/licenseOrderHistory/selectors.ts +++ b/dictation_client/src/features/license/licenseOrderHistory/selectors.ts @@ -4,6 +4,9 @@ import { ceil, floor } from "lodash"; export const selectOrderHisory = (state: RootState) => state.licenseOrderHistory.domain.orderHistories; +export const selectCompanyName = (state: RootState) => + state.licenseOrderHistory.domain.companyName; + export const selectTotal = (state: RootState) => state.licenseOrderHistory.domain.total; diff --git a/dictation_client/src/features/license/licenseOrderHistory/state.ts b/dictation_client/src/features/license/licenseOrderHistory/state.ts index 6713e4a..4abde24 100644 --- a/dictation_client/src/features/license/licenseOrderHistory/state.ts +++ b/dictation_client/src/features/license/licenseOrderHistory/state.ts @@ -8,6 +8,7 @@ export interface LicenseOrderHistoryState { export interface Domain { total: number; orderHistories: LicenseOrder[]; + companyName: string; } export interface Apps { diff --git a/dictation_client/src/features/license/licenseOrderHistory/types.ts b/dictation_client/src/features/license/licenseOrderHistory/types.ts new file mode 100644 index 0000000..b44ced1 --- /dev/null +++ b/dictation_client/src/features/license/licenseOrderHistory/types.ts @@ -0,0 +1,6 @@ +import { GetOrderHistoriesResponse } from "../../../api/api"; + +export interface OrderHistoryView { + histories: GetOrderHistoriesResponse; + companyName: string; +} diff --git a/dictation_client/src/features/license/partnerLicense/partnerLicenseSlice.ts b/dictation_client/src/features/license/partnerLicense/partnerLicenseSlice.ts index 1b584b7..259eb02 100644 --- a/dictation_client/src/features/license/partnerLicense/partnerLicenseSlice.ts +++ b/dictation_client/src/features/license/partnerLicense/partnerLicenseSlice.ts @@ -6,7 +6,7 @@ import { ACCOUNTS_VIEW_LIMIT } from "./constants"; const initialState: PartnerLicensesState = { domain: { - myAccountInfo: { accountId: 0 }, + myAccountInfo: { accountId: 0, companyName: "" }, total: 0, ownPartnerLicense: { accountId: 0, diff --git a/dictation_client/src/features/user/operations.ts b/dictation_client/src/features/user/operations.ts index c364a0c..8c35726 100644 --- a/dictation_client/src/features/user/operations.ts +++ b/dictation_client/src/features/user/operations.ts @@ -1,8 +1,9 @@ import { createAsyncThunk } from "@reduxjs/toolkit"; import type { RootState } from "app/store"; -import { getTranslationID } from "translation"; +import { USER_ROLES } from "components/auth/constants"; import { openSnackbar } from "features/ui/uiSlice"; -import { SignupRequest, UsersApi, GetUsersResponse } from "../../api/api"; +import { getTranslationID } from "translation"; +import { GetUsersResponse, UsersApi } from "../../api/api"; import { Configuration } from "../../api/configuration"; import { ErrorObject, createErrorObject } from "../../common/errors"; @@ -43,7 +44,7 @@ export const addUserAsync = createAsyncThunk< { /* Empty Object */ }, - SignupRequest, + void, { // rejectした時の返却値の型 rejectValue: { @@ -51,40 +52,25 @@ export const addUserAsync = createAsyncThunk< }; } >("users/addUserAsync", async (args, thunkApi) => { - const { - name, - email, - role, - authorId, - typistGroupId, - autoRenew, - licenseAlert, - notification, - } = args; - // apiのConfigurationを取得する const { getState } = thunkApi; const state = getState() as RootState; const { configuration, accessToken } = state.auth; const config = new Configuration(configuration); const usersApi = new UsersApi(config); + const { addUser } = state.user.apps; + // roleがAUTHOR以外の場合、不要なプロパティをundefinedにする + if (addUser.role !== USER_ROLES.AUTHOR) { + addUser.authorId = undefined; + addUser.encryption = undefined; + addUser.prompt = undefined; + addUser.encryptionPassword = undefined; + } try { - await usersApi.signup( - { - name, - email, - role, - authorId, - typistGroupId, - autoRenew, - licenseAlert, - notification, - }, - { - headers: { authorization: `Bearer ${accessToken}` }, - } - ); + await usersApi.signup(addUser, { + headers: { authorization: `Bearer ${accessToken}` }, + }); thunkApi.dispatch( openSnackbar({ level: "info", @@ -119,3 +105,86 @@ export const addUserAsync = createAsyncThunk< return thunkApi.rejectWithValue({ error }); } }); + +export const updateUserAsync = createAsyncThunk< + { + /* Empty Object */ + }, + void, + { + // rejectした時の返却値の型 + rejectValue: { + error: ErrorObject; + }; + } +>("users/updateUserAsync", async (args, thunkApi) => { + // apiのConfigurationを取得する + const { getState } = thunkApi; + const state = getState() as RootState; + const { configuration, accessToken } = state.auth; + const config = new Configuration(configuration); + const usersApi = new UsersApi(config); + const { updateUser } = state.user.apps; + + const authorId = + updateUser.role === USER_ROLES.AUTHOR ? updateUser.authorId : undefined; + const encryption = + updateUser.role === USER_ROLES.AUTHOR ? updateUser.encryption : undefined; + const encryptionPassword = + updateUser.role === USER_ROLES.AUTHOR + ? updateUser.encryptionPassword + : undefined; + const prompt = + updateUser.role === USER_ROLES.AUTHOR ? updateUser.prompt : undefined; + + try { + await usersApi.updateUser( + { + id: updateUser.id, + role: updateUser.role, + authorId, + encryption, + encryptionPassword, + prompt, + autoRenew: updateUser.autoRenew, + licenseAlart: updateUser.licenseAlert, + notification: updateUser.notification, + }, + { + headers: { authorization: `Bearer ${accessToken}` }, + } + ); + thunkApi.dispatch( + openSnackbar({ + level: "info", + message: getTranslationID("common.message.success"), + }) + ); + return {}; + } catch (e) { + // e ⇒ errorObjectに変換"z + const error = createErrorObject(e); + + let errorMessage = getTranslationID("common.message.internalServerError"); + + // Roleが変更できない + if (error.code === "E010207") { + errorMessage = getTranslationID("userListPage.message.roleChangeError"); + } + // AuthorIdが重複している + if (error.code === "E010302") { + errorMessage = getTranslationID( + "userListPage.message.authorIdConflictError" + ); + } + + thunkApi.dispatch( + openSnackbar({ + level: "error", + message: errorMessage, + }) + ); + + return thunkApi.rejectWithValue({ error }); + } +}); diff --git a/dictation_client/src/features/user/selectors.ts b/dictation_client/src/features/user/selectors.ts index 6f2029a..854e02b 100644 --- a/dictation_client/src/features/user/selectors.ts +++ b/dictation_client/src/features/user/selectors.ts @@ -1,15 +1,24 @@ import { RootState } from "app/store"; import { USER_ROLES } from "components/auth/constants"; -import { RoleType, UserView, isLicenseStatusType, isRoleType } from "./types"; +import { + AddUser, + RoleType, + UserView, + isLicenseStatusType, + isRoleType, +} from "./types"; import { LICENSE_STATUS } from "./constants"; export const selectInputValidationErrors = (state: RootState) => { - const { name, email, role, authorId } = state.user.apps.addUser; + const { name, email, role, authorId, encryption, encryptionPassword } = + state.user.apps.addUser; // 必須項目のチェック const hasErrorEmptyName = name === ""; const hasErrorEmptyEmail = email === ""; - const hasErrorEmptyAuthorId = role === USER_ROLES.AUTHOR && authorId === ""; + // Authorの場合、AuthorIDが必須(空文字,undefinedは不可) + const hasErrorEmptyAuthorId = + role === USER_ROLES.AUTHOR && (authorId === "" || !authorId); const hasErrorIncorrectAuthorId = checkErrorIncorrectAuthorId( authorId ?? undefined, @@ -18,14 +27,87 @@ export const selectInputValidationErrors = (state: RootState) => { const hasErrorIncorrectEmail = email.match(/^[^@]+@[^@]+$/)?.length !== 1; + const hasErrorIncorrectEncryptionPassword = + checkErrorIncorrectEncryptionPassword(encryptionPassword, role, encryption); + return { hasErrorEmptyName, hasErrorEmptyEmail, hasErrorEmptyAuthorId, hasErrorIncorrectEmail, hasErrorIncorrectAuthorId, + hasErrorIncorrectEncryptionPassword, }; }; + +export const selectUpdateValidationErrors = (state: RootState) => { + const { role, authorId, encryption, encryptionPassword } = + state.user.apps.updateUser; + const { encryption: initEncryption } = state.user.apps.selectedUser; + + // Authorの場合、AuthorIDが必須(空文字,undefinedは不可) + const hasErrorEmptyAuthorId = + role === USER_ROLES.AUTHOR && (authorId === "" || !authorId); + + const hasErrorIncorrectAuthorId = checkErrorIncorrectAuthorId( + authorId ?? undefined, + role + ); + + let hasErrorIncorrectEncryptionPassword = false; + + const passwordError = checkErrorIncorrectEncryptionPassword( + encryptionPassword, + role, + encryption + ); + + if (passwordError) { + // 最初にEncryptionがfasleで、Encryptionがtrueに変更された場合、EncryptionPasswordが必須 + if (!initEncryption) { + hasErrorIncorrectEncryptionPassword = true; + // Encryptionがある状態で変更がある場合、EncryptionPasswordが空でもエラーにしない + } else if (!encryptionPassword || encryptionPassword === "") { + hasErrorIncorrectEncryptionPassword = false; + } else { + hasErrorIncorrectEncryptionPassword = true; + } + } + + return { + hasErrorEmptyAuthorId, + hasErrorIncorrectAuthorId, + hasErrorIncorrectEncryptionPassword, + }; +}; + +// encreyptionPasswordのチェック +const checkErrorIncorrectEncryptionPassword = ( + encryptionPassword: string | undefined, + role: RoleType, + encryption: boolean | undefined +): boolean => { + // roleがAuthor以外の場合、チェックしない + if (role !== USER_ROLES.AUTHOR) { + return false; + } + // roleがAuthorかつencryptionがfalseの場合、チェックしない + if (!encryption) { + return false; + } + // encryptionPasswordがundefined,空文字の場合、エラー + if (!encryptionPassword || encryptionPassword === "") { + return true; + } + // encryptionPasswordがルールに則していない場合、エラー + const regex = /^[!-~]{4,16}$/; + if (!regex.test(encryptionPassword)) { + return true; + } + // チェックを通ったらエラーではない + return false; +}; + export const checkErrorIncorrectAuthorId = ( authorId: string | undefined, role: string @@ -46,14 +128,15 @@ export const selectEmail = (state: RootState) => state.user.apps.addUser.email; export const selectRole = (state: RootState) => state.user.apps.addUser.role; export const selectAuthorId = (state: RootState) => state.user.apps.addUser.authorId; -export const selectTypistGroupId = (state: RootState) => - state.user.apps.addUser.typistGroupId; export const selectAutoRenew = (state: RootState) => state.user.apps.addUser.autoRenew; export const selectLicenseAlert = (state: RootState) => state.user.apps.addUser.licenseAlert; -export const selectNtotification = (state: RootState) => +export const selectNotification = (state: RootState) => state.user.apps.addUser.notification; +// AddUserを返却する +export const selectAddUser = (state: RootState): AddUser => + state.user.apps.addUser; // usersからUserViewに変換して返却する export const selectUserViews = (state: RootState): UserView[] => { const { users } = state.user.domain; @@ -138,3 +221,9 @@ const convertValueBasedOnRole = ( typistGroupName: "-", }; }; + +export const selectUpdateUser = (state: RootState) => + state.user.apps.updateUser; + +export const selectHasPasswordMask = (state: RootState) => + state.user.apps.hasPasswordMask; diff --git a/dictation_client/src/features/user/state.ts b/dictation_client/src/features/user/state.ts index 1a0933a..503d798 100644 --- a/dictation_client/src/features/user/state.ts +++ b/dictation_client/src/features/user/state.ts @@ -1,4 +1,5 @@ import { User } from "../../api/api"; +import { AddUser, UpdateUser } from "./types"; export interface UsersState { domain: Domain; @@ -11,9 +12,8 @@ export interface Domain { export interface Apps { addUser: AddUser; + selectedUser: UpdateUser; + updateUser: UpdateUser; + hasPasswordMask: boolean; isLoading: boolean; } - -export interface AddUser extends Omit { - typistGroupId?: number | undefined; -} diff --git a/dictation_client/src/features/user/types.ts b/dictation_client/src/features/user/types.ts index ba98cb5..40d03f9 100644 --- a/dictation_client/src/features/user/types.ts +++ b/dictation_client/src/features/user/types.ts @@ -23,6 +23,32 @@ export interface UserView expiration: string; remaining: number | string; } +export interface AddUser { + name: string; + role: RoleType; + email: string; + autoRenew: boolean; + licenseAlert: boolean; + notification: boolean; + authorId?: string; + encryption?: boolean; + encryptionPassword?: string; + prompt?: boolean; +} + +export interface UpdateUser { + id: number; + name: string; + email: string; + role: RoleType; + authorId?: string | undefined; + encryption?: boolean | undefined; + encryptionPassword?: string | undefined; + prompt?: boolean | undefined; + autoRenew: boolean; + licenseAlert: boolean; + notification: boolean; +} export type RoleType = typeof USER_ROLES[keyof typeof USER_ROLES]; diff --git a/dictation_client/src/features/user/userSlice.ts b/dictation_client/src/features/user/userSlice.ts index 8cdf90f..46066d7 100644 --- a/dictation_client/src/features/user/userSlice.ts +++ b/dictation_client/src/features/user/userSlice.ts @@ -1,26 +1,51 @@ import { PayloadAction, createSlice } from "@reduxjs/toolkit"; import { USER_ROLES } from "components/auth/constants"; import { UsersState } from "./state"; -import { addUserAsync, listUsersAsync } from "./operations"; +import { addUserAsync, listUsersAsync, updateUserAsync } from "./operations"; import { RoleType } from "./types"; const initialState: UsersState = { domain: { users: [] }, apps: { - addUser: { + updateUser: { + id: 0, name: "", - role: USER_ROLES.NONE, - authorId: "", - typistGroupName: [], email: "", - emailVerified: true, + role: USER_ROLES.NONE, + authorId: undefined, + encryption: undefined, + encryptionPassword: undefined, + prompt: undefined, autoRenew: true, licenseAlert: true, notification: true, - encryption: true, - licenseStatus: "", - prompt: false, }, + selectedUser: { + id: 0, + name: "", + email: "", + role: USER_ROLES.NONE, + authorId: undefined, + encryption: undefined, + encryptionPassword: undefined, + prompt: undefined, + autoRenew: true, + licenseAlert: true, + notification: true, + }, + addUser: { + name: "", + role: USER_ROLES.NONE, + email: "", + autoRenew: true, + licenseAlert: true, + notification: true, + authorId: "", + encryption: false, + prompt: false, + encryptionPassword: "", + }, + hasPasswordMask: false, isLoading: false, }, }; @@ -48,13 +73,6 @@ export const userSlice = createSlice({ const { authorId } = action.payload; state.apps.addUser.authorId = authorId; }, - changeTypistGroupId: ( - state, - action: PayloadAction<{ typistGroupId: number | undefined }> - ) => { - const { typistGroupId } = action.payload; - state.apps.addUser.typistGroupId = typistGroupId; - }, changeAutoRenew: (state, action: PayloadAction<{ autoRenew: boolean }>) => { const { autoRenew } = action.payload; state.apps.addUser.autoRenew = autoRenew; @@ -66,6 +84,24 @@ export const userSlice = createSlice({ const { licenseAlert } = action.payload; state.apps.addUser.licenseAlert = licenseAlert; }, + changeEncryption: ( + state, + action: PayloadAction<{ encryption: boolean }> + ) => { + const { encryption } = action.payload; + state.apps.addUser.encryption = encryption; + }, + changePrompt: (state, action: PayloadAction<{ prompt: boolean }>) => { + const { prompt } = action.payload; + state.apps.addUser.prompt = prompt; + }, + changeEncryptionPassword: ( + state, + action: PayloadAction<{ encryptionPassword: string }> + ) => { + const { encryptionPassword } = action.payload; + state.apps.addUser.encryptionPassword = encryptionPassword; + }, changeNotification: ( state, action: PayloadAction<{ notification: boolean }> @@ -76,6 +112,108 @@ export const userSlice = createSlice({ cleanupAddUser: (state) => { state.apps.addUser = initialState.apps.addUser; }, + changeUpdateUser: (state, action: PayloadAction<{ id: number }>) => { + const { id } = action.payload; + + const user = state.domain.users.find((x) => x.id === id); + + if (!user) { + return; + } + + state.apps.updateUser.id = user.id; + state.apps.updateUser.name = user.name; + state.apps.updateUser.email = user.email; + state.apps.updateUser.role = user.role as RoleType; + state.apps.updateUser.authorId = user.authorId; + state.apps.updateUser.encryption = user.encryption; + state.apps.updateUser.encryptionPassword = undefined; + state.apps.updateUser.prompt = user.prompt; + state.apps.updateUser.autoRenew = user.autoRenew; + state.apps.updateUser.licenseAlert = user.licenseAlert; + state.apps.updateUser.notification = user.notification; + + state.apps.selectedUser.id = user.id; + state.apps.selectedUser.name = user.name; + state.apps.selectedUser.email = user.email; + state.apps.selectedUser.role = user.role as RoleType; + state.apps.selectedUser.authorId = user.authorId; + state.apps.selectedUser.encryption = user.encryption; + state.apps.selectedUser.encryptionPassword = undefined; + state.apps.selectedUser.prompt = user.prompt; + state.apps.selectedUser.autoRenew = user.autoRenew; + state.apps.selectedUser.licenseAlert = user.licenseAlert; + state.apps.selectedUser.notification = user.notification; + + state.apps.hasPasswordMask = user.encryption; + }, + changeUpdateRole: (state, action: PayloadAction<{ role: RoleType }>) => { + const { role } = action.payload; + state.apps.updateUser.role = role; + }, + changeUpdateAuthorId: ( + state, + action: PayloadAction<{ authorId: string }> + ) => { + const { authorId } = action.payload; + state.apps.updateUser.authorId = authorId; + }, + changeUpdateEncryption: ( + state, + action: PayloadAction<{ encryption: boolean }> + ) => { + const { encryption } = action.payload; + state.apps.updateUser.encryption = encryption; + const initEncryption = state.apps.selectedUser.encryption; + const password = state.apps.updateUser.encryptionPassword; + + if (initEncryption && encryption && !password) { + state.apps.hasPasswordMask = true; + } + }, + changeUpdateEncryptionPassword: ( + state, + action: PayloadAction<{ encryptionPassword: string }> + ) => { + const { encryptionPassword } = action.payload; + state.apps.updateUser.encryptionPassword = + encryptionPassword === "" ? undefined : encryptionPassword; + }, + changeUpdatePrompt: (state, action: PayloadAction<{ prompt: boolean }>) => { + const { prompt } = action.payload; + state.apps.updateUser.prompt = prompt; + }, + changeUpdateAutoRenew: ( + state, + action: PayloadAction<{ autoRenew: boolean }> + ) => { + const { autoRenew } = action.payload; + state.apps.updateUser.autoRenew = autoRenew; + }, + changeUpdateLicenseAlert: ( + state, + action: PayloadAction<{ licenseAlert: boolean }> + ) => { + const { licenseAlert } = action.payload; + state.apps.updateUser.licenseAlert = licenseAlert; + }, + changeUpdateNotification: ( + state, + action: PayloadAction<{ notification: boolean }> + ) => { + const { notification } = action.payload; + state.apps.updateUser.notification = notification; + }, + changeHasPasswordMask: ( + state, + action: PayloadAction<{ hasPasswordMask: boolean }> + ) => { + const { hasPasswordMask } = action.payload; + state.apps.hasPasswordMask = hasPasswordMask; + }, + cleanupUpdateUser: (state) => { + state.apps.updateUser = initialState.apps.updateUser; + }, }, extraReducers: (builder) => { builder.addCase(listUsersAsync.pending, (state) => { @@ -97,6 +235,15 @@ export const userSlice = createSlice({ builder.addCase(addUserAsync.rejected, (state) => { state.apps.isLoading = false; }); + builder.addCase(updateUserAsync.pending, (state) => { + state.apps.isLoading = true; + }); + builder.addCase(updateUserAsync.fulfilled, (state) => { + state.apps.isLoading = false; + }); + builder.addCase(updateUserAsync.rejected, (state) => { + state.apps.isLoading = false; + }); }, }); @@ -105,11 +252,24 @@ export const { changeEmail, changeRole, changeAuthorId, - changeTypistGroupId, changeAutoRenew, changeLicenseAlert, changeNotification, cleanupAddUser, + changeUpdateUser, + changeUpdateRole, + changeUpdateAuthorId, + changeUpdateEncryption, + changeUpdateEncryptionPassword, + changeUpdatePrompt, + changeUpdateAutoRenew, + changeUpdateLicenseAlert, + changeUpdateNotification, + cleanupUpdateUser, + changeEncryption, + changePrompt, + changeEncryptionPassword, + changeHasPasswordMask, } = userSlice.actions; export default userSlice.reducer; diff --git a/dictation_client/src/pages/DictationPage/index.tsx b/dictation_client/src/pages/DictationPage/index.tsx index 021d9fb..27173a5 100644 --- a/dictation_client/src/pages/DictationPage/index.tsx +++ b/dictation_client/src/pages/DictationPage/index.tsx @@ -1098,7 +1098,9 @@ const DictationPage: React.FC = (): JSX.Element => { {x.workType} )} {displayColumn.FileName && ( - {x.fileName} + + {x.fileName.replace(".zip", "")} + )} {displayColumn.FileLength && ( {x.audioDuration} diff --git a/dictation_client/src/pages/LicensePage/licenseOrderHistory.tsx b/dictation_client/src/pages/LicensePage/licenseOrderHistory.tsx index 7cfda4f..8713d6e 100644 --- a/dictation_client/src/pages/LicensePage/licenseOrderHistory.tsx +++ b/dictation_client/src/pages/LicensePage/licenseOrderHistory.tsx @@ -17,6 +17,9 @@ import { selectOrderHisory, selectTotal, selectTotalPage, + selectOffset, + savePageInfo, + selectCompanyName, } from "features/license/licenseOrderHistory"; import { selectSelectedRow } from "features/license/partnerLicense"; import undo from "../../assets/images/undo.svg"; @@ -34,6 +37,7 @@ export const LicenseOrderHistory: React.FC = ( const [t] = useTranslation(); const total = useSelector(selectTotal); const totalPage = useSelector(selectTotalPage); + const offset = useSelector(selectOffset); const currentPage = useSelector(selectCurrentPage); const isLoading = useSelector(selectIsLoading); const selectedRow = useSelector(selectSelectedRow); @@ -47,56 +51,25 @@ export const LicenseOrderHistory: React.FC = ( }, [isLoading, onReturn]); // ページネーションのボタンクリック時のアクション - const getFirstPage = useCallback(() => { + const movePage = (targetOffset: number) => { dispatch( - getLicenseOrderHistoriesAsync({ - limit: LIMIT_ORDER_HISORY_NUM, - offset: 0, - }) + savePageInfo({ limit: LIMIT_ORDER_HISORY_NUM, offset: targetOffset }) ); - }, [dispatch]); - - const getLastPage = useCallback(() => { - const lastPageOffset = (totalPage - 1) * LIMIT_ORDER_HISORY_NUM; - dispatch( - getLicenseOrderHistoriesAsync({ - limit: LIMIT_ORDER_HISORY_NUM, - offset: lastPageOffset, - }) - ); - }, [dispatch, totalPage]); - - const getPrevPage = useCallback(() => { - const prevPageOffset = (currentPage - 2) * LIMIT_ORDER_HISORY_NUM; - dispatch( - getLicenseOrderHistoriesAsync({ - limit: LIMIT_ORDER_HISORY_NUM, - offset: prevPageOffset, - }) - ); - }, [dispatch, currentPage]); - - const getNextPage = useCallback(() => { - const nextPageOffset = currentPage * LIMIT_ORDER_HISORY_NUM; - dispatch( - getLicenseOrderHistoriesAsync({ - limit: LIMIT_ORDER_HISORY_NUM, - offset: nextPageOffset, - }) - ); - }, [dispatch, currentPage]); + }; // apiからの値取得関係 const licenseOrderHistory = useSelector(selectOrderHisory); + const companyName = useSelector(selectCompanyName); useEffect(() => { dispatch( getLicenseOrderHistoriesAsync({ limit: LIMIT_ORDER_HISORY_NUM, - offset: 0, + offset, }) ); - }, [dispatch]); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [dispatch, currentPage]); return (
@@ -104,7 +77,7 @@ export const LicenseOrderHistory: React.FC = (
-
+

{t(getTranslationID("orderHistoriesPage.label.title"))} @@ -112,13 +85,12 @@ export const LicenseOrderHistory: React.FC = (

-

- {t(getTranslationID("orderHistoriesPage.label.subTitle"))} -

- - {t(getTranslationID("orderHistoriesPage.label.orderHistory"))} -

-

+

{companyName}

+

+ + {t(getTranslationID("orderHistoriesPage.label.orderHistory"))} +

+