Iman_AMS/asm_app/src/pages/UsersList.tsx

191 lines
8.7 KiB
TypeScript

import React, { useState, useEffect } from 'react';
import { useNavigate } from 'react-router-dom';
import frappeAPI from '../api/frappeClient';
import type { FrappeDocType } from '../api/frappeClient';
const UsersList: React.FC = () => {
const [users, setUsers] = useState<FrappeDocType[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const [searchTerm, setSearchTerm] = useState('');
const navigate = useNavigate();
useEffect(() => {
loadUsers();
}, []);
const loadUsers = async () => {
try {
setLoading(true);
const response = await frappeAPI.getDocTypeRecords(
'User',
{},
['name', 'full_name', 'email', 'enabled', 'creation', 'modified']
);
setUsers(response.message || []);
} catch (err: any) {
setError(err.response?.data?.message || 'Failed to load users');
} finally {
setLoading(false);
}
};
const filteredUsers = users.filter(user =>
user.full_name?.toLowerCase().includes(searchTerm.toLowerCase()) ||
user.email?.toLowerCase().includes(searchTerm.toLowerCase()) ||
user.name?.toLowerCase().includes(searchTerm.toLowerCase())
);
const handleUserClick = (user: FrappeDocType) => {
navigate(`/users/${user.name}`);
};
if (loading) {
return (
<div className="min-h-screen flex items-center justify-center bg-gray-50 dark:bg-gray-900">
<div className="animate-spin rounded-full h-32 w-32 border-b-2 border-indigo-600"></div>
</div>
);
}
return (
<div className="min-h-screen bg-gray-50 dark:bg-gray-900">
{/* Header */}
<header className="bg-white dark:bg-gray-800 shadow">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div className="flex justify-between items-center py-6">
<div className="flex items-center">
<button
onClick={() => navigate('/dashboard')}
className="mr-4 text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-300"
>
<svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 19l-7-7 7-7" />
</svg>
</button>
<h1 className="text-2xl sm:text-3xl font-bold text-gray-900 dark:text-white break-words">Users</h1>
</div>
<div className="flex items-center space-x-4">
<button
onClick={() => navigate('/dashboard')}
className="bg-gray-600 hover:bg-gray-700 text-white px-4 py-2 rounded-md text-sm font-medium"
>
Back to Dashboard
</button>
</div>
</div>
</div>
</header>
{/* Main Content */}
<main className="max-w-7xl mx-auto py-6 sm:px-6 lg:px-8">
{error && (
<div className="mb-6 rounded-md bg-red-50 dark:bg-red-900/20 p-4">
<div className="text-sm text-red-700 dark:text-red-400">{error}</div>
</div>
)}
{/* Search and Actions */}
<div className="mb-6 flex flex-col sm:flex-row sm:items-center sm:justify-between">
<div className="flex-1 min-w-0">
<div className="relative">
<div className="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
<svg className="h-5 w-5 text-gray-400 dark:text-gray-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" />
</svg>
</div>
<input
type="text"
placeholder="Search users..."
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
className="block w-full pl-10 pr-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md leading-5 bg-white dark:bg-gray-700 placeholder-gray-500 dark:placeholder-gray-400 text-gray-900 dark:text-white focus:outline-none focus:placeholder-gray-400 focus:ring-1 focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm"
/>
</div>
</div>
<div className="mt-4 sm:mt-0 sm:ml-4">
<button
onClick={loadUsers}
className="inline-flex items-center px-4 py-2 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
>
<svg className="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15" />
</svg>
Refresh
</button>
</div>
</div>
{/* Users Table */}
<div className="bg-white dark:bg-gray-800 shadow overflow-hidden sm:rounded-md">
<div className="px-4 py-5 sm:px-6">
<h3 className="text-lg leading-6 font-medium text-gray-900 dark:text-white">
Users ({filteredUsers.length})
</h3>
<p className="mt-1 max-w-2xl text-sm text-gray-500 dark:text-gray-400">
Manage user accounts and permissions
</p>
</div>
{filteredUsers.length === 0 ? (
<div className="text-center py-12">
<svg className="mx-auto h-12 w-12 text-gray-400 dark:text-gray-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 4.354a4 4 0 110 5.292M15 21H3v-1a6 6 0 0112 0v1zm0 0h6v-1a6 6 0 00-9-5.197m13.5-9a2.5 2.5 0 11-5 0 2.5 2.5 0 015 0z" />
</svg>
<h3 className="mt-2 text-sm font-medium text-gray-900 dark:text-white">No users found</h3>
<p className="mt-1 text-sm text-gray-500 dark:text-gray-400">
{searchTerm ? 'Try adjusting your search terms.' : 'No users available.'}
</p>
</div>
) : (
<ul className="divide-y divide-gray-200 dark:divide-gray-700">
{filteredUsers.map((user) => (
<li key={user.name}>
<div
className="px-4 py-4 flex items-center justify-between hover:bg-gray-50 dark:hover:bg-gray-700 cursor-pointer"
onClick={() => handleUserClick(user)}
>
<div className="flex items-center">
<div className="flex-shrink-0 h-10 w-10">
<div className="h-10 w-10 rounded-full bg-indigo-100 dark:bg-indigo-900 flex items-center justify-center">
<span className="text-sm font-medium text-indigo-600 dark:text-indigo-300">
{user.full_name?.charAt(0) || user.name.charAt(0)}
</span>
</div>
</div>
<div className="ml-4">
<div className="text-sm font-medium text-gray-900 dark:text-white">
{user.full_name || user.name}
</div>
<div className="text-sm text-gray-500 dark:text-gray-400">
{user.email || 'No email'}
</div>
</div>
</div>
<div className="flex items-center space-x-4">
<div className="text-sm text-gray-500 dark:text-gray-400">
<div className="flex items-center">
<div className={`w-2 h-2 rounded-full mr-2 ${user.enabled ? 'bg-green-400' : 'bg-red-400'}`}></div>
{user.enabled ? 'Active' : 'Inactive'}
</div>
</div>
<div className="text-sm text-gray-500 dark:text-gray-400">
Created: {new Date(user.creation).toLocaleDateString()}
</div>
<svg className="w-5 h-5 text-gray-400 dark:text-gray-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 5l7 7-7 7" />
</svg>
</div>
</div>
</li>
))}
</ul>
)}
</div>
</main>
</div>
);
};
export default UsersList;