Dual-Purpose Business Web Application — Marketing site, bilingual worker timesheet portal, and full admin suite with PDF invoicing
Modular Systems Solutions is a full-stack business application built for a field assembly and installation company. It serves three distinct audiences from a single codebase: the public (marketing site), field workers (timesheet portal), and management (admin suite).
Field workers — many of whom are Spanish-speaking — submit daily timesheets through a bilingual portal that auto-calculates regular and overtime hours, emails confirmations, and stores submissions to a relational database. Administrators review, edit, approve, and reject submissions through a full dashboard, then group approved work into invoices with PDF generation and SMTP email delivery. Every admin edit is logged as a full revision snapshot for a complete audit trail.
Built entirely without a framework — raw PHP, PDO, and Vanilla JS on shared hosting. Every dependency was chosen deliberately for reliability and ease of deployment.
No framework, no build step, no package manager — just PHP files deployed via SCP. This was a deliberate constraint for the client's shared hosting environment and IT comfort level. The result is a fully functional, multi-user business application with zero external runtime dependencies.
Three distinct systems built into a single, cohesive application.
submission_revisionsThe engineering decisions that made this a non-trivial business application to build.
The timesheet portal serves both English and Spanish-speaking field workers. Language switching is handled entirely in Vanilla JS using a translation object keyed by element IDs — no i18n library, no server round-trip. The selected language persists in localStorage so workers don't have to re-select on each visit. Every visible string, label, placeholder, and error message has a translation entry.
Invoices are generated on the fly using FPDF — a pure-PHP PDF library with no external process dependencies. The PDF includes a company header, structured Bill-To block, period, line items (Regular Hours, OT Hours, Total Due), and a submission breakdown table with dates and job sites. Once generated, the PDF is attached and sent to the client via PHPMailer over SMTP SSL. Every send attempt is logged to invoice_email_log with the SMTP Message-ID, enabling support tracing for any delivery dispute.
Before any admin edit to a submission is saved, the current state is captured in full and written to submission_revisions. This means every version of a submission is recoverable — who changed what, and when. For a payroll-adjacent application where hours data has financial consequences, this audit trail is a hard requirement. The revision table stores the complete row state rather than a diff, keeping replay logic simple and reliable.
All admin POST handlers follow the Post-Redirect-Get pattern to prevent duplicate form submissions on browser refresh. On shared cPanel hosting, this requires discipline: header() calls must happen before any output — including the shared header.php layout that starts the HTML immediately. Every admin POST handler validates, writes to the database, then issues a redirect before including any layout files.
The timesheet's hours table is built entirely with Vanilla JS event delegation — rows are added and removed dynamically, and each row's regular vs. overtime split is calculated in real time as the worker types. Federal OT rules (over 40 hours/week) are enforced client-side with server-side validation on submit. The job site, supervisor, leadman, PO, and order fields use <datalist> autocomplete populated from the worker's past approved submissions — reducing data entry errors on repetitive job site work.
The admin portal is fully usable on a phone — a requirement for management reviewing submissions in the field. A hamburger icon toggles a sidebar drawer with an overlay on screens 768px and below. Stat tiles collapse from a 4-column to a 2-column grid. Data tables scroll horizontally within cards rather than reflowing. Detail page side panels stack to a single column. All touch targets meet minimum size guidelines.
The hardest problems encountered during development, and how they were resolved.
Supporting English and Spanish for field workers who may switch mid-session required translating every visible string dynamically. Building a lightweight translation system in Vanilla JS — keyed by element IDs, driven by a single translation object — avoided adding an i18n library to a no-dependency stack. The language preference is written to localStorage on toggle and applied on every page load, with no server involvement after the initial page render.
FPDF gives you a blank canvas and a cursor — no layout engine, no auto-sizing. Building a professional invoice PDF required calculating every column width, line height, and page break manually. The submissions breakdown table needed to handle variable row counts without overflowing the page. Header and footer blocks needed consistent positioning regardless of how many line items appeared. The result is a clean, client-ready PDF indistinguishable from one produced by dedicated invoicing software.
Invoices are assembled from approved submissions. If an admin subsequently un-approves or rejects a submission, it must be automatically removed from any draft invoices that contain it — otherwise the invoice total would be wrong. The rejection handler queries for draft invoices containing the affected submission and removes the relationship in the same transaction. Sent invoices are deliberately left intact as a historical record.
The hosting environment enforces strict JS sandboxing: no eval(), no new Function(), no smart quotes, and no inline event handlers. All interactivity had to be wired through addEventListener() and event delegation on parent containers. The dynamic timesheet table — which adds, removes, and recalculates rows — was built entirely within these constraints using standard DOM APIs and delegated input events.
From internal tools to client-facing portals — we build practical software that fits your workflow.