diff --git a/admin_booking.php b/admin_booking.php new file mode 100644 index 0000000..7118453 --- /dev/null +++ b/admin_booking.php @@ -0,0 +1,89 @@ +prepare( + "SELECT b.*, s.show_time, s.screen, s.format_label, m.title + FROM bookings b + JOIN shows s ON s.id = b.show_id + JOIN movies m ON m.id = s.movie_id + WHERE b.booking_code = ?" + ); + $stmt->execute([$code]); + $booking = $stmt->fetch(); +} +?> + + + + + + <?= h($projectName) ?> — Booking detail + + + + + + + + + + + + + +
+ + +
+ +
Booking not found.
+ +
+

Booking

+
+
+
Customer
+
+
+
+
+
Showtime
+
+
· ·
+
+
+
+
+
+
Seats
+
+
+
+
Total
+
$
+
+
+
Check-in
+
+
+
+
+ +
+
+ + diff --git a/admin_bookings.php b/admin_bookings.php new file mode 100644 index 0000000..0eccdec --- /dev/null +++ b/admin_bookings.php @@ -0,0 +1,87 @@ +query( + "SELECT b.*, s.show_time, m.title + FROM bookings b + JOIN shows s ON s.id = b.show_id + JOIN movies m ON m.id = s.movie_id + ORDER BY b.created_at DESC + LIMIT 50" +); +$bookings = $stmt->fetchAll(); +?> + + + + + + <?= h($projectName) ?> — Admin bookings + + + + + + + + + + + + + +
+ + +
+
+

Bookings overview

+ Last 50 bookings +
+
+ +
No bookings yet.
+ +
+ + + + + + + + + + + + + + + + + + + + + + + +
CodeMovieShowtimeSeatsStatusCheck-in
+
+ +
+
+
+ + diff --git a/agent.php b/agent.php new file mode 100644 index 0000000..282ef1c --- /dev/null +++ b/agent.php @@ -0,0 +1,84 @@ +query( + "SELECT s.*, m.title + FROM shows s + JOIN movies m ON m.id = s.movie_id + ORDER BY s.show_time ASC" +)->fetchAll(); +?> + + + + + + <?= h($projectName) ?> — Agent POS + + + + + + + + + + + + + +
+ + +
+
+

Agent booking console

