Merged PR 319: 画面実装(ライセンス割り当てポップアップ)
## 概要 [Task2358: 画面実装(ライセンス割り当てポップアップ)](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/2358) - 元PBI or タスクへのリンク(内容・目的などはそちらにあるはず) - 何をどう変更したか、追加したライブラリなど - ライセンス割り当てポップアップを新規に追加しました - ユーザー一覧画面に対して、対象のユーザ情報をstateに格納してライセンス割り当てポップアップを呼び出す処理を追加しました - このPull Requestでの対象/対象外 - 対象外はありません - 影響範囲(他の機能にも影響があるか) - 特にありません ## レビューポイント - 特にレビューしてほしい箇所 - selectors.tsのselectAllocatableLicensesについて、 UTC変換を伴う時刻算出を行っています。その中で共通関数化やサブ関数化を行っていますが、外だしが妥当か確認いただきたいです。 ## 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/Task2358?csf=1&web=1&e=yDndMf ## 動作確認状況 - ローカルで動作確認済 ## 補足 - 相談、参考資料などがあれば
This commit is contained in:
parent
5690b66e41
commit
6f92ef9453
File diff suppressed because it is too large
Load Diff
10
dictation_client/src/common/convertLocalToUTCDate.ts
Normal file
10
dictation_client/src/common/convertLocalToUTCDate.ts
Normal file
@ -0,0 +1,10 @@
|
||||
// タイムゾーンを考慮し、ローカルからUTCとなるよう日付を変換する関数
|
||||
export const convertLocalToUTCDate = (date: Date) => {
|
||||
const MILLISECONDS_IN_A_MINUTE = 60 * 1000;
|
||||
|
||||
const timezoneOffsetMinutes = date.getTimezoneOffset();
|
||||
const timezoneOffsetMilliseconds =
|
||||
timezoneOffsetMinutes * MILLISECONDS_IN_A_MINUTE;
|
||||
|
||||
return new Date(date.getTime() + timezoneOffsetMilliseconds);
|
||||
};
|
||||
@ -44,4 +44,6 @@ export const errorCodes = [
|
||||
"E010802", // ライセンス取り込み済みエラー
|
||||
"E010803", // ライセンス発行済みエラー
|
||||
"E010804", // ライセンス数不足エラー
|
||||
"E010805", // ライセンス有効期限切れエラー
|
||||
"E010806", // ライセンス割り当て不可エラー
|
||||
] as const;
|
||||
|
||||
@ -5,3 +5,9 @@ export const LICENSE_STATUS = {
|
||||
ALERT: "Alert",
|
||||
RENEW: "Renew",
|
||||
} as const;
|
||||
|
||||
// Licenseの割り当て状態
|
||||
export const LICENSE_ALLOCATE_STATUS = {
|
||||
ALLOCATED: "Allocated",
|
||||
NOTALLOCATED: "Not Allocated",
|
||||
} as const;
|
||||
|
||||
@ -3,7 +3,12 @@ import type { RootState } from "app/store";
|
||||
import { USER_ROLES } from "components/auth/constants";
|
||||
import { openSnackbar } from "features/ui/uiSlice";
|
||||
import { getTranslationID } from "translation";
|
||||
import { GetUsersResponse, UsersApi } from "../../api/api";
|
||||
import {
|
||||
GetUsersResponse,
|
||||
UsersApi,
|
||||
LicensesApi,
|
||||
GetAllocatableLicensesResponse,
|
||||
} from "../../api/api";
|
||||
import { Configuration } from "../../api/configuration";
|
||||
import { ErrorObject, createErrorObject } from "../../common/errors";
|
||||
|
||||
@ -189,3 +194,115 @@ export const updateUserAsync = createAsyncThunk<
|
||||
return thunkApi.rejectWithValue({ error });
|
||||
}
|
||||
});
|
||||
|
||||
export const getAllocatableLicensesAsync = createAsyncThunk<
|
||||
// 正常時の戻り値の型
|
||||
GetAllocatableLicensesResponse,
|
||||
// 引数
|
||||
void,
|
||||
{
|
||||
// rejectした時の返却値の型
|
||||
rejectValue: {
|
||||
error: ErrorObject;
|
||||
};
|
||||
}
|
||||
>("users/getAllocatableLicensesAsync", async (args, thunkApi) => {
|
||||
// apiのConfigurationを取得する
|
||||
const { getState } = thunkApi;
|
||||
const state = getState() as RootState;
|
||||
const { configuration, accessToken } = state.auth;
|
||||
const config = new Configuration(configuration);
|
||||
const licensesApi = new LicensesApi(config);
|
||||
|
||||
try {
|
||||
const res = await licensesApi.getAllocatableLicenses({
|
||||
headers: { authorization: `Bearer ${accessToken}` },
|
||||
});
|
||||
|
||||
return res.data;
|
||||
} catch (e) {
|
||||
// e ⇒ errorObjectに変換
|
||||
const error = createErrorObject(e);
|
||||
|
||||
const errorMessage = getTranslationID("common.message.internalServerError");
|
||||
|
||||
thunkApi.dispatch(
|
||||
openSnackbar({
|
||||
level: "error",
|
||||
message: errorMessage,
|
||||
})
|
||||
);
|
||||
|
||||
return thunkApi.rejectWithValue({ error });
|
||||
}
|
||||
});
|
||||
|
||||
export const allocateLicenseAsync = createAsyncThunk<
|
||||
// 正常時の戻り値の型
|
||||
{
|
||||
/* Empty Object */
|
||||
},
|
||||
// 引数
|
||||
{
|
||||
userId: number;
|
||||
newLicenseId: number;
|
||||
},
|
||||
{
|
||||
// rejectした時の返却値の型
|
||||
rejectValue: {
|
||||
error: ErrorObject;
|
||||
};
|
||||
}
|
||||
>("users/allocateLicenseAsync", async (args, thunkApi) => {
|
||||
const { userId, newLicenseId } = args;
|
||||
|
||||
// apiのConfigurationを取得する
|
||||
const { getState } = thunkApi;
|
||||
const state = getState() as RootState;
|
||||
const { configuration, accessToken } = state.auth;
|
||||
const config = new Configuration(configuration);
|
||||
const usersApi = new UsersApi(config);
|
||||
|
||||
try {
|
||||
await usersApi.allocateLicense(
|
||||
{
|
||||
userId,
|
||||
newLicenseId,
|
||||
},
|
||||
{
|
||||
headers: { authorization: `Bearer ${accessToken}` },
|
||||
}
|
||||
);
|
||||
thunkApi.dispatch(
|
||||
openSnackbar({
|
||||
level: "info",
|
||||
message: getTranslationID("common.message.success"),
|
||||
})
|
||||
);
|
||||
return {};
|
||||
} catch (e) {
|
||||
// e ⇒ errorObjectに変換
|
||||
const error = createErrorObject(e);
|
||||
|
||||
let errorMessage = getTranslationID("common.message.internalServerError");
|
||||
|
||||
if (error.code === "E010805") {
|
||||
errorMessage = getTranslationID(
|
||||
"allocateLicensePopupPage.message.licenseAllocationFailure"
|
||||
);
|
||||
} else if (error.code === "E010806") {
|
||||
errorMessage = getTranslationID(
|
||||
"allocateLicensePopupPage.message.licenseAllocationFailure"
|
||||
);
|
||||
}
|
||||
|
||||
thunkApi.dispatch(
|
||||
openSnackbar({
|
||||
level: "error",
|
||||
message: errorMessage,
|
||||
})
|
||||
);
|
||||
|
||||
return thunkApi.rejectWithValue({ error });
|
||||
}
|
||||
});
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import { RootState } from "app/store";
|
||||
import { USER_ROLES } from "components/auth/constants";
|
||||
import { convertLocalToUTCDate } from "common/convertLocalToUTCDate";
|
||||
import {
|
||||
AddUser,
|
||||
RoleType,
|
||||
@ -7,7 +8,7 @@ import {
|
||||
isLicenseStatusType,
|
||||
isRoleType,
|
||||
} from "./types";
|
||||
import { LICENSE_STATUS } from "./constants";
|
||||
import { LICENSE_STATUS, LICENSE_ALLOCATE_STATUS } from "./constants";
|
||||
|
||||
export const selectInputValidationErrors = (state: RootState) => {
|
||||
const { name, email, role, authorId, encryption, encryptionPassword } =
|
||||
@ -224,3 +225,105 @@ export const selectUpdateUser = (state: RootState) =>
|
||||
|
||||
export const selectHasPasswordMask = (state: RootState) =>
|
||||
state.user.apps.hasPasswordMask;
|
||||
|
||||
export const selectLicenseAllocateUserId = (state: RootState) =>
|
||||
state.user.apps.licenseAllocateUser.id;
|
||||
|
||||
export const selectLicenseAllocateUserEmail = (state: RootState) =>
|
||||
state.user.apps.licenseAllocateUser.email;
|
||||
|
||||
export const selectLicenseAllocateUserName = (state: RootState) =>
|
||||
state.user.apps.licenseAllocateUser.name;
|
||||
|
||||
export const selectLicenseAllocateUserAuthorId = (state: RootState) =>
|
||||
state.user.apps.licenseAllocateUser.authorId;
|
||||
|
||||
export const selectLicenseAllocateUserStatus = (state: RootState) =>
|
||||
// ライセンスが割り当てられてるかどうかのステータスを返却する。NORMAL,ALERT,RENEWはすべてライセンス割り当て扱い
|
||||
state.user.apps.licenseAllocateUser.licenseStatus === LICENSE_STATUS.NOLICENSE
|
||||
? LICENSE_ALLOCATE_STATUS.NOTALLOCATED
|
||||
: LICENSE_ALLOCATE_STATUS.ALLOCATED;
|
||||
|
||||
export const selectLicenseAllocateUserExpirationDate = (state: RootState) => {
|
||||
const { licenseStatus, remaining, expiration } =
|
||||
state.user.apps.licenseAllocateUser;
|
||||
|
||||
// ライセンスが割当たっていない場合は-、割当たってる場合はremaining(expiration)の形式で返却
|
||||
if (licenseStatus === LICENSE_STATUS.NOLICENSE) {
|
||||
return "-";
|
||||
}
|
||||
|
||||
return `${expiration}(${remaining})`;
|
||||
};
|
||||
|
||||
export const selectSelectedlicenseId = (state: RootState) =>
|
||||
state.user.apps.selectedlicenseId;
|
||||
|
||||
export const selectAllocatableLicenses = (state: RootState) => {
|
||||
const { allocatableLicenses } = state.user.domain;
|
||||
|
||||
// licenseIdはそのまま返却、expiryDateは「nullならundifined」「null以外ならyyyy/mm/dd(現在との差分日数)」を返却
|
||||
const transformedLicenses = allocatableLicenses.map((license) => ({
|
||||
licenseId: license.licenseId,
|
||||
expiryDate: license.expiryDate
|
||||
? calculateExpiryDate(license.expiryDate)
|
||||
: undefined,
|
||||
}));
|
||||
|
||||
return transformedLicenses;
|
||||
};
|
||||
|
||||
export const selectInputValidationErrorsForLicenseAcclocation = (
|
||||
state: RootState
|
||||
) => {
|
||||
// 必須項目のチェック(License Acclocation画面用)
|
||||
|
||||
// License available選択チェック
|
||||
// 初期値である0と、「Select a license」選択時のNaNの場合エラーとする
|
||||
const hasErrorEmptyLicense =
|
||||
state.user.apps.selectedlicenseId === 0 ||
|
||||
Number.isNaN(state.user.apps.selectedlicenseId);
|
||||
|
||||
return {
|
||||
hasErrorEmptyLicense,
|
||||
};
|
||||
};
|
||||
|
||||
// 日付の差分を計算するサブ関数
|
||||
const calculateExpiryDate = (expiryDate: string) => {
|
||||
const MILLISECONDS_IN_A_DAY = 24 * 60 * 60 * 1000; // 1日のミリ秒数
|
||||
|
||||
const currentDate = new Date();
|
||||
const expirationDate = new Date(expiryDate);
|
||||
|
||||
// タイムゾーンオフセットを考慮して、ローカルタイムでの日付を取得
|
||||
const currentDateLocal = convertLocalToUTCDate(currentDate);
|
||||
const expirationDateLocal = convertLocalToUTCDate(expirationDate);
|
||||
|
||||
// ライセンスは時刻を考慮しないので時分秒を意識しない日付を取得する
|
||||
const currentDateWithoutTime = new Date(
|
||||
currentDateLocal.getFullYear(),
|
||||
currentDateLocal.getMonth(),
|
||||
currentDateLocal.getDate()
|
||||
);
|
||||
|
||||
const expirationDateWithoutTime = new Date(
|
||||
expirationDateLocal.getFullYear(),
|
||||
expirationDateLocal.getMonth(),
|
||||
expirationDateLocal.getDate()
|
||||
);
|
||||
|
||||
// 差分日数を取得
|
||||
const timeDifference =
|
||||
expirationDateWithoutTime.getTime() - currentDateWithoutTime.getTime();
|
||||
const daysDifference = Math.ceil(timeDifference / MILLISECONDS_IN_A_DAY);
|
||||
|
||||
// yyyy/mm/dd形式の年月日を取得
|
||||
const expirationYear = expirationDateWithoutTime.getFullYear();
|
||||
const expirationMonth = expirationDateWithoutTime.getMonth() + 1; // getMonth() の結果は0から始まるため、1を足して実際の月に合わせる
|
||||
const expirationDay = expirationDateWithoutTime.getDate();
|
||||
|
||||
const formattedExpirationDate = `${expirationYear}/${expirationMonth}/${expirationDay}`;
|
||||
|
||||
return `${formattedExpirationDate} (${daysDifference})`;
|
||||
};
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { User } from "../../api/api";
|
||||
import { AddUser, UpdateUser } from "./types";
|
||||
import { User, AllocatableLicenseInfo } from "../../api/api";
|
||||
import { AddUser, UpdateUser, LicenseAllocateUser } from "./types";
|
||||
|
||||
export interface UsersState {
|
||||
domain: Domain;
|
||||
@ -8,12 +8,15 @@ export interface UsersState {
|
||||
|
||||
export interface Domain {
|
||||
users: User[];
|
||||
allocatableLicenses: AllocatableLicenseInfo[];
|
||||
}
|
||||
|
||||
export interface Apps {
|
||||
addUser: AddUser;
|
||||
selectedUser: UpdateUser;
|
||||
updateUser: UpdateUser;
|
||||
licenseAllocateUser: LicenseAllocateUser;
|
||||
selectedlicenseId: number;
|
||||
hasPasswordMask: boolean;
|
||||
isLoading: boolean;
|
||||
}
|
||||
|
||||
@ -50,6 +50,16 @@ export interface UpdateUser {
|
||||
notification: boolean;
|
||||
}
|
||||
|
||||
export interface LicenseAllocateUser {
|
||||
id: number;
|
||||
name: string;
|
||||
email: string;
|
||||
authorId: string;
|
||||
licenseStatus: LicenseStatusType | string;
|
||||
expiration: string;
|
||||
remaining: number | string;
|
||||
}
|
||||
|
||||
export type RoleType = typeof USER_ROLES[keyof typeof USER_ROLES];
|
||||
|
||||
// 受け取った値がUSER_ROLESの型であるかどうかを判定する
|
||||
|
||||
@ -1,11 +1,16 @@
|
||||
import { PayloadAction, createSlice } from "@reduxjs/toolkit";
|
||||
import { USER_ROLES } from "components/auth/constants";
|
||||
import { UsersState } from "./state";
|
||||
import { addUserAsync, listUsersAsync, updateUserAsync } from "./operations";
|
||||
import { RoleType } from "./types";
|
||||
import {
|
||||
addUserAsync,
|
||||
listUsersAsync,
|
||||
updateUserAsync,
|
||||
getAllocatableLicensesAsync,
|
||||
} from "./operations";
|
||||
import { RoleType, UserView } from "./types";
|
||||
|
||||
const initialState: UsersState = {
|
||||
domain: { users: [] },
|
||||
domain: { users: [], allocatableLicenses: [] },
|
||||
apps: {
|
||||
updateUser: {
|
||||
id: 0,
|
||||
@ -45,6 +50,16 @@ const initialState: UsersState = {
|
||||
prompt: false,
|
||||
encryptionPassword: "",
|
||||
},
|
||||
licenseAllocateUser: {
|
||||
id: 0,
|
||||
name: "",
|
||||
email: "",
|
||||
authorId: "",
|
||||
licenseStatus: "",
|
||||
expiration: "",
|
||||
remaining: "",
|
||||
},
|
||||
selectedlicenseId: 0,
|
||||
hasPasswordMask: false,
|
||||
isLoading: false,
|
||||
},
|
||||
@ -219,6 +234,30 @@ export const userSlice = createSlice({
|
||||
cleanupUpdateUser: (state) => {
|
||||
state.apps.updateUser = initialState.apps.updateUser;
|
||||
},
|
||||
changeLicenseAllocateUser: (
|
||||
state,
|
||||
action: PayloadAction<{ selectedUser: UserView }>
|
||||
) => {
|
||||
const { selectedUser } = action.payload;
|
||||
state.apps.licenseAllocateUser.id = selectedUser.id;
|
||||
state.apps.licenseAllocateUser.name = selectedUser.name;
|
||||
state.apps.licenseAllocateUser.email = selectedUser.email;
|
||||
state.apps.licenseAllocateUser.authorId = selectedUser.authorId;
|
||||
state.apps.licenseAllocateUser.licenseStatus = selectedUser.licenseStatus;
|
||||
state.apps.licenseAllocateUser.expiration = selectedUser.expiration;
|
||||
state.apps.licenseAllocateUser.remaining = selectedUser.remaining;
|
||||
},
|
||||
changeSelectedlicenseId: (
|
||||
state,
|
||||
action: PayloadAction<{ selectedlicenseId: number }>
|
||||
) => {
|
||||
const { selectedlicenseId } = action.payload;
|
||||
state.apps.selectedlicenseId = selectedlicenseId;
|
||||
},
|
||||
cleanupLicenseAllocateInfo: (state) => {
|
||||
state.apps.licenseAllocateUser = initialState.apps.licenseAllocateUser;
|
||||
state.apps.selectedlicenseId = initialState.apps.selectedlicenseId;
|
||||
},
|
||||
},
|
||||
extraReducers: (builder) => {
|
||||
builder.addCase(listUsersAsync.pending, (state) => {
|
||||
@ -249,6 +288,16 @@ export const userSlice = createSlice({
|
||||
builder.addCase(updateUserAsync.rejected, (state) => {
|
||||
state.apps.isLoading = false;
|
||||
});
|
||||
builder.addCase(getAllocatableLicensesAsync.pending, (state) => {
|
||||
state.apps.isLoading = true;
|
||||
});
|
||||
builder.addCase(getAllocatableLicensesAsync.fulfilled, (state, action) => {
|
||||
state.domain.allocatableLicenses = action.payload.allocatableLicenses;
|
||||
state.apps.isLoading = false;
|
||||
});
|
||||
builder.addCase(getAllocatableLicensesAsync.rejected, (state) => {
|
||||
state.apps.isLoading = false;
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
@ -275,6 +324,9 @@ export const {
|
||||
changePrompt,
|
||||
changeEncryptionPassword,
|
||||
changeHasPasswordMask,
|
||||
changeLicenseAllocateUser,
|
||||
changeSelectedlicenseId,
|
||||
cleanupLicenseAllocateInfo,
|
||||
} = userSlice.actions;
|
||||
|
||||
export default userSlice.reducer;
|
||||
|
||||
281
dictation_client/src/pages/UserListPage/allocateLicensePopup.tsx
Normal file
281
dictation_client/src/pages/UserListPage/allocateLicensePopup.tsx
Normal file
@ -0,0 +1,281 @@
|
||||
import { AppDispatch } from "app/store";
|
||||
import React, { useCallback, useEffect, useState } from "react";
|
||||
import styles from "styles/app.module.scss";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import {
|
||||
selectLicenseAllocateUserId,
|
||||
selectLicenseAllocateUserEmail,
|
||||
selectLicenseAllocateUserName,
|
||||
selectLicenseAllocateUserAuthorId,
|
||||
selectLicenseAllocateUserStatus,
|
||||
selectLicenseAllocateUserExpirationDate,
|
||||
selectAllocatableLicenses,
|
||||
getAllocatableLicensesAsync,
|
||||
changeSelectedlicenseId,
|
||||
selectSelectedlicenseId,
|
||||
selectIsLoading,
|
||||
listUsersAsync,
|
||||
allocateLicenseAsync,
|
||||
cleanupLicenseAllocateInfo,
|
||||
selectInputValidationErrorsForLicenseAcclocation,
|
||||
LICENSE_ALLOCATE_STATUS,
|
||||
} from "features/user";
|
||||
import { getTranslationID } from "translation";
|
||||
import close from "../../assets/images/close.svg";
|
||||
import progress_activit from "../../assets/images/progress_activit.svg";
|
||||
|
||||
interface AllocateLicensePopupProps {
|
||||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
export const AllocateLicensePopup: React.FC<AllocateLicensePopupProps> = (
|
||||
props
|
||||
) => {
|
||||
const { isOpen, onClose } = props;
|
||||
const dispatch: AppDispatch = useDispatch();
|
||||
const { t } = useTranslation();
|
||||
|
||||
const { hasErrorEmptyLicense } = useSelector(
|
||||
selectInputValidationErrorsForLicenseAcclocation
|
||||
);
|
||||
|
||||
const id = useSelector(selectLicenseAllocateUserId);
|
||||
const email = useSelector(selectLicenseAllocateUserEmail);
|
||||
const name = useSelector(selectLicenseAllocateUserName);
|
||||
const authorId = useSelector(selectLicenseAllocateUserAuthorId);
|
||||
const status = useSelector(selectLicenseAllocateUserStatus);
|
||||
const expirationDate = useSelector(selectLicenseAllocateUserExpirationDate);
|
||||
const allocatableLicenses = useSelector(selectAllocatableLicenses);
|
||||
const selectedlicenseId = useSelector(selectSelectedlicenseId);
|
||||
|
||||
const [isPushAllocateButton, setIsPushAllocateButton] =
|
||||
useState<boolean>(false);
|
||||
|
||||
const isLoading = useSelector(selectIsLoading);
|
||||
|
||||
useEffect(() => {
|
||||
if (isOpen) {
|
||||
// 画面起動時、有効なライセンスを取得する
|
||||
dispatch(getAllocatableLicensesAsync());
|
||||
}
|
||||
}, [isOpen, dispatch]);
|
||||
|
||||
// ポップアップを閉じる処理
|
||||
const closePopup = useCallback(() => {
|
||||
if (isLoading) {
|
||||
return;
|
||||
}
|
||||
setIsPushAllocateButton(false);
|
||||
dispatch(cleanupLicenseAllocateInfo());
|
||||
onClose();
|
||||
}, [isLoading, onClose, dispatch]);
|
||||
|
||||
const onAllocateLicense = useCallback(async () => {
|
||||
setIsPushAllocateButton(true);
|
||||
|
||||
// 入力不正時は何もしない
|
||||
if (hasErrorEmptyLicense) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { meta } = await dispatch(
|
||||
allocateLicenseAsync({ userId: id, newLicenseId: selectedlicenseId })
|
||||
);
|
||||
setIsPushAllocateButton(false);
|
||||
|
||||
if (meta.requestStatus === "fulfilled") {
|
||||
closePopup();
|
||||
dispatch(listUsersAsync());
|
||||
}
|
||||
}, [dispatch, closePopup, id, selectedlicenseId, hasErrorEmptyLicense]);
|
||||
|
||||
return (
|
||||
<div className={`${styles.modal} ${isOpen ? styles.isShow : ""}`}>
|
||||
<div className={styles.modalBox}>
|
||||
<p className={styles.modalTitle}>
|
||||
{t(getTranslationID("allocateLicensePopupPage.label.title"))}
|
||||
<button type="button" onClick={closePopup}>
|
||||
<img src={close} className={styles.modalTitleIcon} alt="close" />
|
||||
</button>
|
||||
</p>
|
||||
<form action="" name="" method="" className={styles.form}>
|
||||
<dl className={`${styles.formList} ${styles.hasbg}`}>
|
||||
<dt className={styles.formTitle}>
|
||||
{t(
|
||||
getTranslationID(
|
||||
"allocateLicensePopupPage.label.personalInformation"
|
||||
)
|
||||
)}
|
||||
</dt>
|
||||
<dt>
|
||||
{t(getTranslationID("allocateLicensePopupPage.label.email"))}
|
||||
</dt>
|
||||
<dd>
|
||||
<input
|
||||
type="text"
|
||||
size={40}
|
||||
name=""
|
||||
value={email}
|
||||
className={styles.formInput}
|
||||
readOnly
|
||||
/>
|
||||
</dd>
|
||||
<dt>
|
||||
{t(getTranslationID("allocateLicensePopupPage.label.name"))}
|
||||
</dt>
|
||||
<dd>
|
||||
<input
|
||||
type="text"
|
||||
size={40}
|
||||
name=""
|
||||
value={name}
|
||||
className={styles.formInput}
|
||||
readOnly
|
||||
/>
|
||||
</dd>
|
||||
<dt>
|
||||
{t(getTranslationID("allocateLicensePopupPage.label.authorID"))}
|
||||
</dt>
|
||||
<dd>
|
||||
<input
|
||||
type="text"
|
||||
size={40}
|
||||
name=""
|
||||
value={authorId}
|
||||
className={styles.formInput}
|
||||
readOnly
|
||||
/>
|
||||
</dd>
|
||||
<dt>
|
||||
{t(getTranslationID("allocateLicensePopupPage.label.status"))}
|
||||
</dt>
|
||||
<dd>
|
||||
<input
|
||||
type="text"
|
||||
size={40}
|
||||
name=""
|
||||
value={(() => {
|
||||
switch (status) {
|
||||
case LICENSE_ALLOCATE_STATUS.ALLOCATED:
|
||||
return t(
|
||||
getTranslationID(
|
||||
"allocateLicensePopupPage.label.allocated"
|
||||
)
|
||||
);
|
||||
case LICENSE_ALLOCATE_STATUS.NOTALLOCATED:
|
||||
return t(
|
||||
getTranslationID(
|
||||
"allocateLicensePopupPage.label.notAllocated"
|
||||
)
|
||||
);
|
||||
default:
|
||||
return status;
|
||||
}
|
||||
})()}
|
||||
className={styles.formInput}
|
||||
readOnly
|
||||
/>
|
||||
</dd>
|
||||
<dt>
|
||||
{t(
|
||||
getTranslationID(
|
||||
"allocateLicensePopupPage.label.expirationDate"
|
||||
)
|
||||
)}
|
||||
</dt>
|
||||
<dd>
|
||||
<input
|
||||
type="text"
|
||||
size={40}
|
||||
name=""
|
||||
value={expirationDate}
|
||||
className={styles.formInput}
|
||||
readOnly
|
||||
/>
|
||||
</dd>
|
||||
<dt className={styles.formTitle}>
|
||||
{t(
|
||||
getTranslationID(
|
||||
"allocateLicensePopupPage.label.licenseInformation"
|
||||
)
|
||||
)}
|
||||
</dt>
|
||||
<dt>
|
||||
{t(
|
||||
getTranslationID(
|
||||
"allocateLicensePopupPage.label.licenseAvailable"
|
||||
)
|
||||
)}
|
||||
</dt>
|
||||
<dd>
|
||||
<select
|
||||
name=""
|
||||
className={`${styles.formInput} ${
|
||||
isPushAllocateButton && hasErrorEmptyLicense && styles.isError
|
||||
}`}
|
||||
onChange={(event) => {
|
||||
dispatch(
|
||||
changeSelectedlicenseId({
|
||||
selectedlicenseId: parseInt(event.target.value, 10),
|
||||
})
|
||||
);
|
||||
}}
|
||||
value={selectedlicenseId ?? ""}
|
||||
>
|
||||
<option value="">
|
||||
{t(
|
||||
getTranslationID(
|
||||
"allocateLicensePopupPage.label.dropDownHeading"
|
||||
)
|
||||
)}
|
||||
</option>
|
||||
{allocatableLicenses.map((x) => (
|
||||
<option key={x.licenseId} value={x.licenseId}>
|
||||
{/* one yearの場合はそれに相当する内容、それ以外の場合は値をそのまま表示 */}
|
||||
{x.expiryDate
|
||||
? x.expiryDate
|
||||
: t(
|
||||
getTranslationID(
|
||||
"allocateLicensePopupPage.label.oneYear"
|
||||
)
|
||||
)}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
{isPushAllocateButton && hasErrorEmptyLicense && (
|
||||
<span className={styles.formError}>
|
||||
{t(
|
||||
getTranslationID(
|
||||
"allocateLicensePopupPage.message.inputEmptyError"
|
||||
)
|
||||
)}
|
||||
</span>
|
||||
)}
|
||||
</dd>
|
||||
<dd className={`${styles.full} ${styles.alignCenter}`}>
|
||||
<input
|
||||
type="button"
|
||||
name="submit"
|
||||
value={t(
|
||||
getTranslationID(
|
||||
"allocateLicensePopupPage.label.allocateLicense"
|
||||
)
|
||||
)}
|
||||
className={`${styles.formSubmit} ${styles.marginBtm1} ${styles.isActive}`}
|
||||
onClick={onAllocateLicense}
|
||||
/>
|
||||
<img
|
||||
style={{ display: isLoading ? "inline" : "none" }}
|
||||
src={progress_activit}
|
||||
className={styles.icLoading}
|
||||
alt="Loading"
|
||||
/>
|
||||
</dd>
|
||||
</dl>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@ -12,17 +12,21 @@ import {
|
||||
} from "features/user";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { getTranslationID } from "translation";
|
||||
import { isLicenseStatusType } from "features/user/types";
|
||||
import { isLicenseStatusType, UserView } from "features/user/types";
|
||||
import { LICENSE_STATUS } from "features/user/constants";
|
||||
import { isApproveTier } from "features/auth/utils";
|
||||
import { TIERS } from "components/auth/constants";
|
||||
import { changeUpdateUser } from "features/user/userSlice";
|
||||
import {
|
||||
changeUpdateUser,
|
||||
changeLicenseAllocateUser,
|
||||
} from "features/user/userSlice";
|
||||
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();
|
||||
@ -30,6 +34,8 @@ const UserListPage: React.FC = (): JSX.Element => {
|
||||
|
||||
const [isPopupOpen, setIsPopupOpen] = useState(false);
|
||||
const [isUpdatePopupOpen, setIsUpdatePopupOpen] = useState(false);
|
||||
const [isAllocateLicensePopupOpen, setIsAllocateLicensePopupOpen] =
|
||||
useState(false);
|
||||
|
||||
const onOpen = useCallback(() => {
|
||||
setIsPopupOpen(true);
|
||||
@ -43,6 +49,14 @@ const UserListPage: React.FC = (): JSX.Element => {
|
||||
[setIsUpdatePopupOpen, dispatch]
|
||||
);
|
||||
|
||||
const onAllocateLicensePopupOpen = useCallback(
|
||||
(selectedUser: UserView) => {
|
||||
setIsAllocateLicensePopupOpen(true);
|
||||
dispatch(changeLicenseAllocateUser({ selectedUser }));
|
||||
},
|
||||
[setIsAllocateLicensePopupOpen, dispatch]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
// ユーザ一覧取得処理を呼び出す
|
||||
dispatch(listUsersAsync());
|
||||
@ -67,6 +81,12 @@ const UserListPage: React.FC = (): JSX.Element => {
|
||||
setIsPopupOpen(false);
|
||||
}}
|
||||
/>
|
||||
<AllocateLicensePopup
|
||||
isOpen={isAllocateLicensePopupOpen}
|
||||
onClose={() => {
|
||||
setIsAllocateLicensePopupOpen(false);
|
||||
}}
|
||||
/>
|
||||
<div className={styles.wrap}>
|
||||
<Header userName="XXXXXX" />
|
||||
<UpdateTokenTimer />
|
||||
@ -162,7 +182,12 @@ const UserListPage: React.FC = (): JSX.Element => {
|
||||
{isTier5 && (
|
||||
<>
|
||||
<li>
|
||||
<a href="">
|
||||
{/* 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"
|
||||
|
||||
@ -330,5 +330,27 @@
|
||||
"notEnoughOfNumberOfLicense": "(de)ライセンスが不足しているため、発行することができませんでした。ライセンスの注文を行ってください。",
|
||||
"alreadyIssueLicense": "(de)すでに発行済みの注文です。画面を更新してください。"
|
||||
}
|
||||
},
|
||||
"allocateLicensePopupPage": {
|
||||
"label": {
|
||||
"title": "(de)License Allocation",
|
||||
"personalInformation": "(de)Personal Information",
|
||||
"email": "(de)Email",
|
||||
"name": "(de)Name",
|
||||
"authorID": "(de)Author ID",
|
||||
"status": "(de)Status",
|
||||
"allocated": "(de)Allocated",
|
||||
"notAllocated": "(de)Not Allocated",
|
||||
"expirationDate": "(de)Expiration date",
|
||||
"licenseInformation": "(de)License Information",
|
||||
"licenseAvailable": "(de)License available",
|
||||
"dropDownHeading": "(de)Select a license.",
|
||||
"oneYear": "(de)One Year",
|
||||
"allocateLicense": "(de)OK"
|
||||
},
|
||||
"message": {
|
||||
"inputEmptyError": "(de)この項目の入力は必須です。入力してください。",
|
||||
"licenseAllocationFailure": "(de)ライセンスの割り当てに失敗しました。他のライセンスを選択して再度割り当てをしてください。"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -330,5 +330,27 @@
|
||||
"notEnoughOfNumberOfLicense": "ライセンスが不足しているため、発行することができませんでした。ライセンスの注文を行ってください。",
|
||||
"alreadyIssueLicense": "すでに発行済みの注文です。画面を更新してください。"
|
||||
}
|
||||
},
|
||||
"allocateLicensePopupPage": {
|
||||
"label": {
|
||||
"title": "License Allocation",
|
||||
"personalInformation": "Personal Information",
|
||||
"email": "Email",
|
||||
"name": "Name",
|
||||
"authorID": "Author ID",
|
||||
"status": "Status",
|
||||
"allocated": "Allocated",
|
||||
"notAllocated": "Not Allocated",
|
||||
"expirationDate": "Expiration date",
|
||||
"licenseInformation": "License Information",
|
||||
"licenseAvailable": "License available",
|
||||
"dropDownHeading": "Select a license.",
|
||||
"oneYear": "One Year",
|
||||
"allocateLicense": "OK"
|
||||
},
|
||||
"message": {
|
||||
"inputEmptyError": "この項目の入力は必須です。入力してください。",
|
||||
"licenseAllocationFailure": "ライセンスの割り当てに失敗しました。他のライセンスを選択して再度割り当てをしてください。"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -330,5 +330,27 @@
|
||||
"notEnoughOfNumberOfLicense": "(es)ライセンスが不足しているため、発行することができませんでした。ライセンスの注文を行ってください。",
|
||||
"alreadyIssueLicense": "(es)すでに発行済みの注文です。画面を更新してください。"
|
||||
}
|
||||
},
|
||||
"allocateLicensePopupPage": {
|
||||
"label": {
|
||||
"title": "(es)License Allocation",
|
||||
"personalInformation": "(es)Personal Information",
|
||||
"email": "(es)Email",
|
||||
"name": "(es)Name",
|
||||
"authorID": "(es)Author ID",
|
||||
"status": "(es)Status",
|
||||
"allocated": "(es)Allocated",
|
||||
"notAllocated": "(es)Not Allocated",
|
||||
"expirationDate": "(es)Expiration date",
|
||||
"licenseInformation": "(es)License Information",
|
||||
"licenseAvailable": "(es)License available",
|
||||
"dropDownHeading": "(es)Select a license.",
|
||||
"oneYear": "(es)One Year",
|
||||
"allocateLicense": "(es)OK"
|
||||
},
|
||||
"message": {
|
||||
"inputEmptyError": "(es)この項目の入力は必須です。入力してください。",
|
||||
"licenseAllocationFailure": "(es)ライセンスの割り当てに失敗しました。他のライセンスを選択して再度割り当てをしてください。"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -330,5 +330,27 @@
|
||||
"notEnoughOfNumberOfLicense": "(fr)ライセンスが不足しているため、発行することができませんでした。ライセンスの注文を行ってください。",
|
||||
"alreadyIssueLicense": "(fr)すでに発行済みの注文です。画面を更新してください。"
|
||||
}
|
||||
},
|
||||
"allocateLicensePopupPage": {
|
||||
"label": {
|
||||
"title": "(fr)License Allocation",
|
||||
"personalInformation": "(fr)Personal Information",
|
||||
"email": "(fr)Email",
|
||||
"name": "(fr)Name",
|
||||
"authorID": "(fr)Author ID",
|
||||
"status": "(fr)Status",
|
||||
"allocated": "(fr)Allocated",
|
||||
"notAllocated": "(fr)Not Allocated",
|
||||
"expirationDate": "(fr)Expiration date",
|
||||
"licenseInformation": "(fr)License Information",
|
||||
"licenseAvailable": "(fr)License available",
|
||||
"dropDownHeading": "(fr)Select a license.",
|
||||
"oneYear": "(fr)One Year",
|
||||
"allocateLicense": "(fr)OK"
|
||||
},
|
||||
"message": {
|
||||
"inputEmptyError": "(fr)この項目の入力は必須です。入力してください。",
|
||||
"licenseAllocationFailure": "(fr)ライセンスの割り当てに失敗しました。他のライセンスを選択して再度割り当てをしてください。"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user