Merged PR 764: 第五階層ライセンス情報画面実装
## 概要 [Task3709: 第五階層ライセンス情報画面実装](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/3709) - ストレージ使用可否切り替えの画面実装をしました - 動作確認中に、既存実装でライセンスオーダーするときとカードライセンスアクティベートするときの操作不能化処理に漏れがあったのを修正しました ## レビューポイント - Redux周りの実装でお作法に違反しているところがないか。もしくは改善点ないか。 - ライセンス情報表示のAPI結果待ち部分のローディング処理で、最低限の改善にしたが現時点ではこれでよいか?(いつ修正するかも未定だけど、実害はないためひとまずこんな感じで。。。) - `licenseSummarySlice.ts` のコメント部分が該当箇所です ## UIの変更 - 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/Task3709?csf=1&web=1&e=bJVzss ## 動作確認状況 - ローカルで動作確認しました。
This commit is contained in:
parent
c95fb1e1f6
commit
5305984b1a
@ -2121,6 +2121,25 @@ export interface UpdateOptionItemsRequest {
|
||||
*/
|
||||
'optionItems': Array<PostWorktypeOptionItem>;
|
||||
}
|
||||
/**
|
||||
*
|
||||
* @export
|
||||
* @interface UpdateRestrictionStatusRequest
|
||||
*/
|
||||
export interface UpdateRestrictionStatusRequest {
|
||||
/**
|
||||
* 操作対象の第五階層アカウントID
|
||||
* @type {number}
|
||||
* @memberof UpdateRestrictionStatusRequest
|
||||
*/
|
||||
'accountId': number;
|
||||
/**
|
||||
* 制限をかけるかどうか(trur:制限をかける)
|
||||
* @type {boolean}
|
||||
* @memberof UpdateRestrictionStatusRequest
|
||||
*/
|
||||
'restricted': boolean;
|
||||
}
|
||||
/**
|
||||
*
|
||||
* @export
|
||||
@ -3443,6 +3462,46 @@ export const AccountsApiAxiosParamCreator = function (configuration?: Configurat
|
||||
options: localVarRequestOptions,
|
||||
};
|
||||
},
|
||||
/**
|
||||
*
|
||||
* @summary
|
||||
* @param {UpdateRestrictionStatusRequest} updateRestrictionStatusRequest
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
updateRestrictionStatus: async (updateRestrictionStatusRequest: UpdateRestrictionStatusRequest, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
|
||||
// verify required parameter 'updateRestrictionStatusRequest' is not null or undefined
|
||||
assertParamExists('updateRestrictionStatus', 'updateRestrictionStatusRequest', updateRestrictionStatusRequest)
|
||||
const localVarPath = `/accounts/restriction-status`;
|
||||
// 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(updateRestrictionStatusRequest, localVarRequestOptions, configuration)
|
||||
|
||||
return {
|
||||
url: toPathString(localVarUrlObj),
|
||||
options: localVarRequestOptions,
|
||||
};
|
||||
},
|
||||
/**
|
||||
* ログインしているユーザーのアカウント配下でIDで指定されたタイピストグループを更新します
|
||||
* @summary
|
||||
@ -3888,6 +3947,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 {UpdateRestrictionStatusRequest} updateRestrictionStatusRequest
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
async updateRestrictionStatus(updateRestrictionStatusRequest: UpdateRestrictionStatusRequest, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<object>> {
|
||||
const localVarAxiosArgs = await localVarAxiosParamCreator.updateRestrictionStatus(updateRestrictionStatusRequest, options);
|
||||
const index = configuration?.serverIndex ?? 0;
|
||||
const operationBasePath = operationServerMap['AccountsApi.updateRestrictionStatus']?.[index]?.url;
|
||||
return (axios, basePath) => createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration)(axios, operationBasePath || basePath);
|
||||
},
|
||||
/**
|
||||
* ログインしているユーザーのアカウント配下でIDで指定されたタイピストグループを更新します
|
||||
* @summary
|
||||
@ -4192,6 +4264,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 {UpdateRestrictionStatusRequest} updateRestrictionStatusRequest
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
updateRestrictionStatus(updateRestrictionStatusRequest: UpdateRestrictionStatusRequest, options?: any): AxiosPromise<object> {
|
||||
return localVarFp.updateRestrictionStatus(updateRestrictionStatusRequest, options).then((request) => request(axios, basePath));
|
||||
},
|
||||
/**
|
||||
* ログインしているユーザーのアカウント配下でIDで指定されたタイピストグループを更新します
|
||||
* @summary
|
||||
@ -4544,6 +4626,18 @@ export class AccountsApi extends BaseAPI {
|
||||
return AccountsApiFp(this.configuration).updateOptionItems(id, updateOptionItemsRequest, options).then((request) => request(this.axios, this.basePath));
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @summary
|
||||
* @param {UpdateRestrictionStatusRequest} updateRestrictionStatusRequest
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
* @memberof AccountsApi
|
||||
*/
|
||||
public updateRestrictionStatus(updateRestrictionStatusRequest: UpdateRestrictionStatusRequest, options?: AxiosRequestConfig) {
|
||||
return AccountsApiFp(this.configuration).updateRestrictionStatus(updateRestrictionStatusRequest, options).then((request) => request(this.axios, this.basePath));
|
||||
}
|
||||
|
||||
/**
|
||||
* ログインしているユーザーのアカウント配下でIDで指定されたタイピストグループを更新します
|
||||
* @summary
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import { createSlice } from "@reduxjs/toolkit";
|
||||
import { LicenseCardActivateState } from "./state";
|
||||
import { activateCardLicenseAsync } from "./operations";
|
||||
|
||||
const initialState: LicenseCardActivateState = {
|
||||
apps: {
|
||||
@ -14,6 +15,17 @@ export const licenseCardActivateSlice = createSlice({
|
||||
state.apps = initialState.apps;
|
||||
},
|
||||
},
|
||||
extraReducers: (builder) => {
|
||||
builder.addCase(activateCardLicenseAsync.pending, (state) => {
|
||||
state.apps.isLoading = true;
|
||||
});
|
||||
builder.addCase(activateCardLicenseAsync.fulfilled, (state) => {
|
||||
state.apps.isLoading = false;
|
||||
});
|
||||
builder.addCase(activateCardLicenseAsync.rejected, (state) => {
|
||||
state.apps.isLoading = false;
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
export const { cleanupApps } = licenseCardActivateSlice.actions;
|
||||
|
||||
@ -1,6 +1,10 @@
|
||||
import { createSlice } from "@reduxjs/toolkit";
|
||||
import { LicenseSummaryState } from "./state";
|
||||
import { getCompanyNameAsync, getLicenseSummaryAsync } from "./operations";
|
||||
import {
|
||||
getCompanyNameAsync,
|
||||
getLicenseSummaryAsync,
|
||||
updateRestrictionStatusAsync,
|
||||
} from "./operations";
|
||||
|
||||
const initialState: LicenseSummaryState = {
|
||||
domain: {
|
||||
@ -35,12 +39,30 @@ export const licenseSummarySlice = createSlice({
|
||||
},
|
||||
},
|
||||
extraReducers: (builder) => {
|
||||
builder.addCase(getLicenseSummaryAsync.pending, (state) => {
|
||||
state.apps.isLoading = true;
|
||||
});
|
||||
builder.addCase(getLicenseSummaryAsync.fulfilled, (state, action) => {
|
||||
state.domain.licenseSummaryInfo = action.payload;
|
||||
state.apps.isLoading = false;
|
||||
});
|
||||
builder.addCase(getLicenseSummaryAsync.rejected, (state) => {
|
||||
state.apps.isLoading = false;
|
||||
});
|
||||
// 画面側ではgetLicenseSummaryAsyncと並行して呼び出されているため、レーシングを考慮してこちらではisLoadingを更新しない
|
||||
// 本来は両方の完了を待ってからisLoadingを更新するべきだが、現時点ではスピード重視のためケアしない。
|
||||
builder.addCase(getCompanyNameAsync.fulfilled, (state, action) => {
|
||||
state.domain.accountInfo.companyName = action.payload.companyName;
|
||||
});
|
||||
builder.addCase(updateRestrictionStatusAsync.pending, (state) => {
|
||||
state.apps.isLoading = true;
|
||||
});
|
||||
builder.addCase(updateRestrictionStatusAsync.fulfilled, (state) => {
|
||||
state.apps.isLoading = false;
|
||||
});
|
||||
builder.addCase(updateRestrictionStatusAsync.rejected, (state) => {
|
||||
state.apps.isLoading = false;
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@ -8,6 +8,7 @@ import {
|
||||
GetCompanyNameResponse,
|
||||
GetLicenseSummaryResponse,
|
||||
PartnerLicenseInfo,
|
||||
UpdateRestrictionStatusRequest,
|
||||
} from "../../../api/api";
|
||||
import { Configuration } from "../../../api/configuration";
|
||||
import { ErrorObject, createErrorObject } from "../../../common/errors";
|
||||
@ -123,3 +124,58 @@ export const getCompanyNameAsync = createAsyncThunk<
|
||||
return thunkApi.rejectWithValue({ error });
|
||||
}
|
||||
});
|
||||
|
||||
export const updateRestrictionStatusAsync = createAsyncThunk<
|
||||
{
|
||||
/* Empty Object */
|
||||
},
|
||||
{
|
||||
accountId: number;
|
||||
restricted: boolean;
|
||||
},
|
||||
{
|
||||
// rejectした時の返却値の型
|
||||
rejectValue: {
|
||||
error: ErrorObject;
|
||||
};
|
||||
}
|
||||
>("accounts/updateRestrictionStatusAsync", 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 requestParam: UpdateRestrictionStatusRequest = {
|
||||
accountId: args.accountId,
|
||||
restricted: args.restricted,
|
||||
};
|
||||
|
||||
try {
|
||||
await accountApi.updateRestrictionStatus(requestParam, {
|
||||
headers: { authorization: `Bearer ${accessToken}` },
|
||||
});
|
||||
thunkApi.dispatch(
|
||||
openSnackbar({
|
||||
level: "info",
|
||||
message: getTranslationID("common.message.success"),
|
||||
})
|
||||
);
|
||||
return {};
|
||||
} catch (e) {
|
||||
const error = createErrorObject(e);
|
||||
|
||||
// このAPIでは個別のエラーメッセージは不要
|
||||
const errorMessage = getTranslationID("common.message.internalServerError");
|
||||
thunkApi.dispatch(
|
||||
openSnackbar({
|
||||
level: "error",
|
||||
message: errorMessage,
|
||||
})
|
||||
);
|
||||
|
||||
return thunkApi.rejectWithValue({ error });
|
||||
}
|
||||
});
|
||||
|
||||
@ -1,10 +1,11 @@
|
||||
import { RootState } from "app/store";
|
||||
|
||||
// 各値はそのまま画面に表示するので、licenseSummaryInfoとして値を取得する
|
||||
export const selecLicenseSummaryInfo = (state: RootState) =>
|
||||
export const selectLicenseSummaryInfo = (state: RootState) =>
|
||||
state.licenseSummary.domain.licenseSummaryInfo;
|
||||
|
||||
export const selectCompanyName = (state: RootState) =>
|
||||
state.licenseSummary.domain.accountInfo.companyName;
|
||||
|
||||
export const selectIsLoading = (state: RootState) => state.license;
|
||||
export const selectIsLoading = (state: RootState) =>
|
||||
state.licenseSummary.apps.isLoading;
|
||||
|
||||
@ -41,9 +41,10 @@ export const LicenseOrderPopup: React.FC<LicenseOrderPopupProps> = (props) => {
|
||||
|
||||
// ポップアップを閉じる処理
|
||||
const closePopup = useCallback(() => {
|
||||
if (isLoading) return;
|
||||
setIsPushOrderButton(false);
|
||||
onClose();
|
||||
}, [onClose]);
|
||||
}, [isLoading, onClose]);
|
||||
|
||||
// 画面からのパラメータ
|
||||
const poNumber = useSelector(selectPoNumber);
|
||||
|
||||
@ -10,12 +10,16 @@ import { useDispatch, useSelector } from "react-redux";
|
||||
import {
|
||||
getCompanyNameAsync,
|
||||
getLicenseSummaryAsync,
|
||||
selecLicenseSummaryInfo,
|
||||
selectLicenseSummaryInfo,
|
||||
selectCompanyName,
|
||||
selectIsLoading,
|
||||
updateRestrictionStatusAsync,
|
||||
} from "features/license/licenseSummary";
|
||||
import { selectSelectedRow } from "features/license/partnerLicense";
|
||||
import { selectDelegationAccessToken } from "features/auth/selectors";
|
||||
import { DelegationBar } from "components/delegate";
|
||||
import { TIERS } from "components/auth/constants";
|
||||
import { isAdminUser, isApproveTier } from "features/auth/utils";
|
||||
import postAdd from "../../assets/images/post_add.svg";
|
||||
import history from "../../assets/images/history.svg";
|
||||
import key from "../../assets/images/key.svg";
|
||||
@ -40,6 +44,8 @@ export const LicenseSummary: React.FC<LicenseSummaryProps> = (
|
||||
// 代行操作用のトークンを取得する
|
||||
const delegationAccessToken = useSelector(selectDelegationAccessToken);
|
||||
|
||||
const isLoading = useSelector(selectIsLoading);
|
||||
|
||||
// popup制御関係
|
||||
const [islicenseOrderPopupOpen, setIslicenseOrderPopupOpen] = useState(false);
|
||||
const [isCardLicenseActivatePopupOpen, setIsCardLicenseActivatePopupOpen] =
|
||||
@ -62,9 +68,12 @@ export const LicenseSummary: React.FC<LicenseSummaryProps> = (
|
||||
}, [setIsLicenseOrderHistoryOpen]);
|
||||
|
||||
// apiからの値取得関係
|
||||
const licenseSummaryInfo = useSelector(selecLicenseSummaryInfo);
|
||||
const licenseSummaryInfo = useSelector(selectLicenseSummaryInfo);
|
||||
const companyName = useSelector(selectCompanyName);
|
||||
|
||||
const isTier1 = isApproveTier([TIERS.TIER1]);
|
||||
const isAdmin = isAdminUser();
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(getLicenseSummaryAsync({ selectedRow }));
|
||||
dispatch(getCompanyNameAsync({ selectedRow }));
|
||||
@ -78,6 +87,35 @@ export const LicenseSummary: React.FC<LicenseSummaryProps> = (
|
||||
}
|
||||
}, [onReturn]);
|
||||
|
||||
const onStorageAvailableChange = useCallback(
|
||||
async (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
if (
|
||||
/* eslint-disable-next-line no-alert */
|
||||
!window.confirm(
|
||||
t(
|
||||
getTranslationID(
|
||||
"LicenseSummaryPage.message.storageUnavalableSwitchingConfirm"
|
||||
)
|
||||
)
|
||||
)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
const restricted = e.target.checked;
|
||||
const accountId = selectedRow?.accountId;
|
||||
// 本関数が実行されるときはselectedRowが存在する前提のため、accountIdが存在しない場合の処理は不要
|
||||
if (!accountId) return;
|
||||
const { meta } = await dispatch(
|
||||
updateRestrictionStatusAsync({ accountId, restricted })
|
||||
);
|
||||
if (meta.requestStatus === "fulfilled") {
|
||||
dispatch(getLicenseSummaryAsync({ selectedRow }));
|
||||
}
|
||||
},
|
||||
[dispatch, selectedRow, t]
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* isPopupOpenがfalseの場合はポップアップのhtmlを生成しないように対応。これによりポップアップは都度生成されて初期化の考慮が減る */}
|
||||
@ -272,6 +310,27 @@ export const LicenseSummary: React.FC<LicenseSummaryProps> = (
|
||||
</dl>
|
||||
</div>
|
||||
<div>
|
||||
{isTier1 && isAdmin && (
|
||||
<p
|
||||
className={`${styles.checkAvail} ${styles.alignRight}`}
|
||||
>
|
||||
{/* eslint-disable-next-line jsx-a11y/label-has-associated-control */}
|
||||
<label>
|
||||
<input
|
||||
type="checkbox"
|
||||
className={styles.formCheck}
|
||||
checked={licenseSummaryInfo.isStorageAvailable}
|
||||
disabled={isLoading}
|
||||
onChange={onStorageAvailableChange}
|
||||
/>
|
||||
{t(
|
||||
getTranslationID(
|
||||
"LicenseSummaryPage.label.storageUnavailableCheckbox"
|
||||
)
|
||||
)}
|
||||
</label>
|
||||
</p>
|
||||
)}
|
||||
<dl
|
||||
className={`${styles.listVertical} ${styles.marginBtm3}`}
|
||||
>
|
||||
|
||||
@ -1857,6 +1857,18 @@ tr.isSelected .menuInTable li a.isDisable {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.license .checkAvail {
|
||||
height: 30px;
|
||||
padding: 0 0.3rem 0.3rem 0;
|
||||
margin-top: -30px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.license .checkAvail label {
|
||||
cursor: pointer;
|
||||
}
|
||||
.license .checkAvail label .formCheck {
|
||||
vertical-align: middle;
|
||||
}
|
||||
.license .listVertical dd img[src*="circle"] {
|
||||
filter: brightness(0) saturate(100%) invert(58%) sepia(41%) saturate(5814%)
|
||||
hue-rotate(143deg) brightness(96%) contrast(101%);
|
||||
|
||||
@ -123,6 +123,7 @@ declare const classNames: {
|
||||
readonly txNormal: "txNormal";
|
||||
readonly manageIcon: "manageIcon";
|
||||
readonly manageIconClose: "manageIconClose";
|
||||
readonly checkAvail: "checkAvail";
|
||||
readonly history: "history";
|
||||
readonly cardHistory: "cardHistory";
|
||||
readonly partner: "partner";
|
||||
|
||||
@ -188,7 +188,11 @@
|
||||
"usedSize": "Gebrauchter Lagerung",
|
||||
"storageAvailable": "Speicher nicht verfügbar (Menge überschritten)",
|
||||
"licenseLabel": "Lizenz",
|
||||
"storageLabel": "Lagerung"
|
||||
"storageLabel": "Lagerung",
|
||||
"storageUnavailableCheckbox": "(de)Storage Unavailable"
|
||||
},
|
||||
"message": {
|
||||
"storageUnavalableSwitchingConfirm": "(de)対象アカウントのストレージ使用制限状態を変更します。よろしいですか?"
|
||||
}
|
||||
},
|
||||
"licenseOrderPage": {
|
||||
|
||||
@ -188,7 +188,11 @@
|
||||
"usedSize": "Storage Used",
|
||||
"storageAvailable": "Storage Unavailable (Exceeded Amount)",
|
||||
"licenseLabel": "License",
|
||||
"storageLabel": "Storage"
|
||||
"storageLabel": "Storage",
|
||||
"storageUnavailableCheckbox": "Storage Unavailable"
|
||||
},
|
||||
"message": {
|
||||
"storageUnavalableSwitchingConfirm": "対象アカウントのストレージ使用制限状態を変更します。よろしいですか?"
|
||||
}
|
||||
},
|
||||
"licenseOrderPage": {
|
||||
|
||||
@ -188,7 +188,11 @@
|
||||
"usedSize": "Almacenamiento utilizado",
|
||||
"storageAvailable": "Almacenamiento no disponible (cantidad excedida)",
|
||||
"licenseLabel": "Licencia",
|
||||
"storageLabel": "Almacenamiento"
|
||||
"storageLabel": "Almacenamiento",
|
||||
"storageUnavailableCheckbox": "(es)Storage Unavailable"
|
||||
},
|
||||
"message": {
|
||||
"storageUnavalableSwitchingConfirm": "(es)対象アカウントのストレージ使用制限状態を変更します。よろしいですか?"
|
||||
}
|
||||
},
|
||||
"licenseOrderPage": {
|
||||
|
||||
@ -188,7 +188,11 @@
|
||||
"usedSize": "Stockage utilisé",
|
||||
"storageAvailable": "Stockage indisponible (montant dépassée)",
|
||||
"licenseLabel": "Licence",
|
||||
"storageLabel": "Stockage"
|
||||
"storageLabel": "Stockage",
|
||||
"storageUnavailableCheckbox": "(fr)Storage Unavailable"
|
||||
},
|
||||
"message": {
|
||||
"storageUnavalableSwitchingConfirm": "(fr)対象アカウントのストレージ使用制限状態を変更します。よろしいですか?"
|
||||
}
|
||||
},
|
||||
"licenseOrderPage": {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user