+ Pick a showtime for walk-in guests +
+
+ +
No showtimes available.
+ +
+ + + + + + + + + + + + + + + + + + + + + + + +
MovieShowtimeScreenFormatPrice
$Book seats
+
+ +
+
+
+ + diff --git a/assets/css/custom.css b/assets/css/custom.css index 789132e..e9dc89d 100644 --- a/assets/css/custom.css +++ b/assets/css/custom.css @@ -1,403 +1,129 @@ body { - background: linear-gradient(-45deg, #ee7752, #e73c7e, #23a6d5, #23d5ab); - background-size: 400% 400%; - animation: gradient 15s ease infinite; - color: #212529; - font-family: 'Inter', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; - font-size: 14px; - margin: 0; - min-height: 100vh; + font-family: "Inter", system-ui, -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; + background: #f6f7f9; + color: #111827; + font-size: 15px; } -.main-wrapper { - display: flex; - align-items: center; - justify-content: center; - min-height: 100vh; - width: 100%; - padding: 20px; - box-sizing: border-box; - position: relative; - z-index: 1; -} - -@keyframes gradient { - 0% { - background-position: 0% 50%; - } - 50% { - background-position: 100% 50%; - } - 100% { - background-position: 0% 50%; - } -} - -.chat-container { - width: 100%; - max-width: 600px; - background: rgba(255, 255, 255, 0.85); - border: 1px solid rgba(255, 255, 255, 0.3); - border-radius: 20px; - display: flex; - flex-direction: column; - height: 85vh; - box-shadow: 0 20px 40px rgba(0,0,0,0.2); - backdrop-filter: blur(15px); - -webkit-backdrop-filter: blur(15px); - overflow: hidden; -} - -.chat-header { - padding: 1.5rem; - border-bottom: 1px solid rgba(0, 0, 0, 0.05); - background: rgba(255, 255, 255, 0.5); - font-weight: 700; - font-size: 1.1rem; - display: flex; - justify-content: space-between; - align-items: center; -} - -.chat-messages { - flex: 1; - overflow-y: auto; - padding: 1.5rem; - display: flex; - flex-direction: column; - gap: 1.25rem; -} - -/* Custom Scrollbar */ -::-webkit-scrollbar { - width: 6px; -} - -::-webkit-scrollbar-track { - background: transparent; -} - -::-webkit-scrollbar-thumb { - background: rgba(255, 255, 255, 0.3); - border-radius: 10px; -} - -::-webkit-scrollbar-thumb:hover { - background: rgba(255, 255, 255, 0.5); -} - -.message { - max-width: 85%; - padding: 0.85rem 1.1rem; - border-radius: 16px; - line-height: 1.5; - font-size: 0.95rem; - box-shadow: 0 4px 15px rgba(0,0,0,0.05); - animation: fadeIn 0.4s cubic-bezier(0.175, 0.885, 0.32, 1.275); -} - -@keyframes fadeIn { - from { opacity: 0; transform: translateY(20px) scale(0.95); } - to { opacity: 1; transform: translateY(0) scale(1); } -} - -.message.visitor { - align-self: flex-end; - background: linear-gradient(135deg, #212529 0%, #343a40 100%); - color: #fff; - border-bottom-right-radius: 4px; -} - -.message.bot { - align-self: flex-start; +.navbar { + border-bottom: 1px solid #e5e7eb; background: #ffffff; - color: #212529; - border-bottom-left-radius: 4px; } -.chat-input-area { - padding: 1.25rem; - background: rgba(255, 255, 255, 0.5); - border-top: 1px solid rgba(0, 0, 0, 0.05); -} - -.chat-input-area form { +.app-shell { + min-height: 100vh; display: flex; - gap: 0.75rem; + flex-direction: column; } -.chat-input-area input { - flex: 1; - border: 1px solid rgba(0, 0, 0, 0.1); - border-radius: 12px; - padding: 0.75rem 1rem; - outline: none; - background: rgba(255, 255, 255, 0.9); - transition: all 0.3s ease; -} - -.chat-input-area input:focus { - border-color: #23a6d5; - box-shadow: 0 0 0 3px rgba(35, 166, 213, 0.2); -} - -.chat-input-area button { - background: #212529; - color: #fff; - border: none; - padding: 0.75rem 1.5rem; - border-radius: 12px; - cursor: pointer; - font-weight: 600; - transition: all 0.3s ease; -} - -.chat-input-area button:hover { - background: #000; - transform: translateY(-2px); - box-shadow: 0 5px 15px rgba(0,0,0,0.2); -} - -/* Background Animations */ -.bg-animations { - position: fixed; - top: 0; - left: 0; - width: 100%; - height: 100%; - z-index: 0; - overflow: hidden; - pointer-events: none; -} - -.blob { - position: absolute; - width: 500px; - height: 500px; - background: rgba(255, 255, 255, 0.2); - border-radius: 50%; - filter: blur(80px); - animation: move 20s infinite alternate cubic-bezier(0.45, 0, 0.55, 1); -} - -.blob-1 { - top: -10%; - left: -10%; - background: rgba(238, 119, 82, 0.4); -} - -.blob-2 { - bottom: -10%; - right: -10%; - background: rgba(35, 166, 213, 0.4); - animation-delay: -7s; - width: 600px; - height: 600px; -} - -.blob-3 { - top: 40%; - left: 30%; - background: rgba(231, 60, 126, 0.3); - animation-delay: -14s; - width: 450px; - height: 450px; -} - -@keyframes move { - 0% { transform: translate(0, 0) rotate(0deg) scale(1); } - 33% { transform: translate(150px, 100px) rotate(120deg) scale(1.1); } - 66% { transform: translate(-50px, 200px) rotate(240deg) scale(0.9); } - 100% { transform: translate(0, 0) rotate(360deg) scale(1); } -} - -.header-link { - font-size: 14px; - color: #fff; - text-decoration: none; - background: rgba(0, 0, 0, 0.2); - padding: 0.5rem 1rem; - border-radius: 8px; - transition: all 0.3s ease; -} - -.header-link:hover { - background: rgba(0, 0, 0, 0.4); - text-decoration: none; -} - -/* Admin Styles */ -.admin-container { - max-width: 900px; - margin: 3rem auto; +.hero { + background: #ffffff; + border: 1px solid #e5e7eb; + border-radius: 10px; padding: 2.5rem; - background: rgba(255, 255, 255, 0.85); - backdrop-filter: blur(20px); - -webkit-backdrop-filter: blur(20px); - border-radius: 24px; - box-shadow: 0 20px 50px rgba(0,0,0,0.15); - border: 1px solid rgba(255, 255, 255, 0.4); - position: relative; - z-index: 1; } -.admin-container h1 { - margin-top: 0; - color: #212529; - font-weight: 800; -} - -.table { - width: 100%; - border-collapse: separate; - border-spacing: 0 8px; - margin-top: 1.5rem; -} - -.table th { - background: transparent; - border: none; - padding: 1rem; - color: #6c757d; +.hero h1 { font-weight: 600; - text-transform: uppercase; + letter-spacing: -0.02em; +} + +.stat-card { + border: 1px solid #e5e7eb; + border-radius: 10px; + background: #ffffff; + padding: 1.25rem; +} + +.movie-card { + border: 1px solid #e5e7eb; + border-radius: 10px; + background: #ffffff; + padding: 1.5rem; + height: 100%; +} + +.badge-soft { + background: #f3f4f6; + color: #374151; + border-radius: 999px; + padding: 0.25rem 0.75rem; + font-weight: 500; font-size: 0.75rem; - letter-spacing: 1px; } -.table td { - background: #fff; - padding: 1rem; - border: none; +.seat-grid { + display: grid; + grid-template-columns: repeat(8, minmax(0, 1fr)); + gap: 0.5rem; } -.table tr td:first-child { border-radius: 12px 0 0 12px; } -.table tr td:last-child { border-radius: 0 12px 12px 0; } - -.form-group { - margin-bottom: 1.25rem; -} - -.form-group label { - display: block; - margin-bottom: 0.5rem; - font-weight: 600; - font-size: 0.9rem; -} - -.form-control { - width: 100%; - padding: 0.75rem 1rem; - border: 1px solid rgba(0, 0, 0, 0.1); - border-radius: 12px; - background: #fff; - transition: all 0.3s ease; - box-sizing: border-box; -} - -.form-control:focus { - outline: none; - border-color: #23a6d5; - box-shadow: 0 0 0 3px rgba(35, 166, 213, 0.1); -} - -.header-container { - display: flex; - justify-content: space-between; - align-items: center; -} - -.header-links { - display: flex; - gap: 1rem; -} - -.admin-card { - background: rgba(255, 255, 255, 0.6); - padding: 2rem; - border-radius: 20px; - border: 1px solid rgba(255, 255, 255, 0.5); - margin-bottom: 2.5rem; - box-shadow: 0 10px 30px rgba(0,0,0,0.05); -} - -.admin-card h3 { - margin-top: 0; - margin-bottom: 1.5rem; - font-weight: 700; -} - -.btn-delete { - background: #dc3545; - color: white; - border: none; - padding: 0.25rem 0.5rem; - border-radius: 4px; - cursor: pointer; -} - -.btn-add { - background: #212529; - color: white; - border: none; - padding: 0.5rem 1rem; - border-radius: 4px; - cursor: pointer; - margin-top: 1rem; -} - -.btn-save { - background: #0088cc; - color: white; - border: none; - padding: 0.8rem 1.5rem; - border-radius: 12px; - cursor: pointer; - font-weight: 600; - width: 100%; - transition: all 0.3s ease; -} - -.webhook-url { - font-size: 0.85em; - color: #555; - margin-top: 0.5rem; -} - -.history-table-container { - overflow-x: auto; - background: rgba(255, 255, 255, 0.4); - padding: 1rem; - border-radius: 12px; - border: 1px solid rgba(255, 255, 255, 0.3); -} - -.history-table { - width: 100%; -} - -.history-table-time { - width: 15%; - white-space: nowrap; - font-size: 0.85em; - color: #555; -} - -.history-table-user { - width: 35%; - background: rgba(255, 255, 255, 0.3); - border-radius: 8px; - padding: 8px; -} - -.history-table-ai { - width: 50%; - background: rgba(255, 255, 255, 0.5); - border-radius: 8px; - padding: 8px; -} - -.no-messages { +.seat { + border: 1px solid #d1d5db; + background: #ffffff; + color: #111827; + border-radius: 6px; + padding: 0.35rem 0; + font-size: 0.75rem; text-align: center; - color: #777; -} \ No newline at end of file + cursor: pointer; + transition: all 0.2s ease; + user-select: none; +} + +.seat:hover { + border-color: #111827; +} + +.seat.selected { + background: #111827; + color: #ffffff; +} + +.seat.taken { + background: #e5e7eb; + color: #9ca3af; + cursor: not-allowed; +} + +.surface { + background: #ffffff; + border: 1px solid #e5e7eb; + border-radius: 10px; + padding: 1.5rem; +} + +.table thead th { + font-weight: 600; + font-size: 0.85rem; + text-transform: uppercase; + letter-spacing: 0.04em; +} + +.btn-primary { + background: #111827; + border-color: #111827; + border-radius: 8px; + padding: 0.55rem 1.25rem; + font-weight: 500; +} + +.btn-outline-dark { + border-radius: 8px; + padding: 0.55rem 1.25rem; +} + +.form-control, .form-select { + border-radius: 8px; + border-color: #d1d5db; +} + +.alert { + border-radius: 8px; +} + +.footer { + margin-top: auto; + border-top: 1px solid #e5e7eb; + background: #ffffff; +} diff --git a/assets/js/main.js b/assets/js/main.js index d349598..634760a 100644 --- a/assets/js/main.js +++ b/assets/js/main.js @@ -1,39 +1,34 @@ document.addEventListener('DOMContentLoaded', () => { - const chatForm = document.getElementById('chat-form'); - const chatInput = document.getElementById('chat-input'); - const chatMessages = document.getElementById('chat-messages'); + const seatGrid = document.querySelector('[data-seat-grid]'); + const seatInput = document.querySelector('[data-seat-input]'); + const totalOutput = document.querySelector('[data-total]'); + const seatOutput = document.querySelector('[data-seat-output]'); + const price = parseFloat(document.body.dataset.price || '0'); - const appendMessage = (text, sender) => { - const msgDiv = document.createElement('div'); - msgDiv.classList.add('message', sender); - msgDiv.textContent = text; - chatMessages.appendChild(msgDiv); - chatMessages.scrollTop = chatMessages.scrollHeight; + if (!seatGrid || !seatInput) { + return; + } + + const updateTotals = () => { + const selectedSeats = Array.from(seatGrid.querySelectorAll('.seat.selected')) + .map((seat) => seat.dataset.seat); + seatInput.value = selectedSeats.join(', '); + if (seatOutput) { + seatOutput.textContent = selectedSeats.length ? selectedSeats.join(', ') : 'None yet'; + } + if (totalOutput) { + totalOutput.textContent = (selectedSeats.length * price).toFixed(2); + } }; - chatForm.addEventListener('submit', async (e) => { - e.preventDefault(); - const message = chatInput.value.trim(); - if (!message) return; - - appendMessage(message, 'visitor'); - chatInput.value = ''; - - try { - const response = await fetch('api/chat.php', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ message }) - }); - const data = await response.json(); - - // Artificial delay for realism - setTimeout(() => { - appendMessage(data.reply, 'bot'); - }, 500); - } catch (error) { - console.error('Error:', error); - appendMessage("Sorry, something went wrong. Please try again.", 'bot'); + seatGrid.addEventListener('click', (event) => { + const seat = event.target.closest('.seat'); + if (!seat || seat.classList.contains('taken')) { + return; } + seat.classList.toggle('selected'); + updateTotals(); }); + + updateTotals(); }); diff --git a/booking.php b/booking.php new file mode 100644 index 0000000..e1d8358 --- /dev/null +++ b/booking.php @@ -0,0 +1,163 @@ +prepare( + "INSERT INTO bookings (show_id, customer_name, customer_email, seats, total_price, booking_code) + VALUES (?, ?, ?, ?, ?, ?)" + ); + $stmt->execute([$showId, $name, $email, implode(', ', $seatList), $total, $bookingCode]); + + header('Location: confirmation.php?code=' . urlencode($bookingCode)); + exit; + } +} +?> + + + + + + <?= h($projectName) ?> — Select seats + + + + + + + + + + + + + +
+ + +
+ +
Select a showtime to continue booking.
+ +
+
+
+

Choose your seats

+

· ·

+
+
Screen
+
+
+
+ + +
+ +
+ +
+
+ Selected: None yet + Total: $0.00 +
+
+
+
+
+

Booking details

+ +
+ +
+ +
+ +
+
+ + +
+
+ + +
+
+ + +
+
+ Price per seat + $ +
+ +
+
+
+
+ +
+ +
+ + + diff --git a/confirmation.php b/confirmation.php new file mode 100644 index 0000000..84e92df --- /dev/null +++ b/confirmation.php @@ -0,0 +1,87 @@ +prepare( + "SELECT b.*, s.show_time, s.screen, s.format_label, m.title + FROM bookings b + JOIN shows s ON s.id = b.show_id + JOIN movies m ON m.id = s.movie_id + WHERE b.booking_code = ?" + ); + $stmt->execute([$code]); + $booking = $stmt->fetch(); +} +?> + + + + + + <?= h($projectName) ?> — Confirmation + + + + + + + + + + + + + +
+ + +
+ +
Booking not found. Please check your code.
+ +
+

Booking confirmed

+
+
+
Showtime
+
+
· ·
+
+
+
+
Seats
+
+
+
+
Total paid
+
$
+
+
+
+
+
+
Entry code
+
+
Show this code at entry for scanning.
+
+
+
+
+ +
+
+ + diff --git a/includes/bootstrap.php b/includes/bootstrap.php new file mode 100644 index 0000000..69e58f6 --- /dev/null +++ b/includes/bootstrap.php @@ -0,0 +1,145 @@ +exec( + "CREATE TABLE IF NOT EXISTS movies ( + id INT AUTO_INCREMENT PRIMARY KEY, + title VARCHAR(160) NOT NULL, + genre VARCHAR(80) NOT NULL, + duration_min INT NOT NULL, + rating VARCHAR(10) NOT NULL, + description TEXT NOT NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4" + ); + + $pdo->exec( + "CREATE TABLE IF NOT EXISTS shows ( + id INT AUTO_INCREMENT PRIMARY KEY, + movie_id INT NOT NULL, + screen VARCHAR(40) NOT NULL, + show_time DATETIME NOT NULL, + format_label VARCHAR(10) NOT NULL, + price DECIMAL(8,2) NOT NULL DEFAULT 12.00, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (movie_id) REFERENCES movies(id) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4" + ); + + $pdo->exec( + "CREATE TABLE IF NOT EXISTS bookings ( + id INT AUTO_INCREMENT PRIMARY KEY, + show_id INT NOT NULL, + customer_name VARCHAR(120) NOT NULL, + customer_email VARCHAR(160) NOT NULL, + seats VARCHAR(120) NOT NULL, + total_price DECIMAL(8,2) NOT NULL, + booking_code VARCHAR(20) NOT NULL UNIQUE, + status VARCHAR(20) NOT NULL DEFAULT 'booked', + checked_in TINYINT(1) NOT NULL DEFAULT 0, + checked_in_at DATETIME NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (show_id) REFERENCES shows(id) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4" + ); +} + +function seed_data(): void { + $pdo = db(); + $count = (int)$pdo->query("SELECT COUNT(*) FROM movies")->fetchColumn(); + if ($count > 0) { + return; + } + + $pdo->beginTransaction(); + $stmt = $pdo->prepare("INSERT INTO movies (title, genre, duration_min, rating, description) VALUES (?, ?, ?, ?, ?)"); + $stmt->execute([ + 'Midnight Signal', + 'Sci-Fi Thriller', + 124, + 'PG-13', + 'A space relay operator uncovers a message that redefines humanity’s future.' + ]); + $stmt->execute([ + 'Velvet Avenue', + 'Drama', + 108, + 'PG', + 'A fashion editor returns home to rebuild her family studio and identity.' + ]); + + $movies = $pdo->query("SELECT id, title FROM movies")->fetchAll(); + $now = new DateTimeImmutable('now'); + $showStmt = $pdo->prepare("INSERT INTO shows (movie_id, screen, show_time, format_label, price) VALUES (?, ?, ?, ?, ?)"); + foreach ($movies as $index => $movie) { + $base = $now->modify('+' . ($index * 2 + 1) . ' hours'); + $showStmt->execute([$movie['id'], 'Screen A', $base->format('Y-m-d H:i:s'), '2D', 11.50]); + $showStmt->execute([$movie['id'], 'Screen B', $base->modify('+3 hours')->format('Y-m-d H:i:s'), 'IMAX', 15.00]); + } + $pdo->commit(); +} + +function fetch_movies_with_shows(): array { + $pdo = db(); + $movies = $pdo->query("SELECT * FROM movies ORDER BY created_at DESC")->fetchAll(); + $shows = $pdo->query( + "SELECT s.*, m.title AS movie_title + FROM shows s + JOIN movies m ON m.id = s.movie_id + ORDER BY s.show_time ASC" + )->fetchAll(); + + $grouped = []; + foreach ($shows as $show) { + $grouped[$show['movie_id']][] = $show; + } + + return [$movies, $grouped]; +} + +function fetch_show(int $showId): ?array { + $pdo = db(); + $stmt = $pdo->prepare( + "SELECT s.*, m.title, m.genre, m.duration_min, m.rating, m.description + FROM shows s + JOIN movies m ON m.id = s.movie_id + WHERE s.id = ?" + ); + $stmt->execute([$showId]); + $row = $stmt->fetch(); + return $row ?: null; +} + +function fetch_booked_seats(int $showId): array { + $pdo = db(); + $stmt = $pdo->prepare("SELECT seats FROM bookings WHERE show_id = ? AND status = 'booked'"); + $stmt->execute([$showId]); + $booked = []; + foreach ($stmt->fetchAll() as $row) { + $parts = array_filter(array_map('trim', explode(',', $row['seats']))); + foreach ($parts as $seat) { + $booked[$seat] = true; + } + } + return array_keys($booked); +} + +function generate_booking_code(PDO $pdo): string { + do { + $code = strtoupper(bin2hex(random_bytes(3))); + $stmt = $pdo->prepare("SELECT COUNT(*) FROM bookings WHERE booking_code = ?"); + $stmt->execute([$code]); + } while ((int)$stmt->fetchColumn() > 0); + return $code; +} + +ensure_schema(); +seed_data(); diff --git a/index.php b/index.php index 7205f3d..52165f5 100644 --- a/index.php +++ b/index.php @@ -1,150 +1,159 @@ - New Style - + <?= h($projectName) ?> — Movie Booking + - - - - - - + + - - - - + + + - - + + -
-
-

