水本 祐希 3af64fbf8e Merged PR 464: データ取得失敗時に各一覧表示画面の表示がそろっていない
## 概要
[Task2720: データ取得失敗時に各一覧表示画面の表示がそろっていない](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/2720)

- 元PBI or タスクへのリンク(内容・目的などはそちらにあるはず)
- 何をどう変更したか、追加したライブラリなど
  - 各一覧表示画面で値0件およびデータ取得エラー時に表示する挙動を統一
 ※Accountに関しては自アカウント情報の表示のため値0件はないとみて、特に本タスクの実装
は必要ないと判断しました。
→対応としてはデータ取得エラー時のスナックバー表示のみ
※Userも自アカウント情報が表示されるため上記と同じ実装にする予定でしたが、スナックバーと0件表示ができました。

![image (4).png](https://dev.azure.com/ODMSCloud/6023ff7b-d41c-4fa7-9c6f-f576ba48c07c/_apis/git/repositories/302da463-a2d7-40f9-b2bb-6e8edf324fa9/pullRequests/464/attachments/image%20%284%29.png)

・〇はそれぞれの検証条件をクリアできているという意味です。
・「データ取得エラーとする」は通常ではAccount、License、Userは値が0件となることはなく、データベースエラーの場合のみと考えたので、値が0件=データ取得エラーとしました。
・「スナックバー表示のみ」はデータ取得エラーが発生した場合、スナックバー表示のみで対応するという意味です。
・「表示できない」はデータベースを切った状態だと、子アカウントのorderhistoryが表示されないため、表示できないと記載しました。

- dictationSlice.tsでbuilder.addCase(getSortColumnAsync.rejected, (state) => {
      state.apps.isLoading = false;
を実装したのは、修正前はデータベースを切った状態だとロードのぐるぐるが消えずにいました。原因はgetSortColumnAsyncにrejectの場合、isLordingをfalseにする実装がなかったためです。
以上のことから上記実装を追加しました。

- このPull Requestでの対象/対象外
- 影響範囲(他の機能にも影響があるか)

## レビューポイント
- 特にレビューしてほしい箇所
- 軽微なものや自明なものは記載不要
- 修正範囲が大きい場合などに記載
- 全体的にや仕様を満たしているか等は本当に必要な時のみ記載

## UIの変更
- Before/Afterのスクショなど
- スクショ置き場
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/Task2720?csf=1&web=1&e=MBNgO8

## 動作確認状況
- ローカルで確認

## 補足
- 相談、参考資料などがあれば
2023-10-10 06:40:15 +00:00

543 lines
20 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, useState, useEffect } from "react";
import Footer from "components/footer";
import Header from "components/header";
import styles from "styles/app.module.scss";
import { UpdateTokenTimer } from "components/auth/updateTokenTimer";
import { useDispatch, useSelector } from "react-redux";
import { AppDispatch } from "app/store";
import { getTranslationID } from "translation";
import { useTranslation } from "react-i18next";
import { PartnerLicenseInfo } from "api";
import { CardLicenseIssuePopup } from "./cardLicenseIssuePopup";
import postAdd from "../../assets/images/post_add.svg";
import history from "../../assets/images/history.svg";
import returnLabel from "../../assets/images/undo.svg";
import { isApproveTier } from "../../features/auth/utils";
import { TIERS } from "../../components/auth/constants";
import {
getPartnerLicenseAsync,
ACCOUNTS_VIEW_LIMIT,
selectMyAccountInfo,
selectTotal,
selectOwnPartnerLicense,
selectChildrenPartnerLicenses,
selectHierarchicalElements,
selectTotalPage,
selectIsLoading,
selectOffset,
selectCurrentPage,
pushHierarchicalElement,
popHierarchicalElement,
spliceHierarchicalElement,
savePageInfo,
getMyAccountAsync,
changeSelectedRow,
} from "../../features/license/partnerLicense";
import { LicenseOrderPopup } from "./licenseOrderPopup";
import { LicenseOrderHistory } from "./licenseOrderHistory";
import { LicenseSummary } from "./licenseSummary";
import progress_activit from "../../assets/images/progress_activit.svg";
const PartnerLicense: React.FC = (): JSX.Element => {
const dispatch: AppDispatch = useDispatch();
const [t] = useTranslation();
// popup制御関係
const [isCardLicenseIssuePopupOpen, setIsCardLicenseIssuePopupOpen] =
useState(false);
const [islicenseOrderPopupOpen, setIslicenseOrderPopupOpen] = useState(false);
const [islicenseOrderHistoryOpen, setIslicenseOrderHistoryOpen] =
useState(false);
const [isViewDetailsOpen, setIsViewDetailsOpen] = useState(false);
// 階層表示用
const tierNames: { [key: number]: string } = {
// eslint-disable-next-line @typescript-eslint/naming-convention
1: t(getTranslationID("common.label.tier1")),
// eslint-disable-next-line @typescript-eslint/naming-convention
2: t(getTranslationID("common.label.tier2")),
// eslint-disable-next-line @typescript-eslint/naming-convention
3: t(getTranslationID("common.label.tier3")),
// eslint-disable-next-line @typescript-eslint/naming-convention
4: t(getTranslationID("common.label.tier4")),
// eslint-disable-next-line @typescript-eslint/naming-convention
5: t(getTranslationID("common.label.tier5")),
};
const onlicenseIssueOpen = useCallback(() => {
setIsCardLicenseIssuePopupOpen(true);
}, [setIsCardLicenseIssuePopupOpen]);
const onlicenseOrderOpen = useCallback(() => {
setIslicenseOrderPopupOpen(true);
}, [setIslicenseOrderPopupOpen]);
// apiからの値取得関係
const myAccountInfo = useSelector(selectMyAccountInfo);
const total = useSelector(selectTotal);
const totalPage = useSelector(selectTotalPage);
const ownPartnerLicenseInfo = useSelector(selectOwnPartnerLicense);
const childrenPartnerLicensesInfo = useSelector(
selectChildrenPartnerLicenses
);
const hierarchicalElements = useSelector(selectHierarchicalElements);
const isLoading = useSelector(selectIsLoading);
// ページネーション制御用
const currentPage = useSelector(selectCurrentPage);
const offset = useSelector(selectOffset);
// ページネーションのボタンクリック時のアクション
const movePage = (targetOffset: number) => {
dispatch(
savePageInfo({ limit: ACCOUNTS_VIEW_LIMIT, offset: targetOffset })
);
};
// パンくずリスト内のアカウント押下時
const onClickBreadCrumbList = (id: number) => {
const clickLevel = hierarchicalElements.findIndex(
(param) => param.accountId === id
);
const nowLevel = hierarchicalElements.length - 1;
const deleteCount = nowLevel - clickLevel;
// 階層を上がった分だけ表示アカウント管理用の配列の最後からパラメータを削除する
if (deleteCount > 0) {
dispatch(spliceHierarchicalElement({ deleteCount }));
movePage(0);
}
};
// 行要素クリック時イベント
const handleRowClick = (value: PartnerLicenseInfo) => {
dispatch(
pushHierarchicalElement({
hierarchicalElement: {
accountId: value.accountId,
companyName: value.companyName,
},
})
);
movePage(0);
};
// returnボタンクリック時イベント
const returnClick = () => {
dispatch(popHierarchicalElement());
movePage(0);
};
// ログイン時に階層をチェック
const isTier1 = isApproveTier([TIERS.TIER1]);
const isTier2ToTier4 = isApproveTier([TIERS.TIER2, TIERS.TIER3, TIERS.TIER4]);
// viewDetailsボタン押下時
const onClickViewDetails = useCallback(
(value?: PartnerLicenseInfo) => {
dispatch(changeSelectedRow({ value }));
setIsViewDetailsOpen(true);
},
[dispatch, setIsViewDetailsOpen]
);
// orderHistoryボタン押下時
const onClickOrderHistory = useCallback(
(value?: PartnerLicenseInfo) => {
dispatch(changeSelectedRow({ value }));
setIslicenseOrderHistoryOpen(true);
},
[dispatch, setIslicenseOrderHistoryOpen]
);
// マウント時のみ実行
useEffect(() => {
dispatch(getMyAccountAsync());
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
// 自アカウントID取得時に実行
useEffect(() => {
if (myAccountInfo.accountId !== 0) {
dispatch(
getPartnerLicenseAsync({
limit: ACCOUNTS_VIEW_LIMIT,
offset,
accountId: myAccountInfo.accountId,
})
);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [myAccountInfo]);
// 現在の表示階層に合わせたボタン制御用
const [buttonLabel, setButtonLabel] = useState("");
// パンくずリスト用stateに自アカウントを追加
useEffect(() => {
if (
hierarchicalElements.length === 0 &&
ownPartnerLicenseInfo.accountId !== 0
) {
dispatch(
pushHierarchicalElement({
hierarchicalElement: {
accountId: ownPartnerLicenseInfo.accountId,
companyName: ownPartnerLicenseInfo.companyName,
},
})
);
}
// 表内のボタン表示判定
if (hierarchicalElements.length === 1 && ownPartnerLicenseInfo.tier !== 4) {
setButtonLabel(
t(getTranslationID("partnerLicense.label.orderHistoryButton"))
);
} else if (ownPartnerLicenseInfo.tier === 4) {
setButtonLabel(t(getTranslationID("partnerLicense.label.viewDetails")));
} else {
setButtonLabel("");
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [ownPartnerLicenseInfo]);
// 表内の情報取得処理
useEffect(() => {
if (hierarchicalElements.length !== 0) {
dispatch(
getPartnerLicenseAsync({
limit: ACCOUNTS_VIEW_LIMIT,
offset,
accountId:
hierarchicalElements[hierarchicalElements.length - 1].accountId,
})
);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [hierarchicalElements, currentPage]);
return (
<>
{/* 表示確認用の仮画面 */}
{/* isPopupOpenがfalseの場合はポップアップのhtmlを生成しないように対応。これによりポップアップは都度生成されて初期化の考慮が減る */}
{isCardLicenseIssuePopupOpen && (
<CardLicenseIssuePopup
onClose={() => {
setIsCardLicenseIssuePopupOpen(false);
dispatch(getMyAccountAsync());
}}
/>
)}
{islicenseOrderPopupOpen && (
<LicenseOrderPopup
onClose={() => {
setIslicenseOrderPopupOpen(false);
dispatch(getMyAccountAsync());
}}
/>
)}
{islicenseOrderHistoryOpen && (
<LicenseOrderHistory
onReturn={() => {
setIslicenseOrderHistoryOpen(false);
}}
/>
)}
{isViewDetailsOpen && (
<LicenseSummary
onReturn={() => {
setIsViewDetailsOpen(false);
}}
/>
)}
{!islicenseOrderHistoryOpen && !isViewDetailsOpen && (
<div className={styles.wrap}>
<Header userName="XXXXXX" />
<UpdateTokenTimer />
<main className={styles.main}>
<div className="">
<div className={styles.pageHeader}>
<h1 className={styles.pageTitle}>
{t(getTranslationID("partnerLicense.label.title"))}
</h1>
</div>
</div>
<section className={styles.license}>
<div>
<h2>{t(getTranslationID("partnerLicense.label.subTitle"))}</h2>
<ul className={styles.menuAction}>
<li>
{hierarchicalElements.length > 1 && (
// 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>
{isTier2ToTier4 && (
// 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(
"partnerLicense.label.orderLicenseButton"
)
)}
</a>
)}
</li>
<li>
{isTier2ToTier4 && (
// eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions
<a
className={`${styles.menuLink} ${styles.isActive}`}
onClick={() => {
onClickOrderHistory();
}}
>
<img src={history} alt="" className={styles.menuIcon} />
{t(
getTranslationID(
"partnerLicense.label.orderHistoryButton"
)
)}
</a>
)}
</li>
{/* 第1階層 */}
<li>
{isTier1 && (
// eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions
<a
className={`${styles.menuLink} ${styles.isActive}`}
onClick={onlicenseIssueOpen}
>
<img src={postAdd} alt="" className={styles.menuIcon} />
{t(
getTranslationID(
"partnerLicense.label.IssueLicenseCardButton"
)
)}
</a>
)}
</li>
</ul>
<ul className={styles.brCrumbLicense}>
{hierarchicalElements.map((value) => (
// eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-noninteractive-element-interactions
<li
key={value.accountId}
onClick={() => {
onClickBreadCrumbList(value.accountId);
}}
>
<a>{value.companyName}</a>
</li>
))}
</ul>
<table className={`${styles.table} ${styles.partner}`}>
<tr className={styles.tableHeader}>
<th>{t(getTranslationID("partnerLicense.label.name"))}</th>
<th>
{t(getTranslationID("partnerLicense.label.category"))}
</th>
<th>
{t(getTranslationID("partnerLicense.label.accountId"))}
</th>
<th>
{t(getTranslationID("partnerLicense.label.stockLicense"))}
</th>
<th>
{t(
getTranslationID("partnerLicense.label.issueRequested")
)}
</th>
<th>
{t(getTranslationID("partnerLicense.label.shortage"))}
</th>
<th className={styles.noLine}>
{t(
getTranslationID("partnerLicense.label.issueRequesting")
)}
</th>
{/* eslint-disable-next-line jsx-a11y/control-has-associated-label */}
<th />
</tr>
<tr className={styles.isOpen}>
<td>{ownPartnerLicenseInfo.companyName}</td>
<td>{tierNames[ownPartnerLicenseInfo.tier]}</td>
<td>{ownPartnerLicenseInfo.accountId}</td>
<td>
{ownPartnerLicenseInfo.tier !== 1
? ownPartnerLicenseInfo.stockLicense
: "-"}
</td>
<td>{ownPartnerLicenseInfo.issuedRequested}</td>
<td>
{ownPartnerLicenseInfo.tier !== 1
? ownPartnerLicenseInfo.shortage
: "-"}
</td>
<td>
{ownPartnerLicenseInfo.tier !== 1
? ownPartnerLicenseInfo.issueRequesting
: "-"}
</td>
<td>
<ul
className={`${styles.menuAction} ${styles.menuInTable}`}
>
<li>{/* レイアウト維持用 */}</li>
</ul>
</td>
</tr>
{childrenPartnerLicensesInfo.map((value) => (
<tr key={value.accountId}>
<td
title={value.tier !== 5 ? "View child accounts" : ""}
onClick={() => {
if (value.tier !== 5) {
handleRowClick(value);
}
}}
>
{value.companyName}
</td>
<td>{tierNames[value.tier]}</td>
<td>{value.accountId}</td>
<td>{value.stockLicense}</td>
<td>{value.issuedRequested}</td>
<td>
<span
className={value.shortage > 0 ? styles.isAlert : ""}
>
{value.shortage}
</span>
</td>
<td>{value.issueRequesting}</td>
<td>
<ul
className={`${styles.menuAction} ${styles.inTable}`}
>
<li>
{/* eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions */}
<a
className={`${styles.menuLink} ${
buttonLabel ? styles.isActive : ""
}`}
onClick={() => {
if (ownPartnerLicenseInfo.tier === 4) {
onClickViewDetails(value);
} else {
onClickOrderHistory(value);
}
}}
>
{buttonLabel}
</a>
</li>
</ul>
</td>
</tr>
))}
</table>
{!isLoading && childrenPartnerLicensesInfo.length === 0 && (
<p
style={{
margin: "10px",
textAlign: "center",
}}
>
{t(getTranslationID("common.message.listEmpty"))}
</p>
)}
{/* pagenation */}
<div className={styles.pagenation}>
<nav className={styles.pagenationNav}>
<span className={styles.pagenationTotal}>
{total}{" "}
{t(getTranslationID("partnerLicense.label.accounts"))}
</span>
{/* eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions */}
<a
onClick={() => {
movePage(0);
}}
className={` ${
!isLoading && currentPage !== 1 ? styles.isActive : ""
}`}
>
«
</a>
{/* eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions */}
<a
onClick={() => {
movePage((currentPage - 2) * ACCOUNTS_VIEW_LIMIT);
}}
className={`${
!isLoading && currentPage !== 1 ? styles.isActive : ""
}`}
>
</a>
{` ${total !== 0 ? currentPage : 0} of ${
total !== 0 ? totalPage : 0
} `}
{/* eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions */}
<a
onClick={() => {
movePage(currentPage * ACCOUNTS_VIEW_LIMIT);
}}
className={`${
!isLoading && currentPage < totalPage
? styles.isActive
: ""
}`}
>
</a>
{/* eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions */}
<a
onClick={() => {
movePage((totalPage - 1) * ACCOUNTS_VIEW_LIMIT);
}}
className={` ${
!isLoading && currentPage < totalPage
? styles.isActive
: ""
}`}
>
»
</a>
</nav>
</div>
<img
style={{ display: isLoading ? "inline" : "none" }}
src={progress_activit}
className={styles.icLoading}
alt="Loading"
/>
</div>
</section>
</main>
<Footer />
</div>
)}
;
</>
);
};
export default PartnerLicense;