Angular + Go + PostgreSQL sample with strict per-tenant isolation. - Backend: Go API (basic auth, tenant-scoped repository layer, pgx pool) - Frontend: Angular 22 SPA (search, checkout, bookings, dashboard) - Database: schema, seed data and demo bookings migrations - Tests: 311 across Go (integration), Angular (unit) and Playwright (e2e), plus a generated evidence report with PDF export - Security: whitebox VAPT report under security/ Secrets (.env, tokens, DB credentials) are git-ignored. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
106 lines
3.1 KiB
HTML
106 lines
3.1 KiB
HTML
<div class="page-head">
|
|
<div>
|
|
<h1>Bookings</h1>
|
|
<p class="subtitle">Every reservation sold by your agency.</p>
|
|
</div>
|
|
<a routerLink="/search" class="btn btn-primary btn-sm">+ New booking</a>
|
|
</div>
|
|
|
|
<div class="filters" role="group" aria-label="Filter by status">
|
|
<button
|
|
type="button"
|
|
class="filter"
|
|
[class.active]="status() === ''"
|
|
(click)="filterBy('')"
|
|
>
|
|
All
|
|
</button>
|
|
<button
|
|
type="button"
|
|
class="filter"
|
|
[class.active]="status() === 'confirmed'"
|
|
(click)="filterBy('confirmed')"
|
|
>
|
|
Confirmed
|
|
</button>
|
|
<button
|
|
type="button"
|
|
class="filter"
|
|
[class.active]="status() === 'cancelled'"
|
|
(click)="filterBy('cancelled')"
|
|
>
|
|
Cancelled
|
|
</button>
|
|
</div>
|
|
|
|
@if (error()) {
|
|
<div class="alert alert-error" role="alert">{{ error() }}</div>
|
|
}
|
|
|
|
@if (loading()) {
|
|
<div class="card card-pad"><div class="skeleton" style="height: 260px"></div></div>
|
|
} @else if (bookings().length === 0) {
|
|
<div class="card empty">
|
|
<h3>No bookings yet</h3>
|
|
<p>Search for a flight to create the first reservation.</p>
|
|
<p style="margin-top: 14px">
|
|
<a routerLink="/search" class="btn btn-primary btn-sm">Search flights</a>
|
|
</p>
|
|
</div>
|
|
} @else {
|
|
<div class="card table-wrap">
|
|
<table class="data">
|
|
<thead>
|
|
<tr>
|
|
<th>Reference</th>
|
|
<th>Route</th>
|
|
<th>Departure</th>
|
|
<th>Travellers</th>
|
|
<th>Total</th>
|
|
<th>Status</th>
|
|
<th>Booked by</th>
|
|
<th></th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
@for (booking of bookings(); track booking.id) {
|
|
<tr>
|
|
<td><span class="pnr">{{ booking.bookingReference }}</span></td>
|
|
<td>
|
|
<span class="route">
|
|
{{ booking.flight.origin.iataCode }} → {{ booking.flight.destination.iataCode }}
|
|
</span>
|
|
<span class="route-meta">
|
|
{{ booking.flight.airline.iataCode }} {{ booking.flight.flightNumber }}
|
|
</span>
|
|
</td>
|
|
<td class="tabular">{{ booking.flight.departureTime | date: 'd MMM y, HH:mm' }}</td>
|
|
<td class="tabular">{{ booking.passengerCount }}</td>
|
|
<td class="tabular">
|
|
{{ booking.totalAmount | currency: booking.currency : 'symbol' : '1.2-2' }}
|
|
</td>
|
|
<td>
|
|
<span class="badge" [class]="'badge-' + booking.status">{{ booking.status }}</span>
|
|
</td>
|
|
<td class="muted">{{ booking.bookedBy }}</td>
|
|
<td class="right">
|
|
<a [routerLink]="['/bookings', booking.id]" class="btn btn-ghost btn-sm">View</a>
|
|
</td>
|
|
</tr>
|
|
}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
|
|
@if (canPrev() || canNext()) {
|
|
<nav class="pager" aria-label="Pagination">
|
|
<button type="button" class="btn btn-ghost btn-sm" [disabled]="!canPrev()" (click)="prevPage()">
|
|
← Previous
|
|
</button>
|
|
<button type="button" class="btn btn-ghost btn-sm" [disabled]="!canNext()" (click)="nextPage()">
|
|
Next →
|
|
</button>
|
|
</nav>
|
|
}
|
|
}
|