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:
saito.k 2023-08-01 01:54:38 +00:00
parent 8be20b7ca8
commit c7d34e1ccb
16 changed files with 3799 additions and 4880 deletions

File diff suppressed because it is too large Load Diff

View 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

View File

@ -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];

View File

@ -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: "-",
};
};

View 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);

View File

@ -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,

View File

@ -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";

View File

@ -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";

View File

@ -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;

View File

@ -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"))}

View File

@ -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 {

View File

@ -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"
}
}
}
}

View File

@ -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"
}
}
}
}

View File

@ -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"
}
}
}
}

View File

@ -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"
}
}
}
}