Merged PR 862: パートナー一覧画面&パートナー編集ポップアップ実装

## 概要
[Task3935: パートナー一覧画面&パートナー編集ポップアップ実装](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/3935)

- パートナー一覧画面からパートナー編集ポップアップを表示して情報を変更できる画面実装をしています。

## レビューポイント
- エラーの表示は適切でしょうか?
- 画面イメージは認識通りでしょうか?

## UIの変更
- [Task3935](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/Task3935?csf=1&web=1&e=FdaUMT)

## クエリの変更
- なし
## 動作確認状況
- ローカルで確認
- 行った修正がデグレを発生させていないことを確認できるか
  - 新規機能なので問題なし
This commit is contained in:
makabe.t 2024-04-08 07:41:05 +00:00
parent 915483c109
commit e6d6e477d9
19 changed files with 701 additions and 28 deletions

View File

@ -1035,6 +1035,32 @@ export interface GetPartnerLicensesResponse {
*/
'childrenPartnerLicenses': Array<PartnerLicenseInfo>;
}
/**
*
* @export
* @interface GetPartnerUsersRequest
*/
export interface GetPartnerUsersRequest {
/**
* ID
* @type {number}
* @memberof GetPartnerUsersRequest
*/
'targetAccountId': number;
}
/**
*
* @export
* @interface GetPartnerUsersResponse
*/
export interface GetPartnerUsersResponse {
/**
*
* @type {Array<PartnerUser>}
* @memberof GetPartnerUsersResponse
*/
'users': Array<PartnerUser>;
}
/**
*
* @export
@ -1585,6 +1611,37 @@ export interface PartnerLicenseInfo {
*/
'issueRequesting': number;
}
/**
*
* @export
* @interface PartnerUser
*/
export interface PartnerUser {
/**
* ID
* @type {number}
* @memberof PartnerUser
*/
'id': number;
/**
*
* @type {string}
* @memberof PartnerUser
*/
'name': string;
/**
*
* @type {string}
* @memberof PartnerUser
*/
'email': string;
/**
*
* @type {boolean}
* @memberof PartnerUser
*/
'isPrimaryAdmin': boolean;
}
/**
*
* @export
@ -2289,6 +2346,31 @@ export interface UpdateOptionItemsRequest {
*/
'optionItems': Array<PostWorktypeOptionItem>;
}
/**
*
* @export
* @interface UpdatePartnerInfoRequest
*/
export interface UpdatePartnerInfoRequest {
/**
* ID
* @type {number}
* @memberof UpdatePartnerInfoRequest
*/
'targetAccountId': number;
/**
* ID
* @type {number}
* @memberof UpdatePartnerInfoRequest
*/
'primaryAdminUserId': number;
/**
*
* @type {string}
* @memberof UpdatePartnerInfoRequest
*/
'companyName': string;
}
/**
*
* @export
@ -3318,6 +3400,46 @@ export const AccountsApiAxiosParamCreator = function (configuration?: Configurat
options: localVarRequestOptions,
};
},
/**
* (APIと合わせてGETではなくPOSTを使用)
* @summary
* @param {GetPartnerUsersRequest} getPartnerUsersRequest
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
getPartnerUsers: async (getPartnerUsersRequest: GetPartnerUsersRequest, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
// verify required parameter 'getPartnerUsersRequest' is not null or undefined
assertParamExists('getPartnerUsers', 'getPartnerUsersRequest', getPartnerUsersRequest)
const localVarPath = `/accounts/partner/users`;
// 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(getPartnerUsersRequest, localVarRequestOptions, configuration)
return {
url: toPathString(localVarUrlObj),
options: localVarRequestOptions,
};
},
/**
*
* @summary
@ -3710,6 +3832,46 @@ export const AccountsApiAxiosParamCreator = function (configuration?: Configurat
options: localVarRequestOptions,
};
},
/**
*
* @summary
* @param {UpdatePartnerInfoRequest} updatePartnerInfoRequest
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
updatePartnerInfo: async (updatePartnerInfoRequest: UpdatePartnerInfoRequest, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
// verify required parameter 'updatePartnerInfoRequest' is not null or undefined
assertParamExists('updatePartnerInfo', 'updatePartnerInfoRequest', updatePartnerInfoRequest)
const localVarPath = `/accounts/partner/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(updatePartnerInfoRequest, localVarRequestOptions, configuration)
return {
url: toPathString(localVarUrlObj),
options: localVarRequestOptions,
};
},
/**
*
* @summary
@ -4092,6 +4254,19 @@ export const AccountsApiFp = function(configuration?: Configuration) {
const operationBasePath = operationServerMap['AccountsApi.getPartnerLicenses']?.[index]?.url;
return (axios, basePath) => createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration)(axios, operationBasePath || basePath);
},
/**
* (APIと合わせてGETではなくPOSTを使用)
* @summary
* @param {GetPartnerUsersRequest} getPartnerUsersRequest
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
async getPartnerUsers(getPartnerUsersRequest: GetPartnerUsersRequest, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<GetPartnerUsersResponse>> {
const localVarAxiosArgs = await localVarAxiosParamCreator.getPartnerUsers(getPartnerUsersRequest, options);
const index = configuration?.serverIndex ?? 0;
const operationBasePath = operationServerMap['AccountsApi.getPartnerUsers']?.[index]?.url;
return (axios, basePath) => createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration)(axios, operationBasePath || basePath);
},
/**
*
* @summary
@ -4221,6 +4396,19 @@ export const AccountsApiFp = function(configuration?: Configuration) {
const operationBasePath = operationServerMap['AccountsApi.updateOptionItems']?.[index]?.url;
return (axios, basePath) => createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration)(axios, operationBasePath || basePath);
},
/**
*
* @summary
* @param {UpdatePartnerInfoRequest} updatePartnerInfoRequest
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
async updatePartnerInfo(updatePartnerInfoRequest: UpdatePartnerInfoRequest, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<object>> {
const localVarAxiosArgs = await localVarAxiosParamCreator.updatePartnerInfo(updatePartnerInfoRequest, options);
const index = configuration?.serverIndex ?? 0;
const operationBasePath = operationServerMap['AccountsApi.updatePartnerInfo']?.[index]?.url;
return (axios, basePath) => createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration)(axios, operationBasePath || basePath);
},
/**
*
* @summary
@ -4459,6 +4647,16 @@ export const AccountsApiFactory = function (configuration?: Configuration, baseP
getPartnerLicenses(getPartnerLicensesRequest: GetPartnerLicensesRequest, options?: any): AxiosPromise<GetPartnerLicensesResponse> {
return localVarFp.getPartnerLicenses(getPartnerLicensesRequest, options).then((request) => request(axios, basePath));
},
/**
* (APIと合わせてGETではなくPOSTを使用)
* @summary
* @param {GetPartnerUsersRequest} getPartnerUsersRequest
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
getPartnerUsers(getPartnerUsersRequest: GetPartnerUsersRequest, options?: any): AxiosPromise<GetPartnerUsersResponse> {
return localVarFp.getPartnerUsers(getPartnerUsersRequest, options).then((request) => request(axios, basePath));
},
/**
*
* @summary
@ -4558,6 +4756,16 @@ export const AccountsApiFactory = function (configuration?: Configuration, baseP
updateOptionItems(id: number, updateOptionItemsRequest: UpdateOptionItemsRequest, options?: any): AxiosPromise<object> {
return localVarFp.updateOptionItems(id, updateOptionItemsRequest, options).then((request) => request(axios, basePath));
},
/**
*
* @summary
* @param {UpdatePartnerInfoRequest} updatePartnerInfoRequest
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
updatePartnerInfo(updatePartnerInfoRequest: UpdatePartnerInfoRequest, options?: any): AxiosPromise<object> {
return localVarFp.updatePartnerInfo(updatePartnerInfoRequest, options).then((request) => request(axios, basePath));
},
/**
*
* @summary
@ -4825,6 +5033,18 @@ export class AccountsApi extends BaseAPI {
return AccountsApiFp(this.configuration).getPartnerLicenses(getPartnerLicensesRequest, options).then((request) => request(this.axios, this.basePath));
}
/**
* (APIと合わせてGETではなくPOSTを使用)
* @summary
* @param {GetPartnerUsersRequest} getPartnerUsersRequest
* @param {*} [options] Override http request option.
* @throws {RequiredError}
* @memberof AccountsApi
*/
public getPartnerUsers(getPartnerUsersRequest: GetPartnerUsersRequest, options?: AxiosRequestConfig) {
return AccountsApiFp(this.configuration).getPartnerUsers(getPartnerUsersRequest, options).then((request) => request(this.axios, this.basePath));
}
/**
*
* @summary
@ -4944,6 +5164,18 @@ export class AccountsApi extends BaseAPI {
return AccountsApiFp(this.configuration).updateOptionItems(id, updateOptionItemsRequest, options).then((request) => request(this.axios, this.basePath));
}
/**
*
* @summary
* @param {UpdatePartnerInfoRequest} updatePartnerInfoRequest
* @param {*} [options] Override http request option.
* @throws {RequiredError}
* @memberof AccountsApi
*/
public updatePartnerInfo(updatePartnerInfoRequest: UpdatePartnerInfoRequest, options?: AxiosRequestConfig) {
return AccountsApiFp(this.configuration).updatePartnerInfo(updatePartnerInfoRequest, options).then((request) => request(this.axios, this.basePath));
}
/**
*
* @summary

View File

@ -82,4 +82,6 @@ export const errorCodes = [
"E017003", // 親アカウント変更不可エラー(リージョンが同一でない)
"E017004", // 親アカウント変更不可エラー(国が同一でない)
"E018001", // パートナーアカウント削除エラー(削除条件を満たしていない)
"E019001", // パートナーアカウント取得不可エラー(階層構造が不正)
"E020001", // パートナーアカウント変更エラー(変更条件を満たしていない)
] as const;

View File

@ -6,4 +6,4 @@ export type ErrorObject = {
statusCode?: number;
};
export type ErrorCodeType = typeof errorCodes[number];
export type ErrorCodeType = (typeof errorCodes)[number];

View File

@ -6,7 +6,7 @@ export const STATUS = {
BACKUP: "Backup",
} as const;
export type StatusType = typeof STATUS[keyof typeof STATUS];
export type StatusType = (typeof STATUS)[keyof typeof STATUS];
export const LIMIT_TASK_NUM = 100;
@ -26,7 +26,7 @@ export const SORTABLE_COLUMN = {
TranscriptionFinishedDate: "TRANSCRIPTION_FINISHED_DATE",
} as const;
export type SortableColumnType =
typeof SORTABLE_COLUMN[keyof typeof SORTABLE_COLUMN];
(typeof SORTABLE_COLUMN)[keyof typeof SORTABLE_COLUMN];
export const isSortableColumnType = (
value: string
@ -36,14 +36,14 @@ export const isSortableColumnType = (
};
export type SortableColumnList =
typeof SORTABLE_COLUMN[keyof typeof SORTABLE_COLUMN];
(typeof SORTABLE_COLUMN)[keyof typeof SORTABLE_COLUMN];
export const DIRECTION = {
ASC: "ASC",
DESC: "DESC",
} as const;
export type DirectionType = typeof DIRECTION[keyof typeof DIRECTION];
export type DirectionType = (typeof DIRECTION)[keyof typeof DIRECTION];
// DirectionTypeの型チェック関数
export const isDirectionType = (arg: string): arg is DirectionType =>

View File

@ -9,6 +9,7 @@ import {
CreatePartnerAccountRequest,
GetPartnersResponse,
DeletePartnerAccountRequest,
GetPartnerUsersResponse,
} from "../../api/api";
import { Configuration } from "../../api/configuration";
@ -176,3 +177,111 @@ export const deletePartnerAccountAsync = createAsyncThunk<
return thunkApi.rejectWithValue({ error });
}
});
// パートナーアカウントユーザー取得
export const getPartnerUsersAsync = createAsyncThunk<
GetPartnerUsersResponse,
{
// パラメータ
accountId: number;
},
{
// rejectした時の返却値の型
rejectValue: {
error: ErrorObject;
};
}
>("partner/getPartnerUsersAsync", async (args, thunkApi) => {
const { accountId } = args;
// apiのConfigurationを取得する
const { getState } = thunkApi;
const state = getState() as RootState;
const { configuration } = state.auth;
const accessToken = getAccessToken(state.auth);
const config = new Configuration(configuration);
const accountApi = new AccountsApi(config);
try {
const res = await accountApi.getPartnerUsers(
{ targetAccountId: accountId },
{
headers: { authorization: `Bearer ${accessToken}` },
}
);
return res.data;
} catch (e) {
const error = createErrorObject(e);
thunkApi.dispatch(
openSnackbar({
level: "error",
message: getTranslationID("common.message.internalServerError"),
})
);
return thunkApi.rejectWithValue({ error });
}
});
// パートナーアカウントユーザー編集
export const editPartnerInfoAsync = createAsyncThunk<
{
/* Empty Object */
},
void,
{
// rejectした時の返却値の型
rejectValue: {
error: ErrorObject;
};
}
>("partner/editPartnerInfoAsync", async (args, thunkApi) => {
// apiのConfigurationを取得する
const { getState } = thunkApi;
const state = getState() as RootState;
const { configuration } = state.auth;
const accessToken = getAccessToken(state.auth);
const config = new Configuration(configuration);
const accountApi = new AccountsApi(config);
const { id, companyName, selectedAdminId } = state.partner.apps.editPartner;
try {
await accountApi.updatePartnerInfo(
{
targetAccountId: id,
primaryAdminUserId: selectedAdminId,
companyName,
},
{
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" || error.code === "E020001") {
errorMessage = getTranslationID("partnerPage.message.editFailedError");
}
thunkApi.dispatch(
openSnackbar({
level: "error",
message: errorMessage,
})
);
return thunkApi.rejectWithValue({ error });
}
});

View File

@ -4,6 +4,8 @@ import {
createPartnerAccountAsync,
getPartnerInfoAsync,
deletePartnerAccountAsync,
getPartnerUsersAsync,
editPartnerInfoAsync,
} from "./operations";
import { LIMIT_PARTNER_VIEW_NUM } from "./constants";
@ -21,6 +23,13 @@ const initialState: PartnerState = {
adminName: "",
email: "",
},
editPartner: {
users: [],
id: 0,
companyName: "",
country: "",
selectedAdminId: 0,
},
limit: LIMIT_PARTNER_VIEW_NUM,
offset: 0,
isLoading: false,
@ -79,6 +88,37 @@ export const partnerSlice = createSlice({
state.apps.delegatedAccountId = undefined;
state.apps.delegatedCompanyName = undefined;
},
changeEditPartner: (
state,
action: PayloadAction<{
id: number;
companyName: string;
country: string;
}>
) => {
const { id, companyName, country } = action.payload;
state.apps.editPartner.id = id;
state.apps.editPartner.companyName = companyName;
state.apps.editPartner.country = country;
},
changeEditCompanyName: (
state,
action: PayloadAction<{ companyName: string }>
) => {
const { companyName } = action.payload;
state.apps.editPartner.companyName = companyName;
},
changeSelectedAdminId: (
state,
action: PayloadAction<{ adminId: number }>
) => {
const { adminId } = action.payload;
state.apps.editPartner.selectedAdminId = adminId;
},
cleanupPartnerAccount: (state) => {
state.apps.editPartner = initialState.apps.editPartner;
},
},
extraReducers: (builder) => {
builder.addCase(createPartnerAccountAsync.pending, (state) => {
@ -110,6 +150,28 @@ export const partnerSlice = createSlice({
builder.addCase(deletePartnerAccountAsync.rejected, (state) => {
state.apps.isLoading = false;
});
builder.addCase(getPartnerUsersAsync.pending, (state) => {
state.apps.isLoading = true;
});
builder.addCase(getPartnerUsersAsync.fulfilled, (state, action) => {
const { users } = action.payload;
state.apps.editPartner.users = users;
state.apps.editPartner.selectedAdminId =
users.find((user) => user.isPrimaryAdmin)?.id ?? 0;
state.apps.isLoading = false;
});
builder.addCase(getPartnerUsersAsync.rejected, (state) => {
state.apps.isLoading = false;
});
builder.addCase(editPartnerInfoAsync.pending, (state) => {
state.apps.isLoading = true;
});
builder.addCase(editPartnerInfoAsync.fulfilled, (state) => {
state.apps.isLoading = false;
});
builder.addCase(editPartnerInfoAsync.rejected, (state) => {
state.apps.isLoading = false;
});
},
});
export const {
@ -121,5 +183,9 @@ export const {
savePageInfo,
changeDelegateAccount,
cleanupDelegateAccount,
changeEditPartner,
changeEditCompanyName,
changeSelectedAdminId,
cleanupPartnerAccount,
} = partnerSlice.actions;
export default partnerSlice.reducer;

View File

@ -62,3 +62,17 @@ export const selectDelegatedAccountId = (state: RootState) =>
state.partner.apps.delegatedAccountId;
export const selectDelegatedCompanyName = (state: RootState) =>
state.partner.apps.delegatedCompanyName;
// edit
export const selectEditPartnerId = (state: RootState) =>
state.partner.apps.editPartner.id;
export const selectEditPartnerCompanyName = (state: RootState) =>
state.partner.apps.editPartner.companyName;
export const selectEditPartnerCountry = (state: RootState) =>
state.partner.apps.editPartner.country;
export const selectEditPartnerUsers = (state: RootState) =>
state.partner.apps.editPartner.users;
export const selectSelectedAdminId = (state: RootState) =>
state.partner.apps.editPartner.selectedAdminId;

View File

@ -1,6 +1,7 @@
import {
CreatePartnerAccountRequest,
GetPartnersResponse,
PartnerUser,
} from "../../api/api";
export interface PartnerState {
@ -19,4 +20,11 @@ export interface Apps {
isLoading: boolean;
delegatedAccountId?: number;
delegatedCompanyName?: string;
editPartner: {
users: PartnerUser[];
id: number;
companyName: string;
country: string;
selectedAdminId: number;
};
}

View File

@ -1,9 +1,5 @@
import { CSVType } from "common/parser";
import {
User,
AllocatableLicenseInfo,
MultipleImportUser,
} from "../../api/api";
import { User, AllocatableLicenseInfo } from "../../api/api";
import { AddUser, UpdateUser, LicenseAllocateUser } from "./types";
export interface UsersState {

View File

@ -54,14 +54,14 @@ export interface LicenseAllocateUser {
remaining?: number;
}
export type RoleType = typeof USER_ROLES[keyof typeof USER_ROLES];
export type RoleType = (typeof USER_ROLES)[keyof typeof USER_ROLES];
// 受け取った値がUSER_ROLESの型であるかどうかを判定する
export const isRoleType = (role: string): role is RoleType =>
Object.values(USER_ROLES).includes(role as RoleType);
export type LicenseStatusType =
typeof LICENSE_STATUS[keyof typeof LICENSE_STATUS];
(typeof LICENSE_STATUS)[keyof typeof LICENSE_STATUS];
// 受け取った値がLicenseStatusTypeの型であるかどうかを判定する
export const isLicenseStatusType = (

View File

@ -2,7 +2,7 @@ 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];
(typeof OPTION_ITEMS_DEFAULT_VALUE_TYPE)[keyof typeof OPTION_ITEMS_DEFAULT_VALUE_TYPE];
// 受け取った値がOptionItemDefaultValueType型かどうかを判定する
export const isOptionItemDefaultValueType = (

View File

@ -110,6 +110,7 @@ export const AddPartnerAccountPopup: React.FC<AddPartnerAccountPopup> = (
country,
adminName,
email,
offset,
]);
return (

View File

@ -0,0 +1,178 @@
import { AppDispatch } from "app/store";
import React, { useCallback, useEffect } from "react";
import styles from "styles/app.module.scss";
import { useDispatch, useSelector } from "react-redux";
import { getTranslationID } from "translation";
import { useTranslation } from "react-i18next";
import {
changeEditCompanyName,
changeSelectedAdminId,
cleanupPartnerAccount,
getPartnerUsersAsync,
editPartnerInfoAsync,
selectEditPartnerCompanyName,
selectEditPartnerCountry,
selectEditPartnerId,
selectEditPartnerUsers,
selectIsLoading,
selectSelectedAdminId,
selectOffset,
getPartnerInfoAsync,
LIMIT_PARTNER_VIEW_NUM,
} from "features/partner";
import close from "../../assets/images/close.svg";
import progress_activit from "../../assets/images/progress_activit.svg";
import { COUNTRY_LIST } from "../SignupPage/constants";
interface EditPartnerAccountPopup {
isOpen: boolean;
onClose: () => void;
}
export const EditPartnerAccountPopup: React.FC<EditPartnerAccountPopup> = (
props
) => {
const { isOpen, onClose } = props;
const dispatch: AppDispatch = useDispatch();
const { t } = useTranslation();
const isLoading = useSelector(selectIsLoading);
const offset = useSelector(selectOffset);
const partnerId = useSelector(selectEditPartnerId);
const companyName = useSelector(selectEditPartnerCompanyName);
const country = useSelector(selectEditPartnerCountry);
const users = useSelector(selectEditPartnerUsers);
const adminUser = users.find((user) => user.isPrimaryAdmin);
const selectedAdminId = useSelector(selectSelectedAdminId);
// ポップアップを閉じる処理
const closePopup = useCallback(() => {
if (isLoading) {
return;
}
dispatch(cleanupPartnerAccount());
onClose();
}, [isLoading, onClose, dispatch]);
useEffect(() => {
if (isOpen) {
dispatch(getPartnerUsersAsync({ accountId: partnerId }));
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [isOpen]);
const onEditPartner = useCallback(async () => {
// eslint-disable-next-line no-alert
if (!window.confirm(t(getTranslationID("common.message.dialogConfirm")))) {
return;
}
const { meta } = await dispatch(editPartnerInfoAsync());
if (meta.requestStatus === "fulfilled") {
dispatch(
getPartnerInfoAsync({
limit: LIMIT_PARTNER_VIEW_NUM,
offset,
})
);
closePopup();
}
}, [dispatch, closePopup, t, offset]);
return (
<div className={`${styles.modal} ${isOpen ? styles.isShow : ""}`}>
<div className={styles.modalBox}>
<p className={styles.modalTitle}>
{t(getTranslationID("partnerPage.label.editAccount"))}
<button type="button" onClick={closePopup}>
<img src={close} className={styles.modalTitleIcon} alt="close" />
</button>
</p>
<form className={styles.form}>
<dl className={`${styles.formList} ${styles.hasbg}`}>
<dt className={styles.formTitle}>
{t(getTranslationID("partnerPage.label.accountInformation"))}
</dt>
<dt>{t(getTranslationID("partnerPage.label.name"))}</dt>
<dd>
<input
type="text"
size={40}
maxLength={255}
value={companyName}
className={styles.formInput}
onChange={(e) => {
dispatch(
changeEditCompanyName({ companyName: e.target.value })
);
}}
/>
</dd>
<dt>{t(getTranslationID("partnerPage.label.country"))}</dt>
<dd className={styles.last}>
<input
type="text"
size={40}
value={COUNTRY_LIST.find((c) => c.value === country)?.label}
className={styles.formInput}
readOnly
/>
</dd>
<dt className={styles.formTitle}>
{t(getTranslationID("partnerPage.label.primaryAdminInfo"))}
</dt>
<dt>{t(getTranslationID("partnerPage.label.adminName"))}</dt>
<dd>
<input
type="text"
size={40}
value={adminUser?.name}
className={styles.formInput}
readOnly
/>
</dd>
<dt>{t(getTranslationID("partnerPage.label.email"))}</dt>
<dd className={styles.last}>
<select
className={styles.formInput}
onChange={(event) => {
dispatch(
changeSelectedAdminId({
adminId: Number(event.target.value),
})
);
}}
value={selectedAdminId}
>
{users.map((user) => (
<option key={user.id} value={user.id}>
{user.email}
</option>
))}
</select>
</dd>
<dd className={`${styles.full} ${styles.alignCenter}`}>
<input
type="button"
name="submit"
value={t(getTranslationID("partnerPage.label.saveChanges"))}
className={`${styles.formSubmit} ${styles.marginBtm1} ${
!isLoading && companyName.length !== 0 ? styles.isActive : ""
}`}
onClick={onEditPartner}
/>
<img
style={{ display: isLoading ? "inline" : "none" }}
src={progress_activit}
className={styles.icLoading}
alt="Loading"
/>
</dd>
</dl>
</form>
</div>
</div>
);
};

View File

@ -20,20 +20,24 @@ import {
} from "features/partner/index";
import {
changeDelegateAccount,
changeEditPartner,
savePageInfo,
} from "features/partner/partnerSlice";
import { getTranslationID } from "translation";
import { useTranslation } from "react-i18next";
import { getDelegationTokenAsync } from "features/auth/operations";
import { useNavigate } from "react-router-dom";
import { Partner } from "api";
import personAdd from "../../assets/images/person_add.svg";
import { TIERS } from "../../components/auth/constants";
import { AddPartnerAccountPopup } from "./addPartnerAccountPopup";
import { EditPartnerAccountPopup } from "./editPartnerAccountPopup";
import checkFill from "../../assets/images/check_fill.svg";
const PartnerPage: React.FC = (): JSX.Element => {
const dispatch: AppDispatch = useDispatch();
const [isPopupOpen, setIsPopupOpen] = useState(false);
const [isEditPopupOpen, setIsEditPopupOpen] = useState(false);
const [t] = useTranslation();
const navigate = useNavigate();
const total = useSelector(selectTotal);
@ -72,6 +76,19 @@ const PartnerPage: React.FC = (): JSX.Element => {
const onOpen = useCallback(() => {
setIsPopupOpen(true);
}, [setIsPopupOpen]);
const onOpenEditPopup = useCallback(
(editPartner: Partner) => {
dispatch(
changeEditPartner({
id: editPartner.accountId,
companyName: editPartner.name,
country: editPartner.country,
})
);
setIsEditPopupOpen(true);
},
[setIsEditPopupOpen, dispatch]
);
// パートナー取得APIを呼び出す
useEffect(() => {
@ -144,6 +161,12 @@ const PartnerPage: React.FC = (): JSX.Element => {
setIsPopupOpen(false);
}}
/>
<EditPartnerAccountPopup
isOpen={isEditPopupOpen}
onClose={() => {
setIsEditPopupOpen(false);
}}
/>
<div className={styles.wrap}>
<Header />
<UpdateTokenTimer />
@ -211,6 +234,22 @@ const PartnerPage: React.FC = (): JSX.Element => {
<tr>
<td className={styles.clm0}>
<ul className={styles.menuInTable}>
{isVisibleButton && (
<li>
{/* eslint-disable-next-line jsx-a11y/click-events-have-key-events,jsx-a11y/no-static-element-interactions */}
<a
onClick={() => {
onOpenEditPopup(x);
}}
>
{t(
getTranslationID(
"partnerPage.label.editAccount"
)
)}
</a>
</li>
)}
{isVisibleButton && (
<li>
{/* eslint-disable-next-line jsx-a11y/click-events-have-key-events,jsx-a11y/no-static-element-interactions */}

View File

@ -532,14 +532,20 @@
"email": "Email",
"dealerManagement": "Erlauben Sie dem Händler, Änderungen vorzunehmen",
"partners": "Partner",
"deleteAccount": "Konto löschen"
"deleteAccount": "Konto löschen",
"editAccount": "(de)Edit Account",
"accountInformation": "(de)Account information",
"primaryAdminInfo": "(de)Primary administrator's information",
"adminName": "(de)Admin Name",
"saveChanges": "(de)Save Changes"
},
"message": {
"delegateNotAllowedError": "Aktionen im Namen des Partners sind nicht zulässig. Bitte aktualisieren Sie den Bildschirm und überprüfen Sie ihn erneut.",
"deleteFailedError": "Der Delegierungsvorgang ist fehlgeschlagen. Bitte aktualisieren Sie den Bildschirm und überprüfen Sie ihn erneut.",
"delegateCancelError": "Der delegierte Vorgang wurde beendet, da die Berechtigung für den delegierten Vorgang widerrufen wurde.",
"partnerDeleteConfirm": "(de)選択したアカウントを削除します。削除したアカウントは復元できませんが本当によろしいですか?対象アカウント:",
"partnerDeleteFailedError": "(de)削除対象アカウントにLower layerアカウントが存在するため削除できません。Lower layerアカウントに対して削除、または現地法人以上のアカウントに階層構造の変更を依頼してください。"
"partnerDeleteFailedError": "(de)削除対象アカウントにLower layerアカウントが存在するため削除できません。Lower layerアカウントに対して削除、または現地法人以上のアカウントに階層構造の変更を依頼してください。",
"editFailedError": "(de)パートナーアカウントの編集に失敗しました。画面を更新し、再度ご確認ください。"
}
},
"accountPage": {
@ -638,4 +644,4 @@
"lowerLayerId": "(de)Lower Layer ID"
}
}
}
}

View File

@ -532,14 +532,20 @@
"email": "Email",
"dealerManagement": "Dealer Management",
"partners": "Partners",
"deleteAccount": "Delete Account"
"deleteAccount": "Delete Account",
"editAccount": "Edit Account",
"accountInformation": "Account information",
"primaryAdminInfo": "Primary administrator's information",
"adminName": "Admin Name",
"saveChanges": "Save Changes"
},
"message": {
"delegateNotAllowedError": "Actions on behalf of partner are not allowed. Please refresh the screen and check again.",
"deleteFailedError": "Delegate operation failed. Please refresh the screen and check again.",
"delegateCancelError": "The delegated operation has been terminated because permission for the delegated operation has been revoked.",
"partnerDeleteConfirm": "(en)選択したアカウントを削除します。削除したアカウントは復元できませんが本当によろしいですか?対象アカウント:",
"partnerDeleteFailedError": "(en)削除対象アカウントにLower layerアカウントが存在するため削除できません。Lower layerアカウントに対して削除、または現地法人以上のアカウントに階層構造の変更を依頼してください。"
"partnerDeleteConfirm": "選択したアカウントを削除します。削除したアカウントは復元できませんが本当によろしいですか?対象アカウント:",
"partnerDeleteFailedError": "削除対象アカウントにLower layerアカウントが存在するため削除できません。Lower layerアカウントに対して削除、または現地法人以上のアカウントに階層構造の変更を依頼してください。",
"editFailedError": "パートナーアカウントの編集に失敗しました。画面を更新し、再度ご確認ください。"
}
},
"accountPage": {
@ -638,4 +644,4 @@
"lowerLayerId": "Lower Layer ID"
}
}
}
}

View File

@ -532,14 +532,20 @@
"email": "Email",
"dealerManagement": "Permitir que el distribuidor realice los cambios",
"partners": "Socios",
"deleteAccount": "Borrar cuenta"
"deleteAccount": "Borrar cuenta",
"editAccount": "(es)Edit Account",
"accountInformation": "(es)Account information",
"primaryAdminInfo": "(es)Primary administrator's information",
"adminName": "(es)Admin Name",
"saveChanges": "(es)Save Changes"
},
"message": {
"delegateNotAllowedError": "No se permiten acciones en nombre del socio. Actualice la pantalla y verifique nuevamente.",
"deleteFailedError": "La operación del delegado falló. Actualice la pantalla y verifique nuevamente.",
"delegateCancelError": "La operación delegada finalizó porque se revocó el permiso para la operación delegada.",
"partnerDeleteConfirm": "(es)選択したアカウントを削除します。削除したアカウントは復元できませんが本当によろしいですか?対象アカウント:",
"partnerDeleteFailedError": "(es削除対象アカウントにLower layerアカウントが存在するため削除できません。Lower layerアカウントに対して削除、または現地法人以上のアカウントに階層構造の変更を依頼してください。"
"partnerDeleteFailedError": "(es)削除対象アカウントにLower layerアカウントが存在するため削除できません。Lower layerアカウントに対して削除、または現地法人以上のアカウントに階層構造の変更を依頼してください。",
"editFailedError": "(es)パートナーアカウントの編集に失敗しました。画面を更新し、再度ご確認ください。"
}
},
"accountPage": {
@ -638,4 +644,4 @@
"lowerLayerId": "(es)Lower Layer ID"
}
}
}
}

