Files
lucia-frontend/src/views/AccountManagement/ModalAccountEditCreate.vue
2024-08-26 13:40:33 +08:00

395 lines
18 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<div id="modal_account_edit_or_create_new" class="w-[640px] bg-[#FFFFFF] rounded
shadow-lg flex flex-col">
<ModalHeader :headerText="modalTitle"/>
<main class="flex row min-h-[96px] my-[32px] flex-col px-6">
<div class="input-row w-full flex py-2 h-[40px] mb-4 items-center
justify-between">
<div class="field-label text-sm flex items-center font-medium justify-end">
<span class="align-right-span flex w-[122px] justify-end">
{{ i18next.t("AcctMgmt.Account") }}
</span>
</div>
<div class="input-and-error flex flex-col">
<input id="input_account_field"
class="w-[454px] rounded p-1 border border-[1px] border-[#64748B] flex items-center h-[40px] outline-none"
v-model="inputUserAccount"
:class="{
'text-[#000000]': isAccountUnique,
'text-[#FF3366]': !isAccountUnique,
'border-[#FF3366]': !isAccountUnique,
}"
:readonly="!isEditable" autocomplete="off"
@dblclick="onInputDoubleClick"
/>
<div v-show="!isAccountUnique" class="error-wrapper my-2">
<div class="error-msg-section flex justify-start">
<img src="@/assets/icon-alert.svg" alt="!" class="exclamation-img flex mr-2">
<span class="error-msg-text flex text-[#FF3366] h-[24px]">
{{ i18next.t("AcctMgmt.AccountNotUnique") }}
</span>
</div>
</div>
</div>
</div>
<div class="input-row w-full flex py-2 h-[40px] mb-4 items-center
justify-between">
<div class="field-label text-sm flex items-center font-medium justify-end">
<span class="align-right-span flex w-[122px] justify-end">
{{ i18next.t("AcctMgmt.FullName") }}
</span>
</div>
<input id="input_name_field"
class="w-[454px] rounded p-1 border border-[1px] border-[#64748B] flex items-center h-[40px] outline-none"
v-model="inputName" :readonly="!isEditable" @dblclick="onInputDoubleClick" @focus="onInputNameFocus"
autocomplete="off"
/>
</div>
<div v-show="!isSSO" class="input-row w-full flex py-2 mb-4 items-center
justify-between">
<div class="field-label text-sm flex items-start font-medium justify-end"
:class="{
'h-[100px]': whichCurrentModal !== MODAL_CREATE_NEW,
}"
>
<span class="align-right-span flex h-full w-[122px] justify-end text-top">
{{ i18next.t("AcctMgmt.Password") }}
</span>
</div>
<div class="reset-btn-and-input-pwd-and-error flex flex-col"
:class="{'h-[100px]': whichCurrentModal !== MODAL_CREATE_NEW,}"
>
<div v-if="whichCurrentModal !== MODAL_CREATE_NEW" class="flex account-edit-section justify-start w-[454px]">
<button class="flex w-[85px] h-[40px] reset-btn rounded-full border-[1px] border-[#666666] cursor-pointer
items-center justify-center hover:text-[#0099FF] hover:border-[#0099FF] mb-[20px]"
@click="onResetPwdButtonClick"
>
{{ i18next.t("AcctMgmt.Reset") }}
</button>
</div>
<div v-if="whichCurrentModal === MODAL_CREATE_NEW || isResetPwdSectionShow"
class="w-[454px] flex flex-col input-and-error-msg">
<div class="input-and-eye flex items-center w-full h-[40px] relative border-[1px] rounded"
:class="{'border-[#FF3366]': !isPwdLengthValid, 'border-[#64748B]': isPwdLengthValid,}"
>
<input id="input_first_pwd" class="outline-none p-1 w-[352px]" :type="isPwdEyeOn ? 'text' : 'password'"
v-model="inputPwd" :readonly="!isEditable" @dblclick="onInputDoubleClick"
autocomplete="off" :class="{'color-[#FF3366]': !isPwdLengthValid, 'color-[#64748B]': isPwdLengthValid,}"/>
<img id='eye_button' v-if="isPwdEyeOn" src='@/assets/icon-eye-open.svg' class="absolute right-[8px] cursor-pointer"
@mousedown="togglePwdEyeBtn(true)" @mouseup="togglePwdEyeBtn(false)" alt=""/>
<img v-else src='@/assets/icon-eye-hide.svg' class="absolute right-[8px] cursor-pointer"
@mousedown="togglePwdEyeBtn(true)" @mouseup="togglePwdEyeBtn(false)" alt="eye"/>
</div>
<div class="error-msg-section flex justify-start mt-4">
<img v-show="!isPwdLengthValid" src="@/assets/icon-alert.svg" alt="!" class="exclamation-img flex mr-2">
<span class="error-msg-text flex text-[#FF3366] h-[24px]">
{{ isPwdLengthValid ? "" : i18next.t("AcctMgmt.PwdLengthNotEnough") }}
</span>
</div>
</div>
</div>
</div>
<div v-show="false" id="confirm_pwd_row" class="input-row w-full grid grid-cols-2 grid-cols-[122px_1fr] gap-x-4
mb-4 flex items-center"> <!-- 2-by-2 的格子其中左下角是一個dummy格子其沒有內容 -->
<span v-show="false" class="field-label w-[122px] text-sm flex items-center justify-end text-right font-medium mr-4 whitespace-nowrap"
:class="{
'text-[#000000]': isPwdMatched,
'text-[#FF3366]': !isPwdMatched,
}">
{{ i18next.t("AcctMgmt.ConfirmPassword") }}
</span>
<div v-show="false" class="input-and-toggle-btn w-[454px] flex flex-row rounded border border-[1px] relative items-center
h-[40px] mb-4"
:class="{
'border-[#000000]': isPwdMatched,
'border-[#FF3366]': !isPwdMatched,
}">
<input v-show="false" id="input_second_pwd" class="outline-none p-1 w-[352px]" :type="isPwdConfirmEyeOn ? 'text' : 'password'"
v-model="inputConfirmPwd" :readonly="!isEditable" @dblclick="onInputDoubleClick"
:class="{
'text-[#000000]': isPwdMatched,
'text-[#FF3366]': !isPwdMatched,
}" autocomplete="off"/>
<img v-if="isPwdConfirmEyeOn" src='@/assets/icon-eye-open.svg' class="absolute right-[8px] cursor-pointer"
@click="togglePwdConfirmEyeBtn" alt="eye"/>
<img v-else src='@/assets/icon-eye-hide.svg' class="absolute right-[8px] cursor-pointer" @click="togglePwdConfirmEyeBtn" alt="eye"/>
</div>
<div class="dummy-grid h-[24px]"></div> <!-- 透過 dummy-grid 來撐起高度-->
<div class="error-msg-section flex justify-start">
<img v-show="!isPwdMatched" src="@/assets/icon-alert.svg" alt="!" class="exclamation-img flex mr-2">
<span class="error-msg-text flex text-[#FF3366] h-[24px]">
{{ isPwdMatched ? "" : i18next.t("AcctMgmt.PwdNotMatch") }}
</span>
</div>
<div class="dummy-grid h-[24px]"></div> <!-- 透過 dummy-grid 來撐起高度-->
<div class="error-msg-section flex justify-start">
<img v-show="!isPwdLengthValid" src="@/assets/icon-alert.svg" alt="!" class="exclamation-img flex mr-2">
<span class="error-msg-text flex text-[#FF3366] h-[24px]">
{{ isPwdLengthValid ? "" : i18next.t("AcctMgmt.PwdLengthNotEnough") }}
</span>
</div>
</div>
<div v-if="whichCurrentModal === MODAL_CREATE_NEW" class="checkbox-row w-full grid-cols-[122px_1fr] gap-x-4 flex py-2 h-[40px] my-4 items-center">
<div class="dummy field-label flex items-center w-[122px]">
</div> <!--這裡也使用了dummy欄位去撐起空間-->
<section id="account_create_checkboxes_section" class="flex flex-col">
<div class="checkbox-and-text flex">
<IconChecked :isChecked="isSetAsAdminChecked" @click="toggleIsAdmin"/>
<span class="flex checkbox-text">
{{ i18next.t("AcctMgmt.SetAsAdmin") }}
</span>
</div>
<div class="checkbox-and-text flex">
<IconChecked :isChecked="isSetActivedChecked" @click="toggleIsActivated"/>
<span class="flex checkbox-text">
{{ i18next.t("AcctMgmt.ActivateNow") }}
</span>
</div>
</section>
</div>
</main>
<footer class="flex row footer justify-end pr-[32px] pb-8">
<button class="cancel-btn rounded-full w-[92px] h-10 px-2.5 py-6 border border-[1px] border-[#64748B]
flex justify-center items-center text-[#4E5969] font-medium
hover:bg-[#64748B] hover:border-[#CBD5E1] hover:text-[#FFFFFF]"
@click="onCancelBtnClick"
>
{{ i18next.t("Global.Cancel") }}
</button>
<button class="confirm-btn rounded-full w-[92px] h-10 px-2.5 py-6
flex justify-center items-center text-[#ffffff] font-medium ml-[16px]"
@click="onConfirmBtnClick" :disabled="isConfirmDisabled"
:class="{
'bg-[#0099FF]': !isConfirmDisabled,
'bg-[#E2E8F0]': isConfirmDisabled,
}"
>
{{ i18next.t("Global.Confirm") }}
</button>
</footer>
</div>
</template>
<script>
import { defineComponent, computed, ref, watch, onMounted, } from 'vue';
import i18next from "@/i18n/i18n.js";
import { mapActions, } from 'pinia';
import { useModalStore } from '@/stores/modal.js';
import { useRouter } from 'vue-router';
import { useToast } from 'vue-toast-notification';
import useAcctMgmtStore from '@/stores/acctMgmt';
import ModalHeader from "./ModalHeader.vue";
import IconChecked from "@/components/icons/IconChecked.vue";
import { MODAL_CREATE_NEW, MODAL_ACCT_EDIT, PWD_VALID_LENGTH } from '@/constants/constants.js';
export default defineComponent({
setup() {
const acctMgmtStore = useAcctMgmtStore();
const modalStore = useModalStore();
const router = useRouter();
const toast = useToast();
const currentViewingUser = computed(() => acctMgmtStore.currentViewingUser);
const isPwdEyeOn = ref(false);
const isConfirmDisabled = ref(false);
const isPwdLengthValid = ref(true);
const isResetPwdSectionShow = ref(false);
const isSetAsAdminChecked = ref(false);
const isSetActivedChecked = ref(true);
const whichCurrentModal = computed(() => modalStore.whichModal);
const isSSO = computed(() => acctMgmtStore.currentViewingUser.is_sso);
const username = computed(() => acctMgmtStore.currentViewingUser.username);
const name = computed(() => acctMgmtStore.currentViewingUser.name);
const inputUserAccount = ref(whichCurrentModal.value === MODAL_CREATE_NEW ? '' : currentViewingUser.value.username);
const inputName = ref(whichCurrentModal.value === MODAL_CREATE_NEW ? '' : currentViewingUser.value.name);
const inputPwd = ref("");
const isAccountUnique = ref(true);
const isEditable = ref(true);
// 自從加入這段 watch 之後,填寫密碼欄位之時,就不會胡亂清空掉 account 或是 full name 欄位了。
watch(whichCurrentModal, (newVal) => {
if (newVal === MODAL_CREATE_NEW) {
inputUserAccount.value = '';
inputName.value = '';
} else {
inputUserAccount.value = currentViewingUser.value.username;
inputName.value = currentViewingUser.value.name;
}
});
const modalTitle = computed(() => {
return modalStore.whichModal === MODAL_CREATE_NEW ? i18next.t('AcctMgmt.CreateNew') : i18next.t('AcctMgmt.AccountEdit');
});
const togglePwdEyeBtn = (toBeOpen) => {
isPwdEyeOn.value = toBeOpen;
};
const validatePwdLength = () => {
isPwdLengthValid.value = inputPwd.value.length >= PWD_VALID_LENGTH;
}
const onInputDoubleClick = () => {
// 允許編輯模式
isEditable.value = true;
}
const onConfirmBtnClick = async () => {
// rule for minimum length
validatePwdLength();
if(!isPwdLengthValid.value) {
return;
}
// rule for account uniqueness
switch(whichCurrentModal.value) {
case MODAL_CREATE_NEW:
await checkAccountIsUnique();
if(!isAccountUnique.value) {
return;
}
await acctMgmtStore.createNewAccount({
username: inputUserAccount.value,
password: inputPwd.value === undefined ? '' : inputPwd.value,
name: inputName.value,
is_admin: isSetAsAdminChecked.value,
is_active: isSetActivedChecked.value,
});
await toast.success(i18next.t("AcctMgmt.MsgAccountAdded"));
await modalStore.closeModal();
acctMgmtStore.setShouldUpdateList(true);
await router.push('/account-admin');
break;
case MODAL_ACCT_EDIT:
await checkAccountIsUnique();
if(!isAccountUnique.value) {
return;
}
// 要注意的是舊的username跟新的username可以是不同的
await acctMgmtStore.editAccount(
currentViewingUser.value.username, {
newUsername: inputUserAccount.value,
password: inputPwd.value,
name: inputName.value === undefined ? '' : inputName.value,
is_active: true,
});
await toast.success(i18next.t("AcctMgmt.MsgAccountEdited"));
isEditable.value = false;
break;
default:
break;
}
}
const checkAccountIsUnique = async() => {
// 如果使用者沒有更動過欄位那就不用調用任何後端的API
if(inputUserAccount.value === username.value) {
return true;
}
const isAccountAlreadyExistAPISuccess = await acctMgmtStore.getUserDetail(inputUserAccount.value);
isAccountUnique.value = !isAccountAlreadyExistAPISuccess;
return isAccountUnique.value;
};
const toggleIsAdmin = () => {
if(isEditable){
isSetAsAdminChecked.value = !isSetAsAdminChecked.value;
}
}
const toggleIsActivated = () => {
if(isEditable){
isSetActivedChecked.value = !isSetActivedChecked.value;
}
}
const onInputNameFocus = () => {
if(isConfirmDisabled.value){
isConfirmDisabled.value = false;
}
}
const onResetPwdButtonClick = () => {
isResetPwdSectionShow.value = !isResetPwdSectionShow.value;
// 必須清空密碼欄位輸入的字串
inputPwd.value = '';
}
watch(
[inputPwd, inputUserAccount, inputName],
([newPwd, newAccount, newName]) => {
// 只要[確認密碼]或[密碼]欄位有更動且所有欄位都不是空的confirm 按鈕就可點選
if (newPwd.length > 0 && newAccount.length > 0 && newName.length > 0) {
isConfirmDisabled.value = false;
} else {
isConfirmDisabled.value = true;
}
}
);
onMounted(() => {
});
return {
isConfirmDisabled,
username,
name,
isSSO,
isPwdEyeOn,
togglePwdEyeBtn,
isPwdLengthValid,
inputUserAccount,
inputName,
inputPwd,
onConfirmBtnClick,
onInputDoubleClick,
onInputNameFocus,
onResetPwdButtonClick,
isSetAsAdminChecked,
isSetActivedChecked,
isResetPwdSectionShow,
toggleIsAdmin,
toggleIsActivated,
whichCurrentModal,
MODAL_CREATE_NEW,
modalTitle,
isAccountUnique,
isEditable,
};
},
data() {
return {
i18next: i18next,
};
},
components: {
ModalHeader,
IconChecked,
},
methods: {
onCloseBtnClick(){
this.closeModal();
},
onCancelBtnClick(){
this.closeModal();
},
...mapActions(useModalStore, ['closeModal']),
}
});
</script>
<style>
#modal_account_edit {
background-color: #ffffff;
backdrop-filter: opacity(1); /*防止子元件繼承父元件的透明度 */
}
</style>