togethere.cloud/private_html/administration/users/index.php

1296 lines
42 KiB
PHP
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
require_once __DIR__ . '/../includes/auth.php';
require_once __DIR__ . '/../includes/config.php';
require_once __DIR__ . '/../includes/header.php';
require_once __DIR__ . '/../includes/sidebar.php';
?>
<div class="admin-content">
<style>
.admin-page-title {
font-size: 24px;
font-weight: 600;
color: #23282d;
margin-bottom: 20px;
padding-bottom: 10px;
border-bottom: 2px solid #0073aa;
}
.admin-content-box {
background: #fff;
border: 1px solid #ddd;
border-radius: 4px;
padding: 20px;
margin-top: 20px;
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
}
/* Stats Cards */
.users-stats {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 15px;
margin-bottom: 20px;
}
.stat-card {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
padding: 20px;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
}
.stat-card:nth-child(2) {
background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
}
.stat-card:nth-child(3) {
background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%);
}
.stat-value {
font-size: 32px;
font-weight: bold;
margin-bottom: 5px;
}
.stat-label {
font-size: 13px;
opacity: 0.9;
}
/* Controls */
.users-controls {
display: flex;
flex-wrap: wrap;
gap: 10px;
margin-bottom: 20px;
padding: 15px;
background: #f8f9fa;
border-radius: 4px;
}
.control-group {
display: flex;
gap: 8px;
align-items: center;
}
.control-group label {
font-size: 13px;
font-weight: 500;
color: #555;
white-space: nowrap;
}
.users-controls input,
.users-controls select {
padding: 8px 12px;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 13px;
background: white;
transition: border-color 0.2s;
}
.users-controls input:focus,
.users-controls select:focus {
outline: none;
border-color: #0073aa;
}
.btn {
padding: 8px 16px;
border: none;
border-radius: 4px;
font-size: 13px;
font-weight: 500;
cursor: pointer;
transition: all 0.2s;
display: inline-flex;
align-items: center;
gap: 5px;
}
.btn-primary {
background: #0073aa;
color: white;
}
.btn-primary:hover {
background: #005a87;
}
.btn-secondary {
background: #6c757d;
color: white;
}
.btn-secondary:hover {
background: #545b62;
}
.btn:disabled {
background: #ccc;
cursor: not-allowed;
opacity: 0.6;
}
/* Table */
.users-table-wrapper {
overflow-x: auto;
margin-bottom: 20px;
}
.users-table {
width: 100%;
border-collapse: collapse;
background: white;
}
.users-table th,
.users-table td {
padding: 12px;
text-align: left;
border-bottom: 1px solid #e5e5e5;
font-size: 13px;
}
.users-table th {
background: #f8f9fa;
font-weight: 600;
color: #23282d;
cursor: pointer;
user-select: none;
position: relative;
white-space: nowrap;
}
.users-table th:hover {
background: #e9ecef;
}
.users-table th.sortable::after {
content: '⇅';
position: absolute;
right: 8px;
opacity: 0.3;
font-size: 11px;
}
.users-table th.sorted-asc::after {
content: '▲';
opacity: 1;
color: #0073aa;
}
.users-table th.sorted-desc::after {
content: '▼';
opacity: 1;
color: #0073aa;
}
.users-table tbody tr:hover {
background: #f8f9fa;
}
.users-table tbody tr:last-child td {
border-bottom: none;
}
/* Badges */
.badge {
display: inline-block;
padding: 3px 8px;
border-radius: 3px;
font-size: 11px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.3px;
}
.badge-success {
background: #d4edda;
color: #155724;
}
.badge-warning {
background: #fff3cd;
color: #856404;
}
.badge-danger {
background: #f8d7da;
color: #721c24;
}
.badge-info {
background: #d1ecf1;
color: #0c5460;
}
.badge-secondary {
background: #e2e3e5;
color: #383d41;
}
/* User Actions */
.user-actions {
display: flex;
gap: 5px;
}
.btn-action {
padding: 4px 8px;
font-size: 12px;
border-radius: 3px;
border: none;
cursor: pointer;
transition: opacity 0.2s;
}
.btn-action:hover {
opacity: 0.8;
}
.btn-edit {
background: #0073aa;
color: white;
}
.btn-delete {
background: #dc3545;
color: white;
}
/* Pagination */
.users-pagination {
display: flex;
justify-content: space-between;
align-items: center;
padding: 15px;
background: #f8f9fa;
border-radius: 4px;
flex-wrap: wrap;
gap: 10px;
}
.pagination-info {
font-size: 13px;
color: #666;
}
.pagination-controls {
display: flex;
gap: 8px;
align-items: center;
flex-wrap: wrap;
}
.page-input {
width: 60px;
text-align: center;
padding: 6px 8px;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 13px;
}
/* Loading & Error */
.loading-overlay {
text-align: center;
padding: 40px;
color: #666;
}
.loading-overlay .spinner {
width: 40px;
height: 40px;
border: 4px solid #f3f3f3;
border-top: 4px solid #0073aa;
border-radius: 50%;
animation: spin 1s linear infinite;
margin: 0 auto 15px;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
.error-message {
background: #f8d7da;
color: #721c24;
padding: 12px 15px;
border-radius: 4px;
margin-bottom: 15px;
border: 1px solid #f5c6cb;
font-size: 13px;
}
.empty-state {
text-align: center;
padding: 60px 20px;
color: #666;
}
.empty-state .icon {
font-size: 64px;
margin-bottom: 15px;
opacity: 0.3;
}
/* Custom Modal/Alert Styles */
.modal-overlay {
display: none;
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.6);
z-index: 10000;
animation: fadeIn 0.2s ease-out;
}
.modal-overlay.active {
display: flex;
align-items: center;
justify-content: center;
}
.modal-box {
background: white;
border-radius: 8px;
box-shadow: 0 10px 40px rgba(0, 0, 0, 0.3);
max-width: 500px;
width: 90%;
animation: slideUp 0.3s ease-out;
}
.modal-header {
padding: 20px 25px;
border-bottom: 1px solid #e5e5e5;
display: flex;
align-items: center;
gap: 12px;
}
.modal-header.alert {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
border-radius: 8px 8px 0 0;
}
.modal-header.confirm {
background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
color: white;
border-radius: 8px 8px 0 0;
}
.modal-header.success {
background: linear-gradient(135deg, #11998e 0%, #38ef7d 100%);
color: white;
border-radius: 8px 8px 0 0;
}
.modal-icon {
font-size: 28px;
}
.modal-title {
font-size: 18px;
font-weight: 600;
margin: 0;
}
.modal-body {
padding: 25px;
font-size: 14px;
line-height: 1.6;
color: #444;
max-height: 70vh;
overflow-y: auto;
}
/* Form Styles in Modal */
.modal-body input[type="text"],
.modal-body input[type="email"],
.modal-body select {
transition: border-color 0.2s, box-shadow 0.2s;
}
.modal-body input[type="text"]:focus,
.modal-body input[type="email"]:focus,
.modal-body select:focus {
outline: none;
border-color: #0073aa;
box-shadow: 0 0 0 3px rgba(0, 115, 170, 0.1);
}
.modal-body input[type="checkbox"] {
accent-color: #0073aa;
}
.modal-footer {
padding: 15px 25px;
border-top: 1px solid #e5e5e5;
display: flex;
gap: 10px;
justify-content: flex-end;
}
.modal-btn {
padding: 10px 20px;
border: none;
border-radius: 5px;
font-size: 14px;
font-weight: 500;
cursor: pointer;
transition: all 0.2s;
}
.modal-btn-primary {
background: #0073aa;
color: white;
}
.modal-btn-primary:hover {
background: #005a87;
transform: translateY(-1px);
box-shadow: 0 4px 12px rgba(0, 115, 170, 0.4);
}
.modal-btn-danger {
background: #dc3545;
color: white;
}
.modal-btn-danger:hover {
background: #c82333;
transform: translateY(-1px);
box-shadow: 0 4px 12px rgba(220, 53, 69, 0.4);
}
.modal-btn-secondary {
background: #f8f9fa;
color: #444;
border: 1px solid #ddd;
}
.modal-btn-secondary:hover {
background: #e9ecef;
}
@keyframes fadeIn {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
@keyframes slideUp {
from {
transform: translateY(50px);
opacity: 0;
}
to {
transform: translateY(0);
opacity: 1;
}
}
/* Responsive */
@media (max-width: 768px) {
.users-stats {
grid-template-columns: 1fr;
}
.users-controls {
flex-direction: column;
}
.control-group {
width: 100%;
}
.control-group input,
.control-group select {
flex: 1;
}
.users-pagination {
flex-direction: column;
}
.pagination-controls {
width: 100%;
justify-content: center;
}
.modal-box {
width: 95%;
}
}
</style>
<h1 class="admin-page-title">👥 Zarządzanie Użytkownikami</h1>
<!-- Stats Cards -->
<div class="users-stats">
<div class="stat-card">
<div class="stat-value" id="totalUsers">-</div>
<div class="stat-label">Wszystkich użytkowników</div>
</div>
<div class="stat-card">
<div class="stat-value" id="currentPage">-</div>
<div class="stat-label">Aktualna strona</div>
</div>
<div class="stat-card">
<div class="stat-value" id="totalPages">-</div>
<div class="stat-label">Wszystkich stron</div>
</div>
</div>
<div class="admin-content-box">
<!-- Error Message -->
<div id="errorMessage" class="error-message" style="display: none;"></div>
<!-- Controls -->
<div class="users-controls">
<div class="control-group">
<label>🔍 Username:</label>
<input type="text" id="filterUsername" placeholder="Szukaj...">
</div>
<div class="control-group">
<label>📧 Email:</label>
<input type="text" id="filterEmail" placeholder="Szukaj...">
</div>
<div class="control-group">
<label>👤 Rola:</label>
<select id="filterRole">
<option value="">Wszystkie</option>
<option value="user">User</option>
<option value="admin">Admin</option>
<option value="moderator">Moderator</option>
</select>
</div>
<div class="control-group">
<label>✓ Status:</label>
<select id="filterVerified">
<option value="">Wszystkie</option>
<option value="1">Zweryfikowani</option>
<option value="0">Niezweryfikowani</option>
</select>
</div>
<button class="btn btn-primary" onclick="applyFilters()">
🔍 Filtruj
</button>
<button class="btn btn-secondary" onclick="clearFilters()">
✖ Wyczyść
</button>
</div>
<!-- Loading State -->
<div id="loadingState" class="loading-overlay" style="display: none;">
<div class="spinner"></div>
<div>Ładowanie użytkowników...</div>
</div>
<!-- Users Table -->
<div class="users-table-wrapper" id="tableWrapper">
<table class="users-table">
<thead>
<tr>
<th class="sortable" onclick="sortBy('id')">ID</th>
<th class="sortable" onclick="sortBy('username')">Username</th>
<th class="sortable" onclick="sortBy('email')">Email</th>
<th>Imię i nazwisko</th>
<th class="sortable" onclick="sortBy('role')">Rola</th>
<th>Weryfikacja</th>
<th>Saldo</th>
<th>Statystyki</th>
<th class="sortable" onclick="sortBy('created_at')">Rejestracja</th>
<th>Akcje</th>
</tr>
</thead>
<tbody id="usersBody">
</tbody>
</table>
</div>
<!-- Pagination -->
<div class="users-pagination">
<div class="pagination-info" id="paginationInfo">
Ładowanie...
</div>
<div class="pagination-controls">
<button class="btn btn-secondary" id="btnFirst" onclick="goToFirstPage()">
⏮ Pierwsza
</button>
<button class="btn btn-secondary" id="btnPrev" onclick="previousPage()">
← Poprzednia
</button>
<input type="number" id="pageInput" class="page-input" min="1" value="1">
<button class="btn btn-primary" onclick="goToPageInput()">
Przejdź
</button>
<button class="btn btn-secondary" id="btnNext" onclick="nextPage()">
Następna →
</button>
<button class="btn btn-secondary" id="btnLast" onclick="goToLastPage()">
Ostatnia ⏭
</button>
</div>
</div>
</div>
</div>
<!-- Custom Modal for Alerts and Confirms -->
<div id="customModal" class="modal-overlay">
<div class="modal-box">
<div id="modalHeader" class="modal-header">
<span id="modalIcon" class="modal-icon"></span>
<h3 id="modalTitle" class="modal-title"></h3>
</div>
<div id="modalBody" class="modal-body"></div>
<div id="modalFooter" class="modal-footer"></div>
</div>
</div>
<script src="../../js/loadUsers.js"></script>
<script>
// Inicjalizacja
const loader = new LoadUsers('../../api/loadUsers.php');
let currentData = null;
let currentSort = { column: 'id', order: 'ASC' };
// Ładowanie przy starcie
document.addEventListener('DOMContentLoaded', () => {
loadUsers();
});
async function loadUsers() {
showLoading(true);
hideError();
try {
currentData = await loader.getUsers();
renderUsers(currentData);
updatePagination(currentData.pagination);
updateStats(currentData.pagination);
} catch (error) {
showError('Błąd podczas ładowania użytkowników: ' + error.message);
console.error(error);
} finally {
showLoading(false);
}
}
function renderUsers(data) {
const tbody = document.getElementById('usersBody');
tbody.innerHTML = '';
if (data.data.length === 0) {
tbody.innerHTML = `
<tr>
<td colspan="10">
<div class="empty-state">
<div class="icon">👥</div>
<h3>Brak użytkowników</h3>
<p>Nie znaleziono użytkowników spełniających kryteria wyszukiwania.</p>
</div>
</td>
</tr>
`;
return;
}
data.data.forEach(user => {
const tr = document.createElement('tr');
// Badge weryfikacji
const verifiedBadge = user.email_verified == 1
? '<span class="badge badge-success">✓ Tak</span>'
: '<span class="badge badge-warning">⚠ Nie</span>';
// Badge roli
let roleBadge = '<span class="badge badge-secondary">' + (user.role || 'user') + '</span>';
if (user.role === 'admin') {
roleBadge = '<span class="badge badge-danger">👑 Admin</span>';
} else if (user.role === 'moderator') {
roleBadge = '<span class="badge badge-info">🛡️ Moderator</span>';
}
// Imię i nazwisko
const fullName = [user.first_name, user.last_name].filter(Boolean).join(' ') || '-';
// Saldo
const balance = user.balance !== null
? parseFloat(user.balance).toFixed(2) + ' PLN'
: '-';
// Statystyki meczów
const matches = user.matches_played !== null
? `<div style="font-size:11px;">
<div>🎮 ${user.matches_played || 0} meczów</div>
<div style="color:#28a745;">✓ ${user.matches_won || 0} wygranych</div>
<div style="color:#dc3545;">✗ ${user.matches_lost || 0} przegranych</div>
</div>`
: '-';
tr.innerHTML = `
<td><strong>#${user.id}</strong></td>
<td><strong>${escapeHtml(user.username)}</strong></td>
<td style="font-size:12px;">${escapeHtml(user.email)}</td>
<td>${escapeHtml(fullName)}</td>
<td>${roleBadge}</td>
<td>${verifiedBadge}</td>
<td><strong>${balance}</strong></td>
<td>${matches}</td>
<td style="font-size:12px;">${formatDate(user.created_at)}</td>
<td>
<div class="user-actions">
<button class="btn-action btn-edit" onclick="editUser(${user.id})" title="Edytuj">
✏️
</button>
<button class="btn-action btn-delete" onclick="deleteUser(${user.id})" title="Usuń">
🗑️
</button>
</div>
</td>
`;
tbody.appendChild(tr);
});
updateSortHeaders();
}
function updatePagination(pagination) {
document.getElementById('paginationInfo').textContent =
`Pokazuje użytkowników ${((pagination.currentPage - 1) * pagination.recordsPerPage) + 1}-${Math.min(pagination.currentPage * pagination.recordsPerPage, pagination.totalRecords)} z ${pagination.totalRecords}`;
document.getElementById('pageInput').value = pagination.currentPage;
document.getElementById('pageInput').max = pagination.totalPages;
document.getElementById('btnPrev').disabled = !pagination.hasPreviousPage;
document.getElementById('btnFirst').disabled = !pagination.hasPreviousPage;
document.getElementById('btnNext').disabled = !pagination.hasNextPage;
document.getElementById('btnLast').disabled = !pagination.hasNextPage;
}
function updateStats(pagination) {
document.getElementById('totalUsers').textContent = pagination.totalRecords.toLocaleString('pl-PL');
document.getElementById('currentPage').textContent = pagination.currentPage;
document.getElementById('totalPages').textContent = pagination.totalPages;
}
async function nextPage() {
showLoading(true);
try {
currentData = await loader.nextPage();
renderUsers(currentData);
updatePagination(currentData.pagination);
updateStats(currentData.pagination);
scrollToTop();
} catch (error) {
showError('Błąd: ' + error.message);
} finally {
showLoading(false);
}
}
async function previousPage() {
showLoading(true);
try {
currentData = await loader.previousPage();
if (currentData) {
renderUsers(currentData);
updatePagination(currentData.pagination);
updateStats(currentData.pagination);
scrollToTop();
}
} catch (error) {
showError('Błąd: ' + error.message);
} finally {
showLoading(false);
}
}
async function goToFirstPage() {
showLoading(true);
try {
currentData = await loader.goToPage(1);
renderUsers(currentData);
updatePagination(currentData.pagination);
updateStats(currentData.pagination);
scrollToTop();
} catch (error) {
showError('Błąd: ' + error.message);
} finally {
showLoading(false);
}
}
async function goToLastPage() {
if (!currentData) return;
showLoading(true);
try {
currentData = await loader.goToPage(currentData.pagination.totalPages);
renderUsers(currentData);
updatePagination(currentData.pagination);
updateStats(currentData.pagination);
scrollToTop();
} catch (error) {
showError('Błąd: ' + error.message);
} finally {
showLoading(false);
}
}
async function goToPageInput() {
const page = parseInt(document.getElementById('pageInput').value);
if (page < 1 || (currentData && page > currentData.pagination.totalPages)) {
showError('Nieprawidłowy numer strony');
return;
}
showLoading(true);
try {
currentData = await loader.goToPage(page);
renderUsers(currentData);
updatePagination(currentData.pagination);
updateStats(currentData.pagination);
scrollToTop();
} catch (error) {
showError('Błąd: ' + error.message);
} finally {
showLoading(false);
}
}
async function sortBy(column) {
if (currentSort.column === column) {
currentSort.order = currentSort.order === 'ASC' ? 'DESC' : 'ASC';
} else {
currentSort.column = column;
currentSort.order = 'ASC';
}
showLoading(true);
try {
currentData = await loader.sort(currentSort.column, currentSort.order);
renderUsers(currentData);
updatePagination(currentData.pagination);
updateStats(currentData.pagination);
} catch (error) {
showError('Błąd: ' + error.message);
} finally {
showLoading(false);
}
}
function updateSortHeaders() {
document.querySelectorAll('.users-table th.sortable').forEach(th => {
th.classList.remove('sorted-asc', 'sorted-desc');
});
const columnMap = {
'id': 0,
'username': 1,
'email': 2,
'role': 4,
'created_at': 8
};
const columnIndex = columnMap[currentSort.column];
if (columnIndex !== undefined) {
const ths = document.querySelectorAll('.users-table th');
const th = ths[columnIndex];
if (th) {
th.classList.add(`sorted-${currentSort.order.toLowerCase()}`);
}
}
}
async function applyFilters() {
const filters = {};
const username = document.getElementById('filterUsername').value.trim();
if (username) filters.username = username;
const email = document.getElementById('filterEmail').value.trim();
if (email) filters.email = email;
const role = document.getElementById('filterRole').value;
if (role) filters.role = role;
const verified = document.getElementById('filterVerified').value;
if (verified) filters.email_verified = verified;
showLoading(true);
try {
currentData = await loader.filter(filters);
renderUsers(currentData);
updatePagination(currentData.pagination);
updateStats(currentData.pagination);
} catch (error) {
showError('Błąd: ' + error.message);
} finally {
showLoading(false);
}
}
async function clearFilters() {
document.getElementById('filterUsername').value = '';
document.getElementById('filterEmail').value = '';
document.getElementById('filterRole').value = '';
document.getElementById('filterVerified').value = '';
showLoading(true);
try {
currentData = await loader.clearFilters();
renderUsers(currentData);
updatePagination(currentData.pagination);
updateStats(currentData.pagination);
} catch (error) {
showError('Błąd: ' + error.message);
} finally {
showLoading(false);
}
}
// Custom Alert and Confirm Functions
function showAlert(message, title = 'Informacja', type = 'alert') {
return new Promise((resolve) => {
const modal = document.getElementById('customModal');
const header = document.getElementById('modalHeader');
const icon = document.getElementById('modalIcon');
const titleEl = document.getElementById('modalTitle');
const body = document.getElementById('modalBody');
const footer = document.getElementById('modalFooter');
// Set header class and icon
header.className = 'modal-header ' + type;
if (type === 'alert') {
icon.textContent = '';
} else if (type === 'success') {
icon.textContent = '✅';
} else if (type === 'confirm') {
icon.textContent = '⚠️';
}
titleEl.textContent = title;
body.innerHTML = message;
// Create OK button
footer.innerHTML = '<button class="modal-btn modal-btn-primary" id="modalOk">OK</button>';
modal.classList.add('active');
document.getElementById('modalOk').onclick = () => {
modal.classList.remove('active');
resolve(true);
};
// Close on overlay click
modal.onclick = (e) => {
if (e.target === modal) {
modal.classList.remove('active');
resolve(false);
}
};
});
}
function showConfirm(message, title = 'Potwierdzenie') {
return new Promise((resolve) => {
const modal = document.getElementById('customModal');
const header = document.getElementById('modalHeader');
const icon = document.getElementById('modalIcon');
const titleEl = document.getElementById('modalTitle');
const body = document.getElementById('modalBody');
const footer = document.getElementById('modalFooter');
header.className = 'modal-header confirm';
icon.textContent = '❓';
titleEl.textContent = title;
body.innerHTML = message;
// Create buttons
footer.innerHTML = `
<button class="modal-btn modal-btn-secondary" id="modalCancel">Anuluj</button>
<button class="modal-btn modal-btn-danger" id="modalConfirm">Potwierdź</button>
`;
modal.classList.add('active');
document.getElementById('modalConfirm').onclick = () => {
modal.classList.remove('active');
resolve(true);
};
document.getElementById('modalCancel').onclick = () => {
modal.classList.remove('active');
resolve(false);
};
// Close on overlay click
modal.onclick = (e) => {
if (e.target === modal) {
modal.classList.remove('active');
resolve(false);
}
};
});
}
function showSuccess(message, title = 'Sukces') {
return showAlert(message, title, 'success');
}
// User actions (placeholder)
async function editUser(userId) {
// Pobierz dane użytkownika
const user = currentData.data.find(u => u.id == userId);
if (!user) {
await showAlert('Nie znaleziono użytkownika', 'Błąd', 'alert');
return;
}
// Utwórz formularz edycji
const formHtml = `
<div style="text-align: left;">
<div style="margin-bottom: 15px;">
<label style="display: block; margin-bottom: 5px; font-weight: 600; color: #333;">Username:</label>
<input type="text" id="editUsername" value="${escapeHtml(user.username)}"
style="width: 100%; padding: 10px; border: 1px solid #ddd; border-radius: 4px; font-size: 14px;">
</div>
<div style="margin-bottom: 15px;">
<label style="display: block; margin-bottom: 5px; font-weight: 600; color: #333;">Email:</label>
<input type="email" id="editEmail" value="${escapeHtml(user.email)}"
style="width: 100%; padding: 10px; border: 1px solid #ddd; border-radius: 4px; font-size: 14px;">
</div>
<div style="margin-bottom: 15px;">
<label style="display: block; margin-bottom: 5px; font-weight: 600; color: #333;">Imię:</label>
<input type="text" id="editFirstName" value="${escapeHtml(user.first_name || '')}"
style="width: 100%; padding: 10px; border: 1px solid #ddd; border-radius: 4px; font-size: 14px;">
</div>
<div style="margin-bottom: 15px;">
<label style="display: block; margin-bottom: 5px; font-weight: 600; color: #333;">Nazwisko:</label>
<input type="text" id="editLastName" value="${escapeHtml(user.last_name || '')}"
style="width: 100%; padding: 10px; border: 1px solid #ddd; border-radius: 4px; font-size: 14px;">
</div>
<div style="margin-bottom: 15px;">
<label style="display: block; margin-bottom: 5px; font-weight: 600; color: #333;">Rola:</label>
<select id="editRole" style="width: 100%; padding: 10px; border: 1px solid #ddd; border-radius: 4px; font-size: 14px;">
<option value="user" ${user.role === 'user' ? 'selected' : ''}>User</option>
<option value="moderator" ${user.role === 'moderator' ? 'selected' : ''}>Moderator</option>
<option value="admin" ${user.role === 'admin' ? 'selected' : ''}>Admin</option>
</select>
</div>
<div style="margin-bottom: 15px;">
<label style="display: flex; align-items: center; cursor: pointer; user-select: none;">
<input type="checkbox" id="editEmailVerified" ${user.email_verified == 1 ? 'checked' : ''}
style="width: 18px; height: 18px; margin-right: 8px; cursor: pointer;">
<span style="font-weight: 600; color: #333;">Email zweryfikowany</span>
</label>
</div>
<div style="margin-bottom: 15px;">
<label style="display: flex; align-items: center; cursor: pointer; user-select: none;">
<input type="checkbox" id="editNewsletter" ${user.newsletter_enabled == 1 ? 'checked' : ''}
style="width: 18px; height: 18px; margin-right: 8px; cursor: pointer;">
<span style="font-weight: 600; color: #333;">Newsletter włączony</span>
</label>
</div>
</div>
`;
// Wyświetl modal z formularzem
const modal = document.getElementById('customModal');
const header = document.getElementById('modalHeader');
const icon = document.getElementById('modalIcon');
const titleEl = document.getElementById('modalTitle');
const body = document.getElementById('modalBody');
const footer = document.getElementById('modalFooter');
header.className = 'modal-header alert';
icon.textContent = '✏️';
titleEl.textContent = `Edycja użytkownika #${userId}`;
body.innerHTML = formHtml;
footer.innerHTML = `
<button class="modal-btn modal-btn-secondary" id="modalCancelEdit">Anuluj</button>
<button class="modal-btn modal-btn-primary" id="modalSaveEdit">💾 Zapisz</button>
`;
modal.classList.add('active');
// Obsługa anulowania
document.getElementById('modalCancelEdit').onclick = () => {
modal.classList.remove('active');
};
// Obsługa zapisywania
document.getElementById('modalSaveEdit').onclick = async () => {
const updateData = {
user_id: userId,
username: document.getElementById('editUsername').value.trim(),
email: document.getElementById('editEmail').value.trim(),
first_name: document.getElementById('editFirstName').value.trim(),
last_name: document.getElementById('editLastName').value.trim(),
role: document.getElementById('editRole').value,
email_verified: document.getElementById('editEmailVerified').checked ? 1 : 0,
newsletter_enabled: document.getElementById('editNewsletter').checked ? 1 : 0
};
// Walidacja
if (!updateData.username || updateData.username.length < 3) {
await showAlert('Username musi mieć minimum 3 znaki', 'Błąd walidacji', 'alert');
return;
}
if (!updateData.email || !updateData.email.includes('@')) {
await showAlert('Nieprawidłowy adres email', 'Błąd walidacji', 'alert');
return;
}
modal.classList.remove('active');
showLoading(true);
try {
const response = await fetch('../../api/updateUser.php', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(updateData)
});
const result = await response.json();
if (!result.success) {
throw new Error(result.error || 'Błąd podczas aktualizacji');
}
await showSuccess('Użytkownik został zaktualizowany pomyślnie!', 'Sukces');
// Odśwież listę użytkowników
await loadUsers();
} catch (error) {
await showAlert('Błąd podczas zapisywania: ' + error.message, 'Błąd', 'alert');
} finally {
showLoading(false);
}
};
// Close on overlay click
modal.onclick = (e) => {
if (e.target === modal) {
modal.classList.remove('active');
}
};
}
async function deleteUser(userId) {
const confirmed = await showConfirm(
`Czy na pewno chcesz usunąć użytkownika <strong>#${userId}</strong>?<br><br>⚠️ Konto zostanie oznaczone jako usunięte i nie będzie już widoczne.`,
'🗑️ Usunięcie użytkownika'
);
if (confirmed) {
showLoading(true);
try {
const response = await fetch('../../api/deleteUser.php', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ user_id: userId })
});
const result = await response.json();
if (!result.success) {
throw new Error(result.error || 'Błąd podczas usuwania');
}
await showSuccess('Użytkownik został pomyślnie usunięty!', 'Sukces');
// Odśwież listę użytkowników
await loadUsers();
} catch (error) {
await showAlert('Błąd podczas usuwania: ' + error.message, 'Błąd', 'alert');
} finally {
showLoading(false);
}
}
}
// Helper functions
function showLoading(show) {
document.getElementById('loadingState').style.display = show ? 'block' : 'none';
document.getElementById('tableWrapper').style.opacity = show ? '0.5' : '1';
}
function showError(message) {
const errorDiv = document.getElementById('errorMessage');
errorDiv.textContent = '⚠️ ' + message;
errorDiv.style.display = 'block';
setTimeout(() => {
errorDiv.style.display = 'none';
}, 5000);
}
function hideError() {
document.getElementById('errorMessage').style.display = 'none';
}
function escapeHtml(text) {
if (!text) return '';
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}
function formatDate(dateString) {
if (!dateString) return '-';
const date = new Date(dateString);
return date.toLocaleString('pl-PL', {
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit'
});
}
function scrollToTop() {
window.scrollTo({ top: 0, behavior: 'smooth' });
}
// Enter key handlers
document.addEventListener('DOMContentLoaded', () => {
['filterUsername', 'filterEmail'].forEach(id => {
document.getElementById(id)?.addEventListener('keypress', (e) => {
if (e.key === 'Enter') applyFilters();
});
});
document.getElementById('pageInput')?.addEventListener('keypress', (e) => {
if (e.key === 'Enter') goToPageInput();
});
});
</script>
<?php
require_once __DIR__ . '/../includes/footer.php';
?>