PRJ-02Large-scale System

TMaTS: PUP CpE Thesis Management and Tracking System

TMaTS: PUP CpE Thesis Management and Tracking System

Role

Lead Backend Developer & Integration Engineer

Team

16 members

Period

December 2025 – January 2026

Status

Prototype

Tech Stack

Laravel 12, React 19, Inertia.js, TypeScript, Tailwind CSS, MySQL, Pest

Tools

Laravel Fortify, Laravel Wayfinder, Vite, Radix UI, GitHub Actions, Figma

“A web system that centralizes the PUP Computer Engineering thesis process, from proposal submission and evaluation to defense scheduling, grading, and final archiving.”

Overview

From Manual Forms to a Digital Thesis Workflow

TMaTS overview

TMaTS is a web application built to digitizes the PUP Computer Engineering thesis process, from proposal submission and committee review to defense, grading, and final archiving. Before TMaTS, the process relied on printed forms, manual signiture, and physical handoffs between students, advisers, committees, and coordinators, making progress harder to track and inconvenient for both parties (student and professors).

The system brings these workflows into one platform, with role-based tools for students, faculty, coordinators, and administrators, plus a public repository for approved research.

It was built as a Database Management Systems course project by a group of 16 members in under 2 months. With so many developers working on the same system under a tight deadline, the project depended on clear ownership and an architecture that could support parallel development. My work centered on the core backend and, importantly, the integration layer that gave the other developers a stable foundation to build on.

The Problem

The Paper Was the Only Proof It Happened

In a manual process, a piece of paper is the only proof that anything is happening. If a student wants to know where their proposal stands, there’s no record to check. The only option is to ask around and hope someone remembers where they last saw the form.

  • Documents got lost. A misplaced proposal form meant reprinting it and re-collecting every signature from the start.
  • For students, this meant spending time on printing forms, finding advisers/professors, tracking requirements, and waiting for signatures instead of focusing on research.
  • An adviser with several groups, on top of their own work, doesn't have time to be physically present for every signature and endorsement students need
  • Past research was less accessible and physical library. With no central archive, students proposed topics the department had already supervised to completion.

The system had to be built by 16 contributors in under two months, with most of the team working on the frontend and UI/UX. Many of us were writing production-scale code for the first time, and others were still learning the stack. There was no dedicated QA or DevOps team, and no budget for infrastructure to support a deployable thesis system.

Given the timeline and everyone’s other coursework, we spent real time up front understanding the problem: the manual workflows, and what a system would actually need to replace them. That understanding kept expanding as we dug in, which pushed the scope well past our original plan. We got through it by prioritizing carefully, putting most of our time and people into the frontend and the features tied most directly to completing the thesis process.

My Role

Lead Backend Developer & Integration Engineer

I was the Lead Backend Developer and, in practice, the project’s integration engineer. I was responsible for how the different parts of the system connected and for keeping those integrations working as 16 other developers built and changed their parts.

What I designed and decided:

  • The system architecture, choosing an Inertia.js monolith over a separate REST API, and the layering that followed from that choice. This is covered in detail below, and it was as much an organizational decision as a technical one.

  • The two-tier role model. A faculty member in the department is routinely an Adviser and a Panelist and a Coordinator within the same term. I modeled this as a coarse account role (student / faculty) on the user record, plus assignable sub-roles through an is_active pivot table, rather than the flat role column the original plan implied — which could not have expressed that overlap at all.

  • The shared-prop contract, the single typed payload (user_info, flash, visitor) that every page receives on every request. This became the frontend’s global context and the main interface between my work and the frontend team’s.

  • Route architecture, routes split by audience into separate files (student.php, admin.php, faculty/adviser.php, faculty/panel.php, and so on) with middleware guards layered per group, so contributors working on different roles were rarely editing the same file.

What I built specifically: authentication and role-based access control, the middleware guard layer, the academic-calendar and thesis-stage logic, document submission and file handling, the CSV student-import pipeline with dry-run validation, setup notifications and boiler-template, and the repository and service layer extraction that began moving business logic out of controllers.

