feat(laptops): implement keyboard arrow navigation and select-on-enter for employee assignment dropdown
This commit is contained in:
parent
5e46514f62
commit
ab27bfc34d
1 changed files with 37 additions and 5 deletions
42
laptops.php
42
laptops.php
|
|
@ -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;
|
||||
|
|
|
|||
Loading…
Reference in a new issue