Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 4 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Personal Travel Log

A lightweight personal travel dashboard for GitHub Pages with itinerary, budget, notes, PWA support and automated Google Flights price tracking.
A lightweight personal travel dashboard for GitHub Pages with itinerary, budget, notes, PWA support with itinerary, booking status, flight operations, budget, notes and optional on-demand price checks.

## Features

Expand All @@ -9,7 +9,7 @@ A lightweight personal travel dashboard for GitHub Pages with itinerary, budget,
- Checklist, budget and notes saved in `localStorage`
- Export/import local travel data
- Light/dark mode and PWA/offline support
- **Google Flights price snapshots through SerpApi + GitHub Actions**
- **On-demand Google Flights price snapshots through SerpApi + GitHub Actions**
- Fare-specific baggage details from Google Flights search and Booking Options
- Direct-flight priority, stops/airline filters, price history and price alerts
- SerpApi credit/usage snapshot written automatically to this README
Expand Down Expand Up @@ -153,15 +153,9 @@ data/api-usage.json

and commits refreshed snapshots back to `main`.

### Automatic refresh
### Price refresh mode

The default schedule is:

```text
07:17 Asia/Ho_Chi_Minh
```

At 2 main search requests per refresh, a 30-day month is roughly **60 Google Flights search requests**, plus manual checks. Booking Options can add up to **240 requests/month** at the default 8 requests/day if it is enabled every day. Actual provider usage is shown in the live SerpApi usage table above and resets when SerpApi starts the next billing cycle.
Automatic flight and hotel price schedules are currently **paused because the trip has been booked**. Both workflows retain `workflow_dispatch`, so price snapshots can still be refreshed manually when needed. Flight-status tracking remains active independently.

### Price dashboard

Expand Down
8 changes: 8 additions & 0 deletions app.css
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,11 @@
@media(max-width:520px){
.timeline-item:not(:last-child)::after{left:55px}
}

