175 lines
7.2 KiB
TypeScript
175 lines
7.2 KiB
TypeScript
|
|
import React, { useState, useEffect } from 'react';
|
||
|
|
import { sortByProperty } from '../utils/sort-utils';
|
||
|
|
|
||
|
|
// Default prefix for TMS username search
|
||
|
|
const TMS_USERNAME_PREFIX = 'DKI_';
|
||
|
|
|
||
|
|
interface TmsUser {
|
||
|
|
email: string;
|
||
|
|
name: string;
|
||
|
|
}
|
||
|
|
|
||
|
|
interface UserManagementModalProps {
|
||
|
|
isOpen: boolean;
|
||
|
|
onClose: () => void;
|
||
|
|
onAddUser: (username: string) => void;
|
||
|
|
}
|
||
|
|
|
||
|
|
const UserManagementModal: React.FC<UserManagementModalProps> = ({ isOpen, onClose, onAddUser }) => {
|
||
|
|
const [searchTerm, setSearchTerm] = useState('');
|
||
|
|
const [allUsers, setAllUsers] = useState<TmsUser[]>([]);
|
||
|
|
const [filteredUsers, setFilteredUsers] = useState<TmsUser[]>([]);
|
||
|
|
const [isLoading, setIsLoading] = useState(false);
|
||
|
|
const [error, setError] = useState<string | null>(null);
|
||
|
|
|
||
|
|
// Load all users when modal opens
|
||
|
|
useEffect(() => {
|
||
|
|
if (isOpen) {
|
||
|
|
document.body.style.overflow = 'hidden';
|
||
|
|
setSearchTerm('');
|
||
|
|
setError(null);
|
||
|
|
loadAllUsers();
|
||
|
|
} else {
|
||
|
|
document.body.style.overflow = 'auto';
|
||
|
|
}
|
||
|
|
return () => {
|
||
|
|
document.body.style.overflow = 'auto';
|
||
|
|
};
|
||
|
|
}, [isOpen]);
|
||
|
|
|
||
|
|
// Filter users when search term changes
|
||
|
|
useEffect(() => {
|
||
|
|
if (!searchTerm.trim()) {
|
||
|
|
setFilteredUsers(sortByProperty(allUsers, u => u.name));
|
||
|
|
} else {
|
||
|
|
const term = searchTerm.toLowerCase();
|
||
|
|
setFilteredUsers(
|
||
|
|
sortByProperty(
|
||
|
|
allUsers.filter(u => u.name.toLowerCase().includes(term) || u.email.toLowerCase().includes(term)),
|
||
|
|
u => u.name
|
||
|
|
)
|
||
|
|
);
|
||
|
|
}
|
||
|
|
}, [searchTerm, allUsers]);
|
||
|
|
|
||
|
|
const loadAllUsers = async () => {
|
||
|
|
setIsLoading(true);
|
||
|
|
setError(null);
|
||
|
|
try {
|
||
|
|
// Search with DKI_ prefix to get DKI users only
|
||
|
|
const response = await fetch('/api/user/search', {
|
||
|
|
method: 'POST',
|
||
|
|
headers: { 'Content-Type': 'application/json' },
|
||
|
|
body: JSON.stringify({ query: TMS_USERNAME_PREFIX }),
|
||
|
|
});
|
||
|
|
const data = await response.json();
|
||
|
|
if (data.success && Array.isArray(data.data)) {
|
||
|
|
const sortedUsers = sortByProperty(data.data as TmsUser[], u => u.name);
|
||
|
|
setAllUsers(sortedUsers);
|
||
|
|
setFilteredUsers(sortedUsers);
|
||
|
|
} else {
|
||
|
|
setError('Không thể tải danh sách người dùng');
|
||
|
|
}
|
||
|
|
} catch (err) {
|
||
|
|
console.error('Error loading users:', err);
|
||
|
|
setError('Lỗi khi tải danh sách người dùng');
|
||
|
|
} finally {
|
||
|
|
setIsLoading(false);
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
if (!isOpen) {
|
||
|
|
return null;
|
||
|
|
}
|
||
|
|
|
||
|
|
return (
|
||
|
|
<div
|
||
|
|
className="fixed inset-0 bg-black bg-opacity-70 z-50 flex justify-center items-center p-4 animate-fade-in-fast"
|
||
|
|
onClick={onClose}
|
||
|
|
role="dialog"
|
||
|
|
aria-modal="true"
|
||
|
|
>
|
||
|
|
<div
|
||
|
|
className="bg-slate-800 border border-slate-700 rounded-2xl shadow-2xl w-full max-w-2xl h-[600px] flex flex-col p-6 animate-slide-up"
|
||
|
|
onClick={e => e.stopPropagation()}
|
||
|
|
>
|
||
|
|
<div className="flex justify-between items-center mb-4 pb-4 border-b border-slate-700">
|
||
|
|
<h2 className="text-xl font-semibold text-white">Tìm kiếm người dùng TMS</h2>
|
||
|
|
<button onClick={onClose} className="text-slate-400 hover:text-white transition-colors text-2xl">×</button>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div className="mb-4">
|
||
|
|
<input
|
||
|
|
type="text"
|
||
|
|
placeholder="Lọc theo tên hoặc email..."
|
||
|
|
value={searchTerm}
|
||
|
|
onChange={e => setSearchTerm(e.target.value)}
|
||
|
|
className="bg-slate-900/50 border border-slate-700 text-slate-100 text-sm rounded-lg focus:ring-indigo-500 focus:border-indigo-500 block w-full p-2.5"
|
||
|
|
autoFocus
|
||
|
|
/>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
{error && (
|
||
|
|
<div className="mb-4 p-3 bg-rose-900/20 border border-rose-700 rounded-lg text-sm text-rose-300">
|
||
|
|
{error}
|
||
|
|
</div>
|
||
|
|
)}
|
||
|
|
|
||
|
|
<div className="flex-grow overflow-y-auto pr-2 -mr-2">
|
||
|
|
{isLoading ? (
|
||
|
|
<div className="flex justify-center items-center h-32 text-slate-400">
|
||
|
|
<span>Đang tải danh sách...</span>
|
||
|
|
</div>
|
||
|
|
) : filteredUsers.length === 0 ? (
|
||
|
|
<div className="flex justify-center items-center h-32 text-slate-400">
|
||
|
|
<span>{searchTerm ? 'Không tìm thấy người dùng phù hợp' : 'Không có người dùng nào'}</span>
|
||
|
|
</div>
|
||
|
|
) : (
|
||
|
|
<>
|
||
|
|
<div className="grid grid-cols-2 gap-2 text-sm font-semibold text-slate-400 px-3 py-2 border-b border-slate-700">
|
||
|
|
<span>Tên</span>
|
||
|
|
<span>Email</span>
|
||
|
|
</div>
|
||
|
|
<div className="divide-y divide-slate-700/50">
|
||
|
|
{filteredUsers.map((user) => (
|
||
|
|
<div
|
||
|
|
key={user.email}
|
||
|
|
className="grid grid-cols-2 gap-2 items-center p-3"
|
||
|
|
>
|
||
|
|
<span className="text-slate-200 font-medium">{user.name}</span>
|
||
|
|
<span className="text-slate-400 text-sm truncate">{user.email}</span>
|
||
|
|
</div>
|
||
|
|
))}
|
||
|
|
</div>
|
||
|
|
</>
|
||
|
|
)}
|
||
|
|
</div>
|
||
|
|
|
||
|
|
{/* Footer - fixed at bottom */}
|
||
|
|
<div className="mt-4 pt-4 border-t border-slate-700 text-center text-sm text-slate-500">
|
||
|
|
{searchTerm ? `Tìm thấy ${filteredUsers.length} / ${allUsers.length} người dùng` : `Tổng cộng ${allUsers.length} người dùng`}
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<style>{`
|
||
|
|
@keyframes fade-in-fast {
|
||
|
|
from { opacity: 0; }
|
||
|
|
to { opacity: 1; }
|
||
|
|
}
|
||
|
|
.animate-fade-in-fast {
|
||
|
|
animation: fade-in-fast 0.2s ease-out forwards;
|
||
|
|
}
|
||
|
|
@keyframes slide-up {
|
||
|
|
from { opacity: 0; transform: translateY(20px); }
|
||
|
|
to { opacity: 1; transform: translateY(0); }
|
||
|
|
}
|
||
|
|
.animate-slide-up {
|
||
|
|
animation: slide-up 0.3s ease-out forwards;
|
||
|
|
}
|
||
|
|
`}</style>
|
||
|
|
</div>
|
||
|
|
);
|
||
|
|
};
|
||
|
|
|
||
|
|
export default UserManagementModal;
|