What integration actually involved was less about writing code than maintaining the conditions under which 16 other devs could write it. I set the branching convention and ran the merges across a repository that accumulated 100+ branches — including parallel frontend, backend, integrate, and fusion-staging. When frontend and backend work diverged, as it did repeatedly, reconciling them was my job.

System Architecture

How a request becomes a page

TMaTS is an Inertia monolith: one Laravel application that returns React page components directly, with no separate API layer and no client-side router. This decision was made to save time and more simplier to be implemented by other backend members

Client

BrowserInertia visit via Link, useForm, or router — no fetch, no API client
HTTP request

Server

Middleware guardsauth to role (student/faculty) to faculty.role and faculty.admin sub-role gates
Authorized request
ControllersSplit by audience: Admin, Faculty, Student, Shared
Delegated work
RepositoriesQuery construction — active semester lookups, student listings
ServicesBusiness rules — academic settings, CSV import, file upload
Eloquent ORM
MySQL34 migrations, 29 models, tbl_-prefixed schema
Inertia render: page name plus props

Response

React pageAuto-resolved from resources/js/pages via import.meta.glob
Page props plus shared props
Layout compositionAppLayout wraps AppHeader (role-aware nav) and AppContent
A single round trip. The controller resolves data and names a component; Inertia ships both together.

A request never returns JSON to be fetched and re-rendered by the client. The controller resolves its data and names a React component; Inertia ships both together and swaps the page client-side. One round trip, one authentication model, and no API contract that two teams have to keep in agreement.

What each component owns

Component Responsibility
Middleware guards (CheckRole, CheckFacultyRole, FacultyIsAdmin, Guest) Authorization. The only real gate — client-side role checks are cosmetic.
HandleInertiaRequests The shared-prop contract: identity, faculty sub-roles, flash messages, guest state. Runs on every single request.
Repositories (AcademicPeriodRepository, StudentListingRepository) Query construction — active-semester resolution, school-year ranges, student listings.
Services (AcademicSettingService, StudentImportService, FileUploadService, PdfGenerator) Business rules and multi-step operations.
Wayfinder (build step) Generates typed TypeScript route helpers from PHP routes, so a renamed route breaks the frontend build rather than production.

Two data paths worth explaining

Student CSV import: validate everything before writing anything. Admins onboard a whole batch from a spreadsheet whose column headers are never consistent between terms. A naive importer that writes rows as it parses them leaves the database half-populated when row 340 turns out to be malformed, and unwinding that by hand is worse than not importing at all.

Browser

Parse and mapPapaParse reads headers and samples rows; columns are fuzzy-matched to system fields, with manual override where matching fails
POST with dry run enabled

Server

Validation passStudentImportService walks the full file and returns to_create, to_update, to_skip and a list of failing rows — writing nothing
Admin reviews the projected outcome
Commit passThe identical code path runs again with dry run disabled, this time persisting

The dry run is the entire point: the same validation code executes twice, once reporting and once writing. Errors surface as counts and a list of offending rows before anything is persisted, rather than as a partial import someone has to reverse manually.

Thesis stage — event derived, not stored. Which milestone a group currently sits on (MOR → DP1 → DP2) is computed from the academic calendar rather than stored as a column on the group record:

$yearLevel = 3 + ($activeYear - $groupStartYear);

$currentStageKey = 'mor';                        // 3rd year
if ($yearLevel >= 4) {
    $currentStageKey = Str::contains(Str::lower($activeSemester->name), ['1st', 'first'])
        ? 'dp1'                                  // 4th year, 1st semester
        : 'dp2';                                 // 4th year, 2nd semester
}

A stored column drifts the moment a semester rolls over and nobody remembers to run the update. Deriving the stage means it is always consistent with whatever the calendar says is active. The cost of this choice is real, and I name it honestly under Limitations below.

