I work part-time at my university's IT department with a flexible schedule and felt the need for a simple way to track my daily working hours — to easily know how many hours I have in surplus or deficit at the end of the month.
I'm a first-year Computer Engineering student and, with the help of AI (Claude by Anthropic), I built this application from scratch: a complete web app built on Google Apps Script + Google Sheets, with no external servers or infrastructure costs.
The project grew from a simple hours log into a full tool with an admin panel, leave requests, PDF reports, automatic email summaries, and much more.
- ✅ Daily time logging (up to 3 periods per day)
- 📊 Accumulated balance, statistics and donut chart
- 🏖️ Leave requests with admin approval
- 🔒 Admin can lock schedules or mark mandatory leave
- 💬 Comments per day (shared between employee and admin)
- 📧 Email summary (weekly, monthly or yearly) with PDF attachment
- 📄 Export reports as TXT or PDF (by period and employee)
- 👤 Admin panel with calendar, statistics and user management
- 🔁 Undo/redo changes
- 📱 Responsive interface (mobile)
- 🔐 Persistent session with
localStorage
- Google account (personal or Workspace)
- Google Sheets
- Google Apps Script
- Create a new Google Sheet at sheets.google.com
- Open the menu Extensions → Apps Script
- Paste the contents of
Code.gsinto the editor - Paste the contents of
index.htmlinto a new HTML file (File → New → HTML file)
At the top of Code.gs, adjust the constants:
const START_DATE = '2026-06-01'; // system start date (YYYY-MM-DD)
const DAILY_MINS = 240; // daily workload in minutes (e.g. 240 = 4h, 480 = 8h)In the initializeSheets function, replace the initial admin email and password:
[['admin@example.com', 'Administrator', 'changeme123', 'admin', true, '', '']]
⚠️ Change the password after your first login in the profile settings.
If your city has a specific public holiday, edit it in getPortugueseHolidays:
map[fmtKey(year, 6, 24)] = 'Local Holiday'; // adjust date and name- In Apps Script, run the
setupSpreadsheetfunction once (Run menu) - Accept the permissions requested by Google
- Deploy the app: Deploy → New deployment → Web App
- Execute as: Me
- Who has access: Anyone (or anyone with the link)
- Copy the generated URL — that's your app link
Code.gs — server-side logic (Google Apps Script)
index.html — web interface (HTML + CSS + JS, single file)
Data is stored in sheets inside the Google Sheet:
| Sheet | Contents |
|---|---|
| Users | Users and passwords |
| Entries | Daily time records |
| Comments | Per-day comments |
| Closed | Closed periods (breaks/holidays) |
| Invited | Invited emails (pre-registration) |
- Passwords stored as plain text in Google Sheet (no hashing) — suitable for internal use, not recommended for sensitive data
- Sheet access controlled by Google Drive permissions
- The app runs under the script owner's account — no external credentials exposed
This project was my first real contact with web development and server-side programming. Here are some of the main challenges I faced and what I learned from them:
I learned that Google Apps Script runs on Google's servers and communicates with the web page through google.script.run — meaning each server call takes 1 to 3 seconds. Understanding this was key to knowing why the app was slow and how to optimize it.
I also discovered that Google Sheets automatically converts time values (e.g. "09:00") into Date objects, which caused a hard-to-detect bug: the server response was arriving as null on the client. The fix was to explicitly format all time fields before returning them.
One of the biggest lessons was realizing that each Sheets read is a slow operation. Initially, the code read the same sheet 3 times to get different data (entries, minimum time, maximum time). I learned to consolidate those reads into a single function, which significantly reduced load time.
I also learned to use Apps Script's CacheService to store rarely-changing data (like the user list and closed periods) for 30 seconds, avoiding unnecessary re-reads.
I learned the difference between storing data on the server (Google Sheets) and maintaining state on the client (JavaScript variables). I implemented localStorage to persist the user session across page refreshes — without this, the user would have to log in every time they refreshed the page.
Throughout the project I realized that small interface decisions have a big impact on the experience. For example:
- Auto-save (saving automatically on every time change) seemed convenient but made the app slow — I changed it to save only when the user clicks "Save"
- Decorative undo/redo buttons in the admin panel were confusing because they looked clickable but did nothing — I removed them
- Emoji buttons in the modal were misaligned — I replaced them with consistent SVG icons
- Leave requests with approval: I learned to manage intermediate states (
Pending,Rejected,Approved) on both server and client, and keep the interface in sync with those states - PDF export: I discovered that
DocumentAppin Apps Script doesn't support all the formatting methods the documentation suggests (e.g.body.setMarginsdoesn't exist). The solution was to generate styled HTML with CSS and export it as PDF viaDriveApp - HTML emails: I learned to use
MailAppto send formatted emails with tables and colors, and to attach PDF files
Before publishing on GitHub, I learned the importance of removing sensitive data from the code (real emails, passwords, institution name) and replacing them with generic placeholders, so anyone can use the project without exposing private information.
MIT — free to use, no warranties.








