feature: change password

This commit is contained in:
antv
2026-07-08 13:03:48 +07:00
parent 63764d0718
commit 6056e33ef8
16 changed files with 371 additions and 56 deletions
-3
View File
@@ -106,9 +106,6 @@
@apply min-h-screen flex bg-bg-main;
}
.sidebar {
@apply w-60 h-screen sticky top-0 bg-bg-sidebar border-r border-border-light flex flex-col justify-between shrink-0 shadow-sidebar transition-all duration-300 overflow-hidden;
}
.main-content {
@apply flex-1 flex flex-col min-w-0;
+17 -10
View File
@@ -6,7 +6,7 @@ export class AjaxTable {
this.searchInputId = options.searchInputId || 'searchInput';
this.btnClearSearchId = options.btnClearSearchId || 'btnClearSearch';
this.btnClearFiltersId = options.btnClearFiltersId || 'btnClearFilters';
this.flagSendFilterId = options.flagSendFilterId || 'flagSendFilter';
this.statusFilterId = options.statusFilterId || 'statusFilter';
this.monthFilterId = options.monthFilterId || 'monthFilter';
this.skeletonColumns = options.skeletonColumns || 7;
@@ -22,7 +22,7 @@ export class AjaxTable {
this.searchInput = document.getElementById(this.searchInputId);
this.btnClearSearch = document.getElementById(this.btnClearSearchId);
this.btnClearFilters = document.getElementById(this.btnClearFiltersId);
this.flagSendFilter = document.getElementById(this.flagSendFilterId);
this.statusFilter = document.getElementById(this.statusFilterId);
this.monthFilter = document.getElementById(this.monthFilterId);
if (this.form) {
@@ -35,10 +35,6 @@ export class AjaxTable {
if (this.searchInput) {
this.searchInput.addEventListener('input', () => {
this.toggleClearSearchBtn();
clearTimeout(this.searchDebounceTimer);
this.searchDebounceTimer = setTimeout(() => {
this.submitForm();
}, 500);
});
}
@@ -52,16 +48,26 @@ export class AjaxTable {
});
}
if (this.flagSendFilter) {
this.flagSendFilter.addEventListener('change', () => {
this.submitForm();
if (this.form) {
this.form.querySelectorAll('select').forEach(select => {
select.addEventListener('change', () => {
this.submitForm();
});
});
}
if (this.btnClearFilters) {
this.btnClearFilters.addEventListener('click', () => {
if (this.searchInput) this.searchInput.value = '';
if (this.flagSendFilter) this.flagSendFilter.value = '';
if (this.statusFilter) this.statusFilter.value = '1';
const roleFilter = document.getElementById('roleFilter');
if (roleFilter) roleFilter.value = '';
const departmentFilter = document.getElementById('departmentFilter');
if (departmentFilter) departmentFilter.value = '';
const flagSendFilter = document.getElementById('flagSendFilter');
if (flagSendFilter) flagSendFilter.value = '';
if (this.monthFilter) {
this.monthFilter.value = new Date().toISOString().slice(0, 7);
}
@@ -118,6 +124,7 @@ export class AjaxTable {
<td class="px-6 py-4"><div class="h-4 bg-gray-200 rounded w-12"></div></td>
<td class="px-6 py-4"><div class="h-4 bg-gray-200 rounded w-16"></div></td>
<td class="px-6 py-4"><div class="h-6 bg-gray-200 rounded-full w-20"></div></td>
<td class="px-6 py-4"><div class="h-6 bg-gray-200 rounded-full w-20"></div></td>
<td class="px-6 py-4"><div class="h-8 bg-gray-200 rounded w-20 ml-auto"></div></td>
</tr>`;
}
+10 -2
View File
@@ -2,7 +2,7 @@
@section('title', 'Quản lý Users')
@section('content')
<div class="mb-6 flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4 pl-1">
<div class="mb-6 flex flex-col sm:flex-row flex-wrap sm:items-center sm:justify-between gap-4 pl-1">
<div>
<h1 class="text-[28px] font-extrabold text-[#1a2b49] flex items-center gap-2 mb-1.5 tracking-tight">
<span id="pageTitle" class="inline-flex items-center gap-2">
@@ -29,7 +29,15 @@
@include('admin.users.partials.stats')
</div>
<x-user-filter-form :action="route('admin.users.index')" :search="$search" :flag-send="$flagSend" :selected-month="$selectedMonth" />
<x-user-filter-form
:action="route('admin.users.index')"
:search="$search"
:selected-month="$selectedMonth"
:status="$status"
:role="$role ?? ''"
:department="$department ?? ''"
:flag-send="$flagSend ?? ''"
/>
<div id="userTableContainer" class="bg-white rounded-[24px] shadow-[0_10px_30px_rgba(15,23,42,0.05)] border border-white overflow-hidden mt-4">
@include('admin.users.partials.table')
@@ -1,7 +1,7 @@
<div class="px-6 py-4 border-b border-gray-100 flex justify-between items-center bg-white">
<span class="text-sm font-semibold text-gray-500" id="paginationStats">
@if($users->total() > 0)
Hiển thị {{ $users->firstItem() }}-{{ $users->lastItem() }} trên {{ $users->total() }} nhân viên
Hiển thị {{ $users->firstItem() }}-{{ $users->lastItem() }} trên tổng {{ $users->total() }} nhân viên
@else
Hiển thị 0 nhân viên
@endif
@@ -17,6 +17,7 @@
<th class="table-header-cell">Đã Gửi</th>
<th class="table-header-cell">Số thẻ</th>
<th class="table-header-cell">Quyền gửi</th>
<th class="table-header-cell">Trạng thái</th>
<th class="table-header-cell text-right">Thao tác</th>
</tr>
</thead>
@@ -40,14 +41,20 @@
<span class="px-2.5 py-1 inline-flex text-[10px] leading-5 font-bold rounded-full bg-gray-50 text-gray-500 border border-gray-100">Không</span>
@endif
</td>
<td class="table-body-cell">
@if($u->status == \App\Models\User::STATUS_ACTIVE)
<span class="px-2.5 py-1 inline-flex text-[10px] leading-5 font-bold rounded-full bg-blue-50 text-blue-700 border border-blue-100">Đang làm việc</span>
@else
<span class="px-2.5 py-1 inline-flex text-[10px] leading-5 font-bold rounded-full bg-red-50 text-red-700 border border-red-100">Đã nghỉ việc</span>
@endif
</td>
<td class="table-body-cell text-right font-medium space-x-3">
<a href="{{ route('admin.users.edit', $u->msnv) }}" class="table-action-link !text-[#3462f7] hover:!text-blue-800">Quản </a>
<button type="button" onclick="openDeleteModal('{{ $u->msnv }}', '{{ route('admin.users.destroy', $u->msnv) }}')" class="table-action-link !text-red-500 hover:!text-red-700">Nghỉ việc</button>
</td>
</tr>
@empty
<tr>
<td colspan="7" class="px-6 py-10 text-center text-text-light font-medium">Chưa user nào đang hoạt động.</td>
<td colspan="8" class="px-6 py-10 text-center text-text-light font-medium">Chưa user nào đang hoạt động.</td>
</tr>
@endforelse
</tbody>
+1 -1
View File
@@ -1,6 +1,6 @@
@props(['active' => false])
<aside class="sidebar flex flex-col h-full bg-white border-r border-gray-100 shadow-sm transition-transform duration-300 w-64 fixed inset-y-0 left-0 z-40 lg:relative lg:translate-x-0 -translate-x-full" aria-label="Main navigation">
<aside class="sidebar flex flex-col justify-between h-screen w-60 bg-white border-r border-gray-100 shadow-sm transition-all duration-300 shrink-0 overflow-hidden fixed inset-y-0 left-0 z-40 -translate-x-full lg:sticky lg:top-0 lg:translate-x-0" aria-label="Main navigation">
<div class="p-6 flex items-center justify-between shrink-0">
<img src="{{ asset('images/logo.png') }}" alt="GMO-Z.com RUNSYSTEM" class="h-6 w-auto object-contain">
<!-- Mobile hamburger button/close -->
@@ -1,7 +1,15 @@
@props(['action', 'search' => '', 'flagSend' => '', 'selectedMonth' => ''])
@props([
'action',
'search' => '',
'selectedMonth' => '',
'status' => '1',
'role' => '',
'department' => '',
'flagSend' => ''
])
<form id="filterForm" action="{{ $action }}" method="GET" class="flex flex-col md:flex-row items-stretch md:items-center gap-3.5 w-full mb-6 bg-white p-5 rounded-[24px] border border-white shadow-[0_10px_30px_rgba(15,23,42,0.05)]">
<div class="relative flex items-center flex-1 min-w-[200px]">
<form id="filterForm" action="{{ $action }}" method="GET" class="flex flex-col md:flex-row md:flex-wrap items-stretch md:items-center gap-3.5 w-full mb-6 bg-white p-5 rounded-[24px] border border-white shadow-[0_10px_30px_rgba(15,23,42,0.05)]">
<div class="relative flex items-center w-full lg:flex-1 lg:min-w-[200px]">
<input type="text" id="searchInput" name="search" value="{{ $search }}" placeholder="Tìm MSNV hoặc Email..." class="form-input !py-1.5 !text-sm !pl-10 !pr-10 w-full border-[#d9dfe7] rounded-xl bg-white">
<svg class="w-4 h-4 text-text-light absolute left-3.5" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" style="pointer-events: none;">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" />
@@ -13,15 +21,40 @@
</button>
</div>
<select name="flag_send" id="flagSendFilter" class="form-input !py-1.5 !text-sm w-full md:w-auto border-[#d9dfe7] rounded-xl bg-white min-w-[180px]">
<option value="" {{ is_null($flagSend) || $flagSend === '' ? 'selected' : '' }}>Quyền gửi: Tất cả</option>
<!-- Status Filter -->
<select name="status" id="statusFilter" class="form-input !py-1.5 !text-sm !w-full md:!w-[150px] border-[#d9dfe7] rounded-xl bg-white shrink-0">
<option value="" {{ $status === '' ? 'selected' : '' }}>Tất cả trạng thái</option>
<option value="1" {{ $status === '1' ? 'selected' : '' }}>Hoạt động</option>
<option value="0" {{ $status === '0' ? 'selected' : '' }}>Nghỉ việc</option>
</select>
<!-- Role Filter -->
<select name="role" id="roleFilter" class="form-input !py-1.5 !text-sm !w-full md:!w-[130px] border-[#d9dfe7] rounded-xl bg-white shrink-0">
<option value="" {{ $role === '' ? 'selected' : '' }}>Tất cả vai trò</option>
<option value="1" {{ $role === '1' ? 'selected' : '' }}>Admin</option>
<option value="0" {{ $role === '0' ? 'selected' : '' }}>Member</option>
</select>
<!-- Department Filter -->
<select name="department" id="departmentFilter" class="form-input !py-1.5 !text-sm !w-full md:!w-[170px] border-[#d9dfe7] rounded-xl bg-white shrink-0">
<option value="" {{ $department === '' ? 'selected' : '' }}>Tất cả phòng ban</option>
<option value="1" {{ $department === '1' ? 'selected' : '' }}>Phòng Phát Triển (Dev)</option>
<option value="2" {{ $department === '2' ? 'selected' : '' }}>Phòng Nhân Sự (HR)</option>
<option value="3" {{ $department === '3' ? 'selected' : '' }}>Phòng Kinh Doanh (Sale)</option>
<option value="4" {{ $department === '4' ? 'selected' : '' }}>Ban Giám Đốc (Board)</option>
<option value="5" {{ $department === '5' ? 'selected' : '' }}>Phòng Marketing</option>
</select>
<!-- Flag Send Filter -->
<select name="flag_send" id="flagSendFilter" class="form-input !py-1.5 !text-sm !w-full md:!w-[150px] border-[#d9dfe7] rounded-xl bg-white shrink-0">
<option value="" {{ $flagSend === '' ? 'selected' : '' }}>Tất cả quyền gửi</option>
<option value="1" {{ $flagSend === '1' ? 'selected' : '' }}>Được gửi</option>
<option value="0" {{ $flagSend === '0' ? 'selected' : '' }}>Không được gửi</option>
</select>
<input type="month" name="month" id="monthFilter" value="{{ $selectedMonth }}" class="form-input !py-1.5 !text-sm w-full md:w-auto border-[#d9dfe7] rounded-xl bg-white">
<input type="month" name="month" id="monthFilter" value="{{ $selectedMonth }}" class="form-input !py-1.5 !text-sm !w-full md:!w-[140px] border-[#d9dfe7] rounded-xl bg-white shrink-0">
<div class="flex items-center gap-2 w-full md:w-auto shrink-0">
<div class="flex items-center gap-2 w-full md:w-auto md:ml-auto shrink-0">
<button type="submit" id="btnFilter" class="btn-primary !py-2.5 !px-5 text-sm rounded-xl font-bold shadow-md shadow-blue-500/10 !bg-[#3462f7] hover:!bg-blue-700 flex items-center justify-center min-w-[80px] flex-1 md:flex-none">Tìm</button>
<button type="button" id="btnClearFilters" class="btn-secondary !py-2.5 !px-5 text-sm rounded-xl font-bold border border-[#d9dfe7] hover:bg-gray-100 flex items-center justify-center min-w-[100px] flex-1 md:flex-none">Xóa bộ lọc</button>
</div>
@@ -33,7 +33,7 @@
<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="w-full h-[46px] pl-[42px] pr-12 bg-white border border-gray-200 rounded-lg text-sm text-gray-800 focus:border-[#2563eb] focus:ring-1 focus:ring-[#2563eb] outline-none transition-all shadow-sm placeholder:text-gray-400" required placeholder="Nhập mật khẩu mới">
<input type="password" name="password" id="password" class="w-full h-[46px] pl-[42px] pr-12 bg-white border @error('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" required placeholder="Nhập mật khẩu mới">
<button type="button" onclick="togglePasswordVisibility('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">
<!-- Eye Closed (Default) -->
<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 12C3.226 16.338 7.244 19.5 12 19.5c.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>
@@ -52,7 +52,7 @@
<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="password_confirmation" id="password_confirmation" class="w-full h-[46px] pl-[42px] pr-12 bg-white border border-gray-200 rounded-lg text-sm text-gray-800 focus:border-[#2563eb] focus:ring-1 focus:ring-[#2563eb] outline-none transition-all shadow-sm placeholder:text-gray-400" required placeholder="Nhập lại mật khẩu mới">
<input type="password" name="password_confirmation" id="password_confirmation" class="w-full h-[46px] pl-[42px] pr-12 bg-white border @error('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" required placeholder="Nhập lại mật khẩu mới">
<button type="button" onclick="togglePasswordVisibility('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">
<!-- Eye Closed (Default) -->
<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 12C3.226 16.338 7.244 19.5 12 19.5c.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>
+9
View File
@@ -54,6 +54,15 @@
</p>
</div>
</div>
<div class="flex flex-col gap-2 pt-2">
<div class="flex items-center gap-3 bg-gray-50/50 p-4 rounded-xl border border-[#d9dfe7]/60 hover:bg-gray-50 transition-colors">
<input type="checkbox" name="confirm_written" id="confirm_written" value="1" class="w-5 h-5 text-[#3462f7] border-[#d9dfe7] rounded-md focus:ring-[#3462f7]/20 transition-all cursor-pointer" required>
<label for="confirm_written" class="text-[13px] font-medium text-gray-700 cursor-pointer select-none">
Xác nhận đã viết card <span class="text-red-500">*</span>
</label>
</div>
</div>
</div>
<div class="px-8 py-5 border-t border-[#d9dfe7]/50 bg-gray-50/50 flex flex-col sm:flex-row justify-end gap-3">