This MVP runs on Google Apps Script + Google Sheets with optional Slack notifications.
- Create a new Google Sheet named
EV Charging. - Copy the Spreadsheet ID from the URL.
- In the Apps Script project, set Script Properties:
SPREADSHEET_ID= your sheet ID
- Go to https://script.google.com and create a new project.
- In the project, create files matching the contents of:
apps-script/Code.gsapps-script/index.htmlapps-script/styles.htmlapps-script/script.htmlapps-script/appsscript.json
- In Apps Script, open Project Settings and paste the
appsscript.jsoncontents into the manifest.
- In Apps Script, run
initSheets()once. - In the Google Sheet, populate the
chargerstab with rows like:charger_id:1name:Charger 1max_minutes:120
This creates these tabs: chargers, sessions, config, reservations.
Use the config tab (key/value pairs) or Script Properties.
Recommended keys:
allowed_domain:example.comapp_name:EV Chargingslack_channel_name:ev-chargingslack_channel_url:https://your-workspace.slack.com/archives/CHANNEL_IDadmin_emails:you@example.com,ops@example.comui_version:v1orv2ui_v2_allowlist: comma-separated emails for v2 accessoverdue_repeat_minutes:15session_move_grace_minutes:10slack_webhook_url: webhook URL for a channel (optional)slack_webhook_channel: channel override (optional)slack_bot_token: Slack bot token for DMs (optional)reservation_advance_days:7reservation_max_upcoming:3reservation_max_per_day:1reservation_gap_minutes:1reservation_rounding_minutes:15reservation_checkin_early_minutes:5reservation_early_start_minutes:90reservation_late_grace_minutes:30reservation_open_hour:6reservation_open_minute:0walkup_net_new_window_minutes:10walkup_returning_window_minutes:10
Script Properties equivalents:
ALLOWED_DOMAINAPP_NAMESLACK_CHANNEL_NAMESLACK_CHANNEL_URLADMIN_EMAILSUI_VERSIONUI_V2_ALLOWLISTOVERDUE_REPEAT_MINUTESSESSION_MOVE_GRACE_MINUTESSLACK_WEBHOOK_URLSLACK_WEBHOOK_CHANNELSLACK_BOT_TOKENRESERVATION_ADVANCE_DAYSRESERVATION_MAX_UPCOMINGRESERVATION_MAX_PER_DAYRESERVATION_GAP_MINUTESRESERVATION_ROUNDING_MINUTESRESERVATION_CHECKIN_EARLY_MINUTESRESERVATION_EARLY_START_MINUTESRESERVATION_LATE_GRACE_MINUTESRESERVATION_OPEN_HOURRESERVATION_OPEN_MINUTEWALKUP_NET_NEW_WINDOW_MINUTESWALKUP_RETURNING_WINDOW_MINUTES
- Create a Slack app with an Incoming Webhook.
- Copy the webhook URL into
slack_webhook_url.
- Create a Slack app with OAuth scopes:
users:read.emailconversations:writechat:write
- Install the app to your workspace.
- Copy the Bot User OAuth Token into
slack_bot_token.
Option A (recommended): run the helper function once.
- In Apps Script, run
installReminderTrigger()to install a 5-minute trigger. - (Optional) Run
installReminderTriggerEveryMinute()if you prefer 1-minute cadence.
Option B (manual):
- In Apps Script, open Triggers.
- Add a time-driven trigger:
- Function:
sendReminders - Run every 5 minutes (recommended) or every minute (more immediate)
- Function:
- Click Deploy -> New deployment -> Web app.
- Set Execute as: User accessing the web app.
- Set Who has access: Anyone within your Google Workspace domain.
- Copy the web app URL and share internally.
- Install clasp and log in (
npm i -g @google/clasp,clasp login). - Create a local
.clasp.jsonwith your Apps ScriptscriptIdandrootDir: "apps-script". - Keep
.clasp.jsonout of git (it contains identifiers) and runclasp pushto sync changes.
The app has two modes:
- Now (default): shows charger cards and a single primary action per charger.
- Reserve: shows next available slots across chargers, plus My reservations.
UI versioning (feature flags):
- URL override:
?v=2loads v2. - Allowlist:
ui_v2_allowlistcontains the user email. - Global default:
ui_versionisv2. - Fallback: v1.
On mobile, the mode switch appears as a bottom tab bar and a sticky action bar for the primary action.
Admins are defined by admin_emails. Admins will see:
- Force end to stop an active session
- Reset charger to clear stuck sessions
Standard users can tap Notify owner on in-use or overdue chargers.
- When a user ends a charging session, the matching checked-in reservation is automatically marked
complete. - If a checked-in reservation has no matching active session, the UI offers to clear the checked-in reservation.
- Slots are rounded up to 15-minute increments.
- Reservations are currently same-day only.
- Check-in opens near the start time, but early start can be allowed via config.
- Early start: if the charger is free, a user can start their reservation up to
reservation_early_start_minutesearly (default 90). - Prior reservation protection: early starts are blocked while a prior reservation is still within its no-show grace window.
- No-show after
reservation_late_grace_minutes(default 30) releases the reservation and notifies the user.
Reserve mode uses the Next available list (earliest slots across chargers) as the primary booking UI.