import { RootState } from "app/store"; import { USER_ROLES } from "components/auth/constants"; import { convertLocalToUTCDate } from "common/convertLocalToUTCDate"; import { AddUser, LicenseStatusType, RoleType, UserView, isLicenseStatusType, isRoleType, } from "./types"; import { LICENSE_STATUS, LICENSE_ALLOCATE_STATUS } from "./constants"; export const selectInputValidationErrors = (state: RootState) => { const { name, email, role, authorId, encryption, encryptionPassword } = state.user.apps.addUser; // 必須項目のチェック const hasErrorEmptyName = name === ""; const hasErrorEmptyEmail = email === ""; // Authorの場合、AuthorIDが必須(空文字,undefinedは不可) const hasErrorEmptyAuthorId = role === USER_ROLES.AUTHOR && (authorId === "" || !authorId); const hasErrorIncorrectAuthorId = checkErrorIncorrectAuthorId( authorId ?? undefined, role ); const emailPattern = /^[a-zA-Z0-9!#$%&'_`/=~+\-?^{|}.]+@[a-zA-Z0-9!#$%&'_`/=~+\-?^{|}.]*\.[a-zA-Z0-9!#$%&'_`/=~+\-?^{|}.]*[a-zA-Z]$/; const hasErrorIncorrectEmail = email.match(emailPattern)?.length !== 1; const hasErrorIncorrectEncryptionPassword = checkErrorIncorrectEncryptionPassword(encryptionPassword, role, encryption); return { hasErrorEmptyName, hasErrorEmptyEmail, hasErrorEmptyAuthorId, hasErrorIncorrectEmail, hasErrorIncorrectAuthorId, hasErrorIncorrectEncryptionPassword, }; }; export const selectUpdateValidationErrors = (state: RootState) => { const { role, authorId, encryption, encryptionPassword } = state.user.apps.updateUser; const { encryption: initEncryption } = state.user.apps.selectedUser; // Authorの場合、AuthorIDが必須(空文字,undefinedは不可) const hasErrorEmptyAuthorId = role === USER_ROLES.AUTHOR && (authorId === "" || !authorId); const hasErrorIncorrectAuthorId = checkErrorIncorrectAuthorId( authorId ?? undefined, role ); let hasErrorIncorrectEncryptionPassword = false; const passwordError = checkErrorIncorrectEncryptionPassword( encryptionPassword, role, encryption ); if (passwordError) { // 最初にEncryptionがtrueで、EncryptionPassword変更されていない場合はエラーとしない if (initEncryption && encryptionPassword === undefined) { hasErrorIncorrectEncryptionPassword = false; } else { hasErrorIncorrectEncryptionPassword = true; } } return { hasErrorEmptyAuthorId, hasErrorIncorrectAuthorId, hasErrorIncorrectEncryptionPassword, }; }; // encreyptionPasswordのチェック const checkErrorIncorrectEncryptionPassword = ( encryptionPassword: string | undefined, role: RoleType, encryption: boolean | undefined ): boolean => { // roleがAuthor以外の場合、チェックしない if (role !== USER_ROLES.AUTHOR) { return false; } // roleがAuthorかつencryptionがfalseの場合、チェックしない if (!encryption) { return false; } // encryptionPasswordがundefined,空文字の場合、エラー if (!encryptionPassword || encryptionPassword === "") { return true; } // encryptionPasswordがルールに則していない場合、エラー const regex = /^[!-~]{4,16}$/; if (!regex.test(encryptionPassword)) { return true; } // チェックを通ったらエラーではない return false; }; export const checkErrorIncorrectAuthorId = ( authorId: string | undefined, role: string ): boolean => { if (!authorId || role !== USER_ROLES.AUTHOR) { return false; } // 半角英数字と_の組み合わせで16文字まで const charaTypePattern = /^[A-Z0-9_]{1,16}$/; const charaType = new RegExp(charaTypePattern).test(authorId); return !charaType; }; export const selectName = (state: RootState) => state.user.apps.addUser.name; export const selectEmail = (state: RootState) => state.user.apps.addUser.email; export const selectRole = (state: RootState) => state.user.apps.addUser.role; export const selectAuthorId = (state: RootState) => state.user.apps.addUser.authorId; export const selectAutoRenew = (state: RootState) => state.user.apps.addUser.autoRenew; export const selectNotification = (state: RootState) => state.user.apps.addUser.notification; // AddUserを返却する export const selectAddUser = (state: RootState): AddUser => state.user.apps.addUser; // usersからUserViewに変換して返却する export const selectUserViews = (state: RootState): UserView[] => { const { users } = state.user.domain; const userViews = users.map((user): UserView => { const { role, authorId, encryption, prompt, typistGroupName, 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 ); // licenseStatus,remaining,expirationの値を変換する const { licenseStatus: convertedLicenseStatus, expiration: convertedExpiration, remaining: convertedRemaining, } = convertValueBasedOnLicenseStatus(licenseStatus, expiration, remaining); // restのid以外をUserViewに追加する return { typistGroupName: convertedValues.typistGroupName, prompt: convertedValues.prompt, encryption: convertedValues.encryption, authorId: convertedValues.authorId, // roleの一文字目を大文字に変換する role: role.charAt(0).toUpperCase() + role.slice(1), licenseStatus: convertedLicenseStatus, expiration: convertedExpiration, remaining: convertedRemaining, ...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: "-", }; }; export const selectUpdateUser = (state: RootState) => state.user.apps.updateUser; 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})`; }; // licenseStatus,remainingに応じて値を変換する const convertValueBasedOnLicenseStatus = ( licenseStatus: LicenseStatusType, expiration?: string, remaining?: number ): { licenseStatus: LicenseStatusType; expiration?: string; remaining?: number; } => { if (licenseStatus === LICENSE_STATUS.NOLICENSE) { return { licenseStatus, expiration: undefined, remaining: undefined, }; } // remainingが存在し、かつ負の値である場合は、NoLicenseとする if (remaining && remaining < 0) { return { licenseStatus: LICENSE_STATUS.NOLICENSE, expiration: undefined, remaining: undefined, }; } if ( licenseStatus === LICENSE_STATUS.RENEW || licenseStatus === LICENSE_STATUS.NORMAL || licenseStatus === LICENSE_STATUS.ALERT ) { return { licenseStatus, expiration, remaining, }; } // ここに到達することはない return { licenseStatus, expiration: undefined, remaining: undefined, }; };