## 概要 [Task3670: デザイン反映](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/3670) - ユーザー一覧画面のヘッダーのスタイル修正 - ブラウザのサイズに合わせて折り返すようにした - ユーザー一覧のテーブルを横スクロールできるように修正 - ライセンス履歴画面にパンくずリストを表示 - OptionItem更新ポップアップのスタイル修正 - 端が切れてしまっていたため枠に収まるようにした - Typistを「Selected」と「Pool」で選択するUIのスタイル修正 - typist名やグループ名が長くてもチェックアイコンに重ならないようにした ## レビューポイント - 特になし ## 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/Task3670?csf=1&web=1&e=zIcngJ ## 動作確認状況 - ローカルで確認 ## 補足 - 相談、参考資料などがあれば
387 lines
15 KiB
TypeScript
387 lines
15 KiB
TypeScript
import { AppDispatch } from "app/store";
|
||
import React, { useCallback, useEffect, useState } from "react";
|
||
import Header from "components/header";
|
||
import Footer from "components/footer";
|
||
import styles from "styles/app.module.scss";
|
||
import { UpdateTokenTimer } from "components/auth/updateTokenTimer";
|
||
import { useDispatch, useSelector } from "react-redux";
|
||
import {
|
||
listUsersAsync,
|
||
selectUserViews,
|
||
selectIsLoading,
|
||
deallocateLicenseAsync,
|
||
} from "features/user";
|
||
import { useTranslation } from "react-i18next";
|
||
import { getTranslationID } from "translation";
|
||
import { LicenseStatusType, UserView } from "features/user/types";
|
||
import {
|
||
LICENSE_NORMAL,
|
||
LICENSE_STATUS,
|
||
NO_LICENSE,
|
||
} from "features/user/constants";
|
||
import { isApproveTier } from "features/auth";
|
||
import { TIERS } from "components/auth/constants";
|
||
import {
|
||
changeUpdateUser,
|
||
changeLicenseAllocateUser,
|
||
} from "features/user/userSlice";
|
||
import { DelegationBar } from "components/delegate";
|
||
import { selectDelegationAccessToken } from "features/auth/selectors";
|
||
import personAdd from "../../assets/images/person_add.svg";
|
||
import checkFill from "../../assets/images/check_fill.svg";
|
||
import checkOutline from "../../assets/images/check_outline.svg";
|
||
import progress_activit from "../../assets/images/progress_activit.svg";
|
||
import { UserAddPopup } from "./popup";
|
||
import { UserUpdatePopup } from "./updatePopup";
|
||
import { AllocateLicensePopup } from "./allocateLicensePopup";
|
||
|
||
const UserListPage: React.FC = (): JSX.Element => {
|
||
const dispatch: AppDispatch = useDispatch();
|
||
const [t] = useTranslation();
|
||
// 代行操作用のトークンを取得する
|
||
const delegationAccessToken = useSelector(selectDelegationAccessToken);
|
||
|
||
const [isPopupOpen, setIsPopupOpen] = useState(false);
|
||
const [isUpdatePopupOpen, setIsUpdatePopupOpen] = useState(false);
|
||
const [isAllocateLicensePopupOpen, setIsAllocateLicensePopupOpen] =
|
||
useState(false);
|
||
|
||
const onOpen = useCallback(() => {
|
||
setIsPopupOpen(true);
|
||
}, [setIsPopupOpen]);
|
||
|
||
const onUpdateOpen = useCallback(
|
||
(id: number) => {
|
||
setIsUpdatePopupOpen(true);
|
||
dispatch(changeUpdateUser({ id }));
|
||
},
|
||
[setIsUpdatePopupOpen, dispatch]
|
||
);
|
||
|
||
const onAllocateLicensePopupOpen = useCallback(
|
||
(selectedUser: UserView) => {
|
||
setIsAllocateLicensePopupOpen(true);
|
||
dispatch(changeLicenseAllocateUser({ selectedUser }));
|
||
},
|
||
[setIsAllocateLicensePopupOpen, dispatch]
|
||
);
|
||
|
||
const onLicenseDeallocation = useCallback(
|
||
async (userId: number) => {
|
||
// ダイアログ確認
|
||
if (
|
||
/* eslint-disable-next-line no-alert */
|
||
!window.confirm(t(getTranslationID("common.message.dialogConfirm")))
|
||
) {
|
||
return;
|
||
}
|
||
|
||
const { meta } = await dispatch(deallocateLicenseAsync({ userId }));
|
||
if (meta.requestStatus === "fulfilled") {
|
||
dispatch(listUsersAsync());
|
||
}
|
||
},
|
||
[dispatch, t]
|
||
);
|
||
|
||
useEffect(() => {
|
||
// ユーザ一覧取得処理を呼び出す
|
||
dispatch(listUsersAsync());
|
||
}, [dispatch]);
|
||
|
||
const users = useSelector(selectUserViews);
|
||
const isLoading = useSelector(selectIsLoading);
|
||
// ユーザーが第5階層であるかどうかを判定する(代行操作中は第5階層として扱う)
|
||
const isTier5 =
|
||
isApproveTier([TIERS.TIER5]) || delegationAccessToken !== null;
|
||
|
||
return (
|
||
<>
|
||
<UserUpdatePopup
|
||
isOpen={isUpdatePopupOpen}
|
||
onClose={() => {
|
||
setIsUpdatePopupOpen(false);
|
||
}}
|
||
/>
|
||
<UserAddPopup
|
||
isOpen={isPopupOpen}
|
||
onClose={() => {
|
||
setIsPopupOpen(false);
|
||
}}
|
||
/>
|
||
<AllocateLicensePopup
|
||
isOpen={isAllocateLicensePopupOpen}
|
||
onClose={() => {
|
||
setIsAllocateLicensePopupOpen(false);
|
||
}}
|
||
/>
|
||
<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("userListPage.label.title"))}
|
||
</h1>
|
||
<p className="pageTxt" />
|
||
</div>
|
||
<section className={styles.user}>
|
||
<div>
|
||
<ul className={styles.menuAction}>
|
||
<li>
|
||
{/* eslint-disable-next-line jsx-a11y/click-events-have-key-events,jsx-a11y/no-static-element-interactions */}
|
||
<a
|
||
className={`${styles.menuLink} ${
|
||
!isLoading ? styles.isActive : ""
|
||
}`}
|
||
onClick={onOpen}
|
||
>
|
||
<img src={personAdd} alt="" className={styles.menuIcon} />
|
||
{t(getTranslationID("userListPage.label.addUser"))}
|
||
</a>
|
||
</li>
|
||
</ul>
|
||
<div className={styles.tableWrap}>
|
||
<table className={`${styles.table} ${styles.user}`}>
|
||
<tbody>
|
||
<tr className={styles.tableHeader}>
|
||
<th className={styles.clm0}>{/** th is empty */}</th>
|
||
<th>
|
||
{t(getTranslationID("userListPage.label.name"))}
|
||
</th>
|
||
<th>
|
||
{t(getTranslationID("userListPage.label.role"))}
|
||
</th>
|
||
<th>
|
||
{t(getTranslationID("userListPage.label.authorID"))}
|
||
</th>
|
||
<th>
|
||
{t(getTranslationID("userListPage.label.encryption"))}
|
||
</th>
|
||
<th>
|
||
{t(getTranslationID("userListPage.label.prompt"))}
|
||
</th>
|
||
<th>
|
||
{t(
|
||
getTranslationID("userListPage.label.typistGroup")
|
||
)}
|
||
</th>
|
||
<th>
|
||
{t(getTranslationID("userListPage.label.email"))}
|
||
</th>
|
||
<th>
|
||
{t(getTranslationID("userListPage.label.status"))}
|
||
</th>
|
||
<th>
|
||
{t(getTranslationID("userListPage.label.expiration"))}
|
||
</th>
|
||
<th>
|
||
{t(getTranslationID("userListPage.label.remaining"))}
|
||
</th>
|
||
<th>
|
||
{t(getTranslationID("userListPage.label.autoRenew"))}
|
||
</th>
|
||
<th>
|
||
{t(
|
||
getTranslationID("userListPage.label.notification")
|
||
)}
|
||
</th>
|
||
<th>
|
||
{t(
|
||
getTranslationID("userListPage.label.emailVerified")
|
||
)}
|
||
</th>
|
||
</tr>
|
||
{!isLoading &&
|
||
users.map((user) => (
|
||
<tr key={user.email}>
|
||
<td className={styles.clm0}>
|
||
<ul className={styles.menuInTable}>
|
||
<li>
|
||
{/* eslint-disable-next-line jsx-a11y/click-events-have-key-events,jsx-a11y/no-static-element-interactions */}
|
||
<a
|
||
onClick={() => {
|
||
onUpdateOpen(user.id);
|
||
}}
|
||
>
|
||
{t(
|
||
getTranslationID(
|
||
"userListPage.label.editUser"
|
||
)
|
||
)}
|
||
</a>
|
||
</li>
|
||
{isTier5 && (
|
||
<>
|
||
<li>
|
||
{/* eslint-disable-next-line jsx-a11y/click-events-have-key-events,jsx-a11y/no-static-element-interactions */}
|
||
<a
|
||
onClick={() => {
|
||
onAllocateLicensePopupOpen(user);
|
||
}}
|
||
>
|
||
{t(
|
||
getTranslationID(
|
||
"userListPage.label.licenseAllocation"
|
||
)
|
||
)}
|
||
</a>
|
||
</li>
|
||
<li>
|
||
{/* eslint-disable-next-line jsx-a11y/click-events-have-key-events,jsx-a11y/no-static-element-interactions */}
|
||
<a
|
||
className={
|
||
user.licenseStatus ===
|
||
LICENSE_STATUS.NOLICENSE
|
||
? styles.isDisable
|
||
: ""
|
||
}
|
||
onClick={() => {
|
||
onLicenseDeallocation(user.id);
|
||
}}
|
||
>
|
||
{t(
|
||
getTranslationID(
|
||
"userListPage.label.licenseDeallocation"
|
||
)
|
||
)}
|
||
</a>
|
||
</li>
|
||
</>
|
||
)}
|
||
{/* ユーザー削除 CCB後回し分なので今は非表示
|
||
<li>
|
||
<a href="">
|
||
{t(
|
||
getTranslationID(
|
||
"userListPage.label.deleteUser"
|
||
)
|
||
)}
|
||
</a>
|
||
</li>
|
||
*/}
|
||
</ul>
|
||
</td>
|
||
<td> {user.name}</td>
|
||
<td>{user.role}</td>
|
||
<td>{user.authorId}</td>
|
||
<td>{boolToElement(user.encryption)}</td>
|
||
<td>{boolToElement(user.prompt)}</td>
|
||
<td>{arrayToElement(user.typistGroupName)}</td>
|
||
<td>{user.email}</td>
|
||
<td>
|
||
<span
|
||
className={
|
||
user.licenseStatus ===
|
||
LICENSE_STATUS.NOLICENSE ||
|
||
user.licenseStatus === LICENSE_STATUS.ALERT
|
||
? styles.isAlert
|
||
: ""
|
||
}
|
||
>
|
||
{getLicenseStatus(user.licenseStatus)}
|
||
</span>
|
||
</td>
|
||
<td>
|
||
<span
|
||
className={
|
||
user.licenseStatus === LICENSE_STATUS.ALERT
|
||
? styles.isAlert
|
||
: ""
|
||
}
|
||
>
|
||
{user.expiration ?? "-"}
|
||
</span>
|
||
</td>
|
||
<td>
|
||
<span
|
||
className={
|
||
user.licenseStatus === LICENSE_STATUS.ALERT
|
||
? styles.isAlert
|
||
: ""
|
||
}
|
||
>
|
||
{user.remaining ?? "-"}
|
||
</span>
|
||
</td>
|
||
<td>{boolToElement(user.autoRenew)}</td>
|
||
<td>{boolToElement(user.notification)}</td>
|
||
<td>{boolToElement(user.emailVerified)}</td>
|
||
</tr>
|
||
))}
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
{!isLoading && users.length === 0 && (
|
||
<p
|
||
style={{
|
||
margin: "10px",
|
||
textAlign: "center",
|
||
}}
|
||
>
|
||
{t(getTranslationID("common.message.listEmpty"))}
|
||
</p>
|
||
)}
|
||
{isLoading && (
|
||
<img
|
||
src={progress_activit}
|
||
className={styles.icLoading}
|
||
alt="Loading"
|
||
/>
|
||
)}
|
||
</div>
|
||
</section>
|
||
</div>
|
||
</main>
|
||
<Footer />
|
||
</div>
|
||
</>
|
||
);
|
||
};
|
||
|
||
// boolかstringを受け取って、stringの場合はそのまま返し、boolの場合はチェックマークON/OFFを返す
|
||
const boolToElement = (value: boolean | string): JSX.Element | string => {
|
||
if (typeof value === "string") {
|
||
return value;
|
||
}
|
||
return value ? (
|
||
<img src={checkFill} alt="" className={styles.menuIcon} />
|
||
) : (
|
||
<img src={checkOutline} alt="" className={styles.menuIcon} />
|
||
);
|
||
};
|
||
|
||
// 文字列の配列または文字列を受け取って、文字列の場合はそのまま返し、配列の場合は最後の要素以外に<br>をつけて返す
|
||
const arrayToElement = (
|
||
typistGroupNames: string[] | string
|
||
): JSX.Element[] | string => {
|
||
if (typeof typistGroupNames === "string") {
|
||
return typistGroupNames;
|
||
}
|
||
return typistGroupNames.map((v, i) => (
|
||
<span key={v}>
|
||
{v}
|
||
{i !== typistGroupNames.length - 1 && <br />}
|
||
</span>
|
||
));
|
||
};
|
||
|
||
// ライセンスステータスに応じて、ライセンスステータスの文字列を返す
|
||
const getLicenseStatus = (licenseStatus: LicenseStatusType): string => {
|
||
if (licenseStatus === LICENSE_STATUS.NOLICENSE) {
|
||
return NO_LICENSE;
|
||
}
|
||
if (licenseStatus === LICENSE_STATUS.NORMAL) {
|
||
return LICENSE_NORMAL;
|
||
}
|
||
return licenseStatus;
|
||
};
|
||
|
||
export default UserListPage;
|