Merged PR 226: テキストボックス分割実装
## 概要 [Task2168: テキストボックス分割実装](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/2168) タスク 2168: テキストボックス分割実装 ライセンスキー入力のボックスを5分割に変更。 細かい画面レイアウトについては後々デザイナーさんに依頼することになるかと思いますので、レビュー対象外でお願いします。 ## レビューポイント 入力時の挙動に過不足がないか。 ## 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/Task2168?csf=1&web=1&e=oRgAOK ## 動作確認状況 - ローカルで動作確認済み ## 補足 なし
This commit is contained in:
parent
e4ba5229df
commit
9528bb1ad6
@ -1,10 +1,8 @@
|
||||
import { PayloadAction, createSlice } from "@reduxjs/toolkit";
|
||||
import { createSlice } from "@reduxjs/toolkit";
|
||||
import { LicenseCardActivateState } from "./state";
|
||||
import { activateCardLicenseAsync } from "./operations";
|
||||
|
||||
const initialState: LicenseCardActivateState = {
|
||||
apps: {
|
||||
keyLicense: "",
|
||||
isLoading: false,
|
||||
},
|
||||
};
|
||||
@ -12,20 +10,12 @@ export const licenseCardActivateSlice = createSlice({
|
||||
name: "licenseCardActivate",
|
||||
initialState,
|
||||
reducers: {
|
||||
changeKeyLicense: (
|
||||
state,
|
||||
action: PayloadAction<{ keyLicense: string }>
|
||||
) => {
|
||||
const { keyLicense } = action.payload;
|
||||
state.apps.keyLicense = keyLicense.toUpperCase();
|
||||
},
|
||||
cleanupApps: (state) => {
|
||||
state.apps = initialState.apps;
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
export const { changeKeyLicense, cleanupApps } =
|
||||
licenseCardActivateSlice.actions;
|
||||
export const { cleanupApps } = licenseCardActivateSlice.actions;
|
||||
|
||||
export default licenseCardActivateSlice.reducer;
|
||||
|
||||
@ -1,18 +1,4 @@
|
||||
import { RootState } from "../../../app/store";
|
||||
|
||||
export const selectInputValidationErrors = (state: RootState) => {
|
||||
const { keyLicense } = state.licenseCardActivate.apps;
|
||||
const hasErrorIncorrectKeyNumber = checkErrorIncorrectKeyNumber(keyLicense);
|
||||
return {
|
||||
hasErrorIncorrectKeyNumber,
|
||||
};
|
||||
};
|
||||
export const checkErrorIncorrectKeyNumber = (keyLicense: string): boolean =>
|
||||
// // 20+4(20文字+space4個)以外の場合はエラー
|
||||
keyLicense.length !== 24;
|
||||
|
||||
export const selectKeyLicense = (state: RootState) =>
|
||||
state.licenseCardActivate.apps.keyLicense;
|
||||
|
||||
export const selectIsLoading = (state: RootState) =>
|
||||
state.licenseCardActivate.apps.isLoading;
|
||||
|
||||
@ -3,6 +3,5 @@ export interface LicenseCardActivateState {
|
||||
}
|
||||
|
||||
export interface Apps {
|
||||
keyLicense: string;
|
||||
isLoading: boolean;
|
||||
}
|
||||
|
||||
@ -8,11 +8,8 @@ import { getTranslationID } from "../../translation";
|
||||
import close from "../../assets/images/close.svg";
|
||||
import {
|
||||
activateCardLicenseAsync,
|
||||
selectKeyLicense,
|
||||
cleanupApps,
|
||||
selectIsLoading,
|
||||
changeKeyLicense,
|
||||
selectInputValidationErrors,
|
||||
} from "../../features/license/licenseCardActivate/index";
|
||||
import progress_activit from "../../assets/images/progress_activit.svg";
|
||||
|
||||
@ -26,9 +23,18 @@ export const CardLicenseActivatePopup: React.FC<
|
||||
const { onClose } = props;
|
||||
const { t } = useTranslation();
|
||||
const dispatch: AppDispatch = useDispatch();
|
||||
const cardLicenseKey = useSelector(selectKeyLicense);
|
||||
const [keyNumber, setKeyNumber] = useState<string>(cardLicenseKey);
|
||||
const isLoading = useSelector(selectIsLoading);
|
||||
const [keyNumber1, setKeyNumber1] = useState<string>("");
|
||||
const [keyNumber2, setKeyNumber2] = useState<string>("");
|
||||
const [keyNumber3, setKeyNumber3] = useState<string>("");
|
||||
const [keyNumber4, setKeyNumber4] = useState<string>("");
|
||||
const [keyNumber5, setKeyNumber5] = useState<string>("");
|
||||
const TEXTAREASIZE = 4;
|
||||
const ref1 = useRef<HTMLTextAreaElement>(null);
|
||||
const ref2 = useRef<HTMLTextAreaElement>(null);
|
||||
const ref3 = useRef<HTMLTextAreaElement>(null);
|
||||
const ref4 = useRef<HTMLTextAreaElement>(null);
|
||||
const ref5 = useRef<HTMLTextAreaElement>(null);
|
||||
|
||||
// ポップアップを閉じる処理
|
||||
const closePopup = useCallback(() => {
|
||||
@ -67,17 +73,114 @@ export const CardLicenseActivatePopup: React.FC<
|
||||
const [isPushActivateButton, setIsPushActivateButton] =
|
||||
useState<boolean>(false);
|
||||
|
||||
// エラー宣言
|
||||
const { hasErrorIncorrectKeyNumber } = useSelector(
|
||||
selectInputValidationErrors
|
||||
);
|
||||
// 値を画面に分割して入れる
|
||||
const inputValueOnTextarea = (input: string, startArea: number) => {
|
||||
let roopCount = startArea;
|
||||
while (input.length !== 0 || roopCount === startArea) {
|
||||
switch (roopCount) {
|
||||
case 1:
|
||||
setKeyNumber1(input.slice(0, TEXTAREASIZE));
|
||||
input = input.substring(TEXTAREASIZE);
|
||||
break;
|
||||
case 2:
|
||||
setKeyNumber2(input.slice(0, TEXTAREASIZE));
|
||||
input = input.substring(TEXTAREASIZE);
|
||||
break;
|
||||
case 3:
|
||||
setKeyNumber3(input.slice(0, TEXTAREASIZE));
|
||||
input = input.substring(TEXTAREASIZE);
|
||||
break;
|
||||
case 4:
|
||||
setKeyNumber4(input.slice(0, TEXTAREASIZE));
|
||||
input = input.substring(TEXTAREASIZE);
|
||||
break;
|
||||
case 5:
|
||||
setKeyNumber5(input.slice(0, TEXTAREASIZE));
|
||||
input = input.substring(TEXTAREASIZE);
|
||||
break;
|
||||
default:
|
||||
input = "";
|
||||
}
|
||||
roopCount += 1;
|
||||
}
|
||||
};
|
||||
|
||||
// テキストエリア入力時
|
||||
const changeTextarea = (
|
||||
e: React.ChangeEvent<HTMLTextAreaElement>,
|
||||
areaNum: number
|
||||
) => {
|
||||
const input = e.target.value
|
||||
.toUpperCase()
|
||||
.replace(/[^A-Z0-9]/g, "")
|
||||
.substring(0, 20);
|
||||
inputValueOnTextarea(input, areaNum);
|
||||
if (e.target.value.includes("\n")) {
|
||||
setTimeout(() => {
|
||||
onKeyDownEnter();
|
||||
}, 0);
|
||||
}
|
||||
moveTextarea(input.length, areaNum);
|
||||
};
|
||||
|
||||
// フォーカスを移動する
|
||||
const moveFocus = (target: string) => {
|
||||
// stateの反映を同期してから実施
|
||||
setTimeout(() => {
|
||||
const obj = document.getElementById(target);
|
||||
if (obj) {
|
||||
obj.focus();
|
||||
}
|
||||
}, 0);
|
||||
};
|
||||
|
||||
// フォーカス移動判定
|
||||
const moveTextarea = (length: number, areaNum: number) => {
|
||||
if (length === TEXTAREASIZE && areaNum !== 5) {
|
||||
moveFocus(`textarea${(areaNum + 1).toString()}`);
|
||||
}
|
||||
};
|
||||
|
||||
// キー入力時判定
|
||||
const keyDown = (
|
||||
e: React.KeyboardEvent<HTMLTextAreaElement>,
|
||||
areaNum: number
|
||||
) => {
|
||||
if (e.key === "Enter") {
|
||||
e.preventDefault();
|
||||
onKeyDownEnter();
|
||||
} else if (e.key === "ArrowRight") {
|
||||
if (e.target.selectionStart === e.target.value.length) {
|
||||
moveFocus(`textarea${(areaNum + 1).toString()}`);
|
||||
}
|
||||
} else if (e.key === "ArrowLeft") {
|
||||
if (e.target.selectionStart === 0) {
|
||||
moveFocus(`textarea${(areaNum - 1).toString()}`);
|
||||
}
|
||||
} else if (/^[a-zA-Z0-9]$/.test(e.key)) {
|
||||
if (e.target.value.length === TEXTAREASIZE) {
|
||||
e.preventDefault();
|
||||
}
|
||||
} else if (/^[!-/:-@[-`{-~]$/.test(e.key)) {
|
||||
e.preventDefault();
|
||||
}
|
||||
};
|
||||
|
||||
// Enterキー押下時の処理
|
||||
const onKeyDownEnter = () => {
|
||||
const button = document.getElementById("button");
|
||||
if (button) {
|
||||
button.focus();
|
||||
button.click();
|
||||
}
|
||||
};
|
||||
|
||||
// activateボタン押下時
|
||||
const onActivateLicense = useCallback(async () => {
|
||||
setIsPushActivateButton(true);
|
||||
|
||||
if (keyNumber.length !== 24) {
|
||||
const inputBox = document.getElementById("inputBox");
|
||||
const keyNumber = `${keyNumber1}${keyNumber2}${keyNumber3}${keyNumber4}${keyNumber5}`;
|
||||
if (keyNumber.length !== 20) {
|
||||
const inputBox = document.getElementById("textarea5");
|
||||
// カーソルをテキストボックスに戻す
|
||||
if (inputBox) {
|
||||
inputBox.focus();
|
||||
@ -86,23 +189,26 @@ export const CardLicenseActivatePopup: React.FC<
|
||||
}
|
||||
|
||||
// activateAPIの呼び出し
|
||||
const cardLicenseKeyWithoutSpaces = keyNumber.replace(/\s/g, "");
|
||||
const { meta } = await dispatch(
|
||||
activateCardLicenseAsync({ cardLicenseKey: cardLicenseKeyWithoutSpaces })
|
||||
activateCardLicenseAsync({ cardLicenseKey: keyNumber })
|
||||
);
|
||||
setIsPushActivateButton(false);
|
||||
|
||||
// カーソルをテキストボックスに戻す
|
||||
const inputBox = document.getElementById("inputBox");
|
||||
const inputBox = document.getElementById("textarea1");
|
||||
if (inputBox) {
|
||||
inputBox.focus();
|
||||
}
|
||||
|
||||
if (meta.requestStatus === "fulfilled") {
|
||||
dispatch(cleanupApps());
|
||||
setKeyNumber("");
|
||||
setKeyNumber1("");
|
||||
setKeyNumber2("");
|
||||
setKeyNumber3("");
|
||||
setKeyNumber4("");
|
||||
setKeyNumber5("");
|
||||
}
|
||||
}, [keyNumber, dispatch]);
|
||||
}, [keyNumber1, keyNumber2, keyNumber3, keyNumber4, keyNumber5, dispatch]);
|
||||
|
||||
// HTML
|
||||
return (
|
||||
@ -128,62 +234,163 @@ export const CardLicenseActivatePopup: React.FC<
|
||||
</label>
|
||||
</dt>
|
||||
<dd className="">
|
||||
<input
|
||||
id="inputBox"
|
||||
type="text"
|
||||
size={48}
|
||||
name=""
|
||||
value={keyNumber}
|
||||
maxLength={24} // 20+4(20文字+space4個)
|
||||
className={styles.formInput}
|
||||
<textarea
|
||||
id="textarea1"
|
||||
value={keyNumber1}
|
||||
rows={1}
|
||||
cols={TEXTAREASIZE}
|
||||
ref={ref1}
|
||||
style={{ resize: "none", fontSize: "16px" }}
|
||||
// eslint-disable-next-line jsx-a11y/no-autofocus
|
||||
autoFocus
|
||||
onChange={(e) => {
|
||||
let input = e.target.value.toUpperCase();
|
||||
input = input.replace(/[^A-Z0-9]/g, "");
|
||||
// _.chunk関数で、配列の要素を一定の要素数ごとに分ける
|
||||
input = _.chunk(input, 4)
|
||||
.map((a) => a.join(""))
|
||||
.join(" ");
|
||||
setKeyNumber(input);
|
||||
if (input.includes("\n")) {
|
||||
dispatch(
|
||||
changeKeyLicense({
|
||||
keyLicense: e.target.value.toUpperCase(),
|
||||
})
|
||||
);
|
||||
onActivateLicense();
|
||||
}
|
||||
}}
|
||||
onBlur={(e) => {
|
||||
dispatch(
|
||||
changeKeyLicense({
|
||||
keyLicense: e.target.value.toUpperCase(),
|
||||
})
|
||||
);
|
||||
let cursorPos = 0;
|
||||
cursorPos = e.target.selectionStart;
|
||||
changeTextarea(e, 1);
|
||||
setTimeout(() => {
|
||||
if (ref1.current) {
|
||||
ref1.current.selectionStart = cursorPos;
|
||||
ref1.current.selectionEnd = cursorPos;
|
||||
}
|
||||
}, 0);
|
||||
}}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "Enter") {
|
||||
e.preventDefault();
|
||||
const input = e.target.value.toUpperCase();
|
||||
setKeyNumber(input);
|
||||
const button = document.getElementById("button");
|
||||
if (button) {
|
||||
button.focus();
|
||||
button.click();
|
||||
}
|
||||
keyDown(e, 1);
|
||||
}}
|
||||
onFocus={(e) => {
|
||||
if (ref1.current) {
|
||||
ref1.current.selectionStart = e.target.value.length;
|
||||
ref1.current.selectionEnd = e.target.value.length;
|
||||
}
|
||||
}}
|
||||
/>
|
||||
{isPushActivateButton && hasErrorIncorrectKeyNumber && (
|
||||
<span className={styles.formError}>
|
||||
{t(
|
||||
getTranslationID(
|
||||
"cardLicenseActivatePopupPage.label.keyNumberIncorrectError"
|
||||
)
|
||||
)}
|
||||
</span>
|
||||
)}
|
||||
<textarea
|
||||
id="textarea2"
|
||||
value={keyNumber2}
|
||||
rows={1}
|
||||
cols={TEXTAREASIZE}
|
||||
ref={ref2}
|
||||
style={{ resize: "none", fontSize: "16px" }}
|
||||
onChange={(e) => {
|
||||
let cursorPos = 0;
|
||||
cursorPos = e.target.selectionStart;
|
||||
changeTextarea(e, 2);
|
||||
setTimeout(() => {
|
||||
if (ref2.current) {
|
||||
ref2.current.selectionStart = cursorPos;
|
||||
ref2.current.selectionEnd = cursorPos;
|
||||
}
|
||||
}, 0);
|
||||
}}
|
||||
onKeyDown={(e) => {
|
||||
keyDown(e, 2);
|
||||
}}
|
||||
onFocus={(e) => {
|
||||
if (ref2.current) {
|
||||
ref2.current.selectionStart = e.target.value.length;
|
||||
ref2.current.selectionEnd = e.target.value.length;
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<textarea
|
||||
id="textarea3"
|
||||
value={keyNumber3}
|
||||
rows={1}
|
||||
cols={TEXTAREASIZE}
|
||||
ref={ref3}
|
||||
style={{ resize: "none", fontSize: "16px" }}
|
||||
onChange={(e) => {
|
||||
let cursorPos = 0;
|
||||
cursorPos = e.target.selectionStart;
|
||||
changeTextarea(e, 3);
|
||||
setTimeout(() => {
|
||||
if (ref3.current) {
|
||||
ref3.current.selectionStart = cursorPos;
|
||||
ref3.current.selectionEnd = cursorPos;
|
||||
}
|
||||
}, 0);
|
||||
}}
|
||||
onKeyDown={(e) => {
|
||||
keyDown(e, 3);
|
||||
}}
|
||||
onFocus={(e) => {
|
||||
if (ref3.current) {
|
||||
ref3.current.selectionStart = e.target.value.length;
|
||||
ref3.current.selectionEnd = e.target.value.length;
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<textarea
|
||||
id="textarea4"
|
||||
value={keyNumber4}
|
||||
rows={1}
|
||||
cols={TEXTAREASIZE}
|
||||
ref={ref4}
|
||||
style={{ resize: "none", fontSize: "16px" }}
|
||||
onChange={(e) => {
|
||||
let cursorPos = 0;
|
||||
cursorPos = e.target.selectionStart;
|
||||
changeTextarea(e, 4);
|
||||
setTimeout(() => {
|
||||
if (ref4.current) {
|
||||
ref4.current.selectionStart = cursorPos;
|
||||
ref4.current.selectionEnd = cursorPos;
|
||||
}
|
||||
}, 0);
|
||||
}}
|
||||
onKeyDown={(e) => {
|
||||
keyDown(e, 4);
|
||||
}}
|
||||
onFocus={(e) => {
|
||||
if (ref4.current) {
|
||||
ref4.current.selectionStart = e.target.value.length;
|
||||
ref4.current.selectionEnd = e.target.value.length;
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<textarea
|
||||
id="textarea5"
|
||||
value={keyNumber5}
|
||||
rows={1}
|
||||
cols={TEXTAREASIZE}
|
||||
ref={ref5}
|
||||
style={{ resize: "none", fontSize: "16px" }}
|
||||
onChange={(e) => {
|
||||
let cursorPos = 0;
|
||||
cursorPos = e.target.selectionStart;
|
||||
changeTextarea(e, 5);
|
||||
setTimeout(() => {
|
||||
if (ref5.current) {
|
||||
ref5.current.selectionStart = cursorPos;
|
||||
ref5.current.selectionEnd = cursorPos;
|
||||
}
|
||||
}, 0);
|
||||
}}
|
||||
onKeyDown={(e) => {
|
||||
keyDown(e, 5);
|
||||
}}
|
||||
onFocus={(e) => {
|
||||
if (ref5.current) {
|
||||
ref5.current.selectionStart = e.target.value.length;
|
||||
ref5.current.selectionEnd = e.target.value.length;
|
||||
}
|
||||
}}
|
||||
/>
|
||||
{isPushActivateButton &&
|
||||
keyNumber1.length +
|
||||
keyNumber2.length +
|
||||
keyNumber3.length +
|
||||
keyNumber4.length +
|
||||
keyNumber5.length !==
|
||||
20 && (
|
||||
<span className={styles.formError}>
|
||||
{t(
|
||||
getTranslationID(
|
||||
"cardLicenseActivatePopupPage.label.keyNumberIncorrectError"
|
||||
)
|
||||
)}
|
||||
</span>
|
||||
)}
|
||||
</dd>
|
||||
<dd className={`${styles.full} ${styles.alignCenter}`}>
|
||||
<input
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user