feat(laptops): implement keyboard arrow navigation and select-on-enter for employee assignment dropdown

This commit is contained in:
NAS-Admin 2026-06-01 09:33:31 +09:00
parent 5e46514f62
commit ab27bfc34d

View file

@ -434,8 +434,13 @@
</label>
<div class="relative">
<input type="text" x-model="userSearchQuery"
@focus="showUserDropdown = true; userSearchQuery = ''"
@input="showUserDropdown = true" placeholder="사용자 검색 (이름, 사번)"
@focus="showUserDropdown = true; userSearchQuery = ''; userHighlightIndex = -1"
@input="showUserDropdown = true; userHighlightIndex = -1"
@keydown.arrow-down.prevent="if(showUserDropdown) { userHighlightIndex = (userHighlightIndex + 1) % filteredUsers.length; adjustScroll(); }"
@keydown.arrow-up.prevent="if(showUserDropdown) { userHighlightIndex = (userHighlightIndex - 1 + filteredUsers.length) % filteredUsers.length; adjustScroll(); }"
@keydown.enter.prevent="if(showUserDropdown && userHighlightIndex >= 0 && filteredUsers[userHighlightIndex]) { selectUser(filteredUsers[userHighlightIndex]); } else if (showUserDropdown) { showUserDropdown = false; } else { submitAsset(); }"
@keydown.space="if(showUserDropdown && userHighlightIndex >= 0 && filteredUsers[userHighlightIndex]) { $event.preventDefault(); selectUser(filteredUsers[userHighlightIndex]); }"
placeholder="사용자 검색 (이름, 사번)"
:disabled="formData.status !== 'assigned'"
:class="formData.status !== 'assigned' ? 'bg-slate-100 border-dashed cursor-not-allowed opacity-75' : 'bg-white'"
class="w-full px-4 py-3 border border-slate-200 rounded-xl focus:ring-2 focus:ring-blue-500 outline-none transition-all text-sm font-bold text-slate-700">
@ -458,16 +463,19 @@
<!-- User Search Dropdown -->
<div x-show="showUserDropdown" x-transition
class="absolute z-[120] mt-2 w-full bg-white rounded-2xl shadow-2xl border border-slate-100 max-h-60 overflow-y-auto overflow-x-hidden no-scrollbar">
<template x-for="user in filteredUsers" :key="user.id">
<template x-for="(user, index) in filteredUsers" :key="user.id">
<div @click="selectUser(user)"
:class="userHighlightIndex === index ? 'bg-blue-50' : ''"
class="px-4 py-3 hover:bg-blue-50 cursor-pointer flex items-center justify-between transition-colors group">
<div>
<div class="text-sm font-bold text-slate-700 group-hover:text-blue-600"
<div class="text-sm font-bold text-slate-700 transition-colors"
:class="userHighlightIndex === index ? 'text-blue-600' : 'group-hover:text-blue-600'"
x-text="user.name"></div>
<div class="text-[10px] text-slate-400 font-medium"
x-text="user.dept_name || '부서 정보 없음'"></div>
</div>
<div class="text-[10px] font-black text-slate-300 group-hover:text-blue-400"
<div class="text-[10px] font-black text-slate-300 transition-colors"
:class="userHighlightIndex === index ? 'text-blue-400' : 'group-hover:text-blue-400'"
x-text="user.emp_id"></div>
</div>
</template>
@ -608,6 +616,7 @@
disposal_recipient: '', disposal_date: '', real_user: '', disposal_user_id: ''
},
userSearchQuery: '',
userHighlightIndex: -1,
showUserDropdown: false,
disposalSearchQuery: '',
showDisposalDropdown: false,
@ -660,6 +669,7 @@
this.formData.current_user_id = user.id;
this.userSearchQuery = `${user.name} (${user.emp_id})`;
this.showUserDropdown = false;
this.userHighlightIndex = -1;
this.updateAssignedUserName();
// Do not auto-change status to assigned here because user might be in stock mode
},
@ -667,12 +677,34 @@
clearUser() {
this.formData.current_user_id = '';
this.userSearchQuery = '';
this.userHighlightIndex = -1;
this.updateAssignedUserName();
if (this.formData.status === 'assigned') {
this.formData.status = 'stock';
}
},
adjustScroll() {
this.$nextTick(() => {
const container = this.$el.querySelector('.max-h-60');
if (!container) return;
const items = container.querySelectorAll('.cursor-pointer');
const activeItem = items[this.userHighlightIndex];
if (!activeItem) return;
const containerTop = container.scrollTop;
const containerBottom = containerTop + container.clientHeight;
const elemTop = activeItem.offsetTop;
const elemBottom = elemTop + activeItem.offsetHeight;
if (elemTop < containerTop) {
container.scrollTop = elemTop;
} else if (elemBottom > containerBottom) {
container.scrollTop = elemBottom - container.clientHeight;
}
});
},
get isAssetTagDuplicate() {
const tag = (this.formData.asset_tag || '').trim();
if (!tag) return false;