Restricted
Admin Panel
Recent Activity
Last 10 events across all staff
Staff at a Glance
| Name | Last Seen |
|---|
Access Events Today
Read-only, always
Standing rule for this integration.
The dashboard's Google service account is only ever allowed to read sheet data — it can never write, edit, or delete anything in the source spreadsheets. This is enforced at the API level: every request uses the
https://www.googleapis.com/auth/spreadsheets.readonly
OAuth scope, defined once in netlify/functions/_utils/sheets.js and shared by every function below. A read-only-scoped credential is physically incapable of making write calls — Google's API rejects them before they reach the sheet, regardless of what the function code asks for.
Sheet & Tab Mapping
Exactly which Google Sheet and tab each dashboard section reads from.
| Dashboard Section | Source Google Sheet | Tab(s) Used | Status |
|---|---|---|---|
| Sales | Production Indicator 2026 1506Oa_sb0hxRTz6SfSsztyrDE_iQYfTCBgAR_2fAVCE |
One tab per calendar date (e.g. "10 August") — reads the latest 14 date-tabs for the trend chart, and every date-tab in the current month for MTD totals. | Live |
| Customer Service | CS Ticketing 1-avzvz05DBg7SStJ98HRnc_8gQA0aw0-3Z8jdVlOmtA |
"Dashboard HCN" (ticket list) + "Productivity" (SLA %). Auto-detected by tab name at request time, not hardcoded. | Live |
| Finance | Revman — Revenue Production Report 1VEZyzwldJmicBYdmWPwkQeGeH4L0v6MW4ahrNqdWWKU |
"Summary by Hotel - 2026 (MTD)" — scans for the "Top 10 Hotels (Month)" block matching the current month. | Live · partial |
| Revenue (rate comparison) | IMAGTC — Daily Rate Comparison 1DWHLGgqqHDGZ9sAVkgCe-rH2hpmNLZO3LYipQIoIK5I |
Most recent date-named tab (e.g. "11 August"). "Hotel List" tab is skipped — it has no rate data. | Live · partial |
| Marketing | Marketing Drive folder (Meta Business Suite export) 1LHJaAo97p2VwocnsZjJGj6BIYNAmoE9H |
Picks the most recently modified spreadsheet in the folder (currently an .xlsx export, parsed with SheetJS) instead of a fixed sheet ID — a new export just needs to be dropped in the folder each period. | Live · single period, no Facebook breakdown |
| Contracting | 2026 Acquisition Report — Imagination 1sst0hs3cEu_efrc-vFcSlY-O2cIdYsvqPWx4onspv70 |
"Target & Achievement" (budget/achievement KPIs + PIC leaderboard), "Summary" (deal-type monthly mix), and the current "UPD - <Month> <Year>" tab (recent activity). | Live · partial |
Known Caveats
Honest limitations in the current numbers, so nobody mistakes them for more than they are.
Finance — the source sheet only lists the top 10 hotels per month, so Revenue MTD / Room Nights MTD / ADR are top-10 totals, not full-portfolio totals. A few hotels are priced in MYR instead of IDR; those are excluded from the IDR totals but still shown in the property table.
Revenue — the source sheet has no Traveloka column. "Rate Competitiveness" compares Imagination's IMAGTC rate against Agoda (the real OTA column present in the sheet); MG is also read as a secondary reference. Variance is calculated directly from IMAGTC vs Agoda, not from the sheet's own precomputed variance columns, which are based on a separate "5% Temporary" markup rate.
Sales — each calendar date is its own tab rather than a date column, so "bookings" are row counts within a date's tab and "active agents" are distinct "API Client" values seen this month.
Contracting — Budget/Achievement figures come from the "Target & Achievement" tab's own team-total row. Deal-type monthly counts (Static/Dynamic/Renewal) are re-summed in code from each PIC's rows on the "Summary" tab, not from that sheet's own bottom total rows, which don't reconcile with their own monthly figures. "Recent activity" has no real timestamp to sort by (the sheet's Day/Date column is almost always left blank), so it's ordered by row position on the current month's tab instead — a proxy for recency, not a guarantee.
Revenue — the source sheet has no Traveloka column. "Rate Competitiveness" compares Imagination's IMAGTC rate against Agoda (the real OTA column present in the sheet); MG is also read as a secondary reference. Variance is calculated directly from IMAGTC vs Agoda, not from the sheet's own precomputed variance columns, which are based on a separate "5% Temporary" markup rate.
Sales — each calendar date is its own tab rather than a date column, so "bookings" are row counts within a date's tab and "active agents" are distinct "API Client" values seen this month.
Contracting — Budget/Achievement figures come from the "Target & Achievement" tab's own team-total row. Deal-type monthly counts (Static/Dynamic/Renewal) are re-summed in code from each PIC's rows on the "Summary" tab, not from that sheet's own bottom total rows, which don't reconcile with their own monthly figures. "Recent activity" has no real timestamp to sort by (the sheet's Day/Date column is almost always left blank), so it's ordered by row position on the current month's tab instead — a proxy for recency, not a guarantee.
Home Page Notices
Pinned/update/reminder cards shown on the Home page's Notices panel. Live as soon as you post — no push needed.
Loading…
Who Can Sign In
Only these Google accounts can access the dashboard. Use the "Tabs" dropdown on each row to control which sections they can see — no redeploy needed, but a tab change only reaches someone the next time they sign in (sessions last up to 7 days).
Loading…
API Connection Status
Google Sheets API
Not tested
Google Drive API
Not tested
Netlify Functions
Not tested
Environment Variables
GOOGLE_SERVICE_ACCOUNTFull JSON key. Required for Sheets + Drive API calls.GOOGLE_CLIENT_IDOAuth Web Client ID (from Google Cloud Console, separate from the service account). Not secret — also hardcoded into index.html for the sign-in button.SESSION_SECRETRandom secret used to sign the dashboard's own login session cookie. Generate with e.g. openssl rand -hex 32 — never share this value.ADMIN_SECRETNo longer used — Login Access, Notices, and clearing the Activity Log are now gated by a signed-in session's Admin tab access instead of a second password. Safe to delete this variable from your Cloudflare Pages settings whenever you like.Admin PIN
Change the PIN required to access this page.
Current PIN
Default PIN is 1234. Change it after first login.