events yogibook
This commit is contained in:
@@ -0,0 +1,907 @@
|
||||
<?php
|
||||
require_once('include/headscript.php');
|
||||
|
||||
/**
|
||||
* Modulo Eventi - form creazione/modifica (una sola pagina).
|
||||
* - event_form.php -> nuovo evento
|
||||
* - event_form.php?id=123 -> modifica evento esistente
|
||||
* Invia a event_save.php (insert/update + redirect).
|
||||
*
|
||||
* Extra rispetto alla v1:
|
||||
* - flatpickr per data/ora (fine = inizio + 1h automatica)
|
||||
* - sede da event_locations (default scuola) o indirizzo personalizzato
|
||||
* - "Calcola coordinate" da indirizzo (geocoding via event_geocode.php)
|
||||
* - copertina drag&drop -> event_upload_cover.php -> assets/uploads/events/
|
||||
*/
|
||||
$pdo = DBHandlerSelect::getInstance()->getConnection();
|
||||
|
||||
function e($value): string
|
||||
{
|
||||
return htmlspecialchars((string) $value, ENT_QUOTES, 'UTF-8');
|
||||
}
|
||||
|
||||
$id = isset($_GET['id']) ? (int) $_GET['id'] : 0;
|
||||
$ev = null;
|
||||
|
||||
if ($id > 0) {
|
||||
$stmt = $pdo->prepare("SELECT * FROM events WHERE id = ?");
|
||||
$stmt->execute([$id]);
|
||||
$ev = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
if (!$ev) {
|
||||
header('Location: events.php?message=error');
|
||||
exit();
|
||||
}
|
||||
}
|
||||
|
||||
// Default dal record unico di `option`
|
||||
$opt = $pdo->query("SELECT * FROM `option` LIMIT 1")->fetch(PDO::FETCH_ASSOC);
|
||||
$defaultColor = $opt['event_default_color'] ?? '#4CAF50';
|
||||
$defaultMaxPerBooking = (int) ($opt['event_default_max_per_booking'] ?? 1);
|
||||
|
||||
// Sedi salvate (se la tabella non esiste ancora, si degrada su indirizzo libero)
|
||||
$locations = [];
|
||||
try {
|
||||
$locations = $pdo->query("SELECT * FROM event_locations ORDER BY is_default DESC, name ASC")->fetchAll(PDO::FETCH_ASSOC);
|
||||
} catch (Throwable $e) {
|
||||
$locations = [];
|
||||
}
|
||||
|
||||
$val = [
|
||||
'title' => $ev['title'] ?? '',
|
||||
'slug' => $ev['slug'] ?? '',
|
||||
'organizer_name' => $ev['organizer_name'] ?? '',
|
||||
'starts_at' => !empty($ev['starts_at']) ? date('Y-m-d H:i', strtotime($ev['starts_at'])) : '',
|
||||
'ends_at' => !empty($ev['ends_at']) ? date('Y-m-d H:i', strtotime($ev['ends_at'])) : '',
|
||||
'address' => $ev['address'] ?? '',
|
||||
'lat' => $ev['lat'] ?? '',
|
||||
'lng' => $ev['lng'] ?? '',
|
||||
'cost' => $ev['cost'] ?? '0.00',
|
||||
'currency' => $ev['currency'] ?? 'EUR',
|
||||
'max_seats' => $ev['max_seats'] ?? '',
|
||||
'max_per_booking' => $ev['max_per_booking'] ?? $defaultMaxPerBooking,
|
||||
'color' => $ev['color'] ?? $defaultColor,
|
||||
'cover_image' => $ev['cover_image'] ?? '',
|
||||
'status' => $ev['status'] ?? 'draft',
|
||||
'description' => $ev['description'] ?? '',
|
||||
'instructions' => $ev['instructions'] ?? '',
|
||||
'what_to_bring' => $ev['what_to_bring'] ?? '',
|
||||
'suitable_for' => $ev['suitable_for'] ?? '',
|
||||
'gallery' => $ev['gallery'] ?? '',
|
||||
];
|
||||
|
||||
$isEdit = $id > 0;
|
||||
$pageTitle = $isEdit ? 'Modifica evento' : 'Nuovo evento';
|
||||
?>
|
||||
<!doctype html>
|
||||
<html lang="it">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>YogiBook - <?php echo e($pageTitle); ?></title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link rel="shortcut icon" href="assets/images/favicon.ico">
|
||||
<link href="assets/css/bootstrap.min.css" id="bootstrap-style" rel="stylesheet" type="text/css" />
|
||||
<link href="assets/css/icons.min.css" rel="stylesheet" type="text/css" />
|
||||
<link href="assets/css/app.min.css" id="app-style" rel="stylesheet" type="text/css" />
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0-beta3/css/all.min.css">
|
||||
<!-- Date/ora -->
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/flatpickr/4.6.13/flatpickr.min.css">
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/flatpickr/4.6.13/flatpickr.min.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/flatpickr/4.6.13/l10n/it.min.js"></script>
|
||||
<!-- Editor testo ricco -->
|
||||
<link href="https://cdn.jsdelivr.net/npm/quill@2/dist/quill.snow.css" rel="stylesheet">
|
||||
<script src="https://cdn.jsdelivr.net/npm/quill@2/dist/quill.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
|
||||
|
||||
<style>
|
||||
.admin-card {
|
||||
border: none;
|
||||
border-radius: 14px;
|
||||
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
|
||||
.admin-card .card-body {
|
||||
padding: 26px 30px;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: 15px;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.04em;
|
||||
text-transform: uppercase;
|
||||
color: #6b7280;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.section-lead {
|
||||
color: #9ca3af;
|
||||
font-size: 14px;
|
||||
margin-bottom: 22px;
|
||||
}
|
||||
|
||||
.form-label {
|
||||
font-weight: 600;
|
||||
font-size: 14px;
|
||||
color: #374151;
|
||||
}
|
||||
|
||||
.form-control,
|
||||
.form-select {
|
||||
border-radius: 8px;
|
||||
border: 1px solid #e2e4e9;
|
||||
padding: 10px 12px;
|
||||
}
|
||||
|
||||
.form-control:focus,
|
||||
.form-select:focus {
|
||||
border-color: #1ebf73;
|
||||
box-shadow: 0 0 0 3px rgba(30, 191, 115, 0.12);
|
||||
}
|
||||
|
||||
.form-control[readonly] {
|
||||
background: #f7f8fa;
|
||||
color: #6b7280;
|
||||
}
|
||||
|
||||
.hint {
|
||||
color: #9ca3af;
|
||||
font-size: 12px;
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
#editor {
|
||||
height: 240px;
|
||||
border-bottom-left-radius: 8px;
|
||||
border-bottom-right-radius: 8px;
|
||||
}
|
||||
|
||||
.ql-toolbar.ql-snow,
|
||||
.ql-container.ql-snow {
|
||||
border-color: #e2e4e9;
|
||||
}
|
||||
|
||||
.ql-toolbar.ql-snow {
|
||||
border-top-left-radius: 8px;
|
||||
border-top-right-radius: 8px;
|
||||
}
|
||||
|
||||
input[type="color"].form-control {
|
||||
padding: 4px;
|
||||
height: 44px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.btn-geocode {
|
||||
border: 1px solid #e2e4e9;
|
||||
background: #fff;
|
||||
border-radius: 8px;
|
||||
padding: 10px 14px;
|
||||
font-weight: 600;
|
||||
font-size: 13px;
|
||||
color: #2f7be7;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.btn-geocode:hover {
|
||||
background: #f3f4f6;
|
||||
}
|
||||
|
||||
.btn-save {
|
||||
border-radius: 8px;
|
||||
padding: 10px 26px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.btn-back {
|
||||
border-radius: 8px;
|
||||
padding: 10px 20px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
/* Dropzone copertina */
|
||||
.dropzone {
|
||||
border: 2px dashed #d5d9e0;
|
||||
border-radius: 12px;
|
||||
background: #fafbfc;
|
||||
padding: 22px;
|
||||
text-align: center;
|
||||
transition: border-color 0.15s, background 0.15s;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.dropzone.dragover {
|
||||
border-color: #1ebf73;
|
||||
background: #eef6f1;
|
||||
}
|
||||
|
||||
.dz-empty i {
|
||||
font-size: 34px;
|
||||
color: #b6bcc6;
|
||||
display: block;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.dz-empty p {
|
||||
margin: 0;
|
||||
color: #4b5563;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.dz-empty small {
|
||||
color: #9ca3af;
|
||||
}
|
||||
|
||||
.dz-browse {
|
||||
color: #2f7be7;
|
||||
font-weight: 600;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.dz-preview img {
|
||||
max-height: 170px;
|
||||
border-radius: 10px;
|
||||
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.12);
|
||||
}
|
||||
|
||||
.dz-remove {
|
||||
margin-top: 12px;
|
||||
border: 1px solid #f2c9c4;
|
||||
background: #fff;
|
||||
color: #c0392b;
|
||||
border-radius: 8px;
|
||||
padding: 6px 14px;
|
||||
font-weight: 600;
|
||||
font-size: 13px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.dz-remove:hover {
|
||||
background: #fdecea;
|
||||
}
|
||||
|
||||
.dz-progress {
|
||||
color: #6b7280;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.coord-flash {
|
||||
border-color: #1ebf73 !important;
|
||||
box-shadow: 0 0 0 3px rgba(30, 191, 115, 0.18) !important;
|
||||
transition: box-shadow .2s;
|
||||
}
|
||||
|
||||
.gallery-grid {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 10px;
|
||||
margin-top: 12px;
|
||||
}
|
||||
|
||||
.gallery-cell {
|
||||
position: relative;
|
||||
width: 92px;
|
||||
height: 92px;
|
||||
border-radius: 10px;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.10);
|
||||
}
|
||||
|
||||
.gallery-cell img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.gallery-remove {
|
||||
position: absolute;
|
||||
top: 4px;
|
||||
right: 4px;
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
border: none;
|
||||
border-radius: 50%;
|
||||
background: rgba(0, 0, 0, 0.55);
|
||||
color: #fff;
|
||||
font-size: 15px;
|
||||
line-height: 1;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.gallery-remove:hover {
|
||||
background: #c0392b;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="layout-wrapper">
|
||||
<header id="page-topbar" class="isvertical-topbar">
|
||||
<div class="navbar-header">
|
||||
<div class="d-flex">
|
||||
<?php include('include/logoarea.php'); ?>
|
||||
<button type="button" class="btn btn-sm px-3 font-size-24 header-item waves-effect vertical-menu-btn">
|
||||
<i class="bx bx-menu align-middle"></i>
|
||||
</button>
|
||||
<div class="page-title-box align-self-center d-none d-md-block">
|
||||
<h4 class="page-title mb-0"><?php echo e($pageTitle); ?></h4>
|
||||
</div>
|
||||
</div>
|
||||
<div class="d-flex">
|
||||
<?php include('include/languageselection.php'); ?>
|
||||
<?php include('include/profiletopbar.php'); ?>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
<?php include('include/sidebar.php'); ?>
|
||||
<header class="ishorizontal-topbar">
|
||||
<div class="navbar-header">
|
||||
<div class="d-flex"></div>
|
||||
</div>
|
||||
<div class="topnav">
|
||||
<div class="container-fluid">
|
||||
<nav class="navbar navbar-light navbar-expand-lg topnav-menu"></nav>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div class="main-content">
|
||||
<div class="page-content">
|
||||
<div class="container-fluid">
|
||||
<div class="row">
|
||||
<div class="col-lg-9">
|
||||
<div class="card admin-card">
|
||||
<div class="card-body">
|
||||
<div class="section-title"><?php echo e($pageTitle); ?></div>
|
||||
<p class="section-lead">Compila i dati dell'evento. I campi con * sono obbligatori.</p>
|
||||
|
||||
<form action="event_save.php" method="POST" id="event-form">
|
||||
<input type="hidden" name="id" value="<?php echo (int) $id; ?>">
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-8 mb-3">
|
||||
<label class="form-label">Titolo *</label>
|
||||
<input type="text" class="form-control" name="title" required
|
||||
value="<?php echo e($val['title']); ?>" placeholder="Es. Yoga in Gravidanza & Amaca">
|
||||
</div>
|
||||
<div class="col-md-4 mb-3">
|
||||
<label class="form-label">Slug</label>
|
||||
<input type="text" class="form-control" name="slug"
|
||||
value="<?php echo e($val['slug']); ?>" placeholder="generato dal titolo">
|
||||
<div class="hint">URL pubblico del widget. Vuoto = generato dal titolo.</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6 mb-3">
|
||||
<label class="form-label">Data e ora inizio *</label>
|
||||
<input type="text" class="form-control" id="starts_at" name="starts_at" required
|
||||
value="<?php echo e($val['starts_at']); ?>" placeholder="Scegli data e ora" autocomplete="off">
|
||||
</div>
|
||||
<div class="col-md-6 mb-3">
|
||||
<label class="form-label">Data e ora fine</label>
|
||||
<input type="text" class="form-control" id="ends_at" name="ends_at"
|
||||
value="<?php echo e($val['ends_at']); ?>" placeholder="Auto: inizio + 1h" autocomplete="off">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Sede / indirizzo -->
|
||||
<div class="row">
|
||||
<?php if (!empty($locations)) : ?>
|
||||
<div class="col-md-5 mb-3">
|
||||
<label class="form-label">Sede</label>
|
||||
<select id="location-select" class="form-select">
|
||||
<?php foreach ($locations as $loc) : ?>
|
||||
<option value="<?php echo (int) $loc['id']; ?>"
|
||||
data-address="<?php echo e($loc['address']); ?>"
|
||||
data-lat="<?php echo e($loc['lat']); ?>"
|
||||
data-lng="<?php echo e($loc['lng']); ?>">
|
||||
<?php echo e($loc['name']); ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
<option value="custom">➕ Indirizzo personalizzato</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-7 mb-3">
|
||||
<label class="form-label">Indirizzo</label>
|
||||
<div class="d-flex gap-2">
|
||||
<input type="text" class="form-control" id="address-input" name="address"
|
||||
value="<?php echo e($val['address']); ?>" placeholder="Via, numero, città">
|
||||
<button type="button" class="btn-geocode" id="btn-geocode"><i class="bx bx-map-pin"></i> Calcola</button>
|
||||
</div>
|
||||
<div class="hint" id="geo-hint">Per un indirizzo personalizzato, premi "Calcola" per ottenere le coordinate mappa.</div>
|
||||
</div>
|
||||
<?php else : ?>
|
||||
<div class="col-md-12 mb-3">
|
||||
<label class="form-label">Indirizzo</label>
|
||||
<div class="d-flex gap-2">
|
||||
<input type="text" class="form-control" id="address-input" name="address"
|
||||
value="<?php echo e($val['address']); ?>" placeholder="Via, numero, città">
|
||||
<button type="button" class="btn-geocode" id="btn-geocode"><i class="bx bx-map-pin"></i> Calcola coordinate</button>
|
||||
</div>
|
||||
<div class="hint" id="geo-hint">Premi "Calcola coordinate" per ottenere lat/lng dall'indirizzo.</div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<div class="row" id="coords-row">
|
||||
<div class="col-md-3 mb-3">
|
||||
<label class="form-label">Latitudine</label>
|
||||
<input type="text" class="form-control" id="lat-input" name="lat" value="<?php echo e($val['lat']); ?>" placeholder="45.6600">
|
||||
</div>
|
||||
<div class="col-md-3 mb-3">
|
||||
<label class="form-label">Longitudine</label>
|
||||
<input type="text" class="form-control" id="lng-input" name="lng" value="<?php echo e($val['lng']); ?>" placeholder="8.7900">
|
||||
</div>
|
||||
<div class="col-md-6 mb-3">
|
||||
<label class="form-label">Organizzatore</label>
|
||||
<input type="text" class="form-control" name="organizer_name"
|
||||
value="<?php echo e($val['organizer_name']); ?>" placeholder="Lascia vuoto = tu">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Descrizione</label>
|
||||
<div id="editor"></div>
|
||||
<input type="hidden" name="description" id="description-input">
|
||||
<div class="hint">Testo formattato, elenchi, link e immagini.</div>
|
||||
</div>
|
||||
|
||||
<!-- Dettagli aggiuntivi (facoltativi) -->
|
||||
<div class="row">
|
||||
<div class="col-md-4 mb-3">
|
||||
<label class="form-label">Istruzioni</label>
|
||||
<textarea class="form-control" name="instructions" rows="3" placeholder="Come arrivare, orari, note pratiche…"><?php echo e($val['instructions']); ?></textarea>
|
||||
</div>
|
||||
<div class="col-md-4 mb-3">
|
||||
<label class="form-label">Cosa portare</label>
|
||||
<textarea class="form-control" name="what_to_bring" rows="3" placeholder="Tappetino, acqua, abbigliamento comodo…"><?php echo e($val['what_to_bring']); ?></textarea>
|
||||
</div>
|
||||
<div class="col-md-4 mb-3">
|
||||
<label class="form-label">Per chi è adatto</label>
|
||||
<textarea class="form-control" name="suitable_for" rows="3" placeholder="Principianti, gravidanza, tutti i livelli…"><?php echo e($val['suitable_for']); ?></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<p class="hint" style="margin-top:-8px;">Questi campi sono facoltativi e verranno usati anche nella card pubblica e nelle email.</p>
|
||||
|
||||
<!-- Copertina drag&drop -->
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Immagine di copertina</label>
|
||||
<div class="dropzone" id="cover-dropzone">
|
||||
<div class="dz-empty" id="dz-empty" style="<?php echo $val['cover_image'] ? 'display:none;' : ''; ?>">
|
||||
<i class="bx bx-cloud-upload"></i>
|
||||
<p>Trascina qui l'immagine oppure <span class="dz-browse">sfoglia</span></p>
|
||||
<small>JPG, PNG, WEBP o GIF · max 5 MB</small>
|
||||
</div>
|
||||
<div class="dz-preview" id="dz-preview" style="<?php echo $val['cover_image'] ? '' : 'display:none;'; ?>">
|
||||
<img id="dz-img" src="<?php echo e($val['cover_image']); ?>" alt="Copertina">
|
||||
<div><button type="button" class="dz-remove" id="dz-remove"><i class="bx bx-trash"></i> Rimuovi</button></div>
|
||||
</div>
|
||||
<div class="dz-progress" id="dz-progress" style="display:none;"><i class="bx bx-loader bx-spin"></i> Caricamento…</div>
|
||||
<input type="file" id="cover-file" accept="image/*" hidden>
|
||||
</div>
|
||||
<input type="hidden" name="cover_image" id="cover_image" value="<?php echo e($val['cover_image']); ?>">
|
||||
<div class="hint">Immagine principale mostrata nella card.</div>
|
||||
</div>
|
||||
|
||||
<!-- Galleria foto -->
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Galleria foto</label>
|
||||
<div class="dropzone" id="gallery-dropzone">
|
||||
<div class="dz-empty">
|
||||
<i class="bx bx-images"></i>
|
||||
<p>Trascina qui le immagini oppure <span class="dz-browse">sfoglia</span></p>
|
||||
<small>Più immagini insieme · JPG, PNG, WEBP o GIF · max 5 MB l'una</small>
|
||||
</div>
|
||||
<input type="file" id="gallery-file" accept="image/*" multiple hidden>
|
||||
</div>
|
||||
<div class="gallery-grid" id="gallery-grid"></div>
|
||||
<input type="hidden" name="gallery" id="gallery-input" value="<?php echo e($val['gallery']); ?>">
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-3 mb-3">
|
||||
<label class="form-label">Costo</label>
|
||||
<input type="number" step="0.01" min="0" class="form-control" name="cost" value="<?php echo e($val['cost']); ?>">
|
||||
<div class="hint">0 = gratuito.</div>
|
||||
</div>
|
||||
<div class="col-md-3 mb-3">
|
||||
<label class="form-label">Valuta</label>
|
||||
<select name="currency" class="form-select">
|
||||
<?php foreach (['EUR', 'USD', 'GBP', 'CHF'] as $cur) : ?>
|
||||
<option value="<?php echo $cur; ?>" <?php echo $val['currency'] === $cur ? 'selected' : ''; ?>><?php echo $cur; ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-3 mb-3">
|
||||
<label class="form-label">Posti totali *</label>
|
||||
<input type="number" min="1" class="form-control" name="max_seats" required value="<?php echo e($val['max_seats']); ?>" placeholder="Es. 15">
|
||||
</div>
|
||||
<div class="col-md-3 mb-3">
|
||||
<label class="form-label">Max posti per persona</label>
|
||||
<input type="number" min="1" class="form-control" name="max_per_booking" value="<?php echo e($val['max_per_booking']); ?>">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-3 mb-3">
|
||||
<label class="form-label">Colore card</label>
|
||||
<input type="color" class="form-control" name="color" value="<?php echo e($val['color']); ?>">
|
||||
</div>
|
||||
<div class="col-md-9 mb-3">
|
||||
<label class="form-label">Stato</label>
|
||||
<select name="status" class="form-select">
|
||||
<option value="draft" <?php echo $val['status'] === 'draft' ? 'selected' : ''; ?>>Bozza</option>
|
||||
<option value="published" <?php echo $val['status'] === 'published' ? 'selected' : ''; ?>>Pubblicato</option>
|
||||
<option value="cancelled" <?php echo $val['status'] === 'cancelled' ? 'selected' : ''; ?>>Annullato</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="d-flex justify-content-between mt-2">
|
||||
<a href="events.php" class="btn btn-light btn-back"><i class="bx bx-arrow-back"></i> Indietro</a>
|
||||
<button type="submit" class="btn btn-primary btn-save">
|
||||
<i class="bx bx-save"></i> <?php echo $isEdit ? 'Salva modifiche' : 'Crea evento'; ?>
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php include('include/footer.php'); ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="assets/libs/bootstrap/js/bootstrap.bundle.min.js"></script>
|
||||
<script src="assets/libs/metismenujs/metismenujs.min.js"></script>
|
||||
<script src="assets/libs/simplebar/simplebar.min.js"></script>
|
||||
<script src="assets/libs/eva-icons/eva.min.js"></script>
|
||||
<script src="assets/js/app.js"></script>
|
||||
|
||||
<script>
|
||||
/* ---------- Date/ora con flatpickr (fine = inizio + 1h) ---------- */
|
||||
var fpOpts = {
|
||||
enableTime: true,
|
||||
time_24hr: true,
|
||||
minuteIncrement: 15,
|
||||
dateFormat: "Y-m-d H:i",
|
||||
altInput: true,
|
||||
altFormat: "l j F Y, H:i",
|
||||
locale: "it"
|
||||
};
|
||||
|
||||
var endPicker = flatpickr("#ends_at", fpOpts);
|
||||
var startPicker = flatpickr("#starts_at", Object.assign({}, fpOpts, {
|
||||
onChange: function(dates) {
|
||||
if (!dates[0]) return;
|
||||
endPicker.set('minDate', dates[0]);
|
||||
var end = endPicker.selectedDates[0];
|
||||
if (!end || end <= dates[0]) {
|
||||
endPicker.setDate(new Date(dates[0].getTime() + 60 * 60 * 1000), false);
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
||||
/* ---------- Sede / indirizzo ---------- */
|
||||
var addressInput = document.getElementById('address-input');
|
||||
var latInput = document.getElementById('lat-input');
|
||||
var lngInput = document.getElementById('lng-input');
|
||||
var geoHint = document.getElementById('geo-hint');
|
||||
var locationSelect = document.getElementById('location-select');
|
||||
var currentAddress = <?php echo json_encode($val['address'], JSON_UNESCAPED_UNICODE); ?>;
|
||||
|
||||
function clearCoords() {
|
||||
latInput.value = '';
|
||||
lngInput.value = '';
|
||||
}
|
||||
|
||||
// Dichiarazione (hoisted): usata anche in fase di preselezione qui sotto.
|
||||
function applyLocation() {
|
||||
if (!locationSelect) return;
|
||||
var opt = locationSelect.options[locationSelect.selectedIndex];
|
||||
if (locationSelect.value === 'custom') {
|
||||
// Indirizzo personalizzato: campo vuoto, nessuna coordinata finché non premi "Calcola"
|
||||
addressInput.value = '';
|
||||
clearCoords();
|
||||
geoHint.textContent = 'Scrivi l\'indirizzo e premi "Calcola" per le coordinate.';
|
||||
addressInput.focus();
|
||||
} else {
|
||||
// Sede salvata: indirizzo e coordinate REALI della sede
|
||||
addressInput.value = opt.getAttribute('data-address') || '';
|
||||
latInput.value = opt.getAttribute('data-lat') || '';
|
||||
lngInput.value = opt.getAttribute('data-lng') || '';
|
||||
geoHint.textContent = (latInput.value && lngInput.value) ?
|
||||
'Coordinate della sede caricate.' :
|
||||
'Questa sede non ha coordinate salvate: premi "Calcola".';
|
||||
}
|
||||
}
|
||||
|
||||
if (locationSelect) {
|
||||
var matched = null;
|
||||
Array.prototype.forEach.call(locationSelect.options, function(opt) {
|
||||
if (opt.value !== 'custom' && currentAddress !== '' && opt.getAttribute('data-address') === currentAddress) {
|
||||
matched = opt.value;
|
||||
}
|
||||
});
|
||||
if (currentAddress === '') {
|
||||
locationSelect.selectedIndex = 0; // nuovo evento -> sede di default (scuola)
|
||||
applyLocation();
|
||||
} else if (matched) {
|
||||
locationSelect.value = matched; // l'indirizzo salvato combacia con una sede
|
||||
applyLocation();
|
||||
} else {
|
||||
locationSelect.value = 'custom'; // indirizzo personalizzato salvato: NON toccare i valori esistenti
|
||||
}
|
||||
locationSelect.addEventListener('change', applyLocation);
|
||||
}
|
||||
|
||||
// Se modifichi l'indirizzo a mano, le vecchie coordinate non valgono più:
|
||||
// le azzero subito, così non restano valori "di default" fuorvianti.
|
||||
addressInput.addEventListener('input', function() {
|
||||
clearCoords();
|
||||
geoHint.textContent = 'Indirizzo modificato: premi "Calcola" per aggiornare le coordinate.';
|
||||
});
|
||||
|
||||
/* ---------- Geocoding indirizzo -> lat/lng ---------- */
|
||||
document.getElementById('btn-geocode').addEventListener('click', function() {
|
||||
var q = addressInput.value.trim();
|
||||
if (q === '') {
|
||||
addressInput.focus();
|
||||
return;
|
||||
}
|
||||
geoHint.textContent = 'Ricerca coordinate…';
|
||||
fetch('event_geocode.php?q=' + encodeURIComponent(q))
|
||||
.then(function(r) {
|
||||
return r.json();
|
||||
})
|
||||
.then(function(data) {
|
||||
if (data.ok) {
|
||||
latInput.value = data.lat;
|
||||
lngInput.value = data.lng;
|
||||
latInput.classList.add('coord-flash');
|
||||
lngInput.classList.add('coord-flash');
|
||||
setTimeout(function() {
|
||||
latInput.classList.remove('coord-flash');
|
||||
lngInput.classList.remove('coord-flash');
|
||||
}, 900);
|
||||
geoHint.innerHTML = '<span style="color:#1a7f52;font-weight:600;">✓ Coordinate aggiornate:</span> ' +
|
||||
data.lat + ', ' + data.lng + (data.display ? ' — ' + data.display : '');
|
||||
} else {
|
||||
geoHint.textContent = data.error || 'Indirizzo non trovato.';
|
||||
}
|
||||
})
|
||||
.catch(function() {
|
||||
geoHint.textContent = 'Errore durante il geocoding.';
|
||||
});
|
||||
});
|
||||
|
||||
/* ---------- Editor Quill ---------- */
|
||||
var quill = new Quill('#editor', {
|
||||
theme: 'snow',
|
||||
modules: {
|
||||
toolbar: [
|
||||
[{
|
||||
'header': [2, 3, false]
|
||||
}],
|
||||
['bold', 'italic', 'underline'],
|
||||
[{
|
||||
'list': 'ordered'
|
||||
}, {
|
||||
'list': 'bullet'
|
||||
}],
|
||||
['link', 'image'],
|
||||
['clean']
|
||||
]
|
||||
}
|
||||
});
|
||||
var initialHtml = <?php echo json_encode($val['description'], JSON_UNESCAPED_UNICODE | JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_AMP); ?>;
|
||||
if (initialHtml && initialHtml.trim() !== '') {
|
||||
quill.clipboard.dangerouslyPasteHTML(initialHtml);
|
||||
}
|
||||
|
||||
/* ---------- Copertina drag&drop ---------- */
|
||||
var dz = document.getElementById('cover-dropzone');
|
||||
var fileInput = document.getElementById('cover-file');
|
||||
var dzEmpty = document.getElementById('dz-empty');
|
||||
var dzPreview = document.getElementById('dz-preview');
|
||||
var dzProgress = document.getElementById('dz-progress');
|
||||
var dzImg = document.getElementById('dz-img');
|
||||
var coverHidden = document.getElementById('cover_image');
|
||||
|
||||
dz.addEventListener('click', function(ev) {
|
||||
if (ev.target.id === 'dz-remove' || ev.target.closest('#dz-remove')) return;
|
||||
fileInput.click();
|
||||
});
|
||||
['dragenter', 'dragover'].forEach(function(t) {
|
||||
dz.addEventListener(t, function(ev) {
|
||||
ev.preventDefault();
|
||||
dz.classList.add('dragover');
|
||||
});
|
||||
});
|
||||
['dragleave', 'drop'].forEach(function(t) {
|
||||
dz.addEventListener(t, function(ev) {
|
||||
ev.preventDefault();
|
||||
dz.classList.remove('dragover');
|
||||
});
|
||||
});
|
||||
dz.addEventListener('drop', function(ev) {
|
||||
if (ev.dataTransfer.files.length) uploadCover(ev.dataTransfer.files[0]);
|
||||
});
|
||||
fileInput.addEventListener('change', function() {
|
||||
if (fileInput.files.length) uploadCover(fileInput.files[0]);
|
||||
});
|
||||
document.getElementById('dz-remove').addEventListener('click', function() {
|
||||
coverHidden.value = '';
|
||||
dzImg.src = '';
|
||||
dzPreview.style.display = 'none';
|
||||
dzEmpty.style.display = '';
|
||||
fileInput.value = '';
|
||||
});
|
||||
|
||||
function uploadCover(file) {
|
||||
if (!file.type.startsWith('image/')) {
|
||||
Swal.fire('Formato non valido', 'Seleziona un file immagine.', 'warning');
|
||||
return;
|
||||
}
|
||||
if (file.size > 5 * 1024 * 1024) {
|
||||
Swal.fire('File troppo grande', "L'immagine supera i 5 MB.", 'warning');
|
||||
return;
|
||||
}
|
||||
dzEmpty.style.display = 'none';
|
||||
dzPreview.style.display = 'none';
|
||||
dzProgress.style.display = 'block';
|
||||
|
||||
var fd = new FormData();
|
||||
fd.append('cover', file);
|
||||
fetch('event_upload_cover.php', {
|
||||
method: 'POST',
|
||||
body: fd
|
||||
})
|
||||
.then(function(r) {
|
||||
return r.json();
|
||||
})
|
||||
.then(function(data) {
|
||||
dzProgress.style.display = 'none';
|
||||
if (data.ok) {
|
||||
coverHidden.value = data.path;
|
||||
dzImg.src = data.path + '?t=' + Date.now();
|
||||
dzPreview.style.display = 'block';
|
||||
} else {
|
||||
dzEmpty.style.display = '';
|
||||
Swal.fire('Errore', data.error || 'Caricamento non riuscito.', 'error');
|
||||
}
|
||||
})
|
||||
.catch(function() {
|
||||
dzProgress.style.display = 'none';
|
||||
dzEmpty.style.display = '';
|
||||
Swal.fire('Errore', 'Caricamento non riuscito.', 'error');
|
||||
});
|
||||
}
|
||||
|
||||
/* ---------- Galleria foto (multi-upload) ---------- */
|
||||
var galleryInput = document.getElementById('gallery-input');
|
||||
var galleryGrid = document.getElementById('gallery-grid');
|
||||
var galleryFile = document.getElementById('gallery-file');
|
||||
var galleryDz = document.getElementById('gallery-dropzone');
|
||||
var galleryItems = [];
|
||||
try {
|
||||
galleryItems = JSON.parse(galleryInput.value || '[]');
|
||||
if (!Array.isArray(galleryItems)) galleryItems = [];
|
||||
} catch (e) {
|
||||
galleryItems = [];
|
||||
}
|
||||
|
||||
function renderGallery() {
|
||||
galleryGrid.innerHTML = '';
|
||||
galleryItems.forEach(function(path, idx) {
|
||||
var cell = document.createElement('div');
|
||||
cell.className = 'gallery-cell';
|
||||
var img = document.createElement('img');
|
||||
img.src = path;
|
||||
var btn = document.createElement('button');
|
||||
btn.type = 'button';
|
||||
btn.className = 'gallery-remove';
|
||||
btn.setAttribute('data-idx', idx);
|
||||
btn.innerHTML = '×';
|
||||
cell.appendChild(img);
|
||||
cell.appendChild(btn);
|
||||
galleryGrid.appendChild(cell);
|
||||
});
|
||||
galleryInput.value = JSON.stringify(galleryItems);
|
||||
}
|
||||
renderGallery();
|
||||
|
||||
galleryGrid.addEventListener('click', function(ev) {
|
||||
var btn = ev.target.closest('.gallery-remove');
|
||||
if (!btn) return;
|
||||
galleryItems.splice(parseInt(btn.getAttribute('data-idx'), 10), 1);
|
||||
renderGallery();
|
||||
});
|
||||
galleryDz.addEventListener('click', function() {
|
||||
galleryFile.click();
|
||||
});
|
||||
['dragenter', 'dragover'].forEach(function(t) {
|
||||
galleryDz.addEventListener(t, function(ev) {
|
||||
ev.preventDefault();
|
||||
galleryDz.classList.add('dragover');
|
||||
});
|
||||
});
|
||||
['dragleave', 'drop'].forEach(function(t) {
|
||||
galleryDz.addEventListener(t, function(ev) {
|
||||
ev.preventDefault();
|
||||
galleryDz.classList.remove('dragover');
|
||||
});
|
||||
});
|
||||
galleryDz.addEventListener('drop', function(ev) {
|
||||
if (ev.dataTransfer.files.length) uploadGallery(ev.dataTransfer.files);
|
||||
});
|
||||
galleryFile.addEventListener('change', function() {
|
||||
if (galleryFile.files.length) uploadGallery(galleryFile.files);
|
||||
});
|
||||
|
||||
function uploadGallery(files) {
|
||||
Array.prototype.forEach.call(files, function(file) {
|
||||
if (!file.type.startsWith('image/')) return;
|
||||
if (file.size > 5 * 1024 * 1024) {
|
||||
Swal.fire('File troppo grande', file.name + ' supera i 5 MB.', 'warning');
|
||||
return;
|
||||
}
|
||||
var fd = new FormData();
|
||||
fd.append('cover', file); // stesso handler della copertina
|
||||
fetch('event_upload_cover.php', {
|
||||
method: 'POST',
|
||||
body: fd
|
||||
})
|
||||
.then(function(r) {
|
||||
return r.json();
|
||||
})
|
||||
.then(function(data) {
|
||||
if (data.ok) {
|
||||
galleryItems.push(data.path);
|
||||
renderGallery();
|
||||
} else {
|
||||
Swal.fire('Errore', data.error || 'Caricamento non riuscito.', 'error');
|
||||
}
|
||||
})
|
||||
.catch(function() {
|
||||
Swal.fire('Errore', 'Caricamento non riuscito.', 'error');
|
||||
});
|
||||
});
|
||||
galleryFile.value = '';
|
||||
}
|
||||
|
||||
/* ---------- Slug suggerito dal titolo ---------- */
|
||||
(function() {
|
||||
var titleInput = document.querySelector('input[name="title"]');
|
||||
var slugInput = document.querySelector('input[name="slug"]');
|
||||
if (!titleInput || !slugInput) return;
|
||||
// In modifica lo slug esiste già: lo consideriamo "toccato" e non lo sovrascriviamo.
|
||||
var slugEdited = slugInput.value.trim() !== '';
|
||||
|
||||
function slugify(s) {
|
||||
return s.toString().normalize('NFD').replace(/[\u0300-\u036f]/g, '')
|
||||
.toLowerCase().trim().replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '');
|
||||
}
|
||||
slugInput.addEventListener('input', function() {
|
||||
slugEdited = slugInput.value.trim() !== '';
|
||||
});
|
||||
titleInput.addEventListener('input', function() {
|
||||
if (!slugEdited) slugInput.value = slugify(titleInput.value);
|
||||
});
|
||||
})();
|
||||
|
||||
/* ---------- Submit ---------- */
|
||||
document.getElementById('event-form').addEventListener('submit', function() {
|
||||
var html = quill.root.innerHTML;
|
||||
document.getElementById('description-input').value = (html === '<p><br></p>') ? '' : html;
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
Reference in New Issue
Block a user