Analyzing your requirements and generating your website…

-
- Loading… +
+ + +
+
+
+
+ Live seat selection • Real-time confirmations +

A clean, dependable movie booking workflow.

+

Browse showtimes, choose seats, and deliver instant e-tickets. Agents and staff get dedicated tools for walk-ins and check-ins.

+ +
+
+
+
+
+
Today’s shows
+
+
+
+
Movies live
+
+
+
+
+
Updated format('M d, Y H:i')) ?> UTC
+
+
+
+
+ +
+
+

Now showing

+ Select a showtime to continue +
+ +
No movies yet. Add shows from the admin panel.
+ +
+ +
+
+
+
+

+
· min · Rated
+
+ Now +
+

+
+ + + · + + + + No showtimes loaded. + +
+
+
+ +
+
+ +
+
+
+

Agent POS

+

Create walk-in bookings and print confirmation slips.

+ Open agent view +
+
+
+
+

Staff check-in

+

Scan or enter codes to admit guests and track attendance.

+ Open check-in +
+
+
+
+

Admin overview

+

Review all bookings and manage upcoming showtimes.

+ Open admin +
+
+
-
- Page updated: (UTC) + +
+
+ © format('Y')) ?> + Powered by PHP +
+
+ + diff --git a/staff_checkin.php b/staff_checkin.php new file mode 100644 index 0000000..ae63df6 --- /dev/null +++ b/staff_checkin.php @@ -0,0 +1,115 @@ +prepare( + "SELECT b.*, s.show_time, m.title + FROM bookings b + JOIN shows s ON s.id = b.show_id + JOIN movies m ON m.id = s.movie_id + WHERE b.booking_code = ?" + ); + $stmt->execute([$code]); + $booking = $stmt->fetch(); + + if (!$booking) { + $message = 'No booking found for that code.'; + } elseif ($booking['checked_in']) { + $message = 'Guest already checked in.'; + } else { + $update = $pdo->prepare("UPDATE bookings SET checked_in = 1, checked_in_at = NOW() WHERE id = ?"); + $update->execute([$booking['id']]); + $message = 'Check-in successful for ' . $booking['customer_name'] . '.'; + $booking['checked_in'] = 1; + } + } +} +?> + + + + + + <?= h($projectName) ?> — Staff check-in + + + + + + + + + + + + + +
+ + +
+
+
+
+

Staff check-in

+
+
+ + +
+ +
+ +
+ +
+
+
+
+

Latest scan result

+ +

Scan a ticket to see details here.

+ +
+
+
Guest
+
+
+
+
+
Movie
+
+
+
+
+
Seats
+
+
+
+ +
+
+
+
+
+ +