Key Technical Decision

Choosing the architecture that fit the team, not the textbook

The decision that shaped everything else was whether to build a Laravel REST API with a standalone React SPA, a traditional Blade and Bootstrap application (which is what the original project plan specified), or an Inertia monolith.

The API-plus-SPA route is the conventional “real engineering” answer, and it was the wrong for our scenario. Few of us had built a REST API before, and half the backend team was still getting comfortable with Laravel itself, since everyone came in from a different stack they were more used to. Given that, we chose the option the rest of the team could actually work with, rather than one that would cost us time in confusion and back-and-forth conversation.

I chose Inertia, and the reasoning was about the project deadline and team speciality:

  • One authentication model. Laravel sessions work unchanged, no token storage, no refresh flow, no CORS configuration.
  • The contract is a function signature. A controller passes props to a named component. A frontend developer reads the controller to know exactly what they receive; there is no separate schema to keep synchronized.
  • Type safety across the boundary. Wayfinder generates typed TypeScript route helpers from the PHP routes at build time, so a route rename fails at compile time instead of at runtime in a user’s browser.
  • One pull request per feature. This was the single largest available reduction in coordination overhead, and on this team that mattered more than architectural purity.

What I gave up is concrete. There is no reusable API, so a future mobile client would need one built from scratch. Frontend and backend deploy together, with no independent release. And the application is coupled to Laravel’s session model tightly enough that a stack migration would be expensive.

For a department-internal web application built by a large, junior, limited team, those are the right things to trade away. If TMaTS ever needed a mobile client, I would extract an API for those specific endpoints rather than retrofit the entire system.

A Real Challenge

When No One Owns the Definition, Everyone Writes Their Own.

The problem surfaced during code review around the second month. Anything the project hadn’t defined in one shared place got defined repeatedly, slightly differently each time. Some frontend components had grown their own local definitions instead of a shared one. In places, backend data didn’t line up with how the frontend expected to read it.

The cause was simple. When a shared definition doesn’t exist, writing a local one is the fastest way for a contributor to finish their task and independently produces redundant components. That made integration and maintenance far harder than it needed to be.

Solution: The fix isn’t asking people to be more careful. It’s making the shared version easier to find than writing a new one.

  • Design tokens. Colors became CSS custom properties in one theme file, including per-status token sets for each badge family, so using the system was less typing than writing a hex value.
  • A component registry. 133 icons and 32 status badges registered as named components, so developers looked up a name instead of importing and styling an SVG themselves.
  • Typed routes. Wayfinder generates route helpers from the PHP routes, so a renamed route fails the build instead of breaking a page silently.

This lesson went beyond design. Every consistency problem on this project traced back to the same root: a shared piece that should have existed but didn’t. And on a team this size, if using that shared piece is harder than just copy-pasting, copy-paste wins every time.

System Features

What the system does

TMaTS public access
  • Role-based access for Student, Adviser, Panelist, Committee, Coordinator, Award Committee and Admin with faculty holding several concurrent sub-roles, enforced by middleware rather than by the interface.
  • Document submission across the MOR, DP1 and DP2 milestones, with status tracking and real upload progress reporting.
  • Adviser and committee review with comments, endorsements, and majority-vote (50% + 1) proposal approval.
  • Defense management: panel assignment, a scheduling matrix, and rubric-based grading built on a normalized criteria, rubric, level and score schema.
  • Searchable public repository of approved research journals, reachable by guests through a cookie-based guest gate without requiring an account.
  • Admin console covering the academic calendar, deadline rules and templates, department policies, and the CSV batch import described above.
  • Notifications for status changes, and two-factor authentication (TOTP with recovery codes) via Laravel Fortify.

Tech Stack

The stack as actually built