View File

@ -532,14 +532,20 @@
"email": "Email",
"dealerManagement": "Autoriser le revendeur à modifier les paramètres",
"partners": "Partenaires",
"deleteAccount": "Supprimer le compte"
"deleteAccount": "Supprimer le compte",
"editAccount": "(fr)Edit Account",
"accountInformation": "(fr)Account information",
"primaryAdminInfo": "(fr)Primary administrator's information",
"adminName": "(fr)Admin Name",
"saveChanges": "(fr)Save Changes"
},
"message": {
"delegateNotAllowedError": "Les actions au nom du partenaire ne sont pas autorisées. Veuillez actualiser l'écran et vérifier à nouveau.",
"deleteFailedError": "Lopération de délégation a échoué. Veuillez actualiser l'écran et vérifier à nouveau.",
"delegateCancelError": "L'opération déléguée a été interrompue car l'autorisation pour l'opération déléguée a été révoquée.",
"partnerDeleteConfirm": "(fr)選択したアカウントを削除します。削除したアカウントは復元できませんが本当によろしいですか?対象アカウント:",
"partnerDeleteFailedError": "(fr)削除対象アカウントにLower layerアカウントが存在するため削除できません。Lower layerアカウントに対して削除、または現地法人以上のアカウントに階層構造の変更を依頼してください。"
"partnerDeleteFailedError": "(fr)削除対象アカウントにLower layerアカウントが存在するため削除できません。Lower layerアカウントに対して削除、または現地法人以上のアカウントに階層構造の変更を依頼してください。",
"editFailedError": "(fr)パートナーアカウントの編集に失敗しました。画面を更新し、再度ご確認ください。"
}
},
"accountPage": {
@ -638,4 +644,4 @@
"lowerLayerId": "(fr)Lower Layer ID"
}
}
}
}

View File

@ -2559,9 +2559,13 @@ export class AccountsController {
const context = makeContext(userId, requestId);
this.logger.log(`[${context.getTrackingId()}] ip : ${ip}`);
await this.accountService.getPartnerUsers(context, userId, targetAccountId);
const users = await this.accountService.getPartnerUsers(
context,
userId,
targetAccountId,
);
return { users: [] };
return { users };
}
@Post('partner/update')