39498-vm/index.php
2026-04-06 05:35:15 +00:00

266 lines
15 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
declare(strict_types=1);
require_once __DIR__ . '/app.php';
ensure_schema();
$errors = [];
$input = [
'passenger_name' => '',
'pickup_point' => '',
'destination_area' => 'marina',
'pickup_time' => (new DateTimeImmutable('+20 minutes'))->format('Y-m-d\TH:i'),
'source_channel' => 'hotel',
'party_size' => '2',
'notes' => '',
];
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$input['passenger_name'] = trim((string) ($_POST['passenger_name'] ?? ''));
$input['pickup_point'] = trim((string) ($_POST['pickup_point'] ?? ''));
$input['destination_area'] = trim((string) ($_POST['destination_area'] ?? ''));
$input['pickup_time'] = trim((string) ($_POST['pickup_time'] ?? ''));
$input['source_channel'] = trim((string) ($_POST['source_channel'] ?? ''));
$input['party_size'] = trim((string) ($_POST['party_size'] ?? '1'));
$input['notes'] = trim((string) ($_POST['notes'] ?? ''));
if ($input['passenger_name'] === '') {
$errors['passenger_name'] = 'Add a passenger or booking reference.';
}
if ($input['pickup_point'] === '') {
$errors['pickup_point'] = 'Pickup point is required.';
}
if (!array_key_exists($input['destination_area'], destination_options())) {
$errors['destination_area'] = 'Choose a valid destination area.';
}
$dateTime = DateTimeImmutable::createFromFormat('Y-m-d\TH:i', $input['pickup_time']);
if (!$dateTime) {
$errors['pickup_time'] = 'Choose a valid pickup time.';
} else {
$input['pickup_time'] = $dateTime->format('Y-m-d H:i:s');
}
if (!array_key_exists($input['source_channel'], source_channel_options())) {
$errors['source_channel'] = 'Choose a valid source channel.';
}
$partySize = filter_var($input['party_size'], FILTER_VALIDATE_INT, ['options' => ['min_range' => 1, 'max_range' => 8]]);
if ($partySize === false) {
$errors['party_size'] = 'Party size must be between 1 and 8.';
}
$input['party_size'] = (string) ($partySize ?: 1);
if ($errors === []) {
$requestId = create_taxi_request([
'passenger_name' => $input['passenger_name'],
'pickup_point' => $input['pickup_point'],
'destination_area' => $input['destination_area'],
'pickup_time' => $input['pickup_time'],
'source_channel' => $input['source_channel'],
'party_size' => (int) $input['party_size'],
'notes' => $input['notes'],
]);
header('Location: /request_success.php?id=' . $requestId . '&created=1');
exit;
}
}
$stats = fetch_dashboard_stats();
$recentRequests = fetch_requests(6);
$heroDescription = 'Confirm a taxi request and immediately surface the top 23 contextual offers with simple booking attribution.';
render_head(app_name() . ' | Taxi request workflow', $heroDescription);
render_header('home');
?>
<section class="section-block section-hero">
<div class="container">
<div class="row g-4 align-items-start">
<div class="col-lg-7">
<div class="hero-panel">
<div class="eyebrow">Initial MVP slice · tourist flow</div>
<h1 class="display-title">Taxi confirmation with immediate upsell recommendations.</h1>
<p class="lead-copy"><?= h($heroDescription) ?></p>
<div class="d-flex flex-wrap gap-2 mt-4">
<span class="chip"><i class="bi bi-check2-circle"></i> Taxi request</span>
<span class="chip"><i class="bi bi-stars"></i> Top-3 recommendations</span>
<span class="chip"><i class="bi bi-journal-check"></i> Booking attribution</span>
</div>
<div class="metric-grid mt-4">
<article class="metric-card">
<span class="metric-label">Requests</span>
<strong><?= (int) $stats['total_requests'] ?></strong>
<small>captured end-to-end</small>
</article>
<article class="metric-card">
<span class="metric-label">CTR</span>
<strong><?= h(number_format((float) $stats['ctr'], 1)) ?>%</strong>
<small>offer click-through</small>
</article>
<article class="metric-card">
<span class="metric-label">Bookings</span>
<strong><?= (int) $stats['bookings'] ?></strong>
<small>from taxi flow</small>
</article>
<article class="metric-card">
<span class="metric-label">Avg. delay</span>
<strong><?= $stats['avg_delay'] !== null ? (int) $stats['avg_delay'] . 'm' : '—' ?></strong>
<small>request to booking</small>
</article>
</div>
</div>
</div>
<div class="col-lg-5">
<div class="card-panel shadow-sm">
<div class="card-head">
<div>
<h2 class="h5 mb-1">Create taxi request</h2>
<p class="text-secondary mb-0">Save the ride context and instantly generate contextual offers.</p>
</div>
<span class="status-pill">Live MVP</span>
</div>
<?php if ($errors !== []): ?>
<div class="alert alert-danger mt-3 mb-0" role="alert">Please review the highlighted fields and try again.</div>
<?php endif; ?>
<form method="post" class="row g-3 mt-1" novalidate>
<div class="col-12">
<label for="passenger_name" class="form-label">Passenger / booking reference</label>
<input type="text" class="form-control <?= isset($errors['passenger_name']) ? 'is-invalid' : '' ?>" id="passenger_name" name="passenger_name" value="<?= h($input['passenger_name']) ?>" placeholder="e.g. Room 204 · Sofia M.">
<?php if (isset($errors['passenger_name'])): ?><div class="invalid-feedback"><?= h($errors['passenger_name']) ?></div><?php endif; ?>
</div>
<div class="col-12">
<label for="pickup_point" class="form-label">Pickup point</label>
<input type="text" class="form-control <?= isset($errors['pickup_point']) ? 'is-invalid' : '' ?>" id="pickup_point" name="pickup_point" value="<?= h($input['pickup_point']) ?>" placeholder="Hotel lobby, gate, cruise terminal...">
<?php if (isset($errors['pickup_point'])): ?><div class="invalid-feedback"><?= h($errors['pickup_point']) ?></div><?php endif; ?>
</div>
<div class="col-md-6">
<label for="destination_area" class="form-label">Destination area</label>
<select class="form-select <?= isset($errors['destination_area']) ? 'is-invalid' : '' ?>" id="destination_area" name="destination_area">
<?php foreach (destination_options() as $value => $label): ?>
<option value="<?= h($value) ?>" <?= $input['destination_area'] === $value ? 'selected' : '' ?>><?= h($label) ?></option>
<?php endforeach; ?>
</select>
<?php if (isset($errors['destination_area'])): ?><div class="invalid-feedback"><?= h($errors['destination_area']) ?></div><?php endif; ?>
</div>
<div class="col-md-6">
<label for="pickup_time" class="form-label">Pickup time</label>
<input type="datetime-local" class="form-control <?= isset($errors['pickup_time']) ? 'is-invalid' : '' ?>" id="pickup_time" name="pickup_time" value="<?= h(str_replace(' ', 'T', substr($input['pickup_time'], 0, 16))) ?>">
<?php if (isset($errors['pickup_time'])): ?><div class="invalid-feedback"><?= h($errors['pickup_time']) ?></div><?php endif; ?>
</div>
<div class="col-md-6">
<label for="source_channel" class="form-label">Source channel</label>
<select class="form-select <?= isset($errors['source_channel']) ? 'is-invalid' : '' ?>" id="source_channel" name="source_channel">
<?php foreach (source_channel_options() as $value => $label): ?>
<option value="<?= h($value) ?>" <?= $input['source_channel'] === $value ? 'selected' : '' ?>><?= h($label) ?></option>
<?php endforeach; ?>
</select>
<?php if (isset($errors['source_channel'])): ?><div class="invalid-feedback"><?= h($errors['source_channel']) ?></div><?php endif; ?>
</div>
<div class="col-md-6">
<label for="party_size" class="form-label">Party size</label>
<input type="number" min="1" max="8" class="form-control <?= isset($errors['party_size']) ? 'is-invalid' : '' ?>" id="party_size" name="party_size" value="<?= h($input['party_size']) ?>">
<?php if (isset($errors['party_size'])): ?><div class="invalid-feedback"><?= h($errors['party_size']) ?></div><?php endif; ?>
</div>
<div class="col-12">
<label for="notes" class="form-label">Context notes <span class="text-secondary">(optional)</span></label>
<textarea class="form-control" id="notes" name="notes" rows="3" placeholder="Family, mobility needs, preferred cuisine, celebration, arrival context..."><?= h($input['notes']) ?></textarea>
</div>
<div class="col-12 d-grid">
<button type="submit" class="btn btn-dark btn-lg">Confirm taxi and generate offers</button>
</div>
</form>
</div>
</div>
</div>
</div>
</section>
<section class="section-block border-top bg-white">
<div class="container">
<div class="section-heading">
<div>
<div class="eyebrow">Workflow</div>
<h2 class="section-title">The first thin slice follows the real tourist journey.</h2>
</div>
<a href="/requests.php" class="btn btn-outline-dark">Open operations board</a>
</div>
<div class="row g-3 mt-1">
<div class="col-md-4">
<article class="step-card h-100">
<span class="step-index">01</span>
<h3>Capture the taxi request</h3>
<p>Store pickup, destination, time, party size, and channel using a focused intake form.</p>
</article>
</div>
<div class="col-md-4">
<article class="step-card h-100">
<span class="step-index">02</span>
<h3>Generate contextual offers</h3>
<p>Score nearby restaurants and experiences by geography, timing, quality, and strategic value.</p>
</article>
</div>
<div class="col-md-4">
<article class="step-card h-100">
<span class="step-index">03</span>
<h3>Book and attribute</h3>
<p>Confirm the best offer in one click and keep a simple log for CTR and taxi-to-booking conversion.</p>
</article>
</div>
</div>
</div>
</section>
<section class="section-block border-top">
<div class="container">
<div class="section-heading">
<div>
<div class="eyebrow">Recent activity</div>
<h2 class="section-title">Live requests and current booking outcomes.</h2>
</div>
</div>
<div class="card-panel p-0 overflow-hidden">
<?php if ($recentRequests === []): ?>
<div class="empty-state text-center">
<div class="empty-icon"><i class="bi bi-car-front"></i></div>
<h3>No requests yet</h3>
<p>Submit the first taxi flow above to populate the operations board and recommendation funnel.</p>
</div>
<?php else: ?>
<div class="table-responsive">
<table class="table table-slim align-middle mb-0">
<thead>
<tr>
<th>Passenger</th>
<th>Destination</th>
<th>Channel</th>
<th>Status</th>
<th>Created</th>
<th></th>
</tr>
</thead>
<tbody>
<?php foreach ($recentRequests as $request): ?>
<tr>
<td>
<div class="fw-semibold"><?= h($request['passenger_name']) ?></div>
<div class="text-secondary small"><?= h($request['pickup_point']) ?></div>
</td>
<td><?= h(destination_label((string) $request['destination_area'])) ?></td>
<td><?= h(source_channel_label((string) $request['source_channel'])) ?></td>
<td><span class="badge rounded-pill <?= h(status_badge_class((string) $request['status'])) ?>"><?= h(str_replace('_', ' ', (string) $request['status'])) ?></span></td>
<td><?= h(date('d M H:i', strtotime((string) $request['created_at']))) ?></td>
<td class="text-end"><a href="/request_detail.php?id=<?= (int) $request['id'] ?>" class="btn btn-sm btn-outline-dark">Detail</a></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<?php endif; ?>
</div>
</div>
</section>
<?php render_footer(); ?>