togethere.cloud/public_html/administration/users/preorder/index.php

419 lines
14 KiB
PHP

<?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);
}
.preorder-controls {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(190px, 1fr));
gap: 10px;
margin-bottom: 14px;
}
.preorder-control {
display: grid;
gap: 6px;
}
.preorder-control label {
font-size: 12px;
font-weight: 600;
color: #555;
}
.preorder-controls input,
.preorder-controls select {
width: 100%;
min-height: 36px;
border: 1px solid #d0d7de;
border-radius: 6px;
padding: 8px 10px;
font-size: 13px;
outline: none;
background: #fff;
}
.preorder-controls input:focus,
.preorder-controls select:focus {
border-color: #0073aa;
box-shadow: 0 0 0 3px rgba(0,115,170,0.12);
}
.preorder-actions {
display: flex;
gap: 8px;
margin-bottom: 16px;
flex-wrap: wrap;
}
.preorder-btn {
border: 0;
border-radius: 6px;
padding: 9px 14px;
font-size: 13px;
font-weight: 700;
cursor: pointer;
}
.preorder-btn-primary {
background: #0073aa;
color: #fff;
}
.preorder-btn-primary:hover {
background: #005f8d;
}
.preorder-btn-secondary {
background: #6c757d;
color: #fff;
}
.preorder-btn-secondary:hover {
background: #596167;
}
.preorder-status {
font-size: 13px;
color: #666;
margin-bottom: 10px;
min-height: 18px;
}
.preorder-status.error {
color: #b32d2e;
font-weight: 600;
}
.preorder-summary {
font-size: 13px;
color: #444;
margin-bottom: 12px;
}
.preorder-table-wrap {
width: 100%;
overflow-x: auto;
border: 1px solid #e6e6e6;
border-radius: 8px;
}
.preorder-table {
width: 100%;
border-collapse: collapse;
background: #fff;
min-width: 620px;
}
.preorder-table th,
.preorder-table td {
padding: 10px 12px;
border-bottom: 1px solid #eee;
font-size: 13px;
text-align: left;
vertical-align: top;
}
.preorder-table th {
background: #f8f9fa;
color: #23282d;
font-weight: 700;
white-space: nowrap;
}
.preorder-table tbody tr:hover {
background: #fafcff;
}
.preorder-empty {
padding: 20px;
text-align: center;
color: #666;
font-size: 13px;
}
.preorder-pagination {
display: flex;
align-items: center;
justify-content: space-between;
gap: 10px;
margin-top: 14px;
flex-wrap: wrap;
}
.preorder-page-info {
font-size: 13px;
color: #444;
}
.preorder-page-actions {
display: flex;
gap: 8px;
}
.preorder-page-btn {
border: 1px solid #d0d7de;
background: #fff;
border-radius: 6px;
padding: 8px 12px;
font-size: 13px;
cursor: pointer;
}
.preorder-page-btn:hover {
background: #f3f5f7;
}
.preorder-page-btn:disabled {
opacity: 0.5;
cursor: not-allowed;
background: #f7f7f7;
}
</style>
<h1 class="admin-page-title">📬 Preorder - zapisy newslettera</h1>
<div class="admin-content-box">
<div class="preorder-controls">
<div class="preorder-control">
<label for="preorderEmail">E-mail</label>
<input id="preorderEmail" type="text" placeholder="np. gmail.com" />
</div>
<div class="preorder-control">
<label for="preorderCreatedFrom">Data od</label>
<input id="preorderCreatedFrom" type="datetime-local" />
</div>
<div class="preorder-control">
<label for="preorderCreatedTo">Data do</label>
<input id="preorderCreatedTo" type="datetime-local" />
</div>
<div class="preorder-control">
<label for="preorderPerPage">Na stronę</label>
<select id="preorderPerPage">
<option value="10">10</option>
<option value="20" selected>20</option>
<option value="50">50</option>
<option value="100">100</option>
</select>
</div>
</div>
<div class="preorder-actions">
<button id="preorderSearchBtn" class="preorder-btn preorder-btn-primary" type="button">Filtruj</button>
<button id="preorderResetBtn" class="preorder-btn preorder-btn-secondary" type="button">Wyczyść</button>
</div>
<div id="preorderStatus" class="preorder-status">Ładowanie...</div>
<div id="preorderSummary" class="preorder-summary"></div>
<div class="preorder-table-wrap">
<table class="preorder-table" aria-label="Lista zapisów PREOrder">
<thead>
<tr>
<th style="width: 80px;">ID</th>
<th>E-mail</th>
<th style="width: 170px;">IP</th>
<th style="width: 190px;">Data zapisu</th>
</tr>
</thead>
<tbody id="preorderRows"></tbody>
</table>
</div>
<div class="preorder-pagination">
<div id="preorderPageInfo" class="preorder-page-info">Strona 1 z 1</div>
<div class="preorder-page-actions">
<button id="preorderPrevBtn" class="preorder-page-btn" type="button">← Poprzednia</button>
<button id="preorderNextBtn" class="preorder-page-btn" type="button">Następna →</button>
</div>
</div>
</div>
<script>
(function () {
const API_URL = '/api/admin_preorder.php';
const inputEmail = document.getElementById('preorderEmail');
const inputCreatedFrom = document.getElementById('preorderCreatedFrom');
const inputCreatedTo = document.getElementById('preorderCreatedTo');
const inputPerPage = document.getElementById('preorderPerPage');
const searchBtn = document.getElementById('preorderSearchBtn');
const resetBtn = document.getElementById('preorderResetBtn');
const prevBtn = document.getElementById('preorderPrevBtn');
const nextBtn = document.getElementById('preorderNextBtn');
const statusEl = document.getElementById('preorderStatus');
const summaryEl = document.getElementById('preorderSummary');
const rowsEl = document.getElementById('preorderRows');
const pageInfoEl = document.getElementById('preorderPageInfo');
const state = {
page: 1,
perPage: 20,
totalPages: 1,
totalRecords: 0,
hasNextPage: false,
hasPreviousPage: false
};
function toSqlDateTime(localValue, isEnd) {
if (!localValue) return '';
const withSeconds = localValue.length === 16 ? (localValue + ':00') : localValue;
return withSeconds.replace('T', ' ') + (isEnd && withSeconds.length === 19 ? '' : '');
}
function escapeHtml(value) {
return String(value)
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#039;');
}
function setStatus(text, isError) {
statusEl.textContent = text || '';
statusEl.classList.toggle('error', !!isError);
}
function renderRows(data) {
if (!Array.isArray(data) || data.length === 0) {
rowsEl.innerHTML = '<tr><td colspan="4" class="preorder-empty">Brak zapisów dla wybranych filtrów</td></tr>';
return;
}
rowsEl.innerHTML = data.map(function (row) {
return '<tr>' +
'<td>' + escapeHtml(row.id ?? '') + '</td>' +
'<td>' + escapeHtml(row.email ?? '') + '</td>' +
'<td>' + escapeHtml(row.ip_address ?? '-') + '</td>' +
'<td>' + escapeHtml(row.created_at ?? '') + '</td>' +
'</tr>';
}).join('');
}
function updatePagination() {
pageInfoEl.textContent = 'Strona ' + String(state.page) + ' z ' + String(state.totalPages);
prevBtn.disabled = !state.hasPreviousPage;
nextBtn.disabled = !state.hasNextPage;
summaryEl.textContent = 'Wyniki: ' + String(state.totalRecords) + ' rekordów';
}
async function loadPreorders() {
setStatus('Ładowanie...', false);
const params = new URLSearchParams();
params.set('page', String(state.page));
params.set('perPage', String(state.perPage));
const email = (inputEmail.value || '').trim();
const createdFrom = toSqlDateTime((inputCreatedFrom.value || '').trim(), false);
const createdTo = toSqlDateTime((inputCreatedTo.value || '').trim(), true);
if (email !== '') params.set('email', email);
if (createdFrom !== '') params.set('createdFrom', createdFrom);
if (createdTo !== '') params.set('createdTo', createdTo);
try {
const response = await fetch(API_URL + '?' + params.toString(), {
method: 'GET',
credentials: 'same-origin'
});
const json = await response.json();
if (!json.success) {
throw new Error(json.error || 'Błąd API');
}
const pagination = json.pagination || {};
state.page = Number(pagination.currentPage || state.page || 1);
state.totalPages = Number(pagination.totalPages || 1);
state.totalRecords = Number(pagination.totalRecords || 0);
state.hasNextPage = !!pagination.hasNextPage;
state.hasPreviousPage = !!pagination.hasPreviousPage;
renderRows(json.data || []);
updatePagination();
setStatus('OK', false);
} catch (error) {
renderRows([]);
state.totalPages = 1;
state.totalRecords = 0;
state.hasNextPage = false;
state.hasPreviousPage = false;
updatePagination();
setStatus('Nie udało się pobrać danych: ' + (error && error.message ? error.message : 'nieznany błąd'), true);
}
}
function applyFilters() {
state.page = 1;
state.perPage = Math.max(1, Math.min(100, Number(inputPerPage.value || 20)));
loadPreorders();
}
function resetFilters() {
inputEmail.value = '';
inputCreatedFrom.value = '';
inputCreatedTo.value = '';
inputPerPage.value = '20';
state.page = 1;
state.perPage = 20;
loadPreorders();
}
searchBtn.addEventListener('click', applyFilters);
resetBtn.addEventListener('click', resetFilters);
inputEmail.addEventListener('keydown', function (e) {
if (e.key === 'Enter') {
e.preventDefault();
applyFilters();
}
});
inputPerPage.addEventListener('change', applyFilters);
prevBtn.addEventListener('click', function () {
if (state.page <= 1) return;
state.page -= 1;
loadPreorders();
});
nextBtn.addEventListener('click', function () {
if (!state.hasNextPage) return;
state.page += 1;
loadPreorders();
});
loadPreorders();
})();
</script>
</div>
<?php
require_once __DIR__ . '/../../includes/footer.php';
?>