added cron and waiting
This commit is contained in:
@@ -0,0 +1,15 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
use Phinx\Migration\AbstractMigration;
|
||||||
|
|
||||||
|
final class AddPublishAtToEvents extends AbstractMigration
|
||||||
|
{
|
||||||
|
public function change(): void
|
||||||
|
{
|
||||||
|
$this->table('events')
|
||||||
|
->addColumn('publish_at', 'datetime', ['null' => true, 'comment' => 'apertura prenotazioni programmata', 'after' => 'status'])
|
||||||
|
->update();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,127 @@
|
|||||||
|
<?php
|
||||||
|
require_once __DIR__ . '/bootstrap.php';
|
||||||
|
require_once dirname(__DIR__) . '/include/event_waitlist.php';
|
||||||
|
require_once dirname(__DIR__) . '/include/event_mailer.php';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* CRON unico del modulo Eventi. Da richiamare periodicamente (es. ogni 10 min).
|
||||||
|
* Protezione: chiave segreta in .env (YSE_CRON_KEY) passata come ?key=...
|
||||||
|
*
|
||||||
|
* Fa tre cose a ogni giro:
|
||||||
|
* 1) Pubblica gli eventi programmati arrivati alla data di apertura.
|
||||||
|
* 2) Invia i promemoria automatici (ore effettive per-evento o default), 1 sola volta.
|
||||||
|
* 3) Applica la scadenza delle offerte lista d'attesa (offered non riconfermati):
|
||||||
|
* li annulla e offre il posto al prossimo in coda (email E).
|
||||||
|
*/
|
||||||
|
|
||||||
|
$pdo = pdo_conn(); // carica anche il .env (per la chiave e le MAIL_*)
|
||||||
|
|
||||||
|
$expected = $_ENV['YSE_CRON_KEY'] ?? '';
|
||||||
|
$provided = (string) ($_GET['key'] ?? '');
|
||||||
|
if ($expected === '' || !hash_equals($expected, $provided)) {
|
||||||
|
send_json(['ok' => false, 'error' => 'Forbidden'], 403);
|
||||||
|
}
|
||||||
|
|
||||||
|
$report = ['published' => 0, 'reminders' => 0, 'offers_expired' => 0, 'promoted' => 0];
|
||||||
|
|
||||||
|
// Default scuola
|
||||||
|
$opt = [];
|
||||||
|
try {
|
||||||
|
$opt = $pdo->query("SELECT * FROM `option` LIMIT 1")->fetch(PDO::FETCH_ASSOC) ?: [];
|
||||||
|
} catch (Throwable $e) {
|
||||||
|
$opt = [];
|
||||||
|
}
|
||||||
|
$defRemEnabled = ($opt['event_reminder_enabled'] ?? 'N') === 'Y';
|
||||||
|
$defRemHours = (int) ($opt['event_reminder_hours'] ?? 24);
|
||||||
|
$defOfferHours = (int) ($opt['event_offer_expiry_hours'] ?? 24);
|
||||||
|
|
||||||
|
/* 1) Pubblica gli eventi programmati arrivati a scadenza */
|
||||||
|
try {
|
||||||
|
$pub = $pdo->prepare("UPDATE events SET status = 'published', updated_at = NOW()
|
||||||
|
WHERE status = 'scheduled' AND publish_at IS NOT NULL AND publish_at <= NOW()");
|
||||||
|
$pub->execute();
|
||||||
|
$report['published'] = $pub->rowCount();
|
||||||
|
} catch (Throwable $e) {
|
||||||
|
error_log('[cron publish] ' . $e->getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 2) Promemoria automatici (una sola volta per evento) */
|
||||||
|
try {
|
||||||
|
$evs = $pdo->query("SELECT * FROM events
|
||||||
|
WHERE status = 'published' AND reminder_sent_at IS NULL AND starts_at > NOW()")
|
||||||
|
->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
foreach ($evs as $ev) {
|
||||||
|
$enabled = $ev['reminder_enabled'] !== null ? ($ev['reminder_enabled'] === 'Y') : $defRemEnabled;
|
||||||
|
if (!$enabled) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$hours = $ev['reminder_hours'] !== null ? (int) $ev['reminder_hours'] : $defRemHours;
|
||||||
|
if (strtotime($ev['starts_at']) - $hours * 3600 > time()) {
|
||||||
|
continue; // non ancora l'ora del promemoria
|
||||||
|
}
|
||||||
|
$ps = $pdo->prepare("SELECT * FROM event_bookings WHERE event_id = ? AND status = 'confirmed'");
|
||||||
|
$ps->execute([$ev['id']]);
|
||||||
|
foreach ($ps->fetchAll(PDO::FETCH_ASSOC) as $p) {
|
||||||
|
try {
|
||||||
|
yse_mail_reminder($p, $ev);
|
||||||
|
$report['reminders']++;
|
||||||
|
} catch (Throwable $me) {
|
||||||
|
error_log('[cron reminder] ' . $me->getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$pdo->prepare("UPDATE events SET reminder_sent_at = NOW() WHERE id = ?")->execute([$ev['id']]);
|
||||||
|
}
|
||||||
|
} catch (Throwable $e) {
|
||||||
|
error_log('[cron reminders] ' . $e->getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 3) Scadenza offerte lista d'attesa */
|
||||||
|
try {
|
||||||
|
$offers = $pdo->query("SELECT b.*, e.offer_expiry_hours AS ev_offer_hours
|
||||||
|
FROM event_bookings b JOIN events e ON e.id = b.event_id
|
||||||
|
WHERE b.status = 'offered' AND b.offered_at IS NOT NULL")
|
||||||
|
->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
foreach ($offers as $o) {
|
||||||
|
$hours = $o['ev_offer_hours'] !== null ? (int) $o['ev_offer_hours'] : $defOfferHours;
|
||||||
|
if (strtotime($o['offered_at']) + $hours * 3600 > time()) {
|
||||||
|
continue; // offerta ancora valida
|
||||||
|
}
|
||||||
|
$promoted = [];
|
||||||
|
try {
|
||||||
|
$pdo->beginTransaction();
|
||||||
|
$pdo->prepare("SELECT id FROM events WHERE id = ? FOR UPDATE")->execute([(int) $o['event_id']]);
|
||||||
|
$st = $pdo->prepare("SELECT status FROM event_bookings WHERE id = ?");
|
||||||
|
$st->execute([(int) $o['id']]);
|
||||||
|
if ($st->fetchColumn() === 'offered') {
|
||||||
|
$pdo->prepare("UPDATE event_bookings SET status = 'cancelled', updated_at = NOW() WHERE id = ?")
|
||||||
|
->execute([(int) $o['id']]);
|
||||||
|
$promoted = promoteFromWaitlist($pdo, (int) $o['event_id']);
|
||||||
|
$report['offers_expired']++;
|
||||||
|
}
|
||||||
|
$pdo->commit();
|
||||||
|
|
||||||
|
if (!empty($promoted)) {
|
||||||
|
$ev = $pdo->prepare("SELECT * FROM events WHERE id = ?");
|
||||||
|
$ev->execute([(int) $o['event_id']]);
|
||||||
|
$evRow = $ev->fetch(PDO::FETCH_ASSOC);
|
||||||
|
foreach ($promoted as $pr) {
|
||||||
|
try {
|
||||||
|
yse_mail_promotion($pr, $evRow);
|
||||||
|
$report['promoted']++;
|
||||||
|
} catch (Throwable $pe) {
|
||||||
|
error_log('[cron promo] ' . $pe->getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Throwable $te) {
|
||||||
|
if ($pdo->inTransaction()) {
|
||||||
|
$pdo->rollBack();
|
||||||
|
}
|
||||||
|
error_log('[cron offer] ' . $te->getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Throwable $e) {
|
||||||
|
error_log('[cron offers] ' . $e->getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
send_json(['ok' => true, 'report' => $report, 'at' => date('c')]);
|
||||||
@@ -22,7 +22,7 @@ try {
|
|||||||
$idsParam = trim($_GET['ids'] ?? '');
|
$idsParam = trim($_GET['ids'] ?? '');
|
||||||
$limit = isset($_GET['limit']) ? max(1, min(50, (int) $_GET['limit'])) : 0;
|
$limit = isset($_GET['limit']) ? max(1, min(50, (int) $_GET['limit'])) : 0;
|
||||||
|
|
||||||
$where = ["e.status = 'published'", "COALESCE(e.ends_at, e.starts_at) >= NOW()"];
|
$where = ["e.status IN ('published','scheduled')", "COALESCE(e.ends_at, e.starts_at) >= NOW()"];
|
||||||
$params = [];
|
$params = [];
|
||||||
|
|
||||||
if ($id > 0) {
|
if ($id > 0) {
|
||||||
@@ -96,6 +96,10 @@ try {
|
|||||||
'id' => (int) $r['id'],
|
'id' => (int) $r['id'],
|
||||||
'title' => $r['title'],
|
'title' => $r['title'],
|
||||||
'slug' => $r['slug'],
|
'slug' => $r['slug'],
|
||||||
|
'status' => $r['status'],
|
||||||
|
'scheduled' => $r['status'] === 'scheduled',
|
||||||
|
'bookable' => $r['status'] === 'published',
|
||||||
|
'publish_at' => $r['publish_at'],
|
||||||
'description' => $r['description'], // HTML (sanificare al rendering in WP)
|
'description' => $r['description'], // HTML (sanificare al rendering in WP)
|
||||||
'instructions' => $r['instructions'],
|
'instructions' => $r['instructions'],
|
||||||
'what_to_bring' => $r['what_to_bring'],
|
'what_to_bring' => $r['what_to_bring'],
|
||||||
|
|||||||
@@ -74,6 +74,8 @@ $val = [
|
|||||||
'reminder_enabled' => $ev['reminder_enabled'] ?? null,
|
'reminder_enabled' => $ev['reminder_enabled'] ?? null,
|
||||||
'reminder_hours' => $ev['reminder_hours'] ?? '',
|
'reminder_hours' => $ev['reminder_hours'] ?? '',
|
||||||
'offer_expiry_hours' => $ev['offer_expiry_hours'] ?? '',
|
'offer_expiry_hours' => $ev['offer_expiry_hours'] ?? '',
|
||||||
|
'schedule_enabled' => (($ev['status'] ?? '') === 'scheduled') || !empty($ev['publish_at']),
|
||||||
|
'publish_at' => !empty($ev['publish_at']) ? date('Y-m-d H:i', strtotime($ev['publish_at'])) : '',
|
||||||
];
|
];
|
||||||
|
|
||||||
$isEdit = $id > 0;
|
$isEdit = $id > 0;
|
||||||
@@ -555,6 +557,22 @@ $pageTitle = $isEdit ? 'Modifica evento' : 'Nuovo evento';
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Apertura programmata -->
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-5 mb-3">
|
||||||
|
<label class="form-label">Apertura prenotazioni</label>
|
||||||
|
<div class="form-check form-switch" style="padding-top:8px;">
|
||||||
|
<input class="form-check-input" type="checkbox" role="switch" id="schedule_enabled" name="schedule_enabled" value="Y" <?php echo $val['schedule_enabled'] ? 'checked' : ''; ?>>
|
||||||
|
<label class="form-check-label" for="schedule_enabled" style="font-weight:400;">Apri in una data futura (programmato)</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4 mb-3" id="publish-at-wrap" style="<?php echo $val['schedule_enabled'] ? '' : 'display:none;'; ?>">
|
||||||
|
<label class="form-label">Data/ora apertura</label>
|
||||||
|
<input type="text" class="form-control" id="publish_at" name="publish_at" value="<?php echo e($val['publish_at']); ?>" placeholder="Quando aprire le prenotazioni" autocomplete="off">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<p class="hint" style="margin-top:-8px;">Se attivo, l'evento resta "Programmato" (visibile ma non prenotabile) finché il cron non lo apre alla data indicata.</p>
|
||||||
|
|
||||||
<!-- Promemoria e lista d'attesa (override per evento) -->
|
<!-- Promemoria e lista d'attesa (override per evento) -->
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-4 mb-3">
|
<div class="col-md-4 mb-3">
|
||||||
@@ -612,6 +630,10 @@ $pageTitle = $isEdit ? 'Modifica evento' : 'Nuovo evento';
|
|||||||
};
|
};
|
||||||
|
|
||||||
var endPicker = flatpickr("#ends_at", fpOpts);
|
var endPicker = flatpickr("#ends_at", fpOpts);
|
||||||
|
flatpickr("#publish_at", fpOpts);
|
||||||
|
document.getElementById('schedule_enabled').addEventListener('change', function() {
|
||||||
|
document.getElementById('publish-at-wrap').style.display = this.checked ? '' : 'none';
|
||||||
|
});
|
||||||
var startPicker = flatpickr("#starts_at", Object.assign({}, fpOpts, {
|
var startPicker = flatpickr("#starts_at", Object.assign({}, fpOpts, {
|
||||||
onChange: function(dates) {
|
onChange: function(dates) {
|
||||||
if (!dates[0]) return;
|
if (!dates[0]) return;
|
||||||
|
|||||||
+13
-3
@@ -105,6 +105,15 @@ if (!in_array($status, ['draft', 'published', 'cancelled'], true)) {
|
|||||||
$status = 'draft';
|
$status = 'draft';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Apertura programmata: se attiva con data futura -> stato 'scheduled' + publish_at
|
||||||
|
$scheduleEnabled = isset($_POST['schedule_enabled']) && $_POST['schedule_enabled'] === 'Y';
|
||||||
|
$publishAt = toDateTime($_POST['publish_at'] ?? null);
|
||||||
|
if ($scheduleEnabled && $publishAt && strtotime($publishAt) > time()) {
|
||||||
|
$status = 'scheduled';
|
||||||
|
} else {
|
||||||
|
$publishAt = null; // niente programmazione (o data già passata)
|
||||||
|
}
|
||||||
|
|
||||||
// Campi facoltativi (testo)
|
// Campi facoltativi (testo)
|
||||||
$instructions = trim($_POST['instructions'] ?? '');
|
$instructions = trim($_POST['instructions'] ?? '');
|
||||||
$whatToBring = trim($_POST['what_to_bring'] ?? '');
|
$whatToBring = trim($_POST['what_to_bring'] ?? '');
|
||||||
@@ -144,7 +153,7 @@ try {
|
|||||||
max_seats = :max_seats, max_per_booking = :max_per_booking, color = :color,
|
max_seats = :max_seats, max_per_booking = :max_per_booking, color = :color,
|
||||||
waitlist_enabled = :waitlist_enabled,
|
waitlist_enabled = :waitlist_enabled,
|
||||||
reminder_enabled = :reminder_enabled, reminder_hours = :reminder_hours, offer_expiry_hours = :offer_expiry_hours,
|
reminder_enabled = :reminder_enabled, reminder_hours = :reminder_hours, offer_expiry_hours = :offer_expiry_hours,
|
||||||
cover_image = :cover_image, gallery = :gallery, status = :status, updated_at = NOW()
|
cover_image = :cover_image, gallery = :gallery, status = :status, publish_at = :publish_at, updated_at = NOW()
|
||||||
WHERE id = :id";
|
WHERE id = :id";
|
||||||
$params = [':id' => $id];
|
$params = [':id' => $id];
|
||||||
} else {
|
} else {
|
||||||
@@ -154,14 +163,14 @@ try {
|
|||||||
address, lat, lng, cost, currency, max_seats, max_per_booking, color,
|
address, lat, lng, cost, currency, max_seats, max_per_booking, color,
|
||||||
waitlist_enabled,
|
waitlist_enabled,
|
||||||
reminder_enabled, reminder_hours, offer_expiry_hours,
|
reminder_enabled, reminder_hours, offer_expiry_hours,
|
||||||
cover_image, gallery, status, created_at, updated_at)
|
cover_image, gallery, status, publish_at, created_at, updated_at)
|
||||||
VALUES
|
VALUES
|
||||||
(:user_id, :title, :slug, :description, :instructions, :what_to_bring, :suitable_for,
|
(:user_id, :title, :slug, :description, :instructions, :what_to_bring, :suitable_for,
|
||||||
:organizer_name, :starts_at, :ends_at,
|
:organizer_name, :starts_at, :ends_at,
|
||||||
:address, :lat, :lng, :cost, :currency, :max_seats, :max_per_booking, :color,
|
:address, :lat, :lng, :cost, :currency, :max_seats, :max_per_booking, :color,
|
||||||
:waitlist_enabled,
|
:waitlist_enabled,
|
||||||
:reminder_enabled, :reminder_hours, :offer_expiry_hours,
|
:reminder_enabled, :reminder_hours, :offer_expiry_hours,
|
||||||
:cover_image, :gallery, :status, NOW(), NOW())";
|
:cover_image, :gallery, :status, :publish_at, NOW(), NOW())";
|
||||||
$params = [':user_id' => $currentUserId];
|
$params = [':user_id' => $currentUserId];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -189,6 +198,7 @@ try {
|
|||||||
':color' => $color,
|
':color' => $color,
|
||||||
':cover_image' => $coverImage !== '' ? $coverImage : null,
|
':cover_image' => $coverImage !== '' ? $coverImage : null,
|
||||||
':gallery' => $gallery,
|
':gallery' => $gallery,
|
||||||
|
':publish_at' => $publishAt,
|
||||||
':status' => $status,
|
':status' => $status,
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
+58
-1
@@ -22,6 +22,7 @@ $message = $_GET['message'] ?? '';
|
|||||||
$sql = "SELECT e.*,
|
$sql = "SELECT e.*,
|
||||||
COALESCE(b.booked, 0) AS booked,
|
COALESCE(b.booked, 0) AS booked,
|
||||||
COALESCE(b.bookings_count, 0) AS bookings_count,
|
COALESCE(b.bookings_count, 0) AS bookings_count,
|
||||||
|
COALESCE(w.waiting, 0) AS waiting,
|
||||||
u.first_name AS creator_first,
|
u.first_name AS creator_first,
|
||||||
u.last_name AS creator_last
|
u.last_name AS creator_last
|
||||||
FROM events e
|
FROM events e
|
||||||
@@ -33,6 +34,12 @@ $sql = "SELECT e.*,
|
|||||||
WHERE status IN ('confirmed','pending','offered')
|
WHERE status IN ('confirmed','pending','offered')
|
||||||
GROUP BY event_id
|
GROUP BY event_id
|
||||||
) b ON b.event_id = e.id
|
) b ON b.event_id = e.id
|
||||||
|
LEFT JOIN (
|
||||||
|
SELECT event_id, SUM(seats) AS waiting
|
||||||
|
FROM event_bookings
|
||||||
|
WHERE status = 'waiting'
|
||||||
|
GROUP BY event_id
|
||||||
|
) w ON w.event_id = e.id
|
||||||
LEFT JOIN auth_users u ON u.id = e.user_id
|
LEFT JOIN auth_users u ON u.id = e.user_id
|
||||||
ORDER BY e.starts_at ASC";
|
ORDER BY e.starts_at ASC";
|
||||||
$events = $pdo->query($sql)->fetchAll(PDO::FETCH_ASSOC);
|
$events = $pdo->query($sql)->fetchAll(PDO::FETCH_ASSOC);
|
||||||
@@ -64,6 +71,8 @@ function statusBadge(string $status): array
|
|||||||
switch ($status) {
|
switch ($status) {
|
||||||
case 'published':
|
case 'published':
|
||||||
return ['Aperto', '#e7f7ee', '#1a7f52'];
|
return ['Aperto', '#e7f7ee', '#1a7f52'];
|
||||||
|
case 'scheduled':
|
||||||
|
return ['Programmato', '#eef2fb', '#3a5bb8'];
|
||||||
case 'cancelled':
|
case 'cancelled':
|
||||||
return ['Annullato', '#fdecea', '#c0392b'];
|
return ['Annullato', '#fdecea', '#c0392b'];
|
||||||
default:
|
default:
|
||||||
@@ -209,6 +218,29 @@ function statusBadge(string $status): array
|
|||||||
color: #c0392b;
|
color: #c0392b;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.wait-pill {
|
||||||
|
display: inline-block;
|
||||||
|
background: #eef2fb;
|
||||||
|
color: #3a5bb8;
|
||||||
|
font-weight: 700;
|
||||||
|
font-size: 12px;
|
||||||
|
padding: 2px 9px;
|
||||||
|
border-radius: 20px;
|
||||||
|
margin-left: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.id-chip {
|
||||||
|
display: inline-block;
|
||||||
|
background: #eef0f3;
|
||||||
|
color: #4b5563;
|
||||||
|
font-weight: 700;
|
||||||
|
font-size: 11px;
|
||||||
|
padding: 1px 8px;
|
||||||
|
border-radius: 6px;
|
||||||
|
margin-left: 6px;
|
||||||
|
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
|
||||||
|
}
|
||||||
|
|
||||||
.status-badge {
|
.status-badge {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
@@ -375,7 +407,7 @@ function statusBadge(string $status): array
|
|||||||
<span class="event-thumb placeholder" style="background: <?php echo e($ev['color']); ?>22;"><i class="bx bx-image" style="color: <?php echo e($ev['color']); ?>;"></i></span>
|
<span class="event-thumb placeholder" style="background: <?php echo e($ev['color']); ?>22;"><i class="bx bx-image" style="color: <?php echo e($ev['color']); ?>;"></i></span>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
<span class="color-dot" style="background: <?php echo e($ev['color']); ?>;"></span>
|
<span class="color-dot" style="background: <?php echo e($ev['color']); ?>;"></span>
|
||||||
<span><?php echo e($ev['title']); ?> <span class="event-id">(ID: <?php echo (int) $ev['id']; ?>)</span></span>
|
<span><?php echo e($ev['title']); ?> <span class="id-chip">ID <?php echo (int) $ev['id']; ?></span></span>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td><?php echo e($organizer); ?></td>
|
<td><?php echo e($organizer); ?></td>
|
||||||
@@ -383,11 +415,15 @@ function statusBadge(string $status): array
|
|||||||
<span class="seats-pill <?php echo $isFull ? 'full' : ''; ?>">
|
<span class="seats-pill <?php echo $isFull ? 'full' : ''; ?>">
|
||||||
<?php echo $booked; ?> / <?php echo $maxSeats; ?>
|
<?php echo $booked; ?> / <?php echo $maxSeats; ?>
|
||||||
</span>
|
</span>
|
||||||
|
<?php if ((int) $ev['waiting'] > 0) : ?>
|
||||||
|
<span class="wait-pill" title="In lista d'attesa">+<?php echo (int) $ev['waiting']; ?> in lista</span>
|
||||||
|
<?php endif; ?>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<span class="status-badge" style="background: <?php echo $stBg; ?>; color: <?php echo $stColor; ?>;"><?php echo e($stLabel); ?></span>
|
<span class="status-badge" style="background: <?php echo $stBg; ?>; color: <?php echo $stColor; ?>;"><?php echo e($stLabel); ?></span>
|
||||||
</td>
|
</td>
|
||||||
<td class="text-end">
|
<td class="text-end">
|
||||||
|
<button type="button" class="btn-act btn-shortcode" data-code='[yogasoul_eventi ids="<?php echo (int) $ev['id']; ?>"]'><i class="bx bx-code-alt"></i> Shortcode</button>
|
||||||
<a href="event_preview.php?id=<?php echo (int) $ev['id']; ?>" target="_blank" class="btn-act"><i class="bx bx-show"></i> Anteprima</a>
|
<a href="event_preview.php?id=<?php echo (int) $ev['id']; ?>" target="_blank" class="btn-act"><i class="bx bx-show"></i> Anteprima</a>
|
||||||
<a href="event_participants.php?id=<?php echo (int) $ev['id']; ?>" class="btn-act"><i class="bx bx-group"></i> Partecipanti</a>
|
<a href="event_participants.php?id=<?php echo (int) $ev['id']; ?>" class="btn-act"><i class="bx bx-group"></i> Partecipanti</a>
|
||||||
<a href="event_form.php?id=<?php echo (int) $ev['id']; ?>" class="btn-act"><i class="bx bx-edit"></i> Modifica</a>
|
<a href="event_form.php?id=<?php echo (int) $ev['id']; ?>" class="btn-act"><i class="bx bx-edit"></i> Modifica</a>
|
||||||
@@ -428,6 +464,27 @@ function statusBadge(string $status): array
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
$(document).ready(function() {
|
$(document).ready(function() {
|
||||||
|
// Copia shortcode
|
||||||
|
$('.btn-shortcode').on('click', function() {
|
||||||
|
var code = $(this).data('code');
|
||||||
|
var btn = $(this);
|
||||||
|
var original = btn.html();
|
||||||
|
|
||||||
|
function done() {
|
||||||
|
btn.html('<i class="bx bx-check"></i> Copiato!');
|
||||||
|
setTimeout(function() {
|
||||||
|
btn.html(original);
|
||||||
|
}, 1500);
|
||||||
|
}
|
||||||
|
if (navigator.clipboard && navigator.clipboard.writeText) {
|
||||||
|
navigator.clipboard.writeText(code).then(done).catch(function() {
|
||||||
|
window.prompt('Copia lo shortcode:', code);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
window.prompt('Copia lo shortcode:', code);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
$('.btn-annulla').on('click', function() {
|
$('.btn-annulla').on('click', function() {
|
||||||
var id = $(this).data('id');
|
var id = $(this).data('id');
|
||||||
var title = $(this).data('title');
|
var title = $(this).data('title');
|
||||||
|
|||||||
@@ -1,497 +1,153 @@
|
|||||||
/* YogaSoul Eventi — stili widget (namespaced .yse-) */
|
/* YogaSoul Eventi — stili widget.
|
||||||
|
Tutti i selettori sono prefissati con .yse-widget per alzare la specificità
|
||||||
|
sopra le regole del tema WordPress (evita che tema "vinca" su colori/font). */
|
||||||
|
|
||||||
.yse-widget {
|
.yse-widget {
|
||||||
--yse-ink: #2b2622;
|
--yse-ink: #2b2622;
|
||||||
--yse-muted: #8c837c;
|
--yse-muted: #8c837c;
|
||||||
--yse-line: #ece7e2;
|
--yse-line: #ece7e2;
|
||||||
--yse-card: #ffffff;
|
--yse-card: #ffffff;
|
||||||
max-width: 720px;
|
max-width: 1140px; /* largo come Amelia; su schermi piccoli riempie il contenitore */
|
||||||
|
width: 100%;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
font-family:
|
font-family: inherit;
|
||||||
"Inter",
|
|
||||||
system-ui,
|
|
||||||
-apple-system,
|
|
||||||
sans-serif;
|
|
||||||
color: var(--yse-ink);
|
color: var(--yse-ink);
|
||||||
line-height: 1.55;
|
line-height: 1.55;
|
||||||
|
text-align: left;
|
||||||
}
|
}
|
||||||
.yse-widget * {
|
.yse-widget, .yse-widget * { box-sizing: border-box; }
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
.yse-empty {
|
|
||||||
text-align: center;
|
|
||||||
color: var(--yse-muted);
|
|
||||||
padding: 26px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.yse-card {
|
/* --- Reset mirati contro il tema --- */
|
||||||
--yse-accent: #4caf50;
|
.yse-widget button {
|
||||||
|
font-family: inherit;
|
||||||
|
background: none !important;
|
||||||
|
border: none !important;
|
||||||
|
box-shadow: none !important;
|
||||||
|
text-transform: none !important;
|
||||||
|
letter-spacing: normal !important;
|
||||||
|
min-height: 0 !important;
|
||||||
|
width: auto;
|
||||||
|
margin: 0;
|
||||||
|
line-height: 1.2;
|
||||||
|
}
|
||||||
|
.yse-widget button:hover { filter: none; }
|
||||||
|
.yse-widget input {
|
||||||
|
font-family: inherit;
|
||||||
|
margin: 0;
|
||||||
|
width: 100%;
|
||||||
|
min-height: 0;
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
.yse-widget h3, .yse-widget h4, .yse-widget p { margin: 0; padding: 0; }
|
||||||
|
.yse-widget img { max-width: 100%; height: auto; }
|
||||||
|
.yse-widget a { text-decoration: none; }
|
||||||
|
.yse-widget svg { vertical-align: middle; }
|
||||||
|
|
||||||
|
.yse-empty { text-align: center; color: var(--yse-muted); padding: 26px; }
|
||||||
|
|
||||||
|
.yse-widget .yse-card {
|
||||||
|
--yse-accent: #4CAF50;
|
||||||
--yse-accent-soft: color-mix(in srgb, var(--yse-accent) 10%, white);
|
--yse-accent-soft: color-mix(in srgb, var(--yse-accent) 10%, white);
|
||||||
--yse-accent-ink: color-mix(in srgb, var(--yse-accent) 62%, black);
|
--yse-accent-ink: color-mix(in srgb, var(--yse-accent) 62%, black);
|
||||||
background: var(--yse-card);
|
background: var(--yse-card);
|
||||||
border-radius: 18px;
|
border-radius: 18px;
|
||||||
box-shadow: 0 14px 40px rgba(43, 38, 34, 0.1);
|
box-shadow: 0 14px 40px rgba(43, 38, 34, 0.10);
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
.yse-card + .yse-card {
|
.yse-widget .yse-card + .yse-card { margin-top: 24px; }
|
||||||
margin-top: 22px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Header compatto */
|
/* Header compatto */
|
||||||
.yse-chead {
|
.yse-widget .yse-chead { display: flex; gap: 18px; padding: 20px 26px; align-items: flex-start; }
|
||||||
display: flex;
|
.yse-widget .yse-datechip { flex-shrink: 0; border: 1px solid var(--yse-line); border-radius: 12px; padding: 7px 12px 5px; text-align: center; min-width: 58px; }
|
||||||
gap: 16px;
|
.yse-widget .yse-datechip .yse-m { font-size: 11px; font-weight: 600; letter-spacing: .12em; color: var(--yse-accent-ink); }
|
||||||
padding: 18px 22px;
|
.yse-widget .yse-datechip .yse-d { font-family: inherit; font-size: 24px; font-weight: 600; color: var(--yse-ink); line-height: 1; }
|
||||||
align-items: flex-start;
|
.yse-widget .yse-main { flex: 1; min-width: 0; }
|
||||||
}
|
.yse-widget .yse-title-row { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
|
||||||
.yse-datechip {
|
.yse-widget .yse-title-row h3 { font-family: inherit; font-weight: 500; font-size: 23px; margin: 0; color: var(--yse-ink); }
|
||||||
flex-shrink: 0;
|
.yse-widget .yse-toggle { background: none !important; border: none !important; color: var(--yse-accent-ink) !important; font-weight: 600; font-size: 13px; cursor: pointer; display: inline-flex; align-items: center; gap: 4px; padding: 2px; border-radius: 0; }
|
||||||
border: 1px solid var(--yse-line);
|
.yse-widget .yse-toggle svg { width: 15px; height: 15px; transition: transform .2s; }
|
||||||
border-radius: 12px;
|
.yse-widget .yse-toggle.yse-on svg { transform: rotate(180deg); }
|
||||||
padding: 7px 11px 5px;
|
.yse-widget .yse-meta { display: flex; flex-wrap: wrap; gap: 6px 22px; margin-top: 10px; }
|
||||||
text-align: center;
|
.yse-widget .yse-item { display: flex; align-items: center; gap: 7px; color: var(--yse-muted); font-size: 14px; }
|
||||||
min-width: 54px;
|
.yse-widget .yse-item svg { width: 17px; height: 17px; color: var(--yse-accent-ink); flex-shrink: 0; }
|
||||||
}
|
.yse-widget .yse-item strong { color: var(--yse-ink); font-weight: 600; }
|
||||||
.yse-datechip .yse-m {
|
.yse-widget .yse-price { flex-shrink: 0; align-self: flex-start; background: var(--yse-accent) !important; color: #fff; font-weight: 600; font-size: 15px; padding: 8px 15px; border-radius: 999px; }
|
||||||
font-size: 10.5px;
|
|
||||||
font-weight: 600;
|
|
||||||
letter-spacing: 0.12em;
|
|
||||||
color: var(--yse-accent-ink);
|
|
||||||
}
|
|
||||||
.yse-datechip .yse-d {
|
|
||||||
font-family: "Fraunces", serif;
|
|
||||||
font-size: 23px;
|
|
||||||
font-weight: 600;
|
|
||||||
color: var(--yse-ink);
|
|
||||||
line-height: 1;
|
|
||||||
}
|
|
||||||
.yse-main {
|
|
||||||
flex: 1;
|
|
||||||
min-width: 0;
|
|
||||||
}
|
|
||||||
.yse-title-row {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 10px;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
}
|
|
||||||
.yse-title-row h3 {
|
|
||||||
font-family: "Fraunces", serif;
|
|
||||||
font-weight: 500;
|
|
||||||
font-size: 21px;
|
|
||||||
margin: 0;
|
|
||||||
color: var(--yse-ink);
|
|
||||||
}
|
|
||||||
.yse-toggle {
|
|
||||||
background: none;
|
|
||||||
border: none;
|
|
||||||
color: var(--yse-accent-ink);
|
|
||||||
font-weight: 600;
|
|
||||||
font-size: 13px;
|
|
||||||
cursor: pointer;
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 4px;
|
|
||||||
padding: 2px;
|
|
||||||
}
|
|
||||||
.yse-toggle svg {
|
|
||||||
width: 15px;
|
|
||||||
height: 15px;
|
|
||||||
transition: transform 0.2s;
|
|
||||||
}
|
|
||||||
.yse-toggle.yse-on svg {
|
|
||||||
transform: rotate(180deg);
|
|
||||||
}
|
|
||||||
.yse-meta {
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
gap: 6px 20px;
|
|
||||||
margin-top: 9px;
|
|
||||||
}
|
|
||||||
.yse-item {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 7px;
|
|
||||||
color: var(--yse-muted);
|
|
||||||
font-size: 13.5px;
|
|
||||||
}
|
|
||||||
.yse-item svg {
|
|
||||||
width: 16px;
|
|
||||||
height: 16px;
|
|
||||||
color: var(--yse-accent-ink);
|
|
||||||
flex-shrink: 0;
|
|
||||||
}
|
|
||||||
.yse-item strong {
|
|
||||||
color: var(--yse-ink);
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
.yse-price {
|
|
||||||
flex-shrink: 0;
|
|
||||||
align-self: flex-start;
|
|
||||||
background: var(--yse-accent);
|
|
||||||
color: #fff;
|
|
||||||
font-weight: 600;
|
|
||||||
font-size: 14px;
|
|
||||||
padding: 7px 13px;
|
|
||||||
border-radius: 999px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Dettagli espandibili */
|
/* Dettagli espandibili */
|
||||||
.yse-details {
|
.yse-widget .yse-details { display: none; border-top: 1px solid var(--yse-line); }
|
||||||
display: none;
|
.yse-widget .yse-details.yse-open { display: block; }
|
||||||
border-top: 1px solid var(--yse-line);
|
.yse-widget .yse-hero { position: relative; height: 300px; background: linear-gradient(135deg, var(--yse-accent-ink), var(--yse-accent)); }
|
||||||
}
|
.yse-widget .yse-hero img { width: 100%; height: 100%; object-fit: cover; display: block; }
|
||||||
.yse-details.yse-open {
|
.yse-widget .yse-emblem { position: absolute; inset: 0; display: flex; align-items: center; justify-content: center; color: rgba(255,255,255,.35); }
|
||||||
display: block;
|
.yse-widget .yse-emblem svg { width: 78px; height: 78px; }
|
||||||
}
|
.yse-widget .yse-body { padding: 24px 26px; }
|
||||||
.yse-hero {
|
.yse-widget .yse-section { margin-top: 26px; }
|
||||||
position: relative;
|
.yse-widget .yse-body h4 { font-family: inherit; font-weight: 600; font-size: 19px; color: var(--yse-ink); margin: 0 0 10px; display: flex; align-items: center; gap: 9px; }
|
||||||
height: 260px;
|
.yse-widget .yse-body h4 svg { width: 20px; height: 20px; color: var(--yse-accent-ink); }
|
||||||
background: linear-gradient(
|
.yse-widget .yse-desc { font-size: 15.5px; color: #4a433d; }
|
||||||
135deg,
|
.yse-widget .yse-desc p { margin: 0 0 10px; }
|
||||||
var(--yse-accent-ink),
|
.yse-widget .yse-desc img { max-width: 100%; border-radius: 10px; }
|
||||||
var(--yse-accent)
|
.yse-widget .yse-detail-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: 14px; margin-top: 24px; }
|
||||||
);
|
.yse-widget .yse-detail { background: var(--yse-accent-soft); border: 1px solid color-mix(in srgb, var(--yse-accent) 18%, white); border-radius: 14px; padding: 15px 16px; }
|
||||||
}
|
.yse-widget .yse-dh { display: flex; align-items: center; gap: 8px; font-weight: 600; color: var(--yse-accent-ink); margin-bottom: 6px; font-size: 14px; }
|
||||||
.yse-hero img {
|
.yse-widget .yse-dh svg { width: 17px; height: 17px; }
|
||||||
width: 100%;
|
.yse-widget .yse-dt { font-size: 14px; color: #4a433d; }
|
||||||
height: 100%;
|
.yse-widget .yse-gallery { display: flex; gap: 10px; flex-wrap: wrap; }
|
||||||
object-fit: cover;
|
.yse-widget .yse-gallery img { width: 108px; height: 108px; object-fit: cover; border-radius: 12px; cursor: pointer; transition: transform .15s, box-shadow .15s; }
|
||||||
display: block;
|
.yse-widget .yse-gallery img:hover { transform: translateY(-2px); box-shadow: 0 8px 18px rgba(0,0,0,.15); }
|
||||||
}
|
.yse-widget .yse-map { border: 0; width: 100%; height: 220px; border-radius: 14px; display: block; }
|
||||||
.yse-emblem {
|
|
||||||
position: absolute;
|
|
||||||
inset: 0;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
color: rgba(255, 255, 255, 0.35);
|
|
||||||
}
|
|
||||||
.yse-emblem svg {
|
|
||||||
width: 72px;
|
|
||||||
height: 72px;
|
|
||||||
}
|
|
||||||
.yse-body {
|
|
||||||
padding: 22px;
|
|
||||||
}
|
|
||||||
.yse-section {
|
|
||||||
margin-top: 24px;
|
|
||||||
}
|
|
||||||
.yse-body h4 {
|
|
||||||
font-family: "Fraunces", serif;
|
|
||||||
font-weight: 600;
|
|
||||||
font-size: 18px;
|
|
||||||
color: var(--yse-ink);
|
|
||||||
margin: 0 0 10px;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 9px;
|
|
||||||
}
|
|
||||||
.yse-body h4 svg {
|
|
||||||
width: 19px;
|
|
||||||
height: 19px;
|
|
||||||
color: var(--yse-accent-ink);
|
|
||||||
}
|
|
||||||
.yse-desc {
|
|
||||||
font-size: 15px;
|
|
||||||
color: #4a433d;
|
|
||||||
}
|
|
||||||
.yse-desc p {
|
|
||||||
margin: 0 0 10px;
|
|
||||||
}
|
|
||||||
.yse-desc img {
|
|
||||||
max-width: 100%;
|
|
||||||
border-radius: 10px;
|
|
||||||
}
|
|
||||||
.yse-detail-grid {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(auto-fit, minmax(190px, 1fr));
|
|
||||||
gap: 12px;
|
|
||||||
margin-top: 22px;
|
|
||||||
}
|
|
||||||
.yse-detail {
|
|
||||||
background: var(--yse-accent-soft);
|
|
||||||
border: 1px solid color-mix(in srgb, var(--yse-accent) 18%, white);
|
|
||||||
border-radius: 13px;
|
|
||||||
padding: 14px 15px;
|
|
||||||
}
|
|
||||||
.yse-dh {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 8px;
|
|
||||||
font-weight: 600;
|
|
||||||
color: var(--yse-accent-ink);
|
|
||||||
margin-bottom: 6px;
|
|
||||||
font-size: 13.5px;
|
|
||||||
}
|
|
||||||
.yse-dh svg {
|
|
||||||
width: 16px;
|
|
||||||
height: 16px;
|
|
||||||
}
|
|
||||||
.yse-dt {
|
|
||||||
font-size: 13.5px;
|
|
||||||
color: #4a433d;
|
|
||||||
}
|
|
||||||
.yse-gallery {
|
|
||||||
display: flex;
|
|
||||||
gap: 10px;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
}
|
|
||||||
.yse-gallery img {
|
|
||||||
width: 100px;
|
|
||||||
height: 100px;
|
|
||||||
object-fit: cover;
|
|
||||||
border-radius: 11px;
|
|
||||||
cursor: pointer;
|
|
||||||
transition:
|
|
||||||
transform 0.15s,
|
|
||||||
box-shadow 0.15s;
|
|
||||||
}
|
|
||||||
.yse-gallery img:hover {
|
|
||||||
transform: translateY(-2px);
|
|
||||||
box-shadow: 0 8px 18px rgba(0, 0, 0, 0.15);
|
|
||||||
}
|
|
||||||
.yse-map {
|
|
||||||
border: 0;
|
|
||||||
width: 100%;
|
|
||||||
height: 210px;
|
|
||||||
border-radius: 13px;
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Booking */
|
/* Booking */
|
||||||
.yse-booking {
|
.yse-widget .yse-booking { padding: 22px 26px; border-top: 1px solid var(--yse-line); }
|
||||||
padding: 18px 22px;
|
.yse-widget .yse-soldout { text-align: center; font-weight: 600; color: #c0392b; }
|
||||||
border-top: 1px solid var(--yse-line);
|
.yse-widget .yse-wl-banner { background: color-mix(in srgb, var(--yse-accent) 12%, white); color: var(--yse-accent-ink); border-radius: 12px; padding: 12px 14px; font-size: 13.5px; margin-bottom: 16px; }
|
||||||
}
|
.yse-widget .yse-bcompact { display: flex; align-items: flex-end; gap: 22px; flex-wrap: wrap; }
|
||||||
.yse-soldout {
|
.yse-widget .yse-btitle { font-family: inherit; font-weight: 600; font-size: 19px; color: var(--yse-ink); margin-right: auto; align-self: center; }
|
||||||
text-align: center;
|
.yse-widget .yse-lbl { font-size: 12px; color: var(--yse-muted); text-transform: uppercase; letter-spacing: .05em; margin-bottom: 5px; }
|
||||||
font-weight: 600;
|
.yse-widget .yse-stepper { display: flex; align-items: center; border: 1px solid var(--yse-line); border-radius: 11px; overflow: hidden; background: #fff; }
|
||||||
color: #c0392b;
|
.yse-widget .yse-stepper button { background: #fff !important; width: 44px; height: 46px; font-size: 22px; color: var(--yse-accent-ink) !important; cursor: pointer; }
|
||||||
}
|
.yse-widget .yse-stepper button:disabled { color: #cfc8c2 !important; cursor: not-allowed; }
|
||||||
.yse-wl-banner {
|
.yse-widget .yse-num { width: 54px; text-align: center; font-size: 17px; font-weight: 600; }
|
||||||
background: color-mix(in srgb, var(--yse-accent) 12%, white);
|
.yse-widget .yse-total .yse-val { font-family: inherit; font-size: 26px; font-weight: 600; color: var(--yse-accent-ink); }
|
||||||
color: var(--yse-accent-ink);
|
.yse-widget .yse-cta { background: var(--yse-accent) !important; color: #fff !important; font-size: 15px; font-weight: 600; border-radius: 11px; cursor: pointer; transition: filter .15s; }
|
||||||
border-radius: 12px;
|
.yse-widget .yse-cta:hover { filter: brightness(.94) !important; }
|
||||||
padding: 12px 14px;
|
.yse-widget .yse-cta:disabled { background: #cfc8c2 !important; cursor: not-allowed; }
|
||||||
font-size: 13.5px;
|
.yse-widget .yse-open { padding: 14px 26px; }
|
||||||
margin-bottom: 14px;
|
.yse-widget .yse-full { width: 100%; padding: 15px; margin-top: 6px; }
|
||||||
}
|
.yse-widget .yse-bform { display: none; margin-top: 20px; padding-top: 20px; border-top: 1px dashed var(--yse-line); }
|
||||||
.yse-bcompact {
|
.yse-widget .yse-bform.yse-open { display: block; }
|
||||||
display: flex;
|
.yse-widget .yse-frow { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }
|
||||||
align-items: flex-end;
|
.yse-widget .yse-field { margin-bottom: 12px; }
|
||||||
gap: 18px;
|
.yse-widget .yse-field label { display: block; font-size: 13px; font-weight: 600; color: var(--yse-ink); margin-bottom: 5px; }
|
||||||
flex-wrap: wrap;
|
.yse-widget .yse-field input { border: 1px solid var(--yse-line) !important; border-radius: 10px; padding: 12px 13px; font-size: 15px; background: #fff !important; color: var(--yse-ink) !important; }
|
||||||
}
|
.yse-widget .yse-field input:focus { outline: none; border-color: var(--yse-accent) !important; box-shadow: 0 0 0 3px color-mix(in srgb, var(--yse-accent) 22%, white) !important; }
|
||||||
.yse-btitle {
|
.yse-widget .yse-consent { font-size: 13px; color: #4a433d; display: flex; gap: 9px; align-items: flex-start; margin-bottom: 9px; }
|
||||||
font-family: "Fraunces", serif;
|
.yse-widget .yse-consent input { width: auto; margin-top: 3px; }
|
||||||
font-weight: 600;
|
.yse-widget .yse-hp { position: absolute !important; left: -9999px !important; width: 1px !important; height: 1px !important; }
|
||||||
font-size: 18px;
|
.yse-widget .yse-error { color: #c0392b; font-size: 13px; margin-top: 8px; min-height: 18px; }
|
||||||
color: var(--yse-ink);
|
.yse-widget .yse-done { text-align: center; padding: 10px 4px; }
|
||||||
margin-right: auto;
|
.yse-widget .yse-done .yse-ok { width: 54px; height: 54px; border-radius: 50%; background: var(--yse-accent); color: #fff; display: flex; align-items: center; justify-content: center; margin: 0 auto 12px; }
|
||||||
align-self: center;
|
.yse-widget .yse-done h3 { margin: 0 0 6px; font-family: inherit; font-size: 21px; }
|
||||||
}
|
.yse-widget .yse-done p { color: #4a433d; font-size: 14px; margin: 2px 0; }
|
||||||
.yse-lbl {
|
|
||||||
font-size: 12px;
|
|
||||||
color: var(--yse-muted);
|
|
||||||
text-transform: uppercase;
|
|
||||||
letter-spacing: 0.05em;
|
|
||||||
margin-bottom: 5px;
|
|
||||||
}
|
|
||||||
.yse-stepper {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
border: 1px solid var(--yse-line);
|
|
||||||
border-radius: 11px;
|
|
||||||
overflow: hidden;
|
|
||||||
background: #fff;
|
|
||||||
}
|
|
||||||
.yse-stepper button {
|
|
||||||
border: none;
|
|
||||||
background: #fff;
|
|
||||||
width: 42px;
|
|
||||||
height: 44px;
|
|
||||||
font-size: 21px;
|
|
||||||
color: var(--yse-accent-ink);
|
|
||||||
cursor: pointer;
|
|
||||||
line-height: 1;
|
|
||||||
}
|
|
||||||
.yse-stepper button:disabled {
|
|
||||||
color: #cfc8c2;
|
|
||||||
cursor: not-allowed;
|
|
||||||
}
|
|
||||||
.yse-num {
|
|
||||||
width: 50px;
|
|
||||||
text-align: center;
|
|
||||||
font-size: 16px;
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
.yse-total .yse-val {
|
|
||||||
font-family: "Fraunces", serif;
|
|
||||||
font-size: 24px;
|
|
||||||
font-weight: 600;
|
|
||||||
color: var(--yse-accent-ink);
|
|
||||||
}
|
|
||||||
.yse-cta {
|
|
||||||
border: none;
|
|
||||||
border-radius: 11px;
|
|
||||||
background: var(--yse-accent);
|
|
||||||
color: #fff;
|
|
||||||
font-size: 15px;
|
|
||||||
font-weight: 600;
|
|
||||||
font-family: inherit;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: filter 0.15s;
|
|
||||||
}
|
|
||||||
.yse-cta:hover {
|
|
||||||
filter: brightness(0.94);
|
|
||||||
}
|
|
||||||
.yse-cta:disabled {
|
|
||||||
background: #cfc8c2;
|
|
||||||
cursor: not-allowed;
|
|
||||||
}
|
|
||||||
.yse-open {
|
|
||||||
padding: 13px 24px;
|
|
||||||
}
|
|
||||||
.yse-full {
|
|
||||||
width: 100%;
|
|
||||||
padding: 14px;
|
|
||||||
margin-top: 6px;
|
|
||||||
}
|
|
||||||
.yse-bform {
|
|
||||||
display: none;
|
|
||||||
margin-top: 18px;
|
|
||||||
padding-top: 18px;
|
|
||||||
border-top: 1px dashed var(--yse-line);
|
|
||||||
}
|
|
||||||
.yse-bform.yse-open {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
.yse-frow {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: 1fr 1fr;
|
|
||||||
gap: 12px;
|
|
||||||
}
|
|
||||||
.yse-field {
|
|
||||||
margin-bottom: 12px;
|
|
||||||
}
|
|
||||||
.yse-field label {
|
|
||||||
display: block;
|
|
||||||
font-size: 13px;
|
|
||||||
font-weight: 600;
|
|
||||||
color: var(--yse-ink);
|
|
||||||
margin-bottom: 5px;
|
|
||||||
}
|
|
||||||
.yse-field input {
|
|
||||||
width: 100%;
|
|
||||||
border: 1px solid var(--yse-line);
|
|
||||||
border-radius: 10px;
|
|
||||||
padding: 11px 13px;
|
|
||||||
font-size: 15px;
|
|
||||||
font-family: inherit;
|
|
||||||
background: #fff;
|
|
||||||
color: var(--yse-ink);
|
|
||||||
}
|
|
||||||
.yse-field input:focus {
|
|
||||||
outline: none;
|
|
||||||
border-color: var(--yse-accent);
|
|
||||||
box-shadow: 0 0 0 3px color-mix(in srgb, var(--yse-accent) 22%, white);
|
|
||||||
}
|
|
||||||
.yse-consent {
|
|
||||||
font-size: 13px;
|
|
||||||
color: #4a433d;
|
|
||||||
display: flex;
|
|
||||||
gap: 9px;
|
|
||||||
align-items: flex-start;
|
|
||||||
margin-bottom: 9px;
|
|
||||||
}
|
|
||||||
.yse-consent input {
|
|
||||||
margin-top: 3px;
|
|
||||||
}
|
|
||||||
.yse-hp {
|
|
||||||
position: absolute !important;
|
|
||||||
left: -9999px !important;
|
|
||||||
width: 1px;
|
|
||||||
height: 1px;
|
|
||||||
}
|
|
||||||
.yse-error {
|
|
||||||
color: #c0392b;
|
|
||||||
font-size: 13px;
|
|
||||||
margin-top: 8px;
|
|
||||||
min-height: 18px;
|
|
||||||
}
|
|
||||||
.yse-done {
|
|
||||||
text-align: center;
|
|
||||||
padding: 8px 4px;
|
|
||||||
}
|
|
||||||
.yse-done .yse-ok {
|
|
||||||
width: 52px;
|
|
||||||
height: 52px;
|
|
||||||
border-radius: 50%;
|
|
||||||
background: var(--yse-accent);
|
|
||||||
color: #fff;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
margin: 0 auto 10px;
|
|
||||||
}
|
|
||||||
.yse-done h3 {
|
|
||||||
margin: 0 0 6px;
|
|
||||||
font-family: "Fraunces", serif;
|
|
||||||
}
|
|
||||||
.yse-done p {
|
|
||||||
color: #4a433d;
|
|
||||||
font-size: 14px;
|
|
||||||
margin: 2px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Lightbox */
|
/* Lightbox (montato su <body>, fuori da .yse-widget) */
|
||||||
.yse-lightbox {
|
.yse-lightbox { position: fixed; inset: 0; background: rgba(24,19,16,.9); display: none; align-items: center; justify-content: center; z-index: 99999; padding: 24px; }
|
||||||
position: fixed;
|
.yse-lightbox.yse-open { display: flex; }
|
||||||
inset: 0;
|
.yse-lightbox img { max-width: 92vw; max-height: 86vh; border-radius: 12px; }
|
||||||
background: rgba(24, 19, 16, 0.9);
|
.yse-lightbox .yse-lbclose { position: absolute; top: 18px; right: 22px; color: #fff !important; font-size: 34px; cursor: pointer; background: none !important; border: none !important; line-height: 1; padding: 0; }
|
||||||
display: none;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
z-index: 99999;
|
|
||||||
padding: 24px;
|
|
||||||
}
|
|
||||||
.yse-lightbox.yse-open {
|
|
||||||
display: flex;
|
|
||||||
}
|
|
||||||
.yse-lightbox img {
|
|
||||||
max-width: 92vw;
|
|
||||||
max-height: 86vh;
|
|
||||||
border-radius: 12px;
|
|
||||||
}
|
|
||||||
.yse-lightbox .yse-lbclose {
|
|
||||||
position: absolute;
|
|
||||||
top: 18px;
|
|
||||||
right: 22px;
|
|
||||||
color: #fff;
|
|
||||||
font-size: 34px;
|
|
||||||
cursor: pointer;
|
|
||||||
background: none;
|
|
||||||
border: none;
|
|
||||||
line-height: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 560px) {
|
@media (max-width: 600px) {
|
||||||
.yse-hero {
|
.yse-widget .yse-hero { height: 210px; }
|
||||||
height: 200px;
|
.yse-widget .yse-title-row h3 { font-size: 20px; }
|
||||||
}
|
.yse-widget .yse-frow { grid-template-columns: 1fr; }
|
||||||
.yse-title-row h3 {
|
.yse-widget .yse-btitle { width: 100%; margin-bottom: 4px; }
|
||||||
font-size: 19px;
|
.yse-widget .yse-chead, .yse-widget .yse-booking, .yse-widget .yse-body { padding-left: 18px; padding-right: 18px; }
|
||||||
}
|
|
||||||
.yse-frow {
|
|
||||||
grid-template-columns: 1fr;
|
|
||||||
}
|
|
||||||
.yse-btitle {
|
|
||||||
width: 100%;
|
|
||||||
margin-bottom: 4px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@media (prefers-reduced-motion: reduce) {
|
|
||||||
.yse-widget * {
|
|
||||||
transition: none !important;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@media (prefers-reduced-motion: reduce) { .yse-widget * { transition: none !important; } }
|
||||||
|
|
||||||
|
/* Miniatura copertina nella testata compatta */
|
||||||
|
.yse-widget .yse-thumb { width: 76px; height: 76px; border-radius: 12px; object-fit: cover; flex-shrink: 0; }
|
||||||
|
@media (max-width: 600px) { .yse-widget .yse-thumb { width: 60px; height: 60px; } }
|
||||||
|
|
||||||
|
/* Evento programmato (prenotazioni non ancora aperte) */
|
||||||
|
.yse-widget .yse-scheduled { text-align: center; font-weight: 600; color: var(--yse-accent-ink); background: color-mix(in srgb, var(--yse-accent) 12%, white); border-radius: 12px; padding: 12px 14px; }
|
||||||
@@ -1,5 +1,4 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Plugin Name: YogaSoul Eventi
|
* Plugin Name: YogaSoul Eventi
|
||||||
* Description: Mostra gli eventi di YogiBook con card di prenotazione (lista d'attesa inclusa). Shortcode: [yogasoul_eventi], [yogasoul_eventi ids="12,15"], [yogasoul_eventi id="12"], [yogasoul_eventi limit="3"].
|
* Description: Mostra gli eventi di YogiBook con card di prenotazione (lista d'attesa inclusa). Shortcode: [yogasoul_eventi], [yogasoul_eventi ids="12,15"], [yogasoul_eventi id="12"], [yogasoul_eventi limit="3"].
|
||||||
@@ -19,15 +18,10 @@ if (!defined('YSE_API_BASE')) {
|
|||||||
define('YSE_API_BASE', 'https://yogibook.yogasoul.it/apiwp');
|
define('YSE_API_BASE', 'https://yogibook.yogasoul.it/apiwp');
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Registra gli asset (accodati solo quando lo shortcode è in pagina) */
|
/* Registra gli asset (accodati solo quando lo shortcode è in pagina).
|
||||||
|
Nessun Google Font: il widget eredita il carattere del tema. */
|
||||||
add_action('wp_enqueue_scripts', function () {
|
add_action('wp_enqueue_scripts', function () {
|
||||||
wp_register_style(
|
wp_register_style('yse-eventi', plugins_url('assets/yogasoul-eventi.css', __FILE__), [], '1.0.1');
|
||||||
'yse-fonts',
|
|
||||||
'https://fonts.googleapis.com/css2?family=Fraunces:opsz,wght@9..144,400;9..144,500;9..144,600&family=Inter:wght@400;500;600&display=swap',
|
|
||||||
[],
|
|
||||||
null
|
|
||||||
);
|
|
||||||
wp_register_style('yse-eventi', plugins_url('assets/yogasoul-eventi.css', __FILE__), ['yse-fonts'], '1.0.0');
|
|
||||||
wp_register_script('yse-eventi', plugins_url('assets/yogasoul-eventi.js', __FILE__), [], '1.0.0', true);
|
wp_register_script('yse-eventi', plugins_url('assets/yogasoul-eventi.js', __FILE__), [], '1.0.0', true);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -60,8 +54,11 @@ function yse_render_shortcode($atts)
|
|||||||
}
|
}
|
||||||
$url = YSE_API_BASE . '/events.php' . (!empty($query) ? '?' . http_build_query($query) : '');
|
$url = YSE_API_BASE . '/events.php' . (!empty($query) ? '?' . http_build_query($query) : '');
|
||||||
|
|
||||||
// Lettura server-side (niente CORS)
|
// Lettura server-side (niente CORS) + anti-cache
|
||||||
$resp = wp_remote_get($url, ['timeout' => 8]);
|
$resp = wp_remote_get($url . (strpos($url, '?') !== false ? '&' : '?') . '_=' . time(), [
|
||||||
|
'timeout' => 8,
|
||||||
|
'headers' => ['Cache-Control' => 'no-cache'],
|
||||||
|
]);
|
||||||
if (is_wp_error($resp) || (int) wp_remote_retrieve_response_code($resp) !== 200) {
|
if (is_wp_error($resp) || (int) wp_remote_retrieve_response_code($resp) !== 200) {
|
||||||
return '<div class="yse-widget"><p class="yse-empty">Eventi non disponibili al momento.</p></div>';
|
return '<div class="yse-widget"><p class="yse-empty">Eventi non disponibili al momento.</p></div>';
|
||||||
}
|
}
|
||||||
@@ -150,6 +147,9 @@ function yse_render_card($ev)
|
|||||||
<div class="yse-m"><?php echo esc_html($monAbbr); ?></div>
|
<div class="yse-m"><?php echo esc_html($monAbbr); ?></div>
|
||||||
<div class="yse-d"><?php echo $dayNum; ?></div>
|
<div class="yse-d"><?php echo $dayNum; ?></div>
|
||||||
</div>
|
</div>
|
||||||
|
<?php if (!empty($ev['cover_image'])) : ?>
|
||||||
|
<img class="yse-thumb" src="<?php echo esc_url($ev['cover_image']); ?>" alt="<?php echo esc_attr($ev['title']); ?>">
|
||||||
|
<?php endif; ?>
|
||||||
<div class="yse-main">
|
<div class="yse-main">
|
||||||
<div class="yse-title-row">
|
<div class="yse-title-row">
|
||||||
<h3><?php echo esc_html($ev['title']); ?></h3>
|
<h3><?php echo esc_html($ev['title']); ?></h3>
|
||||||
@@ -188,22 +188,13 @@ function yse_render_card($ev)
|
|||||||
<?php if (trim((string) ($ev['suitable_for'] ?? '')) !== '' || trim((string) ($ev['what_to_bring'] ?? '')) !== '' || trim((string) ($ev['instructions'] ?? '')) !== '') : ?>
|
<?php if (trim((string) ($ev['suitable_for'] ?? '')) !== '' || trim((string) ($ev['what_to_bring'] ?? '')) !== '' || trim((string) ($ev['instructions'] ?? '')) !== '') : ?>
|
||||||
<div class="yse-detail-grid">
|
<div class="yse-detail-grid">
|
||||||
<?php if (trim((string) ($ev['suitable_for'] ?? '')) !== '') : ?>
|
<?php if (trim((string) ($ev['suitable_for'] ?? '')) !== '') : ?>
|
||||||
<div class="yse-detail">
|
<div class="yse-detail"><div class="yse-dh"><?php echo yse_icon('heart'); ?> Per chi è adatto</div><div class="yse-dt"><?php echo nl2br(esc_html($ev['suitable_for'])); ?></div></div>
|
||||||
<div class="yse-dh"><?php echo yse_icon('heart'); ?> Per chi è adatto</div>
|
|
||||||
<div class="yse-dt"><?php echo nl2br(esc_html($ev['suitable_for'])); ?></div>
|
|
||||||
</div>
|
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
<?php if (trim((string) ($ev['what_to_bring'] ?? '')) !== '') : ?>
|
<?php if (trim((string) ($ev['what_to_bring'] ?? '')) !== '') : ?>
|
||||||
<div class="yse-detail">
|
<div class="yse-detail"><div class="yse-dh"><?php echo yse_icon('bag'); ?> Cosa portare</div><div class="yse-dt"><?php echo nl2br(esc_html($ev['what_to_bring'])); ?></div></div>
|
||||||
<div class="yse-dh"><?php echo yse_icon('bag'); ?> Cosa portare</div>
|
|
||||||
<div class="yse-dt"><?php echo nl2br(esc_html($ev['what_to_bring'])); ?></div>
|
|
||||||
</div>
|
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
<?php if (trim((string) ($ev['instructions'] ?? '')) !== '') : ?>
|
<?php if (trim((string) ($ev['instructions'] ?? '')) !== '') : ?>
|
||||||
<div class="yse-detail">
|
<div class="yse-detail"><div class="yse-dh"><?php echo yse_icon('info'); ?> Istruzioni</div><div class="yse-dt"><?php echo nl2br(esc_html($ev['instructions'])); ?></div></div>
|
||||||
<div class="yse-dh"><?php echo yse_icon('info'); ?> Istruzioni</div>
|
|
||||||
<div class="yse-dt"><?php echo nl2br(esc_html($ev['instructions'])); ?></div>
|
|
||||||
</div>
|
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
</div>
|
</div>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
@@ -231,11 +222,21 @@ function yse_render_card($ev)
|
|||||||
|
|
||||||
<!-- BOOKING -->
|
<!-- BOOKING -->
|
||||||
<div class="yse-booking">
|
<div class="yse-booking">
|
||||||
<?php if ($hardFull) : ?>
|
<?php if (!empty($ev['scheduled'])) :
|
||||||
|
$pt = !empty($ev['publish_at']) ? strtotime($ev['publish_at']) : 0;
|
||||||
|
$ptLabel = $pt ? ((int) date('j', $pt) . ' ' . $mesi[(int) date('n', $pt)] . ' ' . date('Y', $pt) . ' alle ' . date('H:i', $pt)) : '';
|
||||||
|
?>
|
||||||
|
<div class="yse-scheduled">Prenotazioni aperte dal <strong><?php echo esc_html($ptLabel); ?></strong></div>
|
||||||
|
<?php elseif ($hardFull) : ?>
|
||||||
<div class="yse-soldout">Evento esaurito — non ci sono più posti disponibili.</div>
|
<div class="yse-soldout">Evento esaurito — non ci sono più posti disponibili.</div>
|
||||||
<?php else : ?>
|
<?php else : ?>
|
||||||
<?php if ($waitMode) : ?>
|
<?php if ($waitMode) :
|
||||||
<div class="yse-wl-banner">Posti esauriti — puoi iscriverti alla <strong>lista d'attesa</strong>: ti avviseremo se si libera un posto.</div>
|
$waitingCount = (int) ($ev['waiting'] ?? 0);
|
||||||
|
$waitingTxt = $waitingCount > 0
|
||||||
|
? ' Al momento ' . ($waitingCount === 1 ? "c'è 1 persona" : "ci sono {$waitingCount} persone") . ' in coda.'
|
||||||
|
: '';
|
||||||
|
?>
|
||||||
|
<div class="yse-wl-banner">Posti esauriti — puoi iscriverti alla <strong>lista d'attesa</strong>: ti avviseremo se si libera un posto.<?php echo esc_html($waitingTxt); ?></div>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
<div class="yse-bcompact">
|
<div class="yse-bcompact">
|
||||||
<div class="yse-btitle"><?php echo $waitMode ? "Lista d'attesa" : 'Prenota questo evento'; ?></div>
|
<div class="yse-btitle"><?php echo $waitMode ? "Lista d'attesa" : 'Prenota questo evento'; ?></div>
|
||||||
|
|||||||
Reference in New Issue
Block a user