Restricted
Admin Panel
Recent Activity
Last 10 events across all staff
Staff at a Glance
| Name | Last Seen | Submits |
|---|
Access Events Today
| Name | Role | Last Active | Total Submits | Pages Visited | Status | Access |
|---|
| Sheet | Category | Submitted By | Date / Time |
|---|
Access & Security Events
Failed PIN attempts, restricted page hits, and denied actions.
Manage Sheets
Sheets shown on the File Submission page.
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 | Not connected | No backend function exists yet for this section. | Not connected |
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.
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.
Admin Secret Required
A separate credential from Google Sign-In itself, so you can always fix this list even if it's empty or wrong.
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.DRIVE_FOLDER_ID1eTKbrsApgHr80giLLXuCFne5AJzZuZVZ — root submissions folder.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_SECRETA second, separate random secret (same generation method) that unlocks the "Login Access" tab below, where the allowlist of who can sign in is actually managed and stored — no env var edit needed for that.Admin PIN
Change the PIN required to access this page.
Current PIN
Default PIN is 1234. Change it after first login.