transform admin panel with comprehensive professional UI
- migrate from SQLite to PostgreSQL with Drizzle ORM - implement comprehensive AdminLayout with expandable sidebar navigation - create professional dashboard with real-time charts and metrics - add advanced monitoring, reporting, and export functionality - fix menu alignment and remove non-existent pages - eliminate duplicate headers and improve UI consistency - add Tailwind CSS v3 for professional styling - expand database schema from 6 to 15 tables - implement role-based access control and API key management - create comprehensive settings, monitoring, and system info pages
This commit is contained in:
@@ -1,7 +1,8 @@
|
||||
import { useState, useEffect } from "react"
|
||||
import type { NextPage } from "next"
|
||||
import Head from "next/head"
|
||||
import Link from "next/link"
|
||||
import AdminLayout from '../../components/AdminLayout'
|
||||
import { UsersIcon, PlusIcon, ShieldCheckIcon } from '@heroicons/react/24/outline'
|
||||
|
||||
interface User {
|
||||
id: number
|
||||
@@ -58,118 +59,183 @@ const UsersManagement: NextPage = () => {
|
||||
}
|
||||
}
|
||||
|
||||
if (loading) return <div>Loading...</div>
|
||||
|
||||
return (
|
||||
<div>
|
||||
<>
|
||||
<Head>
|
||||
<title>Users Management - Infohliadka</title>
|
||||
<title>Používatelia - Hliadka.sk Admin</title>
|
||||
</Head>
|
||||
|
||||
<div style={{ padding: '20px' }}>
|
||||
<div style={{ marginBottom: '20px' }}>
|
||||
<Link href="/admin">← Back to Admin</Link>
|
||||
</div>
|
||||
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '20px' }}>
|
||||
<h1>Users Management</h1>
|
||||
<button
|
||||
onClick={() => setShowAddForm(!showAddForm)}
|
||||
style={{ padding: '10px 15px', backgroundColor: '#28a745', color: 'white', border: 'none', borderRadius: '4px' }}
|
||||
>
|
||||
Add User
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{showAddForm && (
|
||||
<div style={{ marginBottom: '20px', padding: '15px', backgroundColor: '#f8f9fa', border: '1px solid #ddd' }}>
|
||||
<h3>Add New User</h3>
|
||||
<form onSubmit={handleAddUser}>
|
||||
<div style={{ marginBottom: '10px' }}>
|
||||
<input
|
||||
type="email"
|
||||
placeholder="Email"
|
||||
value={newUser.email}
|
||||
onChange={(e) => setNewUser({...newUser, email: e.target.value})}
|
||||
required
|
||||
style={{ width: '200px', padding: '5px', marginRight: '10px' }}
|
||||
/>
|
||||
<input
|
||||
type="password"
|
||||
placeholder="Password"
|
||||
value={newUser.password}
|
||||
onChange={(e) => setNewUser({...newUser, password: e.target.value})}
|
||||
required
|
||||
style={{ width: '200px', padding: '5px', marginRight: '10px' }}
|
||||
/>
|
||||
<select
|
||||
value={newUser.role}
|
||||
onChange={(e) => setNewUser({...newUser, role: e.target.value})}
|
||||
style={{ padding: '5px', marginRight: '10px' }}
|
||||
>
|
||||
<option value="moderator">Moderator</option>
|
||||
<option value="admin">Admin</option>
|
||||
</select>
|
||||
<button type="submit" style={{ padding: '5px 15px', backgroundColor: '#007bff', color: 'white', border: 'none' }}>
|
||||
Add
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
<AdminLayout title="Správa používateľov">
|
||||
<div className="px-4 py-6 sm:px-6 lg:px-8">
|
||||
<div className="sm:flex sm:items-center">
|
||||
<div className="sm:flex-auto">
|
||||
<p className="text-sm text-gray-700">
|
||||
Správa administrátorov, moderátorov a ich oprávnení
|
||||
</p>
|
||||
</div>
|
||||
<div className="mt-4 sm:ml-16 sm:mt-0 sm:flex-none">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setShowAddForm(!showAddForm)}
|
||||
className="btn-primary"
|
||||
>
|
||||
<PlusIcon className="h-4 w-4 mr-2" />
|
||||
Pridať používateľa
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<table style={{ width: '100%', borderCollapse: 'collapse' }}>
|
||||
<thead>
|
||||
<tr style={{ backgroundColor: '#f8f9fa' }}>
|
||||
<th style={{ padding: '10px', textAlign: 'left', border: '1px solid #ddd' }}>Email</th>
|
||||
<th style={{ padding: '10px', textAlign: 'left', border: '1px solid #ddd' }}>Role</th>
|
||||
<th style={{ padding: '10px', textAlign: 'left', border: '1px solid #ddd' }}>Status</th>
|
||||
<th style={{ padding: '10px', textAlign: 'left', border: '1px solid #ddd' }}>Sources Moderated</th>
|
||||
<th style={{ padding: '10px', textAlign: 'left', border: '1px solid #ddd' }}>Created</th>
|
||||
<th style={{ padding: '10px', textAlign: 'left', border: '1px solid #ddd' }}>Last Login</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{users.map((user) => (
|
||||
<tr key={user.id}>
|
||||
<td style={{ padding: '10px', border: '1px solid #ddd' }}>{user.email}</td>
|
||||
<td style={{ padding: '10px', border: '1px solid #ddd' }}>
|
||||
<span style={{
|
||||
padding: '2px 6px',
|
||||
borderRadius: '3px',
|
||||
backgroundColor: user.role === 'admin' ? '#dc3545' : '#17a2b8',
|
||||
color: 'white',
|
||||
fontSize: '12px'
|
||||
}}>
|
||||
{user.role}
|
||||
</span>
|
||||
</td>
|
||||
<td style={{ padding: '10px', border: '1px solid #ddd' }}>
|
||||
<span style={{
|
||||
color: user.is_active ? 'green' : 'red'
|
||||
}}>
|
||||
{user.is_active ? 'Active' : 'Inactive'}
|
||||
</span>
|
||||
</td>
|
||||
<td style={{ padding: '10px', border: '1px solid #ddd' }}>{user.sources_moderated}</td>
|
||||
<td style={{ padding: '10px', border: '1px solid #ddd' }}>
|
||||
{new Date(user.created_at).toLocaleDateString()}
|
||||
</td>
|
||||
<td style={{ padding: '10px', border: '1px solid #ddd' }}>
|
||||
{user.last_login ? new Date(user.last_login).toLocaleDateString() : 'Never'}
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
{users.length === 0 && (
|
||||
<p style={{ textAlign: 'center', marginTop: '20px', color: '#666' }}>
|
||||
No users found.
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
{showAddForm && (
|
||||
<div className="mt-6">
|
||||
<div className="card p-6">
|
||||
<h3 className="text-lg font-medium leading-6 text-gray-900 mb-4">
|
||||
Pridať nového používateľa
|
||||
</h3>
|
||||
<form onSubmit={handleAddUser} className="space-y-4">
|
||||
<div className="grid grid-cols-1 gap-4 sm:grid-cols-3">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700">Email</label>
|
||||
<input
|
||||
type="email"
|
||||
value={newUser.email}
|
||||
onChange={(e) => setNewUser({...newUser, email: e.target.value})}
|
||||
required
|
||||
className="input mt-1"
|
||||
placeholder="email@example.com"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700">Heslo</label>
|
||||
<input
|
||||
type="password"
|
||||
value={newUser.password}
|
||||
onChange={(e) => setNewUser({...newUser, password: e.target.value})}
|
||||
required
|
||||
className="input mt-1"
|
||||
placeholder="********"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700">Rola</label>
|
||||
<select
|
||||
value={newUser.role}
|
||||
onChange={(e) => setNewUser({...newUser, role: e.target.value})}
|
||||
className="input mt-1"
|
||||
>
|
||||
<option value="moderator">Moderátor</option>
|
||||
<option value="admin">Administrátor</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex justify-end space-x-2">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setShowAddForm(false)}
|
||||
className="btn-secondary"
|
||||
>
|
||||
Zrušiť
|
||||
</button>
|
||||
<button type="submit" className="btn-primary">
|
||||
Pridať používateľa
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="mt-8 flow-root">
|
||||
<div className="-mx-4 -my-2 overflow-x-auto sm:-mx-6 lg:-mx-8">
|
||||
<div className="inline-block min-w-full py-2 align-middle sm:px-6 lg:px-8">
|
||||
<div className="card">
|
||||
{loading ? (
|
||||
<div className="p-6">
|
||||
<div className="flex items-center justify-center">
|
||||
<div className="text-gray-500">Načítavanie...</div>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<table className="min-w-full divide-y divide-gray-300">
|
||||
<thead className="bg-gray-50">
|
||||
<tr>
|
||||
<th scope="col" className="py-3.5 pl-4 pr-3 text-left text-sm font-semibold text-gray-900 sm:pl-6">
|
||||
Email
|
||||
</th>
|
||||
<th scope="col" className="px-3 py-3.5 text-left text-sm font-semibold text-gray-900">
|
||||
Rola
|
||||
</th>
|
||||
<th scope="col" className="px-3 py-3.5 text-left text-sm font-semibold text-gray-900">
|
||||
Status
|
||||
</th>
|
||||
<th scope="col" className="px-3 py-3.5 text-left text-sm font-semibold text-gray-900">
|
||||
Moderované zdroje
|
||||
</th>
|
||||
<th scope="col" className="px-3 py-3.5 text-left text-sm font-semibold text-gray-900">
|
||||
Vytvorené
|
||||
</th>
|
||||
<th scope="col" className="px-3 py-3.5 text-left text-sm font-semibold text-gray-900">
|
||||
Posledné prihlásenie
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="divide-y divide-gray-200 bg-white">
|
||||
{users.map((user) => (
|
||||
<tr key={user.id} className="hover:bg-gray-50">
|
||||
<td className="whitespace-nowrap py-4 pl-4 pr-3 sm:pl-6">
|
||||
<div className="flex items-center">
|
||||
<UsersIcon className="h-5 w-5 text-gray-400 mr-3" />
|
||||
<div className="text-sm font-medium text-gray-900">{user.email}</div>
|
||||
</div>
|
||||
</td>
|
||||
<td className="whitespace-nowrap px-3 py-4 text-sm text-gray-500">
|
||||
<span className={`inline-flex items-center rounded-full px-2.5 py-0.5 text-xs font-medium ${
|
||||
user.role === 'admin'
|
||||
? 'bg-red-100 text-red-800'
|
||||
: 'bg-blue-100 text-blue-800'
|
||||
}`}>
|
||||
{user.role === 'admin' && <ShieldCheckIcon className="h-3 w-3 mr-1" />}
|
||||
{user.role === 'admin' ? 'Administrátor' : 'Moderátor'}
|
||||
</span>
|
||||
</td>
|
||||
<td className="whitespace-nowrap px-3 py-4 text-sm text-gray-500">
|
||||
{user.is_active ? (
|
||||
<span className="inline-flex items-center rounded-full bg-green-100 px-2.5 py-0.5 text-xs font-medium text-green-800">
|
||||
Aktívny
|
||||
</span>
|
||||
) : (
|
||||
<span className="inline-flex items-center rounded-full bg-red-100 px-2.5 py-0.5 text-xs font-medium text-red-800">
|
||||
Neaktívny
|
||||
</span>
|
||||
)}
|
||||
</td>
|
||||
<td className="whitespace-nowrap px-3 py-4 text-sm text-gray-500">
|
||||
<span className="font-medium">{user.sources_moderated}</span>
|
||||
</td>
|
||||
<td className="whitespace-nowrap px-3 py-4 text-sm text-gray-500">
|
||||
{new Date(user.created_at).toLocaleDateString('sk-SK')}
|
||||
</td>
|
||||
<td className="whitespace-nowrap px-3 py-4 text-sm text-gray-500">
|
||||
{user.last_login ? new Date(user.last_login).toLocaleDateString('sk-SK') : 'Nikdy'}
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
)}
|
||||
|
||||
{users.length === 0 && !loading && (
|
||||
<div className="p-6 text-center">
|
||||
<UsersIcon className="mx-auto h-12 w-12 text-gray-400" />
|
||||
<h3 className="mt-2 text-sm font-semibold text-gray-900">Žiadni používatelia</h3>
|
||||
<p className="mt-1 text-sm text-gray-500">Začnite pridaním nového používateľa.</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</AdminLayout>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user