Layer Choice
Backend Laravel 12, PHP 8.2
Frontend React 19, TypeScript 5.7
Bridge Inertia.js 2 (no separate API layer, no client-side router)
Styling Tailwind CSS v4 with CSS-variable theming, Radix UI primitives
Database MySQL with Eloquent ORM (34 migrations, 29 models)
Auth Laravel Fortify (session-based, with TOTP two-factor)
Typed routing Laravel Wayfinder
Tools Vite 7, vite-plugin-svgr, PapaParse, Recharts, react-pdf, Browsershot
Deployment Local and departmental hosting (XAMPP, Herd)

The delivered stack diverges from the original project plan, which specified Blade with Bootstrap and Laravel Breeze. That divergence was the Key Technical Decision above, made deliberately and early.

Results

What shipped, and how far it got

A working system covering the full thesis lifecycle: authentication and role-based access control, document submission and review, defense scheduling and rubric grading, the public research repository, and the admin console. The core student and admin workflows run end to end against real data. Some faculty-side evaluation screens are still interface shells rendering fixture data while their backends lands.

The backend carries a Pest suite in CI covering authentication, two-factor enrollment, settings, the two repositories and the student import service. It has a good foundation for a prototype rather than full comprehensive system.

Limitations

What I know is wrong with it

Auditing our codebase near the end of the project surfaced a set of problems worth recording accurately, because a case study that lists only accomplishments is not a useful one.

  • The stage derivation is more fragile than expected. Deriving MOR, DP1, and DP2 from the calendar avoids drift, but the implementation assumes strictly linear progression. The department’s own workflow allows a group to fail DP1 and re-enroll, something the current logic doesn’t account for, since it assumes a group will simply resume where they left off rather than restart.

  • Business logic still sits in controllers. The repository and service extraction is genuinely underway but partial; most controllers still query Eloquent directly, with stage computation, status mapping and grouping inline.

  • No frontend tests at all. Some parts of the design won’t hold up well on mobile, which adds weight to a future rework. If an API is ever built, a dedicated mobile lightweight version would likely be a better investment than trying to make the current interface fully responsive.

  • Accumulated debt from parallel work: duplicate and orphaned pages, inconsistent directory naming across siblings, large commented-out blocks, hardcoded navigation hrefs despite Wayfinder being available, and an unused Redux dependency that was never imported once.

Future Work

What a second iteration should do

In priority order, and separating what is a bug fix from what is a genuine extension:

Structural work: finish the repository and service extraction so controllers only orchestrate; resolve the temp/ directories by promoting or deleting their contents; migrate navigation to Wayfinder helpers so route renames fail loudly at build time.

Feature extensions, scoped in the original project plan but not built: audit logging for the approval chain, an analytics dashboard for coordinators, document version control with diffing between revisions, defense report export, and calendar synchronization for defense scheduling. These are proposals, not partially-completed work.

Reflection

What the project actually taught me

TMaTS: My Reflection

The architecture decision I’m most confident about, Inertia over a REST API, was made for organizational reasons, not technical ones. The technically “better” option would have produced a worse outcome, because it assumed a level of expertise, experience, and time that my team didn’t have. The real lesson: pick the technology that fits the team you actually have, not the team will adjust to it especially for time limited project. That’s the judgment I’d carry into any similar project.

The second lesson was about where the real value sits. On a team this size, the biggest wins don’t come from features. They come from shared infrastructure and more strict policy to ensure codebase will not fall for minimal problems. Looking back, my biggest regret is not locking that consistency in earlier. We were racing the deadline, so we chose speed over strict standards, and I spent a lot of the integration work fixing the same problems again and again, problems a stricter setup from day one would have prevented. Integration isn’t extra work on top of the real work. On a project like this, it is the real work.

The third lesson is the one I wouldn’t have predicted. Writing an honest audit of this codebase, naming my own mistakes and where I fell short, taught me more than I expected. A system you can describe the flaws of precisely is a system you actually understand. TMaTS is where I stopped trying to understand the whole system myself, and started building the structures that let other member understand their part of it.

Resources

Next case study

DepEd Enrollment Analytics Dashboard