Kentaro Fukunaga 5305984b1a 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

## 動作確認状況
- ローカルで動作確認しました。
2024-02-27 00:01:02 +00:00

417 lines
16 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import React, { useCallback, useEffect, useState } from "react";
import { UpdateTokenTimer } from "components/auth/updateTokenTimer";
import Footer from "components/footer";
import Header from "components/header";
import styles from "styles/app.module.scss";
import { getTranslationID } from "translation";
import { useTranslation } from "react-i18next";
import { AppDispatch } from "app/store";
import { useDispatch, useSelector } from "react-redux";
import {
getCompanyNameAsync,
getLicenseSummaryAsync,
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";
import block from "../../assets/images/block.svg";
import circle from "../../assets/images/circle.svg";
import returnLabel from "../../assets/images/undo.svg";
import { LicenseOrderPopup } from "./licenseOrderPopup";
import { CardLicenseActivatePopup } from "./cardLicenseActivatePopup";
// eslint-disable-next-line import/no-named-as-default
import LicenseOrderHistory from "./licenseOrderHistory";
interface LicenseSummaryProps {
onReturn?: () => void;
}
export const LicenseSummary: React.FC<LicenseSummaryProps> = (
props
): JSX.Element => {
const { onReturn } = props;
const dispatch: AppDispatch = useDispatch();
const [t] = useTranslation();
const selectedRow = useSelector(selectSelectedRow);
// 代行操作用のトークンを取得する
const delegationAccessToken = useSelector(selectDelegationAccessToken);
const isLoading = useSelector(selectIsLoading);
// popup制御関係
const [islicenseOrderPopupOpen, setIslicenseOrderPopupOpen] = useState(false);
const [isCardLicenseActivatePopupOpen, setIsCardLicenseActivatePopupOpen] =
useState(false);
const onlicenseOrderOpen = useCallback(() => {
setIslicenseOrderPopupOpen(true);
}, [setIslicenseOrderPopupOpen]);
const onCardLicenseActivateOpen = useCallback(() => {
setIsCardLicenseActivatePopupOpen(true);
}, [setIsCardLicenseActivatePopupOpen]);
// 呼び出し画面制御関係
const [islicenseOrderHistoryOpen, setIsLicenseOrderHistoryOpen] =
useState(false);
const onLicenseOrderHistoryOpen = useCallback(() => {
setIsLicenseOrderHistoryOpen(true);
}, [setIsLicenseOrderHistoryOpen]);
// apiからの値取得関係
const licenseSummaryInfo = useSelector(selectLicenseSummaryInfo);
const companyName = useSelector(selectCompanyName);
const isTier1 = isApproveTier([TIERS.TIER1]);
const isAdmin = isAdminUser();
useEffect(() => {
dispatch(getLicenseSummaryAsync({ selectedRow }));
dispatch(getCompanyNameAsync({ selectedRow }));
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [dispatch]);
// Return押下時の処理
const returnClick = useCallback(() => {
if (onReturn) {
onReturn();
}
}, [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を生成しないように対応。これによりポップアップは都度生成されて初期化の考慮が減る */}
{islicenseOrderPopupOpen && (
<LicenseOrderPopup
onClose={() => {
setIslicenseOrderPopupOpen(false);
dispatch(getLicenseSummaryAsync({ selectedRow }));
}}
/>
)}
{isCardLicenseActivatePopupOpen && (
<CardLicenseActivatePopup
onClose={() => {
setIsCardLicenseActivatePopupOpen(false);
}}
/>
)}
{islicenseOrderHistoryOpen && (
<LicenseOrderHistory
onReturn={() => {
setIsLicenseOrderHistoryOpen(false);
}}
/>
)}
{!islicenseOrderHistoryOpen && (
<div
className={`${styles.wrap} ${
delegationAccessToken ? styles.manage : ""
}`}
>
{delegationAccessToken && <DelegationBar />}
<Header />
<UpdateTokenTimer />
<main className={styles.main}>
<div className="">
<div className={styles.pageHeader}>
<h1 className={styles.pageTitle}>
{t(getTranslationID("LicenseSummaryPage.label.title"))}
</h1>
</div>
<section className={styles.license}>
<div className={styles.boxFlex}>
<h2 className="">{companyName}</h2>
<ul className={`${styles.menuAction} ${styles.box100}`}>
<li>
{/* 他アカウントのライセンス情報を見ている場合は、前画面に戻る用のreturnボタンを表示 */}
{selectedRow && (
// eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions
<a
className={`${styles.menuLink} ${styles.isActive}`}
onClick={returnClick}
>
<img
src={returnLabel}
alt=""
className={styles.menuIcon}
/>
{t(getTranslationID("common.label.return"))}
</a>
)}
</li>
<li>
{/* 他アカウントのライセンス情報を見ている場合は、ライセンス注文ボタンを非表示 */}
{!selectedRow && (
// eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions
<a
className={`${styles.menuLink} ${styles.isActive}`}
onClick={onlicenseOrderOpen}
>
<img
src={postAdd}
alt=""
className={styles.menuIcon}
/>
{t(
getTranslationID(
"LicenseSummaryPage.label.orderLicense"
)
)}
</a>
)}
</li>
<li>
{/* eslint-disable-next-line jsx-a11y/click-events-have-key-events,jsx-a11y/no-static-element-interactions */}
<a
className={`${styles.menuLink} ${styles.isActive}`}
onClick={onLicenseOrderHistoryOpen}
>
<img src={history} alt="" className={styles.menuIcon} />
{t(
getTranslationID(
"LicenseSummaryPage.label.orderHistory"
)
)}
</a>
</li>
<li>
{/* 他アカウントのライセンス情報を見ている場合は、カードライセンス取り込みボタンを非表示 */}
{!selectedRow && (
// eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions
<a
className={`${styles.menuLink} ${styles.isActive}`}
onClick={onCardLicenseActivateOpen}
>
<img src={key} alt="" className={styles.menuIcon} />
{t(
getTranslationID(
"LicenseSummaryPage.label.activateLicenseKey"
)
)}
</a>
)}
</li>
</ul>
<div className={styles.marginRgt3}>
<dl
className={`${styles.listVertical} ${styles.marginBtm3}`}
>
<h4 className={styles.listHeader}>
{t(
getTranslationID(
"LicenseSummaryPage.label.licenseLabel"
)
)}
</h4>
<dt>
{t(
getTranslationID(
"LicenseSummaryPage.label.totalLicense"
)
)}
</dt>
<dd>{licenseSummaryInfo.totalLicense}</dd>
<dt>
{t(
getTranslationID(
"LicenseSummaryPage.label.freeLicense"
)
)}
</dt>
<dd>{licenseSummaryInfo.freeLicense}</dd>
<dt>
{t(
getTranslationID(
"LicenseSummaryPage.label.reusableLicense"
)
)}
</dt>
<dd>{licenseSummaryInfo.reusableLicense}</dd>
<dt>
{t(
getTranslationID(
"LicenseSummaryPage.label.allocatedLicense"
)
)}
</dt>
<dd>{licenseSummaryInfo.allocatedLicense}</dd>
<dt>
{t(
getTranslationID(
"LicenseSummaryPage.label.expiringWithin14daysLicense"
)
)}
</dt>
<dd>{licenseSummaryInfo.expiringWithin14daysLicense}</dd>
<dt>
{t(
getTranslationID("LicenseSummaryPage.label.shortage")
)}
</dt>
<dd>
<span
className={
licenseSummaryInfo.shortage > 0
? styles.isAlert
: ""
}
>
{licenseSummaryInfo.shortage}
</span>
</dd>
<dt>
{t(
getTranslationID(
"LicenseSummaryPage.label.issueRequesting"
)
)}
</dt>
<dd>{licenseSummaryInfo.issueRequesting}</dd>
</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}`}
>
<h4 className={styles.listHeader}>
{t(
getTranslationID(
"LicenseSummaryPage.label.storageLabel"
)
)}
</h4>
<dt>
{t(
getTranslationID(
"LicenseSummaryPage.label.storageSize"
)
)}
</dt>
<dd>
{/** Byte単位で受け取った値をGB単位で表示するため1000^3で割っている小数点以下第三位まで表示で第四位で四捨五入 */}
{(
licenseSummaryInfo.storageSize /
1000 /
1000 /
1000
).toFixed(3)}
GB
</dd>
<dt>
{t(
getTranslationID("LicenseSummaryPage.label.usedSize")
)}
</dt>
<dd>
{/** Byte単位で受け取った値をGB単位で表示するため1000^3で割っている小数点以下第三位まで表示で第四位で四捨五入 */}
{(
licenseSummaryInfo.usedSize /
1000 /
1000 /
1000
).toFixed(3)}
GB
</dd>
<dt className={styles.overLine}>
{t(
getTranslationID(
"LicenseSummaryPage.label.storageAvailable"
)
)}
</dt>
<dd>
{licenseSummaryInfo.isStorageAvailable && (
<img
src={block}
alt=""
className={styles.icInTable}
/>
)}
{!licenseSummaryInfo.isStorageAvailable && (
<img
src={circle}
alt=""
className={styles.icInTable}
/>
)}
</dd>
</dl>
</div>
</div>
</section>
</div>
</main>
<Footer />
</div>
)}
</>
);
};
LicenseSummary.defaultProps = {
onReturn: undefined,
};
export default LicenseSummary;