Managing attendance at scale is a mess of slow grids and broken Excel sheets. This builds a lightning-fast, spreadsheet-style dashboard that untangles employee records and reports in real time, handling the tracking headaches so companies can focus on operations.
A workforce attendance system with both halves written: the API, the database and the auth on one side, the admin panel, the editable grid and the analytics on the other.
The Postman collections are committed alongside both, because an API that only works from the one client that happens to call it is not finished.
This started during my internship at DJUBO, where I arrived having never written a line of AngularJS or Django. Three months later the attendance platform was running. Keeping my own copy of it, rather than leaving the work behind in someone else's repository, is how I make sure I can still build the thing rather than only remember having built it.
It is deployed, and the link above will show you a sign-in page and nothing else. Attendance data is the entire product, so every route sits behind an account, and I would rather show you a locked door than seed a public login into a system whose only content is who came to work and when.
Not the same thing as Angular, and harder in the ways that matter for learning.
You manage when the framework looks. AngularJS watches the model through a digest cycle, so anything that changes state outside its knowledge, a setTimeout, a third-party widget's callback, a raw XHR, updates the data and leaves the screen showing the old value until something else triggers a digest. Modern Angular runs change detection on zones and mostly hides this. Here I had to know where the boundary was, which is also why Handsontable and Highcharts, both of which live outside the framework, needed their own bridge back into it.
Two-way binding is not free. Every binding is a watcher, and a grid of a fortnight of attendance rows creates them by the hundred. The digest walks all of them on every cycle, so the fix is one-time bindings and fewer watchers on the hot surface, which is a problem modern Angular's one-way flow does not hand you in the same shape.
Dependency injection is by name. Inject a service by its parameter name and a minifier renames the parameter, so the app works in development and breaks in production with an unknown provider. The array annotation exists to prevent exactly that, and finding out why is a rite of passage.
No components, no CLI, no types. Directives with their own scope, link and compile phases do the work that components do now, and none of it is typed. The framework gives you less structure, so the structure has to be yours.
I would not start something new in it. Having had to learn it is a different matter: it makes the reasons modern frameworks decided what they decided concrete rather than received.
A grid people can actually work in. Attendance is entered in bulk, not one record at a time, so the admin surface is a spreadsheet rather than a form: Handsontable for the dynamic grid, so an operator can edit a fortnight of rows the way they would in Excel and submit once.
Time is the hard part. Moment.js carries the time-sensitive logic. Shifts, partial days and leave are all arithmetic on dates, which is exactly the arithmetic that goes quietly wrong at month boundaries and across a leave that starts on a weekend.
Reporting, not just recording. Highcharts drives the analytics views, so the same data that gets typed in comes back as something a manager can read: attendance patterns per employee and per department, rather than a table somebody has to sort by hand.
The admin panel filters. Employee name and department filtering came after the first pass, once it was obvious that a list of every employee is not a tool, it is a haystack.
Think of a restaurant. You (the app) want to see the attendance records. The database (the kitchen) has the records. The API is the waiter - it takes your request, goes to the kitchen, and brings the data back to your screen.
A magic mirror effect. If an admin edits a spreadsheet cell on their screen, it instantly updates the main database. If the main database updates, the screen changes instantly. No need to hit "save" or "refresh."