Merged PR 281: ユーザー一覧画面修正
## 概要 [Task2232: ユーザー一覧画面修正](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/2232) - ユーザー一覧の画面修正 - 新しい項目を追加 - Encryption - Prompt - EmailVerified - ユーザーのRoleに応じて表示内容を切り替える - author - Typistgroupをハイフン表示 - typist - AuthorIDをハイフン表示 - Encryptionをハイフン表示 - Promptをハイフン表示 - Statusに応じて表示を変更 - Alert - Status, Expiration , Remainingを赤文字表示 - NoLicense - Statusを赤文字表示 - ログインユーザーのTierによる表示変更 - 行をマウスオーバーすると出てくるボタンの表示非表示 ## レビューポイント - Selectorで画面表示する内容に変換して取得しているが問題ないか - 表示する内容、条件に漏れはないか ## 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/Task2232?csf=1&web=1&e=nX7ayK ## 動作確認状況 - ローカルで確認 ## 補足 - 相談、参考資料などがあれば
This commit is contained in:
parent
8be20b7ca8
commit
c7d34e1ccb
File diff suppressed because it is too large
Load Diff
14
dictation_client/src/assets/images/check_outline.svg
Normal file
14
dictation_client/src/assets/images/check_outline.svg
Normal file
@ -0,0 +1,14 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 27.7.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="レイヤー_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px"
|
||||
y="0px" viewBox="0 0 48 48" style="enable-background:new 0 0 48 48;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{display:none;fill:#282828;}
|
||||
.st1{fill:#A5A5A5;}
|
||||
</style>
|
||||
<path class="st0" d="M39.6,4.4H8.4C6,4.4,4,6.4,4,8.8v30.4c0,2.4,2,4.3,4.4,4.3h31.1c2.5,0,4.4-2,4.4-4.3V8.8
|
||||
C44,6.4,42,4.4,39.6,4.4z M19.6,34.9L8.4,24l3.1-3.1l8,7.8l16.9-16.5l3.1,3.1L19.6,34.9z"/>
|
||||
<path class="st1" d="M7.4,43.8c-0.9,0-1.6-0.3-2.3-1c-0.7-0.7-1-1.4-1-2.3v-33c0-0.9,0.3-1.6,1-2.3c0.7-0.6,1.4-0.9,2.3-0.9h33.1
|
||||
c0.9,0,1.6,0.3,2.3,1s1,1.4,1,2.3v33.1c0,0.9-0.3,1.6-1,2.3c-0.7,0.7-1.4,1-2.3,1L7.4,43.8L7.4,43.8z M7.4,40.5h33.1V7.4H7.4V40.5
|
||||
L7.4,40.5z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 912 B |
Binary file not shown.
|
Before Width: | Height: | Size: 48 KiB |
@ -1,7 +1,7 @@
|
||||
export const ROLE = {
|
||||
AUTHOR: "author",
|
||||
TYPIST: "typist",
|
||||
NONE: "none",
|
||||
// LicenseStatusTypeの値を定数オブジェクトにする
|
||||
export const LICENSE_STATUS = {
|
||||
NORMAL: "Normal",
|
||||
NOLICENSE: "NoLicense",
|
||||
ALERT: "Alert",
|
||||
RENEW: "Renew",
|
||||
} as const;
|
||||
|
||||
export type RoleType = typeof ROLE[keyof typeof ROLE];
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
import { RootState } from "app/store";
|
||||
import { ROLE } from "./constants";
|
||||
import { USER_ROLES } from "components/auth/constants";
|
||||
import { RoleType, UserView, isLicenseStatusType, isRoleType } from "./types";
|
||||
import { LICENSE_STATUS } from "./constants";
|
||||
|
||||
export const selectInputValidationErrors = (state: RootState) => {
|
||||
const { name, email, role, authorId } = state.user.apps.addUser;
|
||||
@ -7,7 +9,7 @@ export const selectInputValidationErrors = (state: RootState) => {
|
||||
// 必須項目のチェック
|
||||
const hasErrorEmptyName = name === "";
|
||||
const hasErrorEmptyEmail = email === "";
|
||||
const hasErrorEmptyAuthorId = role === ROLE.AUTHOR && authorId === "";
|
||||
const hasErrorEmptyAuthorId = role === USER_ROLES.AUTHOR && authorId === "";
|
||||
|
||||
const hasErrorIncorrectAuthorId = checkErrorIncorrectAuthorId(
|
||||
authorId ?? undefined,
|
||||
@ -28,7 +30,7 @@ export const checkErrorIncorrectAuthorId = (
|
||||
authorId: string | undefined,
|
||||
role: string
|
||||
): boolean => {
|
||||
if (!authorId || role !== ROLE.AUTHOR) {
|
||||
if (!authorId || role !== USER_ROLES.AUTHOR) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -52,5 +54,86 @@ export const selectLicenseAlert = (state: RootState) =>
|
||||
state.user.apps.addUser.licenseAlert;
|
||||
export const selectNtotification = (state: RootState) =>
|
||||
state.user.apps.addUser.notification;
|
||||
export const selectDomain = (state: RootState) => state.user.domain;
|
||||
// usersからUserViewに変換して返却する
|
||||
export const selectUserViews = (state: RootState): UserView[] => {
|
||||
const { users } = state.user.domain;
|
||||
const userViews = users.map((user): UserView => {
|
||||
const {
|
||||
typistGroupName,
|
||||
prompt,
|
||||
encryption,
|
||||
authorId,
|
||||
role,
|
||||
licenseStatus,
|
||||
expiration,
|
||||
remaining,
|
||||
...rest
|
||||
} = user;
|
||||
// roleの型がstringなので、isRoleTypeで型ガードを行う
|
||||
// roleの型がRoleTypeでなければ、何も返さない
|
||||
if (!isRoleType(role) || !isLicenseStatusType(licenseStatus)) {
|
||||
return {} as UserView;
|
||||
}
|
||||
const convertedValues = convertValueBasedOnRole(
|
||||
role,
|
||||
authorId,
|
||||
encryption,
|
||||
prompt,
|
||||
typistGroupName
|
||||
);
|
||||
return {
|
||||
typistGroupName: convertedValues.typistGroupName,
|
||||
prompt: convertedValues.prompt,
|
||||
encryption: convertedValues.encryption,
|
||||
authorId: convertedValues.authorId,
|
||||
// roleの一文字目を大文字に変換する
|
||||
role: role.charAt(0).toUpperCase() + role.slice(1),
|
||||
licenseStatus:
|
||||
licenseStatus === LICENSE_STATUS.NORMAL ? "-" : licenseStatus,
|
||||
expiration: expiration ?? "-",
|
||||
remaining: remaining ?? "-",
|
||||
...rest,
|
||||
};
|
||||
});
|
||||
// 空のオブジェクトを除外する
|
||||
return userViews.filter((userView) => Object.keys(userView).length !== 0);
|
||||
};
|
||||
|
||||
export const selectIsLoading = (state: RootState) => state.user.apps.isLoading;
|
||||
|
||||
// roleに応じて値を変換する
|
||||
const convertValueBasedOnRole = (
|
||||
role: RoleType,
|
||||
authorId: string | undefined,
|
||||
encryption: boolean,
|
||||
prompt: boolean,
|
||||
typistGroupName: string[]
|
||||
): {
|
||||
authorId: string;
|
||||
encryption: boolean | string;
|
||||
prompt: boolean | string;
|
||||
typistGroupName: string[] | string;
|
||||
} => {
|
||||
if (role === USER_ROLES.AUTHOR && authorId) {
|
||||
return {
|
||||
authorId,
|
||||
encryption,
|
||||
prompt,
|
||||
typistGroupName: "-",
|
||||
};
|
||||
}
|
||||
if (role === USER_ROLES.TYPIST) {
|
||||
return {
|
||||
authorId: "-",
|
||||
encryption: "-",
|
||||
prompt: "-",
|
||||
typistGroupName,
|
||||
};
|
||||
}
|
||||
return {
|
||||
authorId: "-",
|
||||
encryption: "-",
|
||||
prompt: "-",
|
||||
typistGroupName: "-",
|
||||
};
|
||||
};
|
||||
|
||||
40
dictation_client/src/features/user/types.ts
Normal file
40
dictation_client/src/features/user/types.ts
Normal file
@ -0,0 +1,40 @@
|
||||
import { User } from "api";
|
||||
import { USER_ROLES } from "components/auth/constants";
|
||||
import { LICENSE_STATUS } from "./constants";
|
||||
|
||||
// 画面表示用のUserの型を独自に定義
|
||||
export interface UserView
|
||||
extends Omit<
|
||||
User,
|
||||
"typistGroupName" | "prompt" | "encryption" | "remaining"
|
||||
> {
|
||||
authorId: string;
|
||||
typistGroupName: string[] | string;
|
||||
role: string;
|
||||
licenseStatus: LicenseStatusType | string;
|
||||
prompt: boolean | string;
|
||||
encryption: boolean | string;
|
||||
emailVerified: boolean;
|
||||
autoRenew: boolean;
|
||||
licenseAlert: boolean;
|
||||
notification: boolean;
|
||||
name: string;
|
||||
email: string;
|
||||
expiration: string;
|
||||
remaining: number | string;
|
||||
}
|
||||
|
||||
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];
|
||||
|
||||
// 受け取った値がLicenseStatusTypeの型であるかどうかを判定する
|
||||
export const isLicenseStatusType = (
|
||||
licenseStatus: string
|
||||
): licenseStatus is LicenseStatusType =>
|
||||
Object.values(LICENSE_STATUS).includes(licenseStatus as LicenseStatusType);
|
||||
@ -1,25 +1,25 @@
|
||||
import { PayloadAction, createSlice } from "@reduxjs/toolkit";
|
||||
import { USER_ROLES } from "components/auth/constants";
|
||||
import { UsersState } from "./state";
|
||||
import { addUserAsync, listUsersAsync } from "./operations";
|
||||
import { ROLE, RoleType } from "./constants";
|
||||
import { RoleType } from "./types";
|
||||
|
||||
const initialState: UsersState = {
|
||||
domain: { users: [] },
|
||||
apps: {
|
||||
addUser: {
|
||||
name: "",
|
||||
role: ROLE.NONE,
|
||||
role: USER_ROLES.NONE,
|
||||
authorId: "",
|
||||
typistGroupName: "",
|
||||
typistGroupName: [],
|
||||
email: "",
|
||||
emailVerified: true,
|
||||
autoRenew: true,
|
||||
licenseAlert: true,
|
||||
notification: true,
|
||||
// XXX エラー回避、api.ts生成時にエラー発生
|
||||
encryption: false,
|
||||
prompt: false,
|
||||
encryption: true,
|
||||
licenseStatus: "",
|
||||
prompt: false,
|
||||
},
|
||||
isLoading: false,
|
||||
},
|
||||
@ -46,8 +46,7 @@ export const userSlice = createSlice({
|
||||
action: PayloadAction<{ authorId: string | undefined }>
|
||||
) => {
|
||||
const { authorId } = action.payload;
|
||||
// XXX エラー回避、api.ts生成時にエラー発生、null → undefined
|
||||
state.apps.addUser.authorId = authorId ?? undefined;
|
||||
state.apps.addUser.authorId = authorId;
|
||||
},
|
||||
changeTypistGroupId: (
|
||||
state,
|
||||
|
||||
@ -2,7 +2,6 @@ import React, { useState, useCallback, useEffect, useRef } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { AppDispatch } from "app/store";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import _ from "lodash";
|
||||
import styles from "../../styles/app.module.scss";
|
||||
import { getTranslationID } from "../../translation";
|
||||
import close from "../../assets/images/close.svg";
|
||||
|
||||
@ -2,7 +2,6 @@ import React, { useState, useCallback, useEffect } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { AppDispatch } from "app/store";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import _ from "lodash";
|
||||
import styles from "../../styles/app.module.scss";
|
||||
import { getTranslationID } from "../../translation";
|
||||
import close from "../../assets/images/close.svg";
|
||||
|
||||
@ -5,15 +5,20 @@ 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, selectDomain, selectIsLoading } from "features/user";
|
||||
import {
|
||||
listUsersAsync,
|
||||
selectUserViews,
|
||||
selectIsLoading,
|
||||
} from "features/user";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { getTranslationID } from "translation";
|
||||
import { isLicenseStatusType } from "features/user/types";
|
||||
import { LICENSE_STATUS } from "features/user/constants";
|
||||
import { isApproveTier } from "features/auth/utils";
|
||||
import { TIERS } from "components/auth/constants";
|
||||
import personAdd from "../../assets/images/person_add.svg";
|
||||
import editImg from "../../assets/images/edit.svg";
|
||||
import deleteImg from "../../assets/images/delete.svg";
|
||||
import badgeImg from "../../assets/images/badge.svg";
|
||||
import checkFill from "../../assets/images/check_fill.svg";
|
||||
import circle from "../../assets/images/circle.svg";
|
||||
import checkOutline from "../../assets/images/check_outline.svg";
|
||||
import progress_activit from "../../assets/images/progress_activit.svg";
|
||||
import { UserAddPopup } from "./popup";
|
||||
|
||||
@ -32,8 +37,10 @@ const UserListPage: React.FC = (): JSX.Element => {
|
||||
dispatch(listUsersAsync());
|
||||
}, [dispatch]);
|
||||
|
||||
const domain = useSelector(selectDomain);
|
||||
const users = useSelector(selectUserViews);
|
||||
const isLoading = useSelector(selectIsLoading);
|
||||
// ユーザーが第5階層であるかどうかを判定する
|
||||
const isTier5 = isApproveTier([TIERS.TIER5]);
|
||||
|
||||
return (
|
||||
<>
|
||||
@ -45,8 +52,7 @@ const UserListPage: React.FC = (): JSX.Element => {
|
||||
}}
|
||||
/>
|
||||
<div className={styles.wrap}>
|
||||
{/* XXX デザイン上はヘッダに「Account」「User」「License」等の項目が設定されているが、そのままでは使用できない。PBI1128ではユーザ一覧画面は作りこまないので、ユーザ一覧のPBIでヘッダをデザイン通りにする必要がある */}
|
||||
<Header />
|
||||
<Header userName="XXXXXX" />
|
||||
<UpdateTokenTimer />
|
||||
<main className={styles.main}>
|
||||
<div className="">
|
||||
@ -71,71 +77,34 @@ const UserListPage: React.FC = (): JSX.Element => {
|
||||
{t(getTranslationID("userListPage.label.addUser"))}
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="" className={styles.menuLink}>
|
||||
<img src={editImg} alt="" className={styles.menuIcon} />
|
||||
{t(getTranslationID("userListPage.label.edit"))}
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="" className={styles.menuLink}>
|
||||
<img src={deleteImg} alt="" className={styles.menuIcon} />
|
||||
{t(getTranslationID("userListPage.label.delete"))}
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="" className={styles.menuLink}>
|
||||
<img src={badgeImg} alt="" className={styles.menuIcon} />
|
||||
{t(
|
||||
getTranslationID("userListPage.label.licenseAllocation")
|
||||
)}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
<table className={styles.table}>
|
||||
<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>
|
||||
<a className={styles.hasSort}>
|
||||
{t(getTranslationID("userListPage.label.name"))}
|
||||
</a>
|
||||
{t(getTranslationID("userListPage.label.authorID"))}
|
||||
</th>
|
||||
<th>
|
||||
<a className={styles.hasSort}>
|
||||
{t(getTranslationID("userListPage.label.role"))}
|
||||
</a>
|
||||
{t(getTranslationID("userListPage.label.encryption"))}
|
||||
</th>
|
||||
<th>
|
||||
<a className={styles.hasSort}>
|
||||
{t(getTranslationID("userListPage.label.authorID"))}
|
||||
</a>
|
||||
{t(getTranslationID("userListPage.label.prompt"))}
|
||||
</th>
|
||||
<th>
|
||||
<a className={styles.hasSort}>
|
||||
{t(
|
||||
getTranslationID("userListPage.label.typistGroup")
|
||||
)}
|
||||
</a>
|
||||
{t(getTranslationID("userListPage.label.typistGroup"))}
|
||||
</th>
|
||||
<th>{t(getTranslationID("userListPage.label.email"))}</th>
|
||||
<th>
|
||||
{t(getTranslationID("userListPage.label.status"))}
|
||||
</th>
|
||||
<th>
|
||||
<a className={styles.hasSort}>
|
||||
{t(getTranslationID("userListPage.label.email"))}
|
||||
</a>
|
||||
{t(getTranslationID("userListPage.label.expiration"))}
|
||||
</th>
|
||||
<th>
|
||||
<a className={styles.hasSort}>
|
||||
{t(getTranslationID("userListPage.label.status"))}
|
||||
</a>
|
||||
</th>
|
||||
<th>
|
||||
<a className={styles.hasSort}>
|
||||
{t(getTranslationID("userListPage.label.expiration"))}
|
||||
</a>
|
||||
</th>
|
||||
<th>
|
||||
<a className={styles.hasSort}>
|
||||
{t(getTranslationID("userListPage.label.remaining"))}
|
||||
</a>
|
||||
{t(getTranslationID("userListPage.label.remaining"))}
|
||||
</th>
|
||||
<th>
|
||||
{t(getTranslationID("userListPage.label.autoRenew"))}
|
||||
@ -146,66 +115,103 @@ const UserListPage: React.FC = (): JSX.Element => {
|
||||
<th>
|
||||
{t(getTranslationID("userListPage.label.notification"))}
|
||||
</th>
|
||||
<th>
|
||||
{t(
|
||||
getTranslationID("userListPage.label.emailVerified")
|
||||
)}
|
||||
</th>
|
||||
</tr>
|
||||
{/* XXX 「固定」の項目と、isSelected、isAlertの対応が必要 */}
|
||||
{(isPopupOpen || !isLoading) &&
|
||||
domain.users.map((user) => (
|
||||
<tr key={user.email}>
|
||||
<td>{user.name}</td>
|
||||
<td>{user.role}</td>
|
||||
<td>{user.authorId}</td>
|
||||
<td>{user.typistGroupName}</td>
|
||||
<td>{user.email}</td>
|
||||
<td>固定:Uploaded</td>
|
||||
<td>固定:2023/8/3</td>
|
||||
<td>固定:114</td>
|
||||
<td>
|
||||
{user.autoRenew ? (
|
||||
<img
|
||||
src={checkFill}
|
||||
alt=""
|
||||
className={styles.icCheckCircle}
|
||||
/>
|
||||
) : (
|
||||
<img
|
||||
src={circle}
|
||||
alt=""
|
||||
className={styles.icCheckCircle}
|
||||
/>
|
||||
)}
|
||||
</td>
|
||||
<td>
|
||||
{user.licenseAlert ? (
|
||||
<img
|
||||
src={checkFill}
|
||||
alt=""
|
||||
className={styles.icCheckCircle}
|
||||
/>
|
||||
) : (
|
||||
<img
|
||||
src={circle}
|
||||
alt=""
|
||||
className={styles.icCheckCircle}
|
||||
/>
|
||||
)}
|
||||
</td>
|
||||
<td>
|
||||
{user.notification ? (
|
||||
<img
|
||||
src={checkFill}
|
||||
alt=""
|
||||
className={styles.icCheckCircle}
|
||||
/>
|
||||
) : (
|
||||
<img
|
||||
src={circle}
|
||||
alt=""
|
||||
className={styles.icCheckCircle}
|
||||
/>
|
||||
)}
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
{!isLoading &&
|
||||
users.map((user) => {
|
||||
const { isAlertLicenseStatus, isAlertRemaining } =
|
||||
isAlertElement(user.licenseStatus);
|
||||
return (
|
||||
<tr key={user.email}>
|
||||
<td className={styles.clm0}>
|
||||
<ul className={styles.menuInTable}>
|
||||
<li>
|
||||
<a href="">
|
||||
{t(
|
||||
getTranslationID(
|
||||
"userListPage.label.editUser"
|
||||
)
|
||||
)}
|
||||
</a>
|
||||
</li>
|
||||
{isTier5 && (
|
||||
<>
|
||||
<li>
|
||||
<a href="">
|
||||
{t(
|
||||
getTranslationID(
|
||||
"userListPage.label.licenseAllocation"
|
||||
)
|
||||
)}
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="">
|
||||
{t(
|
||||
getTranslationID(
|
||||
"userListPage.label.licenseDeallocation"
|
||||
)
|
||||
)}
|
||||
</a>
|
||||
</li>
|
||||
</>
|
||||
)}
|
||||
<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={
|
||||
isAlertLicenseStatus ? styles.isAlert : ""
|
||||
}
|
||||
>
|
||||
{user.licenseStatus}
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<span
|
||||
className={
|
||||
isAlertRemaining ? styles.isAlert : ""
|
||||
}
|
||||
>
|
||||
{user.expiration}
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<span
|
||||
className={
|
||||
isAlertRemaining ? styles.isAlert : ""
|
||||
}
|
||||
>
|
||||
{user.remaining}
|
||||
</span>
|
||||
</td>
|
||||
<td>{boolToElement(user.autoRenew)}</td>
|
||||
<td>{boolToElement(user.licenseAlert)}</td>
|
||||
<td>{boolToElement(user.notification)}</td>
|
||||
<td>{boolToElement(user.emailVerified)}</td>
|
||||
</tr>
|
||||
);
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
{isLoading && (
|
||||
@ -215,28 +221,6 @@ const UserListPage: React.FC = (): JSX.Element => {
|
||||
alt="Loading"
|
||||
/>
|
||||
)}
|
||||
<div className={styles.pagenation}>
|
||||
<nav className={styles.pagenationNav}>
|
||||
<span className={styles.pagenationTotal}>
|
||||
{domain.users.length}{" "}
|
||||
{t(getTranslationID("userListPage.label.users"))}
|
||||
</span>
|
||||
{/* XXX 複数ページの挙動、対応が必要 */}
|
||||
<a href="" className="pagenationNavFirst">
|
||||
«
|
||||
</a>
|
||||
<a href="" className="pagenationNavPrev">
|
||||
‹
|
||||
</a>
|
||||
1 {t(getTranslationID("userListPage.label.of"))} 1
|
||||
<a href="" className="pagenationNavNext isActive">
|
||||
›
|
||||
</a>
|
||||
<a href="" className="pagenationNavLast isActive">
|
||||
»
|
||||
</a>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
@ -247,4 +231,65 @@ const UserListPage: React.FC = (): JSX.Element => {
|
||||
);
|
||||
};
|
||||
|
||||
// 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 isAlertElement = (
|
||||
licenseStatus: string
|
||||
): {
|
||||
isAlertLicenseStatus: boolean;
|
||||
isAlertRemaining: boolean;
|
||||
} => {
|
||||
// licenseStatusの型がLicenseStatusTypeでない場合(Normal)、どちらもfalseにする
|
||||
if (isLicenseStatusType(licenseStatus) === false) {
|
||||
return {
|
||||
isAlertLicenseStatus: false,
|
||||
isAlertRemaining: false,
|
||||
};
|
||||
}
|
||||
// licenseStatusがNOLICENSEの場合、isAlertLicenseStatusをtrueにする(Remainingはハイフン)
|
||||
if (licenseStatus === LICENSE_STATUS.NOLICENSE) {
|
||||
return {
|
||||
isAlertLicenseStatus: true,
|
||||
isAlertRemaining: false,
|
||||
};
|
||||
}
|
||||
// licenseStatusがALERTの場合、どちらもtrueにする
|
||||
if (licenseStatus === LICENSE_STATUS.ALERT) {
|
||||
return {
|
||||
isAlertLicenseStatus: true,
|
||||
isAlertRemaining: true,
|
||||
};
|
||||
}
|
||||
// licenseStatusがRENEWの場合、どちらもfalseにする
|
||||
return {
|
||||
isAlertLicenseStatus: false,
|
||||
isAlertRemaining: false,
|
||||
};
|
||||
};
|
||||
|
||||
export default UserListPage;
|
||||
|
||||
@ -24,9 +24,9 @@ import {
|
||||
selectLicenseAlert,
|
||||
selectNtotification,
|
||||
addUserAsync,
|
||||
ROLE,
|
||||
selectIsLoading,
|
||||
} from "features/user";
|
||||
import { USER_ROLES } from "components/auth/constants";
|
||||
import close from "../../assets/images/close.svg";
|
||||
import progress_activit from "../../assets/images/progress_activit.svg";
|
||||
|
||||
@ -78,10 +78,10 @@ export const UserAddPopup: React.FC<UserAddPopupProps> = (props) => {
|
||||
) {
|
||||
return;
|
||||
}
|
||||
if (role !== ROLE.AUTHOR) {
|
||||
if (role !== USER_ROLES.AUTHOR) {
|
||||
changeAuthorId({ authorId: undefined });
|
||||
}
|
||||
if (role !== ROLE.TYPIST) {
|
||||
if (role !== USER_ROLES.TYPIST) {
|
||||
changeTypistGroupId({ typistGroupId: undefined });
|
||||
}
|
||||
|
||||
@ -90,8 +90,9 @@ export const UserAddPopup: React.FC<UserAddPopupProps> = (props) => {
|
||||
name,
|
||||
email,
|
||||
role,
|
||||
authorId: role === ROLE.AUTHOR ? authorId ?? undefined : undefined,
|
||||
typistGroupId: role === ROLE.TYPIST ? typistGroupId : undefined,
|
||||
authorId:
|
||||
role === USER_ROLES.AUTHOR ? authorId ?? undefined : undefined,
|
||||
typistGroupId: role === USER_ROLES.TYPIST ? typistGroupId : undefined,
|
||||
autoRenew,
|
||||
licenseAlert,
|
||||
notification,
|
||||
@ -179,45 +180,45 @@ export const UserAddPopup: React.FC<UserAddPopupProps> = (props) => {
|
||||
</dd>
|
||||
<dt>{t(getTranslationID("userListPage.label.role"))}</dt>
|
||||
<dd>
|
||||
<label htmlFor={ROLE.AUTHOR}>
|
||||
<label htmlFor={USER_ROLES.AUTHOR}>
|
||||
<input
|
||||
type="radio"
|
||||
name="role"
|
||||
className={styles.formRadio}
|
||||
checked={role === ROLE.AUTHOR}
|
||||
checked={role === USER_ROLES.AUTHOR}
|
||||
onChange={() => {
|
||||
dispatch(changeRole({ role: ROLE.AUTHOR }));
|
||||
dispatch(changeRole({ role: USER_ROLES.AUTHOR }));
|
||||
}}
|
||||
/>
|
||||
{t(getTranslationID("userListPage.label.author"))}
|
||||
</label>
|
||||
<label htmlFor={ROLE.TYPIST}>
|
||||
<label htmlFor={USER_ROLES.TYPIST}>
|
||||
<input
|
||||
type="radio"
|
||||
name="role"
|
||||
className={styles.formRadio}
|
||||
checked={role === ROLE.TYPIST}
|
||||
checked={role === USER_ROLES.TYPIST}
|
||||
onChange={() => {
|
||||
dispatch(changeRole({ role: ROLE.TYPIST }));
|
||||
dispatch(changeRole({ role: USER_ROLES.TYPIST }));
|
||||
}}
|
||||
/>
|
||||
{t(getTranslationID("userListPage.label.transcriptionist"))}
|
||||
</label>
|
||||
<label htmlFor={ROLE.NONE}>
|
||||
<label htmlFor={USER_ROLES.NONE}>
|
||||
<input
|
||||
type="radio"
|
||||
name="role"
|
||||
className={styles.formRadio}
|
||||
checked={role === ROLE.NONE}
|
||||
checked={role === USER_ROLES.NONE}
|
||||
onChange={() => {
|
||||
dispatch(changeRole({ role: ROLE.NONE }));
|
||||
dispatch(changeRole({ role: USER_ROLES.NONE }));
|
||||
}}
|
||||
/>
|
||||
{t(getTranslationID("userListPage.label.none"))}
|
||||
</label>
|
||||
</dd>
|
||||
{/** Author 選択時に表示 */}
|
||||
{role === ROLE.AUTHOR && (
|
||||
{role === USER_ROLES.AUTHOR && (
|
||||
<div className={styles.slideSet} id="">
|
||||
<dt>{t(getTranslationID("userListPage.label.authorID"))}</dt>
|
||||
<dd>
|
||||
@ -252,7 +253,7 @@ export const UserAddPopup: React.FC<UserAddPopupProps> = (props) => {
|
||||
)}
|
||||
|
||||
{/** Transcriptionist 選択時に表示 */}
|
||||
{role === ROLE.TYPIST && (
|
||||
{role === USER_ROLES.TYPIST && (
|
||||
<div className={styles.slideSet} id="">
|
||||
<dt className={`${styles.overLine} ${styles.marginBtm0}`}>
|
||||
{t(getTranslationID("userListPage.label.addToGroup"))}
|
||||
|
||||
@ -2014,7 +2014,8 @@ tr.isSelected .menuInTable li a {
|
||||
}
|
||||
.formChange ul.chooseMember li input + label:hover,
|
||||
.formChange ul.holdMember li input + label:hover {
|
||||
background: #e6e6e6 url(../assets/images/arrow_circle_left.svg) no-repeat left center;
|
||||
background: #e6e6e6 url(../assets/images/arrow_circle_left.svg) no-repeat left
|
||||
center;
|
||||
background-size: 1.3rem;
|
||||
}
|
||||
.formChange ul.chooseMember li input:checked + label,
|
||||
@ -2025,8 +2026,8 @@ tr.isSelected .menuInTable li a {
|
||||
}
|
||||
.formChange ul.chooseMember li input:checked + label:hover,
|
||||
.formChange ul.holdMember li input:checked + label:hover {
|
||||
background: #e6e6e6 url(../assets/images/arrow_circle_right.svg) no-repeat right
|
||||
center;
|
||||
background: #e6e6e6 url(../assets/images/arrow_circle_right.svg) no-repeat
|
||||
right center;
|
||||
background-size: 1.3rem;
|
||||
}
|
||||
.formChange > p {
|
||||
|
||||
@ -137,6 +137,12 @@
|
||||
"addToGroup": "(de)Add to group (Optional)",
|
||||
"author": "(de)Author",
|
||||
"transcriptionist": "(de)Transcriptionist",
|
||||
"encryption": "(de)Encryption",
|
||||
"prompt": "(de)Prompt",
|
||||
"emailVerified": "(de)Email Verified",
|
||||
"editUser": "(de)Edit User",
|
||||
"licenseDeallocation": "(de)License Deallocation",
|
||||
"deleteUser": "(de)Delete User",
|
||||
"none": "(de)None"
|
||||
}
|
||||
},
|
||||
@ -316,4 +322,4 @@
|
||||
"orderCancel": "(de)Order Cancel"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -137,6 +137,12 @@
|
||||
"addToGroup": "Add to group (Optional)",
|
||||
"author": "Author",
|
||||
"transcriptionist": "Transcriptionist",
|
||||
"encryption": "Encryption",
|
||||
"prompt": "Prompt",
|
||||
"emailVerified": "Email Verified",
|
||||
"editUser": "Edit User",
|
||||
"licenseDeallocation": "License Deallocation",
|
||||
"deleteUser": "Delete User",
|
||||
"none": "None"
|
||||
}
|
||||
},
|
||||
@ -316,4 +322,4 @@
|
||||
"orderCancel": "Order Cancel"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -137,6 +137,12 @@
|
||||
"addToGroup": "(es)Add to group (Optional)",
|
||||
"author": "(es)Author",
|
||||
"transcriptionist": "(es)Transcriptionist",
|
||||
"encryption": "(es)Encryption",
|
||||
"prompt": "(es)Prompt",
|
||||
"emailVerified": "(es)Email Verified",
|
||||
"editUser": "(es)Edit User",
|
||||
"licenseDeallocation": "(es)License Deallocation",
|
||||
"deleteUser": "(es)Delete User",
|
||||
"none": "(es)None"
|
||||
}
|
||||
},
|
||||
@ -316,4 +322,4 @@
|
||||
"orderCancel": "(es)Order Cancel"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -137,6 +137,12 @@
|
||||
"addToGroup": "(fr)Add to group (Optional)",
|
||||
"author": "(fr)Author",
|
||||
"transcriptionist": "(fr)Transcriptionist",
|
||||
"encryption": "(fr)Encryption",
|
||||
"prompt": "(fr)Prompt",
|
||||
"emailVerified": "(fr)Email Verified",
|
||||
"editUser": "(fr)Edit User",
|
||||
"licenseDeallocation": "(fr)License Deallocation",
|
||||
"deleteUser": "(fr)Delete User",
|
||||
"none": "(fr)None"
|
||||
}
|
||||
},
|
||||
@ -316,4 +322,4 @@
|
||||
"orderCancel": "(fr)Order Cancel"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user