diff --git a/.env b/.env index 2739f99c..b92826c6 100644 --- a/.env +++ b/.env @@ -39,3 +39,4 @@ PUSHER_APP_CLUSTER=mt1 MIX_PUSHER_APP_KEY="${PUSHER_APP_KEY}" MIX_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}" +EXPOSE_API=true \ No newline at end of file diff --git a/.gitignore b/.gitignore index a469a57c..fe7d198a 100644 --- a/.gitignore +++ b/.gitignore @@ -21,3 +21,17 @@ yarn-error.log .env.backup .env.production auth.json + +# Log files +error_log +public/error_log +public/php_errors.log +public/dashboard_log.txt +public/logs/ +public/yogasoulscript/logs/ + +# User uploads +public/user/document/ + +# Python venv +public/yogasoulscript/venv/ \ No newline at end of file diff --git a/public/api/_bootstrap.php b/public/api/_bootstrap.php new file mode 100644 index 00000000..c959cdc8 --- /dev/null +++ b/public/api/_bootstrap.php @@ -0,0 +1,35 @@ +getConnection(); + +// ============================== +// AUTH VANGUARD +// ============================== +require_once $BASE_PATH . '/../../extra/auth.php'; + +// ============================== +// AUTH API (TOKEN) +// ============================== +$user = Auth::guard('api')->user(); + +if (!$user) { + http_response_code(401); + echo json_encode(['error' => 'Unauthorized']); + exit; +} diff --git a/public/api/my-lessons.php b/public/api/my-lessons.php new file mode 100644 index 00000000..41433891 --- /dev/null +++ b/public/api/my-lessons.php @@ -0,0 +1,224 @@ + utente autenticato ($user->id == bookingclass.iduser) + * - $db -> connessione PDO (da DBHandlerSelect) + * + * Parametri GET: + * - month=YYYY-MM (opzionale, default = mese corrente) + * + * Logica ricalcata su userpanel.php: + * - summary: conteggi utente-globali (acquistate/praticate/prenotate/ + * da confermare/da programmare/perse) + * - lessons: lezioni "booked" del mese selezionato, con regole + * can_reschedule / can_delete identiche alla webapp. + * -------------------------------------------------------------------------- + */ + +require_once __DIR__ . '/_bootstrap.php'; + +// A questo punto $user e $db esistono già (altrimenti _bootstrap ha fatto exit 401). + +$userId = (int) $user->id; + +// Location fissa, come nella webapp (userpanel.php la stampa hardcoded). +const LESSON_LOCATION = 'via Valassina 62/B Seregno - Sala Contesto Yoga'; + +// ========================================================================== +// PARAMETRI: mese +// ========================================================================== +$month = $_GET['month'] ?? date('Y-m'); + +// Validazione: deve essere YYYY-MM, altrimenti fallback al mese corrente. +$monthDate = DateTime::createFromFormat('Y-m-d', $month . '-01'); +if ($monthDate === false) { + $month = date('Y-m'); + $monthDate = new DateTime(date('Y-m') . '-01'); +} + +// Finestra del mese. Replichiamo la logica di userpanel.php: +// se il mese richiesto è quello corrente, si parte da OGGI (non dal giorno 1), +// così non si mostrano lezioni già passate del mese in corso. +$monthStart = $monthDate->format('Y-m-01'); +$today = date('Y-m-d'); +if ($today > $monthStart && date('Y-m') === $month) { + $monthStart = $today; +} +// Fine mese + 1 giorno (bound superiore esclusivo), come nella webapp. +$monthEndPlus1 = (clone $monthDate)->modify('first day of next month')->format('Y-m-d'); + +// ========================================================================== +// SUMMARY: conteggi utente-globali +// (tradotti 1:1 dalle query mysqli di userpanel.php in PDO) +// ========================================================================== + +// -- Totale biglietti acquistati (SUM nticket sugli ordini dell'utente) ----- +$stmt = $db->prepare(" + SELECT COALESCE(SUM(nticket), 0) AS total_tickets + FROM orderbook + WHERE iduser = :uid +"); +$stmt->execute([':uid' => $userId]); +$purchased = (int) $stmt->fetchColumn(); + +// -- Stato prenotazioni (passate/future/perse/pending) ---------------------- +$now = date('Y-m-d H:i:s'); +$stmt = $db->prepare(" + SELECT + SUM(CASE WHEN ss.dateschedule <= :now1 + AND bc.status = 'booked' + AND bc.lostlesson = 'N' THEN 1 ELSE 0 END) AS passed, + SUM(CASE WHEN ss.dateschedule > :now2 + AND bc.status = 'booked' + AND bc.lostlesson = 'N' THEN 1 ELSE 0 END) AS future, + SUM(CASE WHEN bc.lostlesson = 'Y' + AND bc.status != 'cancelled' THEN 1 ELSE 0 END) AS lost, + SUM(CASE WHEN bc.status = 'pending' THEN 1 ELSE 0 END) AS pending + FROM bookingclass bc + LEFT JOIN serviceschedule ss + ON bc.idserviceschedule = ss.idserviceschedule + WHERE bc.iduser = :uid + AND bc.status != 'cancelled' +"); +$stmt->execute([ + ':now1' => $now, + ':now2' => $now, + ':uid' => $userId, +]); +$counts = $stmt->fetch() ?: []; + +$practiced = (int) ($counts['passed'] ?? 0); +$booked = (int) ($counts['future'] ?? 0); +$lost = (int) ($counts['lost'] ?? 0); +$pending = (int) ($counts['pending'] ?? 0); + +// "Da programmare" = acquistate - praticate - prenotate - da confermare - perse +// (identico al calcolo $toprogram di userpanel.php) +$toSchedule = $purchased - $practiced - $booked - $pending - $lost; +if ($toSchedule < 0) { + $toSchedule = 0; +} + +$summary = [ + 'purchased' => $purchased, + 'practiced' => $practiced, + 'booked' => $booked, + 'pending' => $pending, + 'to_schedule' => $toSchedule, + 'lost' => $lost, +]; + +// ========================================================================== +// LESSONS: lezioni "booked" del mese selezionato +// (query principale di userpanel.php, tradotta in PDO) +// ========================================================================== +$stmt = $db->prepare(" + SELECT + bc.idbookingclass, + bc.status, + bc.idservice, + bc.idorder, + bc.lostlesson, + ss.dateschedule, + s.servicename, + s.colorclass, + ob.expireon, + ob.maxreschedule, + ob.reprogrammed + FROM bookingclass bc + LEFT JOIN service s ON bc.idservice = s.idservice + LEFT JOIN serviceschedule ss ON bc.idserviceschedule = ss.idserviceschedule + LEFT JOIN orderbook ob ON bc.idorder = ob.idorderbook + WHERE bc.iduser = :uid + AND bc.status = 'booked' + AND ss.dateschedule >= :start + AND ss.dateschedule < :end + ORDER BY ss.dateschedule ASC +"); +$stmt->execute([ + ':uid' => $userId, + ':start' => $monthStart . ' 00:00:00', + ':end' => $monthEndPlus1 . ' 00:00:00', +]); +$rows = $stmt->fetchAll(); + +$lessons = []; +$nowDt = new DateTime(); + +foreach ($rows as $r) { + $dateschedule = $r['dateschedule'] ?? null; + if ($dateschedule === null) { + continue; // riga orfana senza schedule, la saltiamo + } + + $classDt = new DateTime($dateschedule); + + // ------------------------------------------------------------------ + // Regola oraria can_delete (identica a userpanel.php) + // ------------------------------------------------------------------ + $isSameDay = $classDt->format('Y-m-d') === $nowDt->format('Y-m-d'); + $classHour = (int) $classDt->format('H'); + $classMinute = (int) $classDt->format('i'); + // "prima delle 17:00" = ora < 17, oppure esattamente 17:00 + $isBefore1700 = ($classHour < 17) || ($classHour === 17 && $classMinute === 0); + + if ($isSameDay) { + if ($isBefore1700) { + // lezioni prima delle 17: valido fino alle 00:01 dello stesso giorno + $deadline = new DateTime($classDt->format('Y-m-d 00:01:00')); + } else { + // lezioni dalle 17 in poi: valido fino alle 12:00 dello stesso giorno + $deadline = new DateTime($classDt->format('Y-m-d 12:00:00')); + } + $canByTime = $nowDt <= $deadline; + } else { + // giorni futuri: sempre consentito + $canByTime = true; + } + + // ------------------------------------------------------------------ + // Limite riprogrammazioni dell'ordine (identico a userpanel.php) + // ------------------------------------------------------------------ + $maxreschedule = (int) ($r['maxreschedule'] ?? 0); + $reprogrammed = (int) ($r['reprogrammed'] ?? 0); + $canReprogram = $reprogrammed < $maxreschedule; + + // Nella webapp i bottoni "Riprogramma" e "Cancella" compaiono entrambi + // solo se ($canByTime && $canReprogram). Riproduciamo la stessa condizione. + $canReschedule = $canByTime && $canReprogram; + $canDelete = $canByTime && $canReprogram; + + $lessons[] = [ + 'booking_id' => (int) $r['idbookingclass'], + 'status' => (string) $r['status'], + 'datetime' => $classDt->format('Y-m-d H:i:s'), + 'date' => $classDt->format('Y-m-d'), + 'time' => $classDt->format('H:i'), + 'class_name' => (string) ($r['servicename'] ?? ''), + 'color' => (string) ($r['colorclass'] ?? '#1ebf73'), + 'location' => LESSON_LOCATION, + 'expire_on' => $r['expireon'] ?? null, + 'lost_lesson' => ($r['lostlesson'] ?? 'N') === 'Y', + 'can_reschedule' => $canReschedule, + 'can_delete' => $canDelete, + ]; +} + +// ========================================================================== +// OUTPUT +// ========================================================================== +echo json_encode([ + 'success' => true, + 'month' => $month, + 'summary' => $summary, + 'lessons' => $lessons, +], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); diff --git a/public/bookingpanel.php b/public/bookingpanel.php index ab6fc13f..ac5659e6 100644 --- a/public/bookingpanel.php +++ b/public/bookingpanel.php @@ -29,16 +29,23 @@ if (isset($_POST['idserviceordered'])) { $idserviceordered = $_POST['idserviceordered']; } -// Recupera la data di scadenza dell'ordine -$expiryQuery = "SELECT expireon FROM orderbook WHERE order_id = ?"; +// Recover the expiry date of the order linked to the booking being rescheduled +$expiryQuery = "SELECT ob.expireon + FROM bookingclass bc + INNER JOIN orderbook ob ON bc.idorder = ob.idorderbook + WHERE bc.idbookingclass = ?"; + $stmt = $conn->prepare($expiryQuery); $stmt->bind_param("i", $idpreviousbooking); $stmt->execute(); $expiryResult = $stmt->get_result(); + $expiryDate = null; if ($expiryResult->num_rows > 0) { $row = $expiryResult->fetch_assoc(); - $expiryDate = new DateTime($row['expireon']); + if (!empty($row['expireon'])) { + $expiryDate = new DateTime($row['expireon']); + } } $stmt->close();