824 lines
32 KiB
PHP
824 lines
32 KiB
PHP
<?php
|
||
error_reporting(E_ALL);
|
||
ini_set('display_errors', 0);
|
||
ini_set('log_errors', 1);
|
||
|
||
require_once __DIR__ . '/../../includes/auth.php';
|
||
require_once __DIR__ . '/../../includes/config.php';
|
||
require_once __DIR__ . '/../../includes/header.php';
|
||
require_once __DIR__ . '/../../includes/sidebar.php';
|
||
require_once __DIR__ . '/../../../api/DisciplineSettingsModel.php';
|
||
require_once __DIR__ . '/../../../api/DisciplineSettingsService.php';
|
||
|
||
$discipline = 'ping-pong';
|
||
$settingsError = null;
|
||
|
||
try {
|
||
$model = new DisciplineSettingsModel($pdo);
|
||
$service = new DisciplineSettingsService($model);
|
||
$settings = $service->getSettingsForAPI($discipline);
|
||
} catch (Throwable $e) {
|
||
error_log('Ping-Pong settings load error: ' . $e->getMessage());
|
||
$defaults = DisciplineSettingsModel::getDefaults($discipline);
|
||
$settings = [
|
||
'discipline' => $discipline,
|
||
'settingsVersion' => 0,
|
||
'rules' => [
|
||
'pointsToWin' => $defaults['pointsToWin'],
|
||
'setsToWin' => $defaults['setsToWin'],
|
||
'serveRotation' => $defaults['serveRotation'],
|
||
'specialRules' => $defaults['specialRules']
|
||
],
|
||
'customization' => $defaults['customization'] ?? [],
|
||
'metadata' => [
|
||
'created_at' => null,
|
||
'updated_at' => null,
|
||
'updated_by' => null
|
||
],
|
||
'status' => 'default'
|
||
];
|
||
$settingsError = 'Błąd wczytywania ustawień. Spróbuj odświeżyć stronę.';
|
||
}
|
||
?>
|
||
|
||
<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;
|
||
}
|
||
|
||
.settings-container {
|
||
display: grid;
|
||
grid-template-columns: 1fr 1fr;
|
||
gap: 30px;
|
||
margin-top: 30px;
|
||
}
|
||
|
||
.settings-section {
|
||
background: #fff;
|
||
border: 1px solid #ddd;
|
||
border-radius: 4px;
|
||
padding: 25px;
|
||
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
|
||
}
|
||
|
||
.settings-section h2 {
|
||
font-size: 18px;
|
||
font-weight: 600;
|
||
color: #23282d;
|
||
margin-bottom: 15px;
|
||
padding-bottom: 10px;
|
||
border-bottom: 2px solid #0073aa;
|
||
}
|
||
|
||
.form-group {
|
||
margin-bottom: 20px;
|
||
}
|
||
|
||
.form-group label {
|
||
display: block;
|
||
font-weight: 500;
|
||
margin-bottom: 5px;
|
||
color: #333;
|
||
font-size: 14px;
|
||
}
|
||
|
||
.form-group input,
|
||
.form-group textarea,
|
||
.form-group select {
|
||
width: 100%;
|
||
padding: 10px;
|
||
border: 1px solid #ddd;
|
||
border-radius: 4px;
|
||
font-size: 14px;
|
||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
|
||
}
|
||
|
||
.form-group textarea {
|
||
resize: vertical;
|
||
min-height: 80px;
|
||
max-height: 300px;
|
||
font-family: monospace;
|
||
}
|
||
|
||
.form-group input[type="number"] {
|
||
width: 100%;
|
||
}
|
||
|
||
.form-hint {
|
||
font-size: 12px;
|
||
color: #666;
|
||
margin-top: 3px;
|
||
}
|
||
|
||
.button-group {
|
||
display: flex;
|
||
gap: 10px;
|
||
margin-top: 25px;
|
||
}
|
||
|
||
.btn {
|
||
padding: 10px 20px;
|
||
border: none;
|
||
border-radius: 4px;
|
||
font-weight: 500;
|
||
cursor: pointer;
|
||
transition: all 0.2s;
|
||
font-size: 14px;
|
||
}
|
||
|
||
.btn-primary {
|
||
background: #0073aa;
|
||
color: white;
|
||
}
|
||
|
||
.btn-primary:hover {
|
||
background: #005a87;
|
||
}
|
||
|
||
.btn-secondary {
|
||
background: #6c757d;
|
||
color: white;
|
||
}
|
||
|
||
.btn-secondary:hover {
|
||
background: #545b62;
|
||
}
|
||
|
||
.btn-danger {
|
||
background: #dc3545;
|
||
color: white;
|
||
}
|
||
|
||
.btn-danger:hover {
|
||
background: #c82333;
|
||
}
|
||
|
||
.alert {
|
||
padding: 15px;
|
||
margin-bottom: 20px;
|
||
border-radius: 8px;
|
||
font-size: 14px;
|
||
font-weight: 500;
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 10px;
|
||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||
}
|
||
|
||
.alert-success {
|
||
background: #d4edda;
|
||
color: #155724;
|
||
border: 1px solid #c3e6cb;
|
||
}
|
||
|
||
.alert-error {
|
||
background: #f8d7da;
|
||
color: #721c24;
|
||
border: 1px solid #f5c6cb;
|
||
}
|
||
|
||
.alert-info {
|
||
background: #d1ecf1;
|
||
color: #0c5460;
|
||
border: 1px solid #bee5eb;
|
||
}
|
||
|
||
.alert svg {
|
||
flex-shrink: 0;
|
||
width: 20px;
|
||
height: 20px;
|
||
}
|
||
|
||
.alert-success svg {
|
||
fill: #155724;
|
||
}
|
||
|
||
.alert-error svg {
|
||
fill: #721c24;
|
||
}
|
||
|
||
.alert-info svg {
|
||
fill: #0c5460;
|
||
}
|
||
|
||
/* Toasts (custom alerts) */
|
||
.toast-container {
|
||
position: fixed;
|
||
top: 20px;
|
||
right: 20px;
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 10px;
|
||
z-index: 9999;
|
||
}
|
||
|
||
.toast {
|
||
min-width: 280px;
|
||
max-width: 420px;
|
||
padding: 12px 14px;
|
||
border-radius: 10px;
|
||
border: 1px solid transparent;
|
||
display: grid;
|
||
grid-template-columns: 22px 1fr auto;
|
||
align-items: center;
|
||
gap: 10px;
|
||
box-shadow: 0 10px 20px rgba(0,0,0,0.12);
|
||
animation: toast-in 180ms ease-out;
|
||
background: #fff;
|
||
}
|
||
|
||
.toast-success { border-color: #c3e6cb; background: #f6fffa; }
|
||
.toast-error { border-color: #f5c6cb; background: #fff6f6; }
|
||
.toast-info { border-color: #bee5eb; background: #f6fdff; }
|
||
|
||
.toast .icon { width: 22px; height: 22px; }
|
||
.toast-success .icon { color: #2f7a3e; }
|
||
.toast-error .icon { color: #b42318; }
|
||
.toast-info .icon { color: #0b6b8c; }
|
||
|
||
.toast .close-btn {
|
||
border: none;
|
||
background: transparent;
|
||
cursor: pointer;
|
||
color: #555;
|
||
padding: 4px;
|
||
border-radius: 6px;
|
||
}
|
||
.toast .close-btn:hover { background: rgba(0,0,0,0.06); }
|
||
|
||
@keyframes toast-in {
|
||
from { opacity: 0; transform: translateY(-6px) scale(0.98); }
|
||
to { opacity: 1; transform: translateY(0) scale(1); }
|
||
}
|
||
|
||
/* Modal (custom confirm/preview) */
|
||
.modal-overlay {
|
||
position: fixed;
|
||
inset: 0;
|
||
background: rgba(0,0,0,0.45);
|
||
display: none;
|
||
align-items: center;
|
||
justify-content: center;
|
||
z-index: 9998;
|
||
}
|
||
.modal {
|
||
width: min(720px, 92vw);
|
||
background: #fff;
|
||
border-radius: 12px;
|
||
box-shadow: 0 30px 50px rgba(0,0,0,0.25);
|
||
overflow: hidden;
|
||
animation: modal-in 160ms ease-out;
|
||
}
|
||
@keyframes modal-in { from {opacity:0; transform: translateY(8px);} to {opacity:1; transform:none;} }
|
||
.modal-header {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
padding: 14px 16px;
|
||
border-bottom: 1px solid #eee;
|
||
}
|
||
.modal-title { font-weight: 600; font-size: 16px; }
|
||
|
||
.modal-header .close-btn {
|
||
width: 34px;
|
||
height: 34px;
|
||
border: 1px solid rgba(0,0,0,0.10);
|
||
background: rgba(0,0,0,0.02);
|
||
color: #444;
|
||
border-radius: 10px;
|
||
cursor: pointer;
|
||
display: grid;
|
||
place-items: center;
|
||
font-size: 16px;
|
||
line-height: 1;
|
||
padding: 0;
|
||
transition: background 140ms ease, border-color 140ms ease, transform 140ms ease, box-shadow 140ms ease;
|
||
}
|
||
|
||
.modal-header .close-btn:hover {
|
||
background: rgba(0,0,0,0.06);
|
||
border-color: rgba(0,0,0,0.16);
|
||
}
|
||
|
||
.modal-header .close-btn:active {
|
||
transform: translateY(1px);
|
||
}
|
||
|
||
.modal-header .close-btn:focus {
|
||
outline: none;
|
||
}
|
||
|
||
.modal-header .close-btn:focus-visible {
|
||
box-shadow: 0 0 0 3px rgba(0,115,170,0.22);
|
||
border-color: rgba(0,115,170,0.55);
|
||
}
|
||
.modal-body { padding: 16px; }
|
||
.modal-actions { display:flex; gap:10px; padding: 0 16px 16px; }
|
||
.btn-outline { background:#fff; color:#333; border:1px solid #ddd; }
|
||
.btn-outline:hover { background:#f6f6f6; }
|
||
|
||
/* Inline mini preview */
|
||
.preview-wrap { display:grid; grid-template-columns: 220px 1fr; gap:14px; align-items:center; }
|
||
.mini-table {
|
||
position: relative;
|
||
width: 220px; height: 130px;
|
||
border-radius: 8px;
|
||
border: 1px solid #ddd;
|
||
overflow: hidden;
|
||
}
|
||
.mini-table .net {
|
||
position:absolute; left:50%; top:0; bottom:0; width:2px; background:rgba(255,255,255,0.7); transform: translateX(-50%);
|
||
}
|
||
.mini-table .paddle {
|
||
position:absolute; width:10px; height:36px; top:50%; transform:translateY(-50%); border-radius:3px;
|
||
box-shadow: inset 0 0 0 1px rgba(255,255,255,0.25);
|
||
}
|
||
.mini-table .paddle.left { left:8px; }
|
||
.mini-table .paddle.right { right:8px; }
|
||
.mini-table .ball { position:absolute; width:12px; height:12px; border-radius:50%; top:calc(50% - 6px); left:calc(50% - 6px); box-shadow:0 1px 2px rgba(0,0,0,0.3); }
|
||
.preview-details { font-size:12px; line-height:1.6; }
|
||
|
||
.info-box {
|
||
background: #f8f9fa;
|
||
border-left: 4px solid #0073aa;
|
||
padding: 15px;
|
||
margin-bottom: 20px;
|
||
border-radius: 4px;
|
||
font-size: 13px;
|
||
}
|
||
|
||
.current-values {
|
||
background: #f8f9fa;
|
||
padding: 15px;
|
||
padding-top: 10px;
|
||
border-radius: 4px;
|
||
margin-top: 15px;
|
||
font-size: 13px;
|
||
}
|
||
|
||
.current-values dt {
|
||
font-weight: 600;
|
||
margin-top: 10px;
|
||
}
|
||
|
||
.current-values dd {
|
||
margin-left: 0;
|
||
color: #0073aa;
|
||
font-family: monospace;
|
||
}
|
||
|
||
.color-input-wrapper {
|
||
display: flex;
|
||
gap: 10px;
|
||
align-items: center;
|
||
}
|
||
|
||
input[type="color"] {
|
||
height: 40px;
|
||
border: 1px solid #ddd;
|
||
border-radius: 4px;
|
||
cursor: pointer;
|
||
}
|
||
|
||
@media (max-width: 1200px) {
|
||
.settings-container {
|
||
grid-template-columns: 1fr;
|
||
}
|
||
}
|
||
|
||
.loading {
|
||
opacity: 0.6;
|
||
pointer-events: none;
|
||
}
|
||
|
||
.version-info {
|
||
background: #e7f3ff;
|
||
border-left: 4px solid #0073aa;
|
||
padding: 10px;
|
||
margin-bottom: 15px;
|
||
border-radius: 4px;
|
||
font-size: 12px;
|
||
}
|
||
</style>
|
||
|
||
<h1 class="admin-page-title">🏓 Ping-Pong - Ustawienia Dyscypliny</h1>
|
||
|
||
<div id="alertContainer"></div>
|
||
|
||
<?php if (!empty($settingsError)): ?>
|
||
<div class="alert alert-error">
|
||
<?php echo htmlspecialchars($settingsError); ?>
|
||
</div>
|
||
<?php endif; ?>
|
||
|
||
<div class="info-box">
|
||
<strong>ℹ️ Informacja:</strong> Każda zmiana ustawień zwiększa wersję. Gry są zawsze uruchamiane z
|
||
snapshot'em ustawień z momentu startu, więc stare mecze nie są dotknięte zmianami.
|
||
</div>
|
||
|
||
<div class="version-info">
|
||
<strong>Obecna wersja:</strong> v<?php echo $settings['settingsVersion']; ?>
|
||
<br/>
|
||
<strong>Ostatnia zmiana:</strong> <?php echo $settings['metadata']['updated_at'] ?: 'brak'; ?>
|
||
</div>
|
||
|
||
<form id="settingsForm">
|
||
<div class="settings-container">
|
||
<!-- SEKCJA REGUŁ GRY -->
|
||
<div class="settings-section">
|
||
<h2>🎮 Reguły Gry (Logika)</h2>
|
||
|
||
<div class="form-group">
|
||
<label for="pointsToWin">Punkty do wygrania seta *</label>
|
||
<input
|
||
type="number"
|
||
id="pointsToWin"
|
||
name="pointsToWin"
|
||
min="1"
|
||
max="100"
|
||
value="<?php echo $settings['rules']['pointsToWin']; ?>"
|
||
required
|
||
/>
|
||
<div class="form-hint">Liczba punktów potrzebnych do wygrania seta (min: 1, max: 100)</div>
|
||
</div>
|
||
|
||
<div class="form-group">
|
||
<label for="setsToWin">Sety do wygrania meczu *</label>
|
||
<input
|
||
type="number"
|
||
id="setsToWin"
|
||
name="setsToWin"
|
||
min="1"
|
||
max="100"
|
||
value="<?php echo $settings['rules']['setsToWin']; ?>"
|
||
required
|
||
/>
|
||
<div class="form-hint">Liczba setów potrzebnych do wygrania meczu (min: 1, max: 100)</div>
|
||
</div>
|
||
|
||
<div class="form-group">
|
||
<label for="serveRotation">Punkty do zmiany serwisu *</label>
|
||
<input
|
||
type="number"
|
||
id="serveRotation"
|
||
name="serveRotation"
|
||
min="1"
|
||
max="50"
|
||
value="<?php echo $settings['rules']['serveRotation']; ?>"
|
||
required
|
||
/>
|
||
<div class="form-hint">Po ilu punktach następuje zmiana serwisu (min: 1, max: 50)</div>
|
||
</div>
|
||
|
||
<div class="form-group">
|
||
<label for="specialRules">Specjalne reguły</label>
|
||
<textarea
|
||
id="specialRules"
|
||
name="specialRules"
|
||
placeholder="np. Deuce at 10-10, brak przerw, tie-break w ostatnim secie..."
|
||
><?php echo htmlspecialchars($settings['rules']['specialRules'] ?? ''); ?></textarea>
|
||
<div class="form-hint">Dodatkowe reguły (opcjonalne)</div>
|
||
</div>
|
||
|
||
<div class="current-values">
|
||
<dt>Aktualne wartości:</dt>
|
||
<dd>pointsToWin: <?php echo $settings['rules']['pointsToWin']; ?></dd>
|
||
<dd>setsToWin: <?php echo $settings['rules']['setsToWin']; ?></dd>
|
||
<dd>serveRotation: <?php echo $settings['rules']['serveRotation']; ?></dd>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- SEKCJA PERSONALIZACJI UI -->
|
||
<div class="settings-section">
|
||
<h2>🎨 Personalizacja UI</h2>
|
||
|
||
<div class="form-group">
|
||
<label for="tableColor">Kolor stołu</label>
|
||
<div class="color-input-wrapper">
|
||
<input
|
||
type="color"
|
||
id="tableColor"
|
||
name="tableColor"
|
||
value="<?php echo htmlspecialchars($settings['customization']['tableColor'] ?? '#2d5016'); ?>"
|
||
/>
|
||
<input
|
||
type="text"
|
||
name="tableColorText"
|
||
value="<?php echo htmlspecialchars($settings['customization']['tableColor'] ?? '#2d5016'); ?>"
|
||
style="flex: 1;"
|
||
readonly
|
||
/>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="form-group">
|
||
<label for="ballColor">Kolor piłki</label>
|
||
<div class="color-input-wrapper">
|
||
<input
|
||
type="color"
|
||
id="ballColor"
|
||
name="ballColor"
|
||
value="<?php echo htmlspecialchars($settings['customization']['ballColor'] ?? '#ff6600'); ?>"
|
||
/>
|
||
<input
|
||
type="text"
|
||
name="ballColorText"
|
||
value="<?php echo htmlspecialchars($settings['customization']['ballColor'] ?? '#ff6600'); ?>"
|
||
style="flex: 1;"
|
||
readonly
|
||
/>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="form-group">
|
||
<label for="paddleColor">Kolor rakietki</label>
|
||
<div class="color-input-wrapper">
|
||
<input
|
||
type="color"
|
||
id="paddleColor"
|
||
name="paddleColor"
|
||
value="<?php echo htmlspecialchars($settings['customization']['paddleColor'] ?? '#000000'); ?>"
|
||
/>
|
||
<input
|
||
type="text"
|
||
name="paddleColorText"
|
||
value="<?php echo htmlspecialchars($settings['customization']['paddleColor'] ?? '#000000'); ?>"
|
||
style="flex: 1;"
|
||
readonly
|
||
/>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="form-group">
|
||
<label for="uiTheme">Motyw interfejsu</label>
|
||
<select id="uiTheme" name="uiTheme">
|
||
<option value="dark" <?php echo ($settings['customization']['uiTheme'] ?? 'dark') === 'dark' ? 'selected' : ''; ?>>Ciemny (Dark)</option>
|
||
<option value="light" <?php echo ($settings['customization']['uiTheme'] ?? 'light') === 'light' ? 'selected' : ''; ?>>Jasny (Light)</option>
|
||
<option value="auto">Automatyczny (Auto)</option>
|
||
</select>
|
||
<div class="form-hint">Wybierz motyw interfejsu gry</div>
|
||
</div>
|
||
|
||
<div class="current-values">
|
||
<dt>Podgląd:</dt>
|
||
<div class="preview-wrap" style="margin-top:10px;">
|
||
<div class="mini-table" id="miniTable" style="background: <?php echo htmlspecialchars($settings['customization']['tableColor'] ?? '#2d5016'); ?>;">
|
||
<div class="net"></div>
|
||
<div class="paddle left" id="miniPaddleLeft" style="background: <?php echo htmlspecialchars($settings['customization']['paddleColor'] ?? '#000000'); ?>;"></div>
|
||
<div class="paddle right" id="miniPaddleRight" style="background: <?php echo htmlspecialchars($settings['customization']['paddleColor'] ?? '#000000'); ?>;"></div>
|
||
<div class="ball" id="miniBall" style="background: <?php echo htmlspecialchars($settings['customization']['ballColor'] ?? '#ff6600'); ?>;"></div>
|
||
</div>
|
||
<div class="preview-details" id="previewDetails">
|
||
Stół: <?php echo htmlspecialchars($settings['customization']['tableColor'] ?? '#2d5016'); ?><br/>
|
||
Piłka: <?php echo htmlspecialchars($settings['customization']['ballColor'] ?? '#ff6600'); ?><br/>
|
||
Rakietka: <?php echo htmlspecialchars($settings['customization']['paddleColor'] ?? '#000000'); ?><br/>
|
||
Motyw: <?php echo htmlspecialchars($settings['customization']['uiTheme'] ?? 'dark'); ?>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="button-group" style="margin-top: 30px;">
|
||
<button type="submit" class="btn btn-primary">💾 Zapisz ustawienia</button>
|
||
<button type="button" class="btn btn-secondary" onclick="resetToDefaults()">🔄 Resetuj do defaults</button>
|
||
<button type="button" class="btn btn-danger" onclick="previewChanges()">👁️ Podgląd zmian</button>
|
||
</div>
|
||
</form>
|
||
</div>
|
||
|
||
<script>
|
||
const settingsEndpoint = '/administration/disciplines/ping-pong/settings/';
|
||
|
||
// Synchronizuj kolory
|
||
document.getElementById('tableColor')?.addEventListener('change', function() {
|
||
document.querySelector('input[name="tableColorText"]').value = this.value;
|
||
});
|
||
document.getElementById('ballColor')?.addEventListener('change', function() {
|
||
document.querySelector('input[name="ballColorText"]').value = this.value;
|
||
});
|
||
document.getElementById('paddleColor')?.addEventListener('change', function() {
|
||
document.querySelector('input[name="paddleColorText"]').value = this.value;
|
||
});
|
||
|
||
// Obsługa formularza
|
||
document.getElementById('settingsForm')?.addEventListener('submit', async function(e) {
|
||
e.preventDefault();
|
||
|
||
const formData = {
|
||
rules: {
|
||
pointsToWin: parseInt(document.getElementById('pointsToWin').value),
|
||
setsToWin: parseInt(document.getElementById('setsToWin').value),
|
||
serveRotation: parseInt(document.getElementById('serveRotation').value),
|
||
specialRules: document.getElementById('specialRules').value || null
|
||
},
|
||
customization: {
|
||
tableColor: document.getElementById('tableColor').value,
|
||
ballColor: document.getElementById('ballColor').value,
|
||
paddleColor: document.getElementById('paddleColor').value,
|
||
uiTheme: document.getElementById('uiTheme').value
|
||
}
|
||
};
|
||
|
||
try {
|
||
const response = await fetch(settingsEndpoint, {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify(formData)
|
||
});
|
||
|
||
const result = await response.json();
|
||
|
||
if (result.success) {
|
||
showAlert(result.message, 'success');
|
||
// Odśwież stronę po 2 sekundach
|
||
setTimeout(() => location.reload(), 2000);
|
||
} else {
|
||
showAlert(result.message, 'error');
|
||
}
|
||
} catch (error) {
|
||
showAlert('Błąd: ' + error.message, 'error');
|
||
}
|
||
});
|
||
|
||
function resetToDefaults() {
|
||
confirmDialog('Jesteś pewny? Ustawienia zostaną zresetowane do defaults.')
|
||
.then(ok => {
|
||
if (!ok) return;
|
||
fetch(settingsEndpoint, {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify({ reset: true })
|
||
})
|
||
.then(r => r.json())
|
||
.then(data => {
|
||
if (data.success) {
|
||
showAlert(data.message, 'success');
|
||
setTimeout(() => location.reload(), 2000);
|
||
} else {
|
||
showAlert(data.message, 'error');
|
||
}
|
||
})
|
||
.catch(e => showAlert('Błąd: ' + e.message, 'error'));
|
||
});
|
||
}
|
||
|
||
function previewChanges() {
|
||
const rules = {
|
||
pointsToWin: parseInt(document.getElementById('pointsToWin').value),
|
||
setsToWin: parseInt(document.getElementById('setsToWin').value),
|
||
serveRotation: parseInt(document.getElementById('serveRotation').value)
|
||
};
|
||
|
||
openPreviewModal(rules);
|
||
}
|
||
|
||
function showAlert(message, type) {
|
||
let container = document.getElementById('toastContainer');
|
||
if (!container) {
|
||
container = document.createElement('div');
|
||
container.id = 'toastContainer';
|
||
container.className = 'toast-container';
|
||
document.body.appendChild(container);
|
||
}
|
||
|
||
const toast = document.createElement('div');
|
||
toast.className = `toast toast-${type}`;
|
||
toast.innerHTML = `
|
||
<span class="icon">${type === 'success' ? '✅' : type === 'error' ? '⛔' : 'ℹ️'}</span>
|
||
<div>${message}</div>
|
||
<button class="close-btn" aria-label="Zamknij">✖</button>
|
||
`;
|
||
toast.querySelector('.close-btn').addEventListener('click', () => toast.remove());
|
||
container.appendChild(toast);
|
||
setTimeout(() => toast.remove(), 5000);
|
||
}
|
||
|
||
// Custom confirm dialog returning a Promise
|
||
function confirmDialog(text) {
|
||
return new Promise((resolve) => {
|
||
let overlay = document.getElementById('modalOverlay');
|
||
if (!overlay) {
|
||
overlay = document.createElement('div');
|
||
overlay.id = 'modalOverlay';
|
||
overlay.className = 'modal-overlay';
|
||
overlay.innerHTML = `
|
||
<div class="modal" role="dialog" aria-modal="true">
|
||
<div class="modal-header">
|
||
<div class="modal-title">Potwierdzenie</div>
|
||
<button class="close-btn" aria-label="Zamknij">✖</button>
|
||
</div>
|
||
<div class="modal-body" id="modalBody"></div>
|
||
<div class="modal-actions">
|
||
<button class="btn btn-outline" id="modalCancel">Anuluj</button>
|
||
<button class="btn btn-primary" id="modalOk">Potwierdź</button>
|
||
</div>
|
||
</div>`;
|
||
document.body.appendChild(overlay);
|
||
}
|
||
overlay.style.display = 'flex';
|
||
overlay.querySelector('#modalBody').textContent = text;
|
||
const close = () => overlay.style.display = 'none';
|
||
const okBtn = overlay.querySelector('#modalOk');
|
||
const cancelBtn = overlay.querySelector('#modalCancel');
|
||
const xBtn = overlay.querySelector('.close-btn');
|
||
const cleanup = () => {
|
||
okBtn.onclick = null; cancelBtn.onclick = null; xBtn.onclick = null;
|
||
};
|
||
okBtn.onclick = () => { cleanup(); close(); resolve(true); };
|
||
cancelBtn.onclick = () => { cleanup(); close(); resolve(false); };
|
||
xBtn.onclick = () => { cleanup(); close(); resolve(false); };
|
||
});
|
||
}
|
||
|
||
// Modal preview showing table and rules
|
||
function openPreviewModal(rules) {
|
||
let overlay = document.getElementById('previewOverlay');
|
||
if (!overlay) {
|
||
overlay = document.createElement('div');
|
||
overlay.id = 'previewOverlay';
|
||
overlay.className = 'modal-overlay';
|
||
overlay.innerHTML = `
|
||
<div class="modal" role="dialog" aria-modal="true">
|
||
<div class="modal-header">
|
||
<div class="modal-title">Podgląd zmian</div>
|
||
<button class="close-btn" aria-label="Zamknij">✖</button>
|
||
</div>
|
||
<div class="modal-body">
|
||
<div style="display:grid; grid-template-columns: 1fr 1fr; gap:16px; align-items:center;">
|
||
<div class="mini-table" id="previewTable" style="width:100%; height:220px;"></div>
|
||
<div id="previewInfo" style="font-size:14px; line-height:1.7;"></div>
|
||
</div>
|
||
</div>
|
||
</div>`;
|
||
document.body.appendChild(overlay);
|
||
overlay.addEventListener('click', (e) => { if (e.target === overlay) overlay.style.display = 'none'; });
|
||
overlay.querySelector('.close-btn').addEventListener('click', () => overlay.style.display = 'none');
|
||
}
|
||
|
||
const tableColor = document.getElementById('tableColor').value;
|
||
const ballColor = document.getElementById('ballColor').value;
|
||
const paddleColor = document.getElementById('paddleColor').value;
|
||
const uiTheme = document.getElementById('uiTheme').value;
|
||
|
||
const table = overlay.querySelector('#previewTable');
|
||
table.innerHTML = `
|
||
<div class="net"></div>
|
||
<div class="paddle left" style="width:14px;height:60px;background:${paddleColor};left:12px;"></div>
|
||
<div class="paddle right" style="width:14px;height:60px;background:${paddleColor};right:12px;"></div>
|
||
<div class="ball" style="width:18px;height:18px;background:${ballColor};top:calc(50% - 9px);left:calc(50% - 9px);"></div>
|
||
`;
|
||
table.style.background = tableColor;
|
||
|
||
const info = overlay.querySelector('#previewInfo');
|
||
info.innerHTML = `
|
||
<strong>Reguły gry</strong><br/>
|
||
Punkty do seta: <b>${rules.pointsToWin}</b><br/>
|
||
Sety do meczu: <b>${rules.setsToWin}</b><br/>
|
||
Zmiana serwisu: co <b>${rules.serveRotation}</b> pkt<br/>
|
||
<br/>
|
||
<strong>Wygląd</strong><br/>
|
||
Stół: <span style="font-family:monospace">${tableColor}</span><br/>
|
||
Piłka: <span style="font-family:monospace">${ballColor}</span><br/>
|
||
Rakietka: <span style="font-family:monospace">${paddleColor}</span><br/>
|
||
Motyw: <b>${uiTheme}</b>
|
||
`;
|
||
|
||
overlay.style.display = 'flex';
|
||
}
|
||
|
||
// Live inline preview updates
|
||
function updateInlinePreview() {
|
||
const tableColor = document.getElementById('tableColor').value;
|
||
const ballColor = document.getElementById('ballColor').value;
|
||
const paddleColor = document.getElementById('paddleColor').value;
|
||
const uiTheme = document.getElementById('uiTheme').value;
|
||
const miniTable = document.getElementById('miniTable');
|
||
if (miniTable) {
|
||
miniTable.style.background = tableColor;
|
||
const left = document.getElementById('miniPaddleLeft');
|
||
const right = document.getElementById('miniPaddleRight');
|
||
const ball = document.getElementById('miniBall');
|
||
if (left) left.style.background = paddleColor;
|
||
if (right) right.style.background = paddleColor;
|
||
if (ball) ball.style.background = ballColor;
|
||
}
|
||
const details = document.getElementById('previewDetails');
|
||
if (details) {
|
||
details.innerHTML = `Stół: ${tableColor}<br/>Piłka: ${ballColor}<br/>Rakietka: ${paddleColor}<br/>Motyw: ${uiTheme}`;
|
||
}
|
||
}
|
||
|
||
['tableColor','ballColor','paddleColor','uiTheme','pointsToWin','setsToWin','serveRotation','specialRules']
|
||
.forEach(id => { const el = document.getElementById(id); if (el) el.addEventListener('input', updateInlinePreview); });
|
||
updateInlinePreview();
|
||
</script>
|
||
|
||
<?php require_once __DIR__ . '/../../includes/footer.php'; ?>
|