select box

This commit is contained in:
antv
2026-07-13 16:28:44 +07:00
parent f0a25b840c
commit 47a8892623
24 changed files with 889 additions and 302 deletions
+104
View File
@@ -51,6 +51,110 @@
@import 'tailwindcss';
@import 'tom-select/dist/css/tom-select.css';
/* --- TOM SELECT CUSTOM DESIGN SYSTEM --- */
.ts-wrapper.form-input {
padding: 0 !important;
border: none !important;
background: transparent !important;
height: 48px !important;
box-shadow: none !important;
}
.ts-wrapper.form-input .ts-control {
border: 1px solid #d9dfe7 !important;
border-radius: 0.75rem !important; /* 12px / rounded-xl */
height: 48px !important;
padding: 0 1rem !important;
display: flex !important;
align-items: center !important;
font-size: 0.875rem !important;
color: #1e293b !important;
background-color: #ffffff !important;
box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05) !important;
transition: border-color 0.2s, box-shadow 0.2s !important;
position: relative !important;
}
.ts-wrapper.form-input:hover .ts-control {
border-color: #9ca3af !important;
}
.ts-wrapper.form-input.focus .ts-control {
border-color: #3462f7 !important;
box-shadow: 0 0 0 4px rgba(52, 98, 247, 0.2) !important;
outline: none !important;
}
.ts-wrapper.form-input.focus:hover .ts-control {
border-color: #3462f7 !important;
}
.ts-wrapper.form-input-with-icon .ts-control {
padding-left: 48px !important;
}
/* Selected value item styling */
.ts-wrapper.form-input .ts-control .item {
white-space: nowrap !important;
overflow: hidden !important;
text-overflow: ellipsis !important;
max-width: calc(100% - 24px) !important;
color: #1e293b !important;
font-size: 0.875rem !important;
padding: 0 !important;
margin: 0 !important;
line-height: normal !important;
}
/* Search input field styling */
.ts-wrapper.form-input .ts-control input {
font-size: 0.875rem !important;
padding: 0 !important;
margin: 0 !important;
height: auto !important;
line-height: normal !important;
background: transparent !important;
border: none !important;
box-shadow: none !important;
}
/* Dropdown popup list styling */
.ts-wrapper.form-input .ts-dropdown {
border: 1px solid #d9dfe7 !important;
border-radius: 0.75rem !important;
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -4px rgba(0, 0, 0, 0.1) !important;
margin-top: 4px !important;
z-index: 100 !important;
background-color: #ffffff !important;
overflow: hidden !important;
}
.ts-wrapper.form-input .ts-dropdown .option {
padding: 0.65rem 1rem !important;
font-size: 0.875rem !important;
color: #475569 !important;
white-space: normal !important;
word-break: break-word !important;
}
.ts-wrapper.form-input .ts-dropdown .option.active {
background-color: #e8f0fe !important;
color: #3462f7 !important;
}
/* Select dropdown arrow icon */
.ts-wrapper.form-input.single .ts-control::after {
right: 1.25rem !important;
border-color: #6b7280 transparent transparent transparent !important;
border-width: 5px 5px 0 5px !important;
top: 50% !important;
margin-top: -2.5px !important;
}
.ts-wrapper.form-input.single.dropdown-active .ts-control::after {
border-color: transparent transparent #6b7280 transparent !important;
border-width: 0 5px 5px 5px !important;
margin-top: -2.5px !important;
}
/* Specific height override for the search filter form */
#filterForm .ts-wrapper.form-input {
height: 38px !important;
}
#filterForm .ts-wrapper.form-input .ts-control {
height: 38px !important;
min-height: 38px !important;
padding: 0.375rem 1rem !important;
}
@source '../../vendor/laravel/framework/src/Illuminate/Pagination/resources/views/*.blade.php';
@source '../../storage/framework/views/*.php';
@source '../**/*.blade.php';
@@ -15,5 +15,6 @@
<button type="submit" class="inline-flex items-center justify-center bg-red-600 hover:bg-red-700 text-white font-bold py-2.5 px-6 rounded-xl text-sm transition-colors shadow-md shadow-red-500/10 min-w-[120px] h-[45px]">Nghỉ việc</button>
@elseif($mode === 'self')
<a href="{{ route('user.dashboard') }}" class="inline-flex items-center justify-center bg-white border border-[#d9dfe7] hover:bg-gray-50 text-[#1a2b49] font-bold py-2.5 px-6 rounded-xl text-sm transition-colors shadow-sm min-w-[120px] h-[45px]">Quay lại</a>
<button type="submit" class="inline-flex items-center justify-center bg-[#3462f7] hover:bg-blue-700 text-white font-bold py-2.5 px-6 rounded-xl text-sm transition-colors shadow-md shadow-blue-500/10 min-w-[120px] h-[45px]">Lưu Thay Đổi</button>
@endif
</div>
@@ -0,0 +1,55 @@
@props(['mode', 'user' => null])
@if($mode === 'create' || $mode === 'edit')
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<!-- Quyền hạn -->
<div class="relative">
<label class="block text-[14px] font-bold text-[#1a2b49] mb-2">Quyền hạn <span class="text-red-500">*</span></label>
<div class="grid grid-cols-2 gap-2 mt-2 bg-gray-100/80 p-1 rounded-xl">
<label class="flex items-center justify-center gap-2 cursor-pointer py-2 rounded-lg transition-all text-center has-[:checked]:bg-white has-[:checked]:text-[#3462f7] has-[:checked]:shadow-sm text-gray-500 font-bold text-sm hover:bg-white/50">
<input type="radio" name="role" value="{{ config('constants.ROLE_MEMBER') }}" class="sr-only" {{ old('role', $user?->role) == config('constants.ROLE_MEMBER') || (is_null($user) && old('role') != config('constants.ROLE_ADMIN')) ? 'checked' : '' }}>
<span>Member</span>
</label>
<label class="flex items-center justify-center gap-2 cursor-pointer py-2 rounded-lg transition-all text-center has-[:checked]:bg-white has-[:checked]:text-[#3462f7] has-[:checked]:shadow-sm text-gray-500 font-bold text-sm hover:bg-white/50">
<input type="radio" name="role" value="{{ config('constants.ROLE_ADMIN') }}" class="sr-only" {{ old('role', $user?->role) == config('constants.ROLE_ADMIN') ? 'checked' : '' }}>
<span>Admin</span>
</label>
</div>
@error('role')<span class="text-red-500 text-[13px] font-semibold mt-1.5 block">{{ $message }}</span>@enderror
</div>
@if($mode === 'edit')
<!-- Trạng thái -->
<div class="relative">
<label class="block text-[14px] font-bold text-[#1a2b49] mb-2">Trạng thái <span class="text-red-500">*</span></label>
<div class="grid grid-cols-2 gap-2 mt-2 bg-gray-100/80 p-1 rounded-xl">
<label class="flex items-center justify-center gap-2 cursor-pointer py-2 rounded-lg transition-all text-center has-[:checked]:bg-white has-[:checked]:text-[#3462f7] has-[:checked]:shadow-sm text-gray-500 font-bold text-sm hover:bg-white/50">
<input type="radio" name="status" value="{{ config('constants.STATUS_ACTIVE') }}" class="sr-only" {{ old('status', $user?->status) == config('constants.STATUS_ACTIVE') ? 'checked' : '' }}>
<span>Đang làm việc</span>
</label>
<label class="flex items-center justify-center gap-2 cursor-pointer py-2 rounded-lg transition-all text-center has-[:checked]:bg-white has-[:checked]:text-[#3462f7] has-[:checked]:shadow-sm text-gray-500 font-bold text-sm hover:bg-white/50">
<input type="radio" name="status" value="{{ config('constants.STATUS_INACTIVE') }}" class="sr-only" {{ old('status', $user?->status) == config('constants.STATUS_INACTIVE') ? 'checked' : '' }}>
<span>Đã nghỉ việc</span>
</label>
</div>
@error('status')<span class="text-red-500 text-[13px] font-semibold mt-1.5 block">{{ $message }}</span>@enderror
</div>
<!-- Quyền gửi thẻ -->
<div class="relative col-span-1">
<label class="block text-[14px] font-bold text-[#1a2b49] mb-2">Quyền gửi thẻ <span class="text-red-500">*</span></label>
<div class="grid grid-cols-2 gap-2 mt-2 bg-gray-100/80 p-1 rounded-xl">
<label class="flex items-center justify-center gap-2 cursor-pointer py-2 rounded-lg transition-all text-center has-[:checked]:bg-white has-[:checked]:text-[#3462f7] has-[:checked]:shadow-sm text-gray-500 font-bold text-sm hover:bg-white/50">
<input type="radio" name="flag_send" value="{{ config('constants.FLAG_SEND_ENABLED') }}" class="sr-only" {{ old('flag_send', $user?->flag_send) == config('constants.FLAG_SEND_ENABLED') ? 'checked' : '' }}>
<span>Được phép gửi</span>
</label>
<label class="flex items-center justify-center gap-2 cursor-pointer py-2 rounded-lg transition-all text-center has-[:checked]:bg-white has-[:checked]:text-[#3462f7] has-[:checked]:shadow-sm text-gray-500 font-bold text-sm hover:bg-white/50">
<input type="radio" name="flag_send" value="{{ config('constants.FLAG_SEND_DISABLED') }}" class="sr-only" {{ old('flag_send', $user?->flag_send) == config('constants.FLAG_SEND_DISABLED') ? 'checked' : '' }}>
<span>Không cho phép</span>
</label>
</div>
@error('flag_send')<span class="text-red-500 text-[13px] font-semibold mt-1.5 block">{{ $message }}</span>@enderror
</div>
@endif
</div>
@endif
@@ -0,0 +1,29 @@
@props(['mode', 'user' => null])
<div class="flex items-center gap-4 mb-6">
<div class="w-16 h-16 rounded-full overflow-hidden border border-gray-200 shrink-0">
<img src="{{ ($user && $user->avatar) ? asset($user->avatar) : asset('images/avatar.png') }}" alt="Avatar" class="w-full h-full object-cover" id="avatar-preview" onerror="this.onerror=null; this.src='{{ asset('images/avatar.png') }}';">
</div>
<div class="flex-1">
<label class="block text-[13px] font-bold text-gray-700 mb-1" for="avatar">Thay đổi ảnh đại diện</label>
<input type="file" name="avatar" id="avatar" accept="image/*" class="block w-full text-sm text-gray-500 file:mr-4 file:py-2 file:px-3 file:rounded-full file:border-0 file:text-sm file:font-semibold file:bg-[#3462f7] file:text-white hover:file:bg-[#254edb]" onchange="previewAvatar(event)">
@error('avatar')<span class="text-red-500 text-[13px] font-semibold mt-1.5 block">{{ $message }}</span>@enderror
</div>
</div>
@once
@push('scripts')
<script>
function previewAvatar(event) {
const input = event.target;
if (input.files && input.files[0]) {
const reader = new FileReader();
reader.onload = function(e) {
document.getElementById('avatar-preview').src = e.target.result;
};
reader.readAsDataURL(input.files[0]);
}
}
</script>
@endpush
@endonce
@@ -0,0 +1,95 @@
@props(['mode', 'user' => null])
<div class="grid grid-cols-1 md:grid-cols-2 gap-5">
<!-- MSNV -->
<div class="relative">
<label class="block text-[14px] font-bold text-[#1a2b49] mb-2" for="msnv"> nhân viên (MSNV) <span class="text-red-500">*</span></label>
<div class="relative flex items-center w-full">
<span class="absolute left-4 text-gray-400 pointer-events-none w-5 h-5 flex items-center justify-center">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.8" stroke="currentColor" class="w-5 h-5">
<path stroke-linecap="round" stroke-linejoin="round" d="M15 9h3.75M15 12h3.75M15 15h3.75M4.5 19.5h15a2.25 2.25 0 002.25-2.25V6.75A2.25 2.25 0 0019.5 4.5h-15a2.25 2.25 0 00-2.25 2.25v10.5A2.25 2.25 0 004.5 19.5zm6-10.125a1.875 1.875 0 11-3.75 0 1.875 1.875 0 013.75 0zm1.294 6.336a6.721 6.721 0 01-3.17.789 6.721 6.721 0 01-3.168-.789 3.376 3.376 0 016.338 0z" />
</svg>
</span>
<input type="number" name="msnv" id="msnv"
class="form-input form-input-with-icon !rounded-xl !focus:border-[#3462f7] !focus:ring-[#3462f7]/20 {{ $mode !== 'create' ? '!bg-gray-50 !text-gray-500 cursor-not-allowed' : '' }}"
style="padding-left: 48px;"
{{ $mode !== 'create' ? 'readonly' : 'required' }}
value="{{ old('msnv', $user?->msnv) }}"
placeholder="VD: 1001">
</div>
@error('msnv')<span class="text-red-500 text-[13px] font-semibold mt-1.5 block">{{ $message }}</span>@enderror
</div>
<!-- Name -->
<div class="relative">
<label class="block text-[14px] font-bold text-[#1a2b49] mb-2" for="name">Họ tên <span class="text-red-500">*</span></label>
<div class="relative flex items-center w-full">
<span class="absolute left-4 text-gray-400 pointer-events-none w-5 h-5 flex items-center justify-center">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.8" stroke="currentColor" class="w-5 h-5">
<path stroke-linecap="round" stroke-linejoin="round" d="M15.75 6a3.75 3.75 0 1 1-7.5 0 3.75 3.75 0 0 1 7.5 0ZM4.501 20.118a7.5 7.5 0 0 1 14.998 0A17.933 17.933 0 0 1 12 21.75c-2.676 0-5.216-.584-7.499-1.632Z" />
</svg>
</span>
<input type="text" name="name" id="name"
class="form-input form-input-with-icon !rounded-xl !focus:border-[#3462f7] !focus:ring-[#3462f7]/20"
style="padding-left: 48px;"
required
value="{{ old('name', $user?->name) }}"
placeholder="VD: Nguyễn Văn A">
</div>
@error('name')<span class="text-red-500 text-[13px] font-semibold mt-1.5 block">{{ $message }}</span>@enderror
</div>
<!-- Email -->
<div class="relative">
<label class="block text-[14px] font-bold text-[#1a2b49] mb-2" for="mail">Email <span class="text-red-500">*</span></label>
<div class="relative flex items-center w-full">
<span class="absolute left-4 text-gray-400 pointer-events-none w-5 h-5 flex items-center justify-center">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.8" stroke="currentColor" class="w-5 h-5">
<path stroke-linecap="round" stroke-linejoin="round" d="M21.75 6.75v10.5a2.25 2.25 0 01-2.25 2.25h-15a2.25 2.25 0 01-2.25-2.25V6.75m19.5 0A2.25 2.25 0 0019.5 4.5h-15a2.25 2.25 0 00-2.25 2.25m19.5 0v.243a2.25 2.25 0 01-1.07 1.916l-7.5 4.615a2.25 2.25 0 01-2.36 0L3.32 8.91a2.25 2.25 0 01-1.07-1.916V6.75" />
</svg>
</span>
<input type="email" name="mail" id="mail"
class="form-input form-input-with-icon !rounded-xl !focus:border-[#3462f7] !focus:ring-[#3462f7]/20 {{ $mode === 'self' ? '!bg-gray-50 !text-gray-500 cursor-not-allowed' : '' }}"
style="padding-left: 48px;"
{{ $mode === 'self' ? 'readonly' : 'required' }}
value="{{ old('mail', $user?->mail) }}"
placeholder="VD: nguyenvana@runsystem.net">
</div>
@error('mail')<span class="text-red-500 text-[13px] font-semibold mt-1.5 block">{{ $message }}</span>@enderror
</div>
<!-- Department -->
<div class="relative">
<label class="block text-[14px] font-bold text-[#1a2b49] mb-2" for="departments">Phòng ban <span class="text-red-500">*</span></label>
@if($mode === 'self')
<div class="relative flex items-center w-full">
<span class="absolute left-4 text-gray-400 pointer-events-none w-5 h-5 flex items-center justify-center">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.8" stroke="currentColor" class="w-5 h-5">
<path stroke-linecap="round" stroke-linejoin="round" d="M20.25 14.15v4.25c0 .621-.504 1.125-1.125 1.125H4.875c-.621 0-1.125-.504-1.125-1.125v-4.25m16.5 0a2.18 2.18 0 0 0 .75-1.661V8.706c0-1.081-.768-2.015-1.837-2.175a48.114 48.114 0 0 0-3.413-.387m4.5 8.006c-.194.165-.453.254-.718.254H4.875a1.03 1.03 0 0 1-.718-.254m16.5 0c-.417-.38-.953-.59-1.517-.59H4.875A2.25 2.25 0 0 0 2.25 14.15v-4.25c0-1.081.768-2.015 1.837-2.175a48.067 48.067 0 0 1 16.076 0c1.069.16 1.837 1.094 1.837 2.175v4.25Z" />
</svg>
</span>
<input type="text"
class="form-input form-input-with-icon !rounded-xl !bg-gray-50 !text-gray-500 cursor-not-allowed w-full"
style="padding-left: 48px;"
readonly
title="{{ $user?->departments }}"
value="{{ $user?->departments }}">
</div>
@else
<div class="relative flex items-center w-full">
<span class="absolute left-4 text-gray-400 pointer-events-none w-5 h-5 flex items-center justify-center z-10">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.8" stroke="currentColor" class="w-5 h-5">
<path stroke-linecap="round" stroke-linejoin="round" d="M20.25 14.15v4.25c0 .621-.504 1.125-1.125 1.125H4.875c-.621 0-1.125-.504-1.125-1.125v-4.25m16.5 0a2.18 2.18 0 0 0 .75-1.661V8.706c0-1.081-.768-2.015-1.837-2.175a48.114 48.114 0 0 0-3.413-.387m4.5 8.006c-.194.165-.453.254-.718.254H4.875a1.03 1.03 0 0 1-.718-.254m16.5 0c-.417-.38-.953-.59-1.517-.59H4.875A2.25 2.25 0 0 0 2.25 14.15v-4.25c0-1.081.768-2.015 1.837-2.175a48.067 48.067 0 0 1 16.076 0c1.069.16 1.837 1.094 1.837 2.175v4.25Z" />
</svg>
</span>
<select name="departments" id="departments" class="form-input form-input-with-icon !rounded-xl !focus:border-[#3462f7] !focus:ring-[#3462f7]/20 w-full" style="padding-left: 48px;" required>
<option value="">-- Chọn phòng ban --</option>
@foreach(config('constants.DEPARTMENTS') as $dept)
<option value="{{ $dept }}" {{ old('departments', $user?->departments) == $dept ? 'selected' : '' }}>{{ $dept }}</option>
@endforeach
</select>
</div>
@endif
@error('departments')<span class="text-red-500 text-[13px] font-semibold mt-1.5 block">{{ $message }}</span>@enderror
</div>
</div>
@@ -68,8 +68,6 @@
width: 220px !important;
}
}
.ts-control { border-radius: 0.75rem; border-color: #d9dfe7; padding: 0.45rem 1rem; }
.ts-wrapper.form-input { padding: 0; border: none; }
</style>
@endpush
@@ -84,6 +82,9 @@
},
placeholder: "🔍 Tìm phòng ban...",
render: {
item: function(data, escape) {
return '<div class="item" title="' + escape(data.text) + '">' + escape(data.text) + '</div>';
},
no_results: function(data, escape) {
return '<div class="no-results p-3 text-gray-500">Không tìm thấy phòng ban nào</div>';
}
+46 -183
View File
@@ -22,18 +22,19 @@
@endphp
<div class="bg-white rounded-[24px] shadow-[0_10px_30px_rgba(15,23,42,0.05)] border border-white overflow-hidden flex flex-col justify-between">
<div class="bg-gradient-to-r from-blue-50/70 via-indigo-50/50 to-purple-50/30 px-6 py-6 sm:px-8 border-b border-gray-100">
<h3 class="text-[17px] font-extrabold text-[#1a2b49] leading-tight flex items-center gap-2">
<span>{{ match($mode) {
'create' => '👥 Thêm User Mới',
'edit' => '⚙️ Quản Lý Thẻ: ' . ($user?->msnv),
'delete' => '⚠️ Xác nhận nghỉ việc: ' . ($user?->msnv),
'self' => '👤 Thông tin cá nhân: ' . ($user?->msnv),
} }}</span>
</h3>
</div>
@if($mode !== 'edit')
<div class="bg-gradient-to-r from-blue-50/70 via-indigo-50/50 to-purple-50/30 px-6 py-6 sm:px-8 border-b border-gray-100">
<h3 class="text-[17px] font-extrabold text-[#1a2b49] leading-tight flex items-center gap-2">
<span>{{ match($mode) {
'create' => '👥 Thêm User Mới',
'delete' => '⚠️ Xác nhận nghỉ việc: ' . ($user?->msnv),
'self' => '👤 Thông tin cá nhân: ' . ($user?->msnv),
} }}</span>
</h3>
</div>
@endif
@if(!$isCreate && $user && !$isEdit)
@if(!$isCreate && $user && !$isEdit && !$isSelf)
<div class="p-6 sm:p-8 pb-0">
<div class="mb-2 p-5 rounded-2xl bg-gradient-to-br from-slate-50 to-gray-50 border border-gray-100 flex items-center gap-4 shadow-sm">
<div class="w-12 h-12 rounded-full bg-blue-50 text-[#3462f7] flex items-center justify-center font-extrabold text-lg shadow-inner flex-shrink-0">
@@ -55,168 +56,32 @@
@endif
@if($actionUrl)
<form action="{{ $actionUrl }}" method="POST" class="flex flex-col flex-1 justify-between">
<form action="{{ $actionUrl }}" method="POST" enctype="multipart/form-data" class="flex flex-col flex-1 justify-between">
@csrf
@if($method !== 'POST')
@method($method)
@endif
@endif
<div class="p-6 sm:p-8 {{ !$isCreate && !$isEdit ? 'pt-0' : '' }} space-y-5">
@if($isCreate)
<div class="relative">
<label class="block text-[14px] font-bold text-[#1a2b49] mb-2" for="msnv"> nhân viên (MSNV) <span class="text-red-500">*</span></label>
<div class="relative flex items-center w-full">
<span class="absolute left-4 text-gray-400 pointer-events-none w-5 h-5 flex items-center justify-center">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.8" stroke="currentColor" class="w-5 h-5">
<path stroke-linecap="round" stroke-linejoin="round" d="M15 9h3.75M15 12h3.75M15 15h3.75M4.5 19.5h15a2.25 2.25 0 002.25-2.25V6.75A2.25 2.25 0 0019.5 4.5h-15a2.25 2.25 0 00-2.25 2.25v10.5A2.25 2.25 0 004.5 19.5zm6-10.125a1.875 1.875 0 11-3.75 0 1.875 1.875 0 013.75 0zm1.294 6.336a6.721 6.721 0 01-3.17.789 6.721 6.721 0 01-3.168-.789 3.376 3.376 0 016.338 0z" />
</svg>
</span>
<input type="number" name="msnv" id="msnv" class="form-input form-input-with-icon !rounded-xl !focus:border-[#3462f7] !focus:ring-[#3462f7]/20" style="padding-left: 48px;" required value="{{ old('msnv') }}" placeholder="VD: 1001">
</div>
@error('msnv')<span class="text-red-500 text-[13px] font-semibold mt-1.5 block">{{ $message }}</span>@enderror
</div>
<div class="relative">
<label class="block text-[14px] font-bold text-[#1a2b49] mb-2" for="name">Họ tên <span class="text-red-500">*</span></label>
<div class="relative flex items-center w-full">
<span class="absolute left-4 text-gray-400 pointer-events-none w-5 h-5 flex items-center justify-center">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.8" stroke="currentColor" class="w-5 h-5">
<path stroke-linecap="round" stroke-linejoin="round" d="M15.75 6a3.75 3.75 0 1 1-7.5 0 3.75 3.75 0 0 1 7.5 0ZM4.501 20.118a7.5 7.5 0 0 1 14.998 0A17.933 17.933 0 0 1 12 21.75c-2.676 0-5.216-.584-7.499-1.632Z" />
</svg>
</span>
<input type="text" name="name" id="name" class="form-input form-input-with-icon !rounded-xl !focus:border-[#3462f7] !focus:ring-[#3462f7]/20" style="padding-left: 48px;" required value="{{ old('name') }}" placeholder="VD: Nguyễn Văn A">
</div>
@error('name')<span class="text-red-500 text-[13px] font-semibold mt-1.5 block">{{ $message }}</span>@enderror
</div>
<div class="relative">
<label class="block text-[14px] font-bold text-[#1a2b49] mb-2" for="mail">Email <span class="text-red-500">*</span></label>
<div class="relative flex items-center w-full">
<span class="absolute left-4 text-gray-400 pointer-events-none w-5 h-5 flex items-center justify-center">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.8" stroke="currentColor" class="w-5 h-5">
<path stroke-linecap="round" stroke-linejoin="round" d="M21.75 6.75v10.5a2.25 2.25 0 01-2.25 2.25h-15a2.25 2.25 0 01-2.25-2.25V6.75m19.5 0A2.25 2.25 0 0019.5 4.5h-15a2.25 2.25 0 00-2.25 2.25m19.5 0v.243a2.25 2.25 0 01-1.07 1.916l-7.5 4.615a2.25 2.25 0 01-2.36 0L3.32 8.91a2.25 2.25 0 01-1.07-1.916V6.75" />
</svg>
</span>
<input type="email" name="mail" id="mail" class="form-input form-input-with-icon !rounded-xl !focus:border-[#3462f7] !focus:ring-[#3462f7]/20" style="padding-left: 48px;" required value="{{ old('mail') }}" placeholder="VD: nguyenvana@runsystem.net">
</div>
@error('mail')<span class="text-red-500 text-[13px] font-semibold mt-1.5 block">{{ $message }}</span>@enderror
</div>
<div class="relative">
<label class="block text-[14px] font-bold text-[#1a2b49] mb-2" for="departments">Phòng ban <span class="text-red-500">*</span></label>
<select name="departments" id="departments" class="form-input !rounded-xl !focus:border-[#3462f7] !focus:ring-[#3462f7]/20" required>
<option value="">-- Chọn phòng ban --</option>
@foreach(config('constants.DEPARTMENTS') as $dept)
<option value="{{ $dept }}" {{ old('departments') == $dept ? 'selected' : '' }}>{{ $dept }}</option>
@endforeach
</select>
@error('departments')<span class="text-red-500 text-[13px] font-semibold mt-1.5 block">{{ $message }}</span>@enderror
</div>
<div class="relative">
<label class="block text-[14px] font-bold text-[#1a2b49] mb-2" for="password">Mật khẩu khởi tạo <span class="text-red-500">*</span></label>
<div class="relative flex items-center w-full">
<span class="absolute left-4 text-gray-400 pointer-events-none w-5 h-5 flex items-center justify-center">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.8" stroke="currentColor" class="w-5 h-5">
<path stroke-linecap="round" stroke-linejoin="round" d="M16.5 10.5V6.75a4.5 4.5 0 10-9 0V10.5m-2.812 10.5h14.625c.621 0 1.125-.504 1.125-1.125V11.25c0-.621-.504-1.125-1.125-1.125H3.75c-.621 0-1.125.504-1.125 1.125v7.875c0 .621.504 1.125 1.125 1.125z" />
</svg>
</span>
<input type="text" name="password" id="password" class="form-input form-input-with-icon !rounded-xl !bg-gray-50 !text-gray-500 cursor-not-allowed" style="padding-left: 48px;" readonly required value="1234@Dcba">
</div>
<span class="text-[13px] text-gray-400 mt-2 block">Mật khẩu mặc định cho tất cả user mới tạo.</span>
@error('password')<span class="text-red-500 text-[13px] font-semibold mt-1.5 block">{{ $message }}</span>@enderror
</div>
<div class="relative">
<label class="block text-[14px] font-bold text-[#1a2b49] mb-2" for="role">Quyền hạn <span class="text-red-500">*</span></label>
<select name="role" id="role" class="form-input !rounded-xl !focus:border-[#3462f7] !focus:ring-[#3462f7]/20" required>
<option value="{{ config('constants.ROLE_MEMBER') }}" {{ old('role') == config('constants.ROLE_MEMBER') ? 'selected' : '' }}>Member</option>
<option value="{{ config('constants.ROLE_ADMIN') }}" {{ old('role') == config('constants.ROLE_ADMIN') ? 'selected' : '' }}>Admin</option>
</select>
@error('role')<span class="text-red-500 text-[13px] font-semibold mt-1.5 block">{{ $message }}</span>@enderror
</div>
@endif
@if($isEdit && $user)
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<div class="relative">
<label class="block text-[14px] font-bold text-[#1a2b49] mb-2" for="mail">Email (Chỉ xem)</label>
<div class="relative flex items-center w-full">
<span class="absolute left-4 text-gray-400 pointer-events-none w-5 h-5 flex items-center justify-center">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.8" stroke="currentColor" class="w-5 h-5">
<path stroke-linecap="round" stroke-linejoin="round" d="M21.75 6.75v10.5a2.25 2.25 0 01-2.25 2.25h-15a2.25 2.25 0 01-2.25-2.25V6.75m19.5 0A2.25 2.25 0 0019.5 4.5h-15a2.25 2.25 0 00-2.25 2.25m19.5 0v.243a2.25 2.25 0 01-1.07 1.916l-7.5 4.615a2.25 2.25 0 01-2.36 0L3.32 8.91a2.25 2.25 0 01-1.07-1.916V6.75" />
</svg>
</span>
<input type="email" name="mail" id="mail" class="form-input form-input-with-icon !rounded-xl !bg-gray-50 !text-gray-500 cursor-not-allowed w-full" style="padding-left: 48px;" readonly value="{{ $user->mail }}">
</div>
</div>
<div class="relative">
<label class="block text-[14px] font-bold text-[#1a2b49] mb-2" for="password">Mật khẩu (Chỉ xem)</label>
<div class="relative flex items-center w-full">
<span class="absolute left-4 text-gray-400 pointer-events-none w-5 h-5 flex items-center justify-center">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.8" stroke="currentColor" class="w-5 h-5">
<path stroke-linecap="round" stroke-linejoin="round" d="M16.5 10.5V6.75a4.5 4.5 0 10-9 0V10.5m-2.812 10.5h14.625c.621 0 1.125-.504 1.125-1.125V11.25c0-.621-.504-1.125-1.125-1.125H3.75c-.621 0-1.125.504-1.125 1.125v7.875c0 .621.504 1.125 1.125 1.125z" />
</svg>
</span>
<input type="password" name="password" id="password" class="form-input form-input-with-icon !rounded-xl !bg-gray-50 !text-gray-500 cursor-not-allowed w-full" style="padding-left: 48px;" readonly value="********">
</div>
</div>
<div class="relative">
<label class="block text-[14px] font-bold text-[#1a2b49] mb-2">Quyền hạn</label>
<div class="grid grid-cols-2 gap-2 mt-2 bg-gray-100/80 p-1 rounded-xl">
<label class="flex items-center justify-center gap-2 cursor-pointer py-2 rounded-lg transition-all text-center has-[:checked]:bg-white has-[:checked]:text-[#3462f7] has-[:checked]:shadow-sm text-gray-500 font-bold text-sm hover:bg-white/50">
<input type="radio" name="role" value="{{ config('constants.ROLE_MEMBER') }}" class="sr-only" {{ old('role', $user->role) == config('constants.ROLE_MEMBER') ? 'checked' : '' }}>
<span>Member</span>
</label>
<label class="flex items-center justify-center gap-2 cursor-pointer py-2 rounded-lg transition-all text-center has-[:checked]:bg-white has-[:checked]:text-[#3462f7] has-[:checked]:shadow-sm text-gray-500 font-bold text-sm hover:bg-white/50">
<input type="radio" name="role" value="{{ config('constants.ROLE_ADMIN') }}" class="sr-only" {{ old('role', $user->role) == config('constants.ROLE_ADMIN') ? 'checked' : '' }}>
<span>Admin</span>
</label>
</div>
@error('role')<span class="text-red-500 text-[13px] font-semibold mt-1.5 block">{{ $message }}</span>@enderror
</div>
<div class="relative">
<label class="block text-[14px] font-bold text-[#1a2b49] mb-2">Trạng thái</label>
<div class="grid grid-cols-2 gap-2 mt-2 bg-gray-100/80 p-1 rounded-xl">
<label class="flex items-center justify-center gap-2 cursor-pointer py-2 rounded-lg transition-all text-center has-[:checked]:bg-white has-[:checked]:text-[#3462f7] has-[:checked]:shadow-sm text-gray-500 font-bold text-sm hover:bg-white/50">
<input type="radio" name="status" value="{{ config('constants.STATUS_ACTIVE') }}" class="sr-only" {{ old('status', $user->status) == config('constants.STATUS_ACTIVE') ? 'checked' : '' }}>
<span>Đang làm việc</span>
</label>
<label class="flex items-center justify-center gap-2 cursor-pointer py-2 rounded-lg transition-all text-center has-[:checked]:bg-white has-[:checked]:text-[#3462f7] has-[:checked]:shadow-sm text-gray-500 font-bold text-sm hover:bg-white/50">
<input type="radio" name="status" value="{{ config('constants.STATUS_INACTIVE') }}" class="sr-only" {{ old('status', $user->status) == config('constants.STATUS_INACTIVE') ? 'checked' : '' }}>
<span>Đã nghỉ việc</span>
</label>
</div>
@error('status')<span class="text-red-500 text-[13px] font-semibold mt-1.5 block">{{ $message }}</span>@enderror
</div>
<div class="relative">
<label class="block text-[14px] font-bold text-[#1a2b49] mb-2">Quyền gửi thẻ</label>
<div class="grid grid-cols-2 gap-2 mt-2 bg-gray-100/80 p-1 rounded-xl">
<label class="flex items-center justify-center gap-2 cursor-pointer py-2 rounded-lg transition-all text-center has-[:checked]:bg-white has-[:checked]:text-[#3462f7] has-[:checked]:shadow-sm text-gray-500 font-bold text-sm hover:bg-white/50">
<input type="radio" name="flag_send" value="{{ config('constants.FLAG_SEND_ENABLED') }}" class="sr-only" {{ old('flag_send', $user->flag_send) == config('constants.FLAG_SEND_ENABLED') ? 'checked' : '' }}>
<span>Được phép gửi</span>
</label>
<label class="flex items-center justify-center gap-2 cursor-pointer py-2 rounded-lg transition-all text-center has-[:checked]:bg-white has-[:checked]:text-[#3462f7] has-[:checked]:shadow-sm text-gray-500 font-bold text-sm hover:bg-white/50">
<input type="radio" name="flag_send" value="{{ config('constants.FLAG_SEND_DISABLED') }}" class="sr-only" {{ old('flag_send', $user->flag_send) == config('constants.FLAG_SEND_DISABLED') ? 'checked' : '' }}>
<span>Không cho phép</span>
</label>
</div>
@error('flag_send')<span class="text-red-500 text-[13px] font-semibold mt-1.5 block">{{ $message }}</span>@enderror
</div>
<div class="hidden md:block"></div>
</div>
@endif
<div class="p-6 sm:p-8 {{ !$isCreate && !$isEdit && !$isSelf ? 'pt-0' : '' }} space-y-5">
@if($isDelete && $user)
<p class="text-sm text-red-600 leading-relaxed font-semibold">
Bạn chắc chắn muốn cho nhân viên {{ $user->msnv }} nghỉ việc không? Tài khoản này sẽ không thể đăng nhập hoặc thực hiện các giao dịch thẻ sau khi xác nhận.
</p>
@else
<!-- 1. User Avatar Section -->
@if($isSelf || $isEdit)
<x-user-avatar-section :mode="$mode" :user="$user" />
@endif
<!-- 2. User Basic Information Section -->
<x-user-basic-info :mode="$mode" :user="$user" />
<!-- 3. User Security Section -->
<x-user-security-section :mode="$mode" :user="$user" />
<!-- 4. User Administration Fields Section -->
<x-user-admin-section :mode="$mode" :user="$user" />
@endif
</div>
@@ -227,30 +92,28 @@
@endif
</div>
@if($isCreate)
@push('styles')
<style>
.ts-control { border-radius: 0.75rem; border-color: #d9dfe7; padding: 0.65rem 1rem; }
.ts-wrapper.form-input { padding: 0; border: none; }
</style>
@endpush
@if($isCreate || $isEdit)
@push('scripts')
<script>
document.addEventListener('DOMContentLoaded', function () {
new TomSelect("#departments", {
create: false,
sortField: {
field: "text",
direction: "asc"
},
placeholder: "🔍 Tìm phòng ban...",
render: {
no_results: function(data, escape) {
return '<div class="no-results p-3 text-gray-500">Không tìm thấy phòng ban nào</div>';
if (document.getElementById('departments') && typeof TomSelect === 'function') {
new TomSelect("#departments", {
create: false,
sortField: {
field: "text",
direction: "asc"
},
placeholder: "Tìm phòng ban...",
render: {
item: function(data, escape) {
return '<div class="item" title="' + escape(data.text) + '">' + escape(data.text) + '</div>';
},
no_results: function(data, escape) {
return '<div class="no-results p-3 text-gray-500">Không tìm thấy phòng ban nào</div>';
}
}
}
});
});
}
});
</script>
@endpush
+55 -22
View File
@@ -44,11 +44,11 @@
</div>
</div>
<div class="flex items-center gap-4 bg-slate-50 border border-slate-100 rounded-2xl p-4 relative z-10 min-w-[200px] shadow-sm">
<div class="flex items-center gap-4 bg-slate-50 border border-slate-100 rounded-2xl p-4 relative z-10 min-w-[220px] shadow-sm">
<div class="w-11 h-11 bg-white rounded-xl shadow-sm flex items-center justify-center text-xl">
🎫
</div>
<div>
<div class="flex-1">
<span class="text-[10px] font-bold text-gray-500 uppercase tracking-wider block">Số thẻ hiện </span>
<span id="user-card-balance" class="text-xl font-black text-[#1a2b49]">{{ $user->card }}</span>
</div>
@@ -82,10 +82,10 @@
<table class="min-w-full divide-y divide-gray-100 text-sm">
<thead class="bg-gray-50/50 sticky top-0 z-10">
<tr>
<th class="px-6 py-4 text-center text-xs font-bold text-text-light uppercase tracking-wider w-24">Hành động</th>
<th class="px-6 py-4 text-left text-xs font-bold text-text-light uppercase tracking-wider">Ngày cấp</th>
<th class="px-6 py-4 text-left text-xs font-bold text-text-light uppercase tracking-wider">Người cấp (Admin)</th>
<th class="px-6 py-4 text-left text-xs font-bold text-text-light uppercase tracking-wider">Số lượng thẻ</th>
<th class="px-6 py-4 text-center text-xs font-bold text-text-light uppercase tracking-wider w-24">Hành động</th>
</tr>
</thead>
<tbody class="bg-white divide-y divide-gray-100">
@@ -94,6 +94,16 @@
$isCurrentMonth = Carbon\Carbon::parse($addCard->date)->format('Y-m') === Carbon\Carbon::now()->format('Y-m');
@endphp
<tr class="hover:bg-slate-50/60 transition-colors">
<td class="px-6 py-3.5 whitespace-nowrap text-center">
@if($isCurrentMonth)
<div class="flex items-center justify-center gap-3">
<button type="button" onclick="openEditAddCardModal({{ json_encode($addCard) }})" class="text-blue-600 hover:text-blue-900 font-bold transition-colors">Sửa</button>
<button type="button" onclick="confirmDeleteAddCard({{ $addCard->id }})" class="text-red-600 hover:text-red-900 font-bold transition-colors">Xóa</button>
</div>
@else
<span class="text-gray-400 font-semibold text-xs cursor-not-allowed" title="Không thể chỉnh sửa giao dịch tháng trước">Tháng trước</span>
@endif
</td>
<td class="px-6 py-3.5 whitespace-nowrap text-text-medium font-medium">{{ Carbon\Carbon::parse($addCard->date)->format('d/m/Y') }}</td>
<td class="px-6 py-3.5 whitespace-nowrap font-bold text-[#1a2b49]">
@if($addCard->sellerUser)
@@ -106,13 +116,6 @@
@endif
</td>
<td class="px-6 py-3.5 whitespace-nowrap font-extrabold text-base text-green-600">+{{ $addCard->num_card }}</td>
<td class="px-6 py-3.5 whitespace-nowrap text-center">
@if($isCurrentMonth)
<button type="button" onclick="openEditAddCardModal({{ json_encode($addCard) }})" class="text-blue-600 hover:text-blue-900 font-bold transition-colors">Sửa</button>
@else
<span class="text-gray-400 font-semibold text-xs cursor-not-allowed" title="Không thể chỉnh sửa giao dịch tháng trước">Tháng trước</span>
@endif
</td>
</tr>
@empty
<tr>
@@ -124,9 +127,12 @@
</tbody>
</table>
</div>
@php
$isCurrentMonthFilter = $selectedMonth === Carbon\Carbon::now()->format('Y-m');
@endphp
<div class="px-6 py-4 bg-slate-50 border-t border-gray-100 flex justify-end">
<button type="button" onclick="openAddCardModal()" class="inline-flex items-center gap-2 bg-[#3462f7] hover:bg-blue-700 text-white font-bold text-sm px-5 py-2.5 rounded-xl shadow-md shadow-blue-500/10 transition-all">
Thêm thẻ
<button type="button" onclick="openAddCardModal()" {{ !$isCurrentMonthFilter ? 'disabled' : '' }} class="inline-flex items-center justify-center bg-[#3462f7] hover:bg-blue-700 disabled:bg-gray-300 disabled:text-gray-500 disabled:cursor-not-allowed disabled:shadow-none text-white font-bold text-sm px-5 py-2.5 rounded-xl shadow-md shadow-blue-500/10 transition-all">
Thêm thẻ
</button>
</div>
</div>
@@ -171,10 +177,10 @@
<table class="min-w-full divide-y divide-gray-100 text-sm">
<thead class="bg-gray-50/50 sticky top-0">
<tr>
<th class="px-6 py-4 text-center text-xs font-bold text-text-light uppercase tracking-wider w-24">Hành động</th>
<th class="px-6 py-4 text-left text-xs font-bold text-text-light uppercase tracking-wider">Ngày cấp</th>
<th class="px-6 py-4 text-left text-xs font-bold text-text-light uppercase tracking-wider">Người cấp (Admin)</th>
<th class="px-6 py-4 text-left text-xs font-bold text-text-light uppercase tracking-wider">Số lượng thẻ</th>
<th class="px-6 py-4 text-center text-xs font-bold text-text-light uppercase tracking-wider w-24">Hành động</th>
</tr>
</thead>
<tbody class="bg-white divide-y divide-gray-100">
@@ -183,6 +189,16 @@
$isCurrentMonth = Carbon\Carbon::parse($addCard->date)->format('Y-m') === Carbon\Carbon::now()->format('Y-m');
@endphp
<tr class="hover:bg-slate-50/60 transition-colors">
<td class="px-6 py-3.5 whitespace-nowrap text-center">
@if($isCurrentMonth)
<div class="flex items-center justify-center gap-3">
<button type="button" onclick="openEditAddCardModal({{ json_encode($addCard) }})" class="text-blue-600 hover:text-blue-900 font-bold transition-colors">Sửa</button>
<button type="button" onclick="confirmDeleteAddCard({{ $addCard->id }})" class="text-red-600 hover:text-red-900 font-bold transition-colors">Xóa</button>
</div>
@else
<span class="text-gray-400 font-semibold text-xs cursor-not-allowed" title="Không thể chỉnh sửa giao dịch tháng trước">Tháng trước</span>
@endif
</td>
<td class="px-6 py-3.5 whitespace-nowrap text-text-medium font-medium">{{ Carbon\Carbon::parse($addCard->date)->format('d/m/Y') }}</td>
<td class="px-6 py-3.5 whitespace-nowrap font-bold text-[#1a2b49]">
@if($addCard->sellerUser)
@@ -195,13 +211,6 @@
@endif
</td>
<td class="px-6 py-3.5 whitespace-nowrap font-extrabold text-base text-green-600">+{{ $addCard->num_card }}</td>
<td class="px-6 py-3.5 whitespace-nowrap text-center">
@if($isCurrentMonth)
<button type="button" onclick="openEditAddCardModal({{ json_encode($addCard) }})" class="text-blue-600 hover:text-blue-900 font-bold transition-colors">Sửa</button>
@else
<span class="text-gray-400 font-semibold text-xs cursor-not-allowed" title="Không thể chỉnh sửa giao dịch tháng trước">Tháng trước</span>
@endif
</td>
</tr>
@empty
<tr>
@@ -213,9 +222,12 @@
</tbody>
</table>
</div>
@php
$isCurrentMonthFilter = $selectedMonth === Carbon\Carbon::now()->format('Y-m');
@endphp
<div class="px-6 py-4 bg-slate-50 border-t border-gray-100 flex justify-end">
<button type="button" onclick="openAddCardModal()" class="inline-flex items-center gap-2 bg-[#3462f7] hover:bg-blue-700 text-white font-bold text-sm px-5 py-2.5 rounded-xl shadow-md shadow-blue-500/10 transition-all">
Thêm thẻ
<button type="button" onclick="openAddCardModal()" {{ !$isCurrentMonthFilter ? 'disabled' : '' }} class="inline-flex items-center justify-center bg-[#3462f7] hover:bg-blue-700 disabled:bg-gray-300 disabled:text-gray-500 disabled:cursor-not-allowed disabled:shadow-none text-white font-bold text-sm px-5 py-2.5 rounded-xl shadow-md shadow-blue-500/10 transition-all">
Thêm thẻ
</button>
</div>
</div>
@@ -275,6 +287,8 @@
</form>
</x-modal>
<x-confirm-modal id="deleteAddCardModal" title="Xác nhận xóa giao dịch" confirmText="Xác nhận xóa" />
<script>
function openEditAddCardModal(record) {
const form = document.getElementById('editAddCardForm');
@@ -340,6 +354,20 @@
}
}
function confirmDeleteAddCard(id) {
const messageEl = document.getElementById('deleteAddCardModal-message');
if (messageEl) {
messageEl.innerHTML = 'Bạn có chắc chắn muốn xóa giao dịch cấp phát thẻ này không? Thao tác này sẽ trừ số thẻ tương ứng của user.';
}
const formEl = document.getElementById('deleteAddCardModal-form');
if (formEl) {
formEl.action = `/admin/add-cards/${id}`;
}
openModal('deleteAddCardModal');
}
async function reloadListsSection() {
try {
const response = await fetch(window.location.href, {
@@ -379,6 +407,11 @@
if (editForm) {
editForm.addEventListener('submit', (e) => submitModalForm(e, 'editAddCardForm', 'editAddCardModal'));
}
const deleteForm = document.getElementById('deleteAddCardModal-form');
if (deleteForm) {
deleteForm.addEventListener('submit', (e) => submitModalForm(e, 'deleteAddCardModal-form', 'deleteAddCardModal'));
}
}
if (document.readyState !== 'loading') {
@@ -0,0 +1,103 @@
@props(['mode', 'user' => null])
@if($mode === 'create')
<div class="relative">
<label class="block text-[14px] font-bold text-[#1a2b49] mb-2" for="password">Mật khẩu khởi tạo <span class="text-red-500">*</span></label>
<div class="relative flex items-center w-full">
<span class="absolute left-4 text-gray-400 pointer-events-none w-5 h-5 flex items-center justify-center">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.8" stroke="currentColor" class="w-5 h-5">
<path stroke-linecap="round" stroke-linejoin="round" d="M16.5 10.5V6.75a4.5 4.5 0 10-9 0V10.5m-2.812 10.5h14.625c.621 0 1.125-.504 1.125-1.125V11.25c0-.621-.504-1.125-1.125-1.125H3.75c-.621 0-1.125.504-1.125 1.125v7.875c0 .621.504 1.125 1.125 1.125z" />
</svg>
</span>
<input type="text" name="password" id="password" class="form-input form-input-with-icon !rounded-xl !bg-gray-50 !text-gray-500 cursor-not-allowed" style="padding-left: 48px;" readonly required value="1234@Dcba">
</div>
<span class="text-[13px] text-gray-400 mt-2 block">Mật khẩu mặc định cho tất cả user mới tạo.</span>
</div>
@elseif($mode === 'edit')
<div class="relative">
<label class="block text-[14px] font-bold text-[#1a2b49] mb-2" for="password">Mật khẩu (Chỉ xem)</label>
<div class="relative flex items-center w-full">
<span class="absolute left-4 text-gray-400 pointer-events-none w-5 h-5 flex items-center justify-center">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.8" stroke="currentColor" class="w-5 h-5">
<path stroke-linecap="round" stroke-linejoin="round" d="M16.5 10.5V6.75a4.5 4.5 0 10-9 0V10.5m-2.812 10.5h14.625c.621 0 1.125-.504 1.125-1.125V11.25c0-.621-.504-1.125-1.125-1.125H3.75c-.621 0-1.125.504-1.125 1.125v7.875c0 .621.504 1.125 1.125 1.125z" />
</svg>
</span>
<input type="password" name="password" id="password" class="form-input form-input-with-icon !rounded-xl !bg-gray-50 !text-gray-500 cursor-not-allowed w-full" style="padding-left: 48px;" readonly value="********">
</div>
</div>
@elseif($mode === 'self')
<div class="border-t border-gray-100 pt-5 mt-5">
<h4 class="text-sm font-bold text-[#1a2b49] mb-4 flex items-center gap-2">
🔑 Bảo mật & Đổi mật khẩu
</h4>
<div class="space-y-4">
<!-- Current Password -->
<div class="flex flex-col">
<label class="text-[13px] font-bold text-gray-700 mb-1.5" for="current_password">Mật khẩu hiện tại</label>
<div class="relative">
<span class="absolute left-3.5 top-1/2 -translate-y-1/2 text-gray-400">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" class="w-5 h-5">
<path stroke-linecap="round" stroke-linejoin="round" d="M16.5 10.5V6.75a4.5 4.5 0 10-9 0V10.5m-2.812 10.5h14.625c.621 0 1.125-.504 1.125-1.125V11.25c0-.621-.504-1.125-1.125-1.125H3.75c-.621 0-1.125.504-1.125 1.125v7.875c0 .621.504 1.125 1.125 1.125z"/>
</svg>
</span>
<input type="password" name="current_password" id="current_password"
class="w-full h-[46px] pl-[42px] pr-12 bg-white border @error('current_password') border-red-500 focus:border-red-500 focus:ring-red-500/20 @else border-gray-200 focus:border-[#3462f7] focus:ring-[#3462f7]/20 @enderror rounded-xl text-sm text-gray-800 outline-none transition-all shadow-sm placeholder:text-gray-400"
placeholder="Nhập mật khẩu hiện tại (chỉ cần khi đổi mật khẩu)">
<button type="button" onclick="togglePasswordVisibility('current_password', this)" class="absolute right-3.5 top-1/2 -translate-y-1/2 text-gray-400 hover:text-gray-600 focus:outline-none cursor-pointer" aria-label="Show password">
<svg class="w-5 h-5 eye-icon-closed" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.5"><path stroke-linecap="round" stroke-linejoin="round" d="M3.98 8.223A10.477 10.477 0 001.934 12c1.292 4.338 5.31 7.5 10.066 7.5.993 0 1.953-.138 2.863-.395M6.228 6.228A10.45 10.45 0 0112 4.5c4.756 0 8.773 3.162 10.065 7.498a10.523 10.523 0 01-4.293 5.774M6.228 6.228L3 3m3.228 3.228l3.65 3.65m7.894 7.894L21 21m-3.228-3.228l-3.65-3.65m0 0a3 3 0 10-4.243-4.243m4.242 4.242L9.88 9.88"/></svg>
<svg class="w-5 h-5 eye-icon-open hidden" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.5"><path stroke-linecap="round" stroke-linejoin="round" d="M2.036 12.322a1.012 1.012 0 010-.639C3.423 7.51 7.36 4.5 12 4.5c4.638 0 8.573 3.007 9.963 7.178.07.207.07.431 0 .639C20.577 16.49 16.64 19.5 12 19.5c-4.638 0-8.573-3.007-9.963-7.178z"/><path stroke-linecap="round" stroke-linejoin="round" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"/></svg>
</button>
</div>
@error('current_password')
<span class="text-red-500 text-[12px] font-semibold mt-1.5 block">{{ $message }}</span>
@enderror
</div>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<!-- New Password -->
<div class="flex flex-col">
<label class="text-[13px] font-bold text-gray-700 mb-1.5" for="new_password">Mật khẩu mới</label>
<div class="relative">
<span class="absolute left-3.5 top-1/2 -translate-y-1/2 text-gray-400">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" class="w-5 h-5">
<path stroke-linecap="round" stroke-linejoin="round" d="M16.5 10.5V6.75a4.5 4.5 0 10-9 0V10.5m-2.812 10.5h14.625c.621 0 1.125-.504 1.125-1.125V11.25c0-.621-.504-1.125-1.125-1.125H3.75c-.621 0-1.125.504-1.125 1.125v7.875c0 .621.504 1.125 1.125 1.125z"/>
</svg>
</span>
<input type="password" name="new_password" id="new_password"
class="w-full h-[46px] pl-[42px] pr-12 bg-white border @error('new_password') border-red-500 focus:border-red-500 focus:ring-red-500/20 @else border-gray-200 focus:border-[#3462f7] focus:ring-[#3462f7]/20 @enderror rounded-xl text-sm text-gray-800 outline-none transition-all shadow-sm placeholder:text-gray-400"
placeholder="Nhập mật khẩu mới">
<button type="button" onclick="togglePasswordVisibility('new_password', this)" class="absolute right-3.5 top-1/2 -translate-y-1/2 text-gray-400 hover:text-gray-600 focus:outline-none cursor-pointer" aria-label="Show password">
<svg class="w-5 h-5 eye-icon-closed" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.5"><path stroke-linecap="round" stroke-linejoin="round" d="M3.98 8.223A10.477 10.477 0 001.934 12c1.292 4.338 5.31 7.5 10.066 7.5.993 0 1.953-.138 2.863-.395M6.228 6.228A10.45 10.45 0 0112 4.5c4.756 0 8.773 3.162 10.065 7.498a10.523 10.523 0 01-4.293 5.774M6.228 6.228L3 3m3.228 3.228l3.65 3.65m7.894 7.894L21 21m-3.228-3.228l-3.65-3.65m0 0a3 3 0 10-4.243-4.243m4.242 4.242L9.88 9.88"/></svg>
<svg class="w-5 h-5 eye-icon-open hidden" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.5"><path stroke-linecap="round" stroke-linejoin="round" d="M2.036 12.322a1.012 1.012 0 010-.639C3.423 7.51 7.36 4.5 12 4.5c4.638 0 8.573 3.007 9.963 7.178.07.207.07.431 0 .639C20.577 16.49 16.64 19.5 12 19.5c-4.638 0-8.573-3.007-9.963-7.178z"/><path stroke-linecap="round" stroke-linejoin="round" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"/></svg>
</button>
</div>
@error('new_password')
<span class="text-red-500 text-[12px] font-semibold mt-1.5 block">{{ $message }}</span>
@enderror
</div>
<!-- Confirm Password -->
<div class="flex flex-col">
<label class="text-[13px] font-bold text-gray-700 mb-1.5" for="new_password_confirmation">Xác nhận mật khẩu mới</label>
<div class="relative">
<span class="absolute left-3.5 top-1/2 -translate-y-1/2 text-gray-400">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" class="w-5 h-5">
<path stroke-linecap="round" stroke-linejoin="round" d="M9 12.75L11.25 15 15 9.75M21 12a9 9 0 11-18 0 9 9 0 0118 0z"/>
</svg>
</span>
<input type="password" name="new_password_confirmation" id="new_password_confirmation"
class="w-full h-[46px] pl-[42px] pr-12 bg-white border @error('new_password_confirmation') border-red-500 focus:border-red-500 focus:ring-red-500/20 @else border-gray-200 focus:border-[#3462f7] focus:ring-[#3462f7]/20 @enderror rounded-xl text-sm text-gray-800 outline-none transition-all shadow-sm placeholder:text-gray-400"
placeholder="Nhập lại mật khẩu mới">
<button type="button" onclick="togglePasswordVisibility('new_password_confirmation', this)" class="absolute right-3.5 top-1/2 -translate-y-1/2 text-gray-400 hover:text-gray-600 focus:outline-none cursor-pointer" aria-label="Show password">
<svg class="w-5 h-5 eye-icon-closed" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.5"><path stroke-linecap="round" stroke-linejoin="round" d="M3.98 8.223A10.477 10.477 0 001.934 12c1.292 4.338 5.31 7.5 10.066 7.5.993 0 1.953-.138 2.863-.395M6.228 6.228A10.45 10.45 0 0112 4.5c4.756 0 8.773 3.162 10.065 7.498a10.523 10.523 0 01-4.293 5.774M6.228 6.228L3 3m3.228 3.228l3.65 3.65m7.894 7.894L21 21m-3.228-3.228l-3.65-3.65m0 0a3 3 0 10-4.243-4.243m4.242 4.242L9.88 9.88"/></svg>
<svg class="w-5 h-5 eye-icon-open hidden" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.5"><path stroke-linecap="round" stroke-linejoin="round" d="M2.036 12.322a1.012 1.012 0 010-.639C3.423 7.51 7.36 4.5 12 4.5c4.638 0 8.573 3.007 9.963 7.178.07.207.07.431 0 .639C20.577 16.49 16.64 19.5 12 19.5c-4.638 0-8.573-3.007-9.963-7.178z"/><path stroke-linecap="round" stroke-linejoin="round" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"/></svg>
</button>
</div>
@error('new_password_confirmation')
<span class="text-red-500 text-[12px] font-semibold mt-1.5 block">{{ $message }}</span>
@enderror
</div>
</div>
</div>
</div>
@endif
+6 -68
View File
@@ -3,76 +3,14 @@
@section('title', 'Cài đặt tài khoản')
@section('content')
<div class="max-w-[480px] mx-auto bg-white rounded-[24px] shadow-[0_10px_30px_rgba(15,23,42,0.05)] p-8 mt-6">
<h3 class="text-[20px] font-extrabold text-[#1a2b49] mb-4 text-center">Cài đặt tài khoản</h3>
<div class="max-w-2xl mx-auto w-full mt-6">
@if(session('success'))
<div class="mb-4 p-3 bg-green-100 text-green-800 rounded-xl">{{ session('success') }}</div>
<div class="mb-4 p-4 bg-green-50 border border-green-200 text-green-800 rounded-2xl font-semibold flex items-center gap-2 shadow-sm">
<span></span>
<span>{{ session('success') }}</span>
</div>
@endif
<form action="{{ route('user.update_profile') }}" method="POST" enctype="multipart/form-data">
@csrf
<!-- Avatar preview and upload -->
<div class="flex items-center gap-4 mb-6">
<div class="w-16 h-16 rounded-full overflow-hidden border border-gray-200">
<img src="{{ Auth::user()->avatar ?? asset('images/avatar.png') }}" alt="Avatar" class="w-full h-full object-cover" id="avatar-preview" onerror="this.onerror=null; this.src='{{ asset('images/avatar.png') }}';">
</div>
<div class="flex-1">
<label class="block text-[13px] font-bold text-gray-700 mb-1" for="avatar">Thay đổi ảnh đại diện</label>
<input type="file" name="avatar" id="avatar" accept="image/*" class="block w-full text-sm text-gray-500 file:mr-4 file:py-2 file:px-3 file:rounded-full file:border-0 file:text-sm file:font-semibold file:bg-[#3462f7] file:text-white hover:file:bg-[#254edb]" onchange="previewAvatar(event)">
</div>
</div>
<!-- New password -->
<div class="space-y-6 mb-4">
<div class="flex flex-col">
<label class="text-[13px] font-bold text-gray-700 mb-1.5" for="new_password">Mật khẩu mới <span class="text-red-500">*</span></label>
<div class="relative">
<span class="absolute left-3.5 top-1/2 -translate-y-1/2 text-gray-400"><svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"2\" stroke=\"currentColor\" class=\"w-5 h-5\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M16.5 10.5V6.75a4.5 4.5 0 10-9 0V10.5m-2.812 10.5h14.625c.621 0 1.125-.504 1.125-1.125V11.25c0-.621-.504-1.125-1.125-1.125H3.75c-.621 0-1.125.504-1.125 1.125v7.875c0 .621.504 1.125 1.125 1.125z\"/></svg></span>
<input type="password" name="new_password" id="new_password" class="w-full h-[46px] pl-[42px] pr-12 bg-white border @error('new_password') border-red-500 focus:border-red-500 focus:ring-red-500/20 @else border-gray-200 focus:border-[#2563eb] focus:ring-[#2563eb]/20 @enderror rounded-lg text-sm text-gray-800 outline-none transition-all shadow-sm placeholder:text-gray-400" placeholder="Nhập mật khẩu mới">
<button type="button" onclick="togglePasswordVisibility('new_password', this)" class="absolute right-3.5 top-1/2 -translate-y-1/2 text-gray-400 hover:text-gray-600 focus:outline-none cursor-pointer" aria-label="Show password">
<svg class="w-5 h-5 eye-icon-closed" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.5"><path stroke-linecap="round" stroke-linejoin="round" d="M3.98 8.223A10.477 10.477 0 001.934 12c1.292 4.338 5.31 7.5 10.066 7.5.993 0 1.953-.138 2.863-.395M6.228 6.228A10.45 10.45 0 0112 4.5c4.756 0 8.773 3.162 10.065 7.498a10.523 10.523 0 01-4.293 5.774M6.228 6.228L3 3m3.228 3.228l3.65 3.65m7.894 7.894L21 21m-3.228-3.228l-3.65-3.65m0 0a3 3 0 10-4.243-4.243m4.242 4.242L9.88 9.88"/></svg>
<svg class="w-5 h-5 eye-icon-open hidden" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.5"><path stroke-linecap="round" stroke-linejoin="round" d="M2.036 12.322a1.012 1.012 0 010-.639C3.423 7.51 7.36 4.5 12 4.5c4.638 0 8.573 3.007 9.963 7.178.07.207.07.431 0 .639C20.577 16.49 16.64 19.5 12 19.5c-4.638 0-8.573-3.007-9.963-7.178z"/><path stroke-linecap="round" stroke-linejoin="round" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"/></svg>
</button>
</div>
@error('new_password')
<span class="text-red-500 text-[12px] font-semibold mt-1.5 block">{{ $message }}</span>
@enderror
</div>
<div class="flex flex-col">
<label class="text-[13px] font-bold text-gray-700 mb-1.5" for="new_password_confirmation">Xác nhận mật khẩu mới <span class="text-red-500">*</span></label>
<div class="relative">
<span class="absolute left-3.5 top-1/2 -translate-y-1/2 text-gray-400"><svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"2\" stroke=\"currentColor\" class=\"w-5 h-5\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M9 12.75L11.25 15 15 9.75M21 12a9 9 0 11-18 0 9 9 0 0118 0z\"/></svg></span>
<input type="password" name="new_password_confirmation" id="new_password_confirmation" class="w-full h-[46px] pl-[42px] pr-12 bg-white border @error('new_password_confirmation') border-red-500 focus:border-red-500 focus:ring-red-500/20 @else border-gray-200 focus:border-[#2563eb] focus:ring-[#2563eb]/20 @enderror rounded-lg text-sm text-gray-800 outline-none transition-all shadow-sm placeholder:text-gray-400" placeholder="Nhập lại mật khẩu mới">
<button type="button" onclick="togglePasswordVisibility('new_password_confirmation', this)" class="absolute right-3.5 top-1/2 -translate-y-1/2 text-gray-400 hover:text-gray-600 focus:outline-none cursor-pointer" aria-label="Show password">
<svg class="w-5 h-5 eye-icon-closed" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.5"><path stroke-linecap="round" stroke-linejoin="round" d="M3.98 8.223A10.477 10.477 0 001.934 12c1.292 4.338 5.31 7.5 10.066 7.5.993 0 1.953-.138 2.863-.395M6.228 6.228A10.45 10.45 0 0112 4.5c4.756 0 8.773 3.162 10.065 7.498a10.523 10.523 0 01-4.293 5.774M6.228 6.228L3 3m3.228 3.228l3.65 3.65m7.894 7.894L21 21m-3.228-3.228l-3.65-3.65m0 0a3 3 0 10-4.243-4.243m4.242 4.242L9.88 9.88"/></svg>
<svg class="w-5 h-5 eye-icon-open hidden" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.5"><path stroke-linecap="round" stroke-linejoin="round" d="M2.036 12.322a1.012 1.012 0 010-.639C3.423 7.51 7.36 4.5 12 4.5c4.638 0 8.573 3.007 9.963 7.178.07.207.07.431 0 .639C20.577 16.49 16.64 19.5 12 19.5c-4.638 0-8.573-3.007-9.963-7.178z"/><path stroke-linecap="round" stroke-linejoin="round" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"/></svg>
</button>
</div>
@error('new_password_confirmation')
<span class="text-red-500 text-[12px] font-semibold mt-1.5 block">{{ $message }}</span>
@enderror
</div>
</div>
<div class="flex items-center gap-3 pt-4">
<a href="{{ route('user.dashboard') }}" class="flex-1 text-center py-2 bg-gray-100 hover:bg-gray-200 text-gray-700 rounded-xl font-medium">Hủy bỏ</a>
<button type="submit" class="flex-1 py-2 bg-[#3462f7] hover:bg-[#254edb] text-white rounded-xl font-bold">Lưu thay đổi</button>
</div>
</form>
<x-user-form mode="self" :user="Auth::user()" />
</div>
<script>
function previewAvatar(event) {
const input = event.target;
if (input.files && input.files[0]) {
const reader = new FileReader();
reader.onload = function(e) {
document.getElementById('avatar-preview').src = e.target.result;
};
reader.readAsDataURL(input.files[0]);
}
}
</script>
@endsection
-7
View File
@@ -1,13 +1,6 @@
@extends('layouts.app')
@section('title', 'Gửi Thank Card')
@push('styles')
<style>
.ts-control { border-radius: 0.75rem; border-color: #d9dfe7; padding: 0.65rem 1rem; }
.ts-wrapper.form-input { padding: 0; border: none; }
</style>
@endpush
@section('content')
<div class="max-w-[640px] mx-auto w-full pb-10">
<div class="bg-white rounded-[24px] shadow-[0_10px_30px_rgba(15,23,42,0.05)] border border-white overflow-hidden">