Files
infohliadka/pages/admin/users.tsx
Lukas Davidovic 249a672cd7 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
2025-09-06 15:14:20 +02:00

242 lines
10 KiB
TypeScript

import { useState, useEffect } from "react"
import type { NextPage } from "next"
import Head from "next/head"
import AdminLayout from '../../components/AdminLayout'
import { UsersIcon, PlusIcon, ShieldCheckIcon } from '@heroicons/react/24/outline'
interface User {
id: number
email: string
role: string
is_active: boolean
created_at: string
last_login: string | null
sources_moderated: number
}
const UsersManagement: NextPage = () => {
const [users, setUsers] = useState<User[]>([])
const [loading, setLoading] = useState(true)
const [showAddForm, setShowAddForm] = useState(false)
const [newUser, setNewUser] = useState({ email: '', password: '', role: 'moderator' })
useEffect(() => {
fetchUsers()
}, [])
const fetchUsers = async () => {
try {
const response = await fetch('/api/admin/users')
const data = await response.json()
setUsers(data.users || [])
} catch (error) {
console.error('Error fetching users:', error)
}
setLoading(false)
}
const handleAddUser = async (e: React.FormEvent) => {
e.preventDefault()
if (!newUser.email || !newUser.password) return
try {
const response = await fetch('/api/admin/users', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(newUser)
})
if (response.ok) {
setNewUser({ email: '', password: '', role: 'moderator' })
setShowAddForm(false)
fetchUsers()
} else {
const error = await response.json()
alert('Error: ' + error.error)
}
} catch (error) {
alert('Failed to add user')
}
}
return (
<>
<Head>
<title>Používatelia - Hliadka.sk Admin</title>
</Head>
<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>
{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>
</>
)
}
export default UsersManagement