/* Post-booking trip mode */
.booking-summary{display:flex;flex-wrap:wrap;gap:8px;margin-top:14px}
.booking-chip{display:inline-flex;align-items:center;min-height:30px;padding:6px 10px;border:1px solid var(--line);border-radius:999px;font-size:12px;font-weight:750;background:var(--surface)}
.booking-chip.success{border-color:rgba(34,197,94,.25);background:rgba(34,197,94,.09)}
.booking-chip.muted{color:var(--muted)}
.booking-dot{display:inline-block;width:8px;height:8px;border-radius:50%;background:#22c55e;box-shadow:0 0 0 4px rgba(34,197,94,.12);margin-right:6px}
@media(max-width:640px){.booking-summary{gap:6px}.booking-chip{font-size:11px;padding:5px 8px}.hero-actions{display:grid;grid-template-columns:1fr 1fr}.hero-actions .btn:first-child{grid-column:1/-1}}
46 changes: 46 additions & 0 deletions dashboard.css
Original file line number Diff line number Diff line change
Expand Up @@ -153,3 +153,49 @@ body:has(#itinerary) .notes-actions{margin-top:11px}
body:has(#itinerary) .checklist,body:has(#itinerary) .budget{padding-left:14px;padding-right:14px}
body:has(#itinerary) .notes-wrap{padding:14px}
}


/* Post-booking dashboard */
body:has(#itinerary) .booking-summary{display:flex;flex-wrap:wrap;gap:7px;margin-top:13px}
body:has(#itinerary) .booking-chip{display:inline-flex;align-items:center;min-height:26px;padding:0 9px;border:1px solid rgba(255,255,255,.18);border-radius:999px;background:rgba(255,255,255,.1);color:#fff;font-size:10.5px;font-weight:700}
body:has(#itinerary) .booking-chip.success{background:rgba(34,197,94,.16)}
body:has(#itinerary) .booking-dot{width:6px;height:6px;margin-right:6px;border-radius:50%;background:#4ade80;box-shadow:0 0 0 3px rgba(74,222,128,.15)}
body:has(#itinerary) .booking-center{grid-column:1/-1;min-width:0}
body:has(#itinerary) .booking-center-grid{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:11px;padding:16px 20px 0}
body:has(#itinerary) .booking-panel{min-width:0;padding:14px;border:1px solid var(--dashboard-border);border-radius:12px;background:var(--surface);display:flex;flex-direction:column;justify-content:space-between;gap:12px}
body:has(#itinerary) .booking-panel-head{display:flex;align-items:flex-start;gap:9px;min-width:0}
body:has(#itinerary) .booking-panel-head>span{font-size:18px;line-height:1.3}
body:has(#itinerary) .booking-panel-head>div{display:grid;gap:3px;min-width:0}
body:has(#itinerary) .booking-panel-head b{font-size:12px}
body:has(#itinerary) .booking-panel-head small{color:var(--muted);font-size:10.5px;line-height:1.45;overflow-wrap:anywhere}
body:has(#itinerary) .booking-panel-actions{display:flex;align-items:center;flex-wrap:wrap;gap:7px}
body:has(#itinerary) .booking-panel .booking-chip{border-color:var(--line);background:var(--surface-2);color:var(--muted)}
body:has(#itinerary) .booking-fields{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:10px;padding:12px 20px 0}
body:has(#itinerary) .booking-fields label{display:grid;min-width:0;gap:5px;color:var(--muted);font-size:10.5px;font-weight:750}
body:has(#itinerary) .booking-fields input{width:100%;min-width:0;height:36px;box-sizing:border-box;padding:0 10px;border:1px solid var(--line);border-radius:9px;background:var(--surface);color:var(--text);font:inherit;outline:none}
body:has(#itinerary) .booking-fields input:focus{border-color:#8eb0f6;box-shadow:0 0 0 3px var(--primary-soft)}
body:has(#itinerary) .booking-save{display:flex;align-items:center;justify-content:space-between;gap:10px;padding:12px 20px 18px;color:var(--muted);font-size:10.5px}

@media(max-width:820px){
body:has(#itinerary) .trip-hero{display:block;min-height:0}
body:has(#itinerary) .hero-copy{padding-right:24px}
body:has(#itinerary) .hero-copy h1,
body:has(#itinerary) .hero-route,
body:has(#itinerary) .hero-meta{max-width:none}
body:has(#itinerary) .countdown{position:relative;right:auto;top:auto;width:auto;min-height:0;height:auto;margin:0 24px 24px;padding:12px 14px;display:flex;align-items:baseline;justify-content:center;gap:7px}
body:has(#itinerary) .countdown strong{margin:0;font-size:28px}
body:has(#itinerary) .countdown b{display:block;margin:0 0 0 auto}
body:has(#itinerary) .booking-center-grid{grid-template-columns:1fr}
}

@media(max-width:520px){
body:has(#itinerary) .hero-copy{padding:20px 16px 16px}
body:has(#itinerary) .hero-actions{display:grid;grid-template-columns:1fr 1fr}
body:has(#itinerary) .hero-actions .btn{width:100%;box-sizing:border-box;white-space:normal;text-align:center;justify-content:center}
body:has(#itinerary) .countdown{margin:0 16px 16px;justify-content:flex-start;flex-wrap:wrap}
body:has(#itinerary) .countdown b{width:100%;margin:0;font-size:9.5px}
body:has(#itinerary) .booking-center-grid{padding:12px 14px 0}
body:has(#itinerary) .booking-fields{grid-template-columns:1fr;padding:10px 14px 0}
body:has(#itinerary) .booking-save{align-items:stretch;flex-direction:column;padding:10px 14px 14px}
body:has(#itinerary) .booking-save .btn{width:100%;justify-content:center}
}
42 changes: 31 additions & 11 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,10 @@
<nav class="side-nav">
<a class="active" href="#top"><span class="nav-icon"><svg><use href="#i-home"/></svg></span>Tổng quan</a>
<a href="#itinerary"><span class="nav-icon"><svg><use href="#i-calendar"/></svg></span>Lịch trình</a>
<a href="./flights/"><span class="nav-icon"><svg><use href="#i-plane"/></svg></span>Giá vé</a>
<a href="./flights/"><span class="nav-icon"><svg><use href="#i-plane"/></svg></span>Chuyến bay</a>
<a href="./flight-status.html"><span class="nav-icon"><svg><use href="#i-status"/></svg></span>Trạng thái</a>
<a href="./hotels/"><span class="nav-icon"><svg><use href="#i-pin"/></svg></span>Giá phòng</a>
<a href="./hotels/"><span class="nav-icon"><svg><use href="#i-pin"/></svg></span>Lưu trú</a>
<a href="#bookings"><span class="nav-icon"><svg><use href="#i-note"/></svg></span>Booking Center</a>
<a href="#budget"><span class="nav-icon"><svg><use href="#i-wallet"/></svg></span>Ngân sách</a>
<a href="#checklist"><span class="nav-icon"><svg><use href="#i-check"/></svg></span>Chuẩn bị</a>
<a href="#notes"><span class="nav-icon"><svg><use href="#i-note"/></svg></span>Ghi chú</a>
Expand All @@ -41,8 +42,8 @@
<select class="language-select" data-language-select aria-label="Ngôn ngữ">
<option value="vi">VI</option><option value="en">EN</option><option value="zh">中文</option><option value="ja">日本語</option>
</select>
<a class="btn sm" href="./hotels/">Giá phòng</a>
<a class="btn sm" href="./flights/">Xem giá vé</a>
<a class="btn sm" href="./hotels/">Lưu trú</a>
<a class="btn sm" href="./flights/">Chuyến bay</a>
<a class="btn sm" href="./flight-status.html">Trạng thái chuyến bay</a>
<button class="icon-btn" data-theme-toggle aria-label="Đổi giao diện"><svg class="ui-icon"><use href="#i-moon"/></svg></button>
</div>
Expand All @@ -61,25 +62,25 @@
<main class="content" id="top">
<section class="trip-hero">
<div class="hero-copy">
<div class="hero-kicker">Chuyến đi sắp tới</div>
<div class="hero-kicker"><span class="booking-dot"></span> Chuyến đi đã được đặt</div>
<h1>Trung Quốc · 19–26/10/2026</h1>
<div class="hero-route">TP.HCM → Thượng Hải → Bắc Kinh → TP.HCM</div>
<div class="hero-meta">8 ngày 7 đêm · 6 người lớn + 1 em bé</div>
<div class="hero-actions"><a class="btn" href="#itinerary">Xem lịch trình</a><a class="btn secondary" href="./flights/">Theo dõi giá vé</a><a class="btn secondary" href="./flight-status.html">Trạng thái chuyến bay</a><a class="btn secondary" href="./hotels/">Theo dõi giá phòng</a></div>
<div class="hero-meta">8 ngày 7 đêm · 6 người lớn + 1 em bé</div><div class="booking-summary"><span class="booking-chip success">✓ Vé máy bay đã đặt</span><span class="booking-chip success">✓ Khách sạn đã đặt</span><span class="booking-chip muted">Theo dõi giá tự động đã tạm dừng</span></div>
<div class="hero-actions"><a class="btn" href="#itinerary">Xem lịch trình</a><a class="btn secondary" href="./flights/">Chi tiết chuyến bay</a><a class="btn secondary" href="./flight-status.html">Trạng thái chuyến bay</a><a class="btn secondary" href="./hotels/">Thông tin lưu trú</a></div>
</div>
<aside class="countdown"><span id="daysPrefix">Còn</span><strong id="daysLeft">—</strong><span id="daysUnit">ngày đến chuyến đi</span><b>Khởi hành 19/10/2026</b></aside>
</section>

<section class="stat-grid">
<article class="card stat-card"><div class="stat-top"><span class="label">Ngân sách dự kiến</span><span class="stat-icon"><svg><use href="#i-wallet"/></svg></span></div><strong id="summaryBudget">Chưa thiết lập</strong><small id="budgetPerPerson">Nhập ngân sách bên dưới</small></article>
<article class="card stat-card"><div class="stat-top"><span class="label">Chuyến bay</span><span class="stat-icon"><svg><use href="#i-plane"/></svg></span></div><strong>2 chặng</strong><small id="flightStatNote">Đang tải dữ liệu giá vé…</small></article>
<article class="card stat-card"><div class="stat-top"><span class="label">Chuyến bay</span><span class="stat-icon"><svg><use href="#i-plane"/></svg></span></div><strong>2 chặng</strong><small id="flightStatNote">Đã đặt · theo dõi trạng thái chuyến bay</small></article>
<article class="card stat-card"><div class="stat-top"><span class="label">Thành phố chính</span><span class="stat-icon"><svg><use href="#i-pin"/></svg></span></div><strong>2</strong><small>Thượng Hải · Bắc Kinh</small></article>
<article class="card stat-card"><div class="stat-top"><span class="label">Việc còn lại</span><span class="stat-icon"><svg><use href="#i-check"/></svg></span></div><strong id="taskCount">7</strong><small id="taskStatus">0/7 đã hoàn thành</small></article>
</section>

<section class="dashboard-grid">
<article class="card" id="itinerary">
<div class="card-head"><h2>Lịch trình chính</h2><a href="./flights/">Xem giá vé</a></div>
<div class="card-head"><h2>Lịch trình chính</h2><a href="./flights/">Chuyến bay</a></div>
<div class="timeline">
<div class="timeline-item"><div class="date-box"><strong>19</strong><span>TH10</span></div><span class="time-dot"></span><div class="timeline-info"><strong>TP.HCM → Thượng Hải <span class="pill success" id="outboundBadge">Đang kiểm tra</span></strong><div class="sub" id="outboundFlightText">Đang tải chuyến bay phù hợp…</div><div class="detail" id="outboundFlightMeta">SGN → PVG / SHA</div></div><div class="timeline-photo shanghai">上海</div></div>
<div class="timeline-item"><div class="date-box"><strong>20</strong><span>TH10</span></div><span class="time-dot"></span><div class="timeline-info"><strong>Thượng Hải</strong><div class="sub">Bến Thượng Hải · Dự Viên · phố cổ</div><div class="detail">Lịch nhẹ ngày đầu, ưu tiên nghỉ ngơi</div></div><div class="timeline-photo shanghai">上海</div></div>
Expand Down Expand Up @@ -118,6 +119,22 @@ <h1>Trung Quốc · 19–26/10/2026</h1>
</article>
</div>

<article class="card booking-center" id="bookings">
<div class="card-head"><div><h2>Booking Center</h2><small>Thông tin cần mở nhanh trong chuyến đi</small></div><span class="pill success">Đã đặt</span></div>
<div class="booking-center-grid">
<section class="booking-panel"><div class="booking-panel-head"><span>✈️</span><div><b>Chuyến bay</b><small>SGN → Shanghai · Beijing → SGN</small></div></div><a class="btn sm" href="./flight-status.html">Theo dõi trạng thái</a></section>
<section class="booking-panel"><div class="booking-panel-head"><span>🏨</span><div><b>Lưu trú</b><small>Shanghai · Beijing</small></div></div><a class="btn sm" href="./hotels/">Xem thông tin khách sạn</a></section>
<section class="booking-panel"><div class="booking-panel-head"><span>🚄</span><div><b>Tàu cao tốc</b><small>Shanghai → Beijing · 21/10/2026</small></div></div><div class="booking-panel-actions"><span class="booking-chip muted">Chưa mở bán</span><a class="btn sm" href="https://www.12306.cn/en/index.html" target="_blank" rel="noopener">China Railway 12306</a></div></section>
</div>
<div class="booking-fields">
<label>Mã đặt chỗ / PNR<input data-booking="pnr" placeholder="Nhập mã booking"></label>
<label>Khách sạn / mã xác nhận<input data-booking="hotel" placeholder="Tên khách sạn hoặc mã xác nhận"></label>
<label>Tàu / số hiệu ghế<input data-booking="train" placeholder="VD: Gxx · Coach 05 · Seat 08A"></label>
<label>Ghi chú quan trọng<input data-booking="important" placeholder="Ga, terminal, giờ check-in..."></label>
</div>
<div class="booking-save"><span>Dữ liệu lưu cục bộ trên thiết bị này.</span><button class="btn sm" id="copyBookings">Sao chép Booking Center</button></div>
</article>

<article class="card notes-card" id="notes">
<div class="card-head"><h3>Ghi chú</h3><span class="pill">Lưu trên thiết bị này</span></div>
<div class="notes-wrap"><textarea id="tripNotes" placeholder="Địa chỉ khách sạn, mã đặt chỗ, quán ăn, chi tiết tàu, điều cần nhớ..."></textarea><div class="notes-actions"><button class="btn sm" id="copyNotes">Sao chép</button><button class="btn sm" id="exportData">Xuất dữ liệu</button><label class="btn sm" for="importData">Nhập dữ liệu<input id="importData" type="file" accept="application/json" hidden></label><span class="save-state" id="saveState">Sẵn sàng</span></div></div>
Expand All @@ -130,8 +147,8 @@ <h1>Trung Quốc · 19–26/10/2026</h1>
<nav class="mobile-bottom">
<a class="active" href="#top"><span class="nav-icon"><svg><use href="#i-home"/></svg></span>Tổng quan</a>
<a href="./flight-status.html"><span class="nav-icon"><svg><use href="#i-status"/></svg></span>Trạng thái</a>
<a href="./flights/"><span class="nav-icon"><svg><use href="#i-plane"/></svg></span>Giá vé</a>
<a href="./hotels/"><span class="nav-icon"><svg><use href="#i-pin"/></svg></span>Giá phòng</a>
<a href="./flights/"><span class="nav-icon"><svg><use href="#i-plane"/></svg></span>Chuyến bay</a>
<a href="./hotels/"><span class="nav-icon"><svg><use href="#i-pin"/></svg></span>Lưu trú</a>
</nav>

<svg width="0" height="0" style="position:absolute;overflow:hidden" aria-hidden="true">
Expand Down Expand Up @@ -269,5 +286,8 @@ <h1>Trung Quốc · 19–26/10/2026</h1>
if('serviceWorker'in navigator)window.addEventListener('load',()=>navigator.serviceWorker.register('./sw.js').catch(()=>{}));
})();
</script>
<script>
(()=>{const key="travel-booking-center-v1";const fields=[...document.querySelectorAll("[data-booking]")];let saved={};try{saved=JSON.parse(localStorage.getItem(key)||"{}")}catch{}fields.forEach(el=>{el.value=saved[el.dataset.booking]||"";el.addEventListener("input",()=>{const data=Object.fromEntries(fields.map(x=>[x.dataset.booking,x.value]));localStorage.setItem(key,JSON.stringify(data))})});document.getElementById("copyBookings")?.addEventListener("click",async()=>{const lines=fields.filter(x=>x.value.trim()).map(x=>`${x.closest("label")?.childNodes[0]?.textContent?.trim()}: ${x.value.trim()}`);if(!lines.length)return;try{await navigator.clipboard.writeText(lines.join("\\n"))}catch{}})})();
</script>
</body>
</html>
Loading