Case study · Fixa tenant web app · 2026

Rebuilding Fixa's tenant web app

Fixa tracks attendance and pays 8,500 casual and permanent workers across six tea estates, construction sites and warehouses in Rwanda. I led the redesign of the tenant web app, the desk where HR and payroll admins approve time and move money. It shipped as the new Fixa.

Role
Lead product designer · PM for Payments
Surface
Tenant web app (desktop)
Scope
54 screens across 10 modules
Status
Shipped as the new Fixa
The new dashboard: good morning, approvals waiting, next payroll, and today's numbers by module

01 · Context

A product that worked, wrapped in a UI that made people nervous.

Fixa covers the whole loop for labour-heavy businesses: register a worker, record shifts and output, approve attendance, compute deductions and taxes, then pay out to mobile money or bank. Many of those workers are unbanked and paid by the day or by the kilo.

V2 did its job: it shipped, and admins ran real payroll on it. But it had grown to more than 800 frames of accumulated states, with each module solving the same problems its own way. It read as dated and dense. The bigger lesson was about risk: the moments that move money and approve people's time looked exactly like the moments that do neither, and that is where trust leaks away.

Who it is for

HR and payroll admins

At a desk, in long sessions. They approve attendance from many sites, build payrolls, top up the wallet, send money and file taxes. Mistakes cost real money and trust.

What was broken

Risky moments looked like routine ones

Approvals had no visible owner, a single bad row could block a whole import, and a payroll with failed transfers showed a generic error.

Before
The V2 dashboard: donut charts and an actions list
V2 dashboard: charts first, decisions nowhere.
After
The new dashboard: good morning, approvals waiting, next payroll, and today's numbers by module
After: today grouped by module, with approvals and the next payroll pinned at the top.

02 · Principles

The rules I checked every screen against.

  1. 01

    Quiet by default

    White surfaces, navy for structure, one orange accent. If nothing is urgent, the screen should feel like nothing is urgent.

  2. 02

    Colour means something

    Green is paid or approved, red is failed or declined, orange asks for a decision. No decorative colour on data.

  3. 03

    Show the people

    Every worker shows with initials or a photo, a trade and a site. Payroll is people getting paid.

  4. 04

    Design the failure

    Declined records, failed transfers and bad imports each get a screen of their own. A generic error tells an admin nothing they can act on.

03 · System

One quiet kit, built on the brand variables.

Every colour is bound to the Bihamye-Fixa brand variables, so engineering picks up tokens instead of hex codes. On top I defined a small set of parts: a slim navigation rail, page header with breadcrumb, stat tiles, status pills, filter chips, table cards, side panels and confirm dialogs.

Illustrations with a job

A muted navy library with one orange accent. They appear only in moments of transition (first run, empty, success, errors) and never next to live data. That one rule kept them charming instead of cluttered.

Six illustrations: team, onboarding, attendance, payout, wallet and empty box

Deep dives

Module by module

Each module was redesigned around the question the admin is really asking when they open it.

  • Dashboard
  • Employees
  • Attendance
  • Leaves
  • Payments
  • Deductions
  • Wallet
  • Taxes
  • Audit
  • Auth

W1 · Dashboard

From "here are some charts" to "here's what needs you".

The old dashboard opened on donut charts, and the thing an admin actually came for, what was waiting on them, sat in a list further down. The new one starts there: approvals waiting and the next payroll sit in the greeting, and today's numbers are grouped by module underneath, each group one click from its task.

First-time tenants get a different screen. A dashboard full of zeros on day one says the product is empty, so they get a five-step setup checklist instead.

First-time dashboard: welcome, a five-step setup checklist and a quick tour
First run: a checklist instead of empty charts.

W2 · Employees

Fewer typos at the moment of entry.

Bad worker data is the root cause of most failed payouts. The add-employee flow checks as you type: a national ID autofills the legal name, and the mobile money number is name-matched against it before saving. If the names don't match, the admin finds out while typing, rather than on payday.

Bulk import no longer fails as a whole. It validates every row, imports the good ones and hands back a short list to fix.

Add employee, step 3: mobile money number confirmed as registered to Aline Uwase
Payment method step with live name match.
Import employees: 298 ready, 14 need fixing, with the problem on each row
Import the good rows now, fix the flagged ones later.
Employee list with avatars, trade, phone, how they're paid and status
The list puts people first: avatar, trade and site, with status as the only colour.
Aline Uwase's profile: this month's work, personal, employment and payment details
Profile: identity, pay, attendance and leave in one place.

W3 · Attendance

Make the approval chain visible.

Records pass through supervisors, site managers and HR before they turn into pay. In V2 that chain was invisible, so admins couldn't tell who was holding a record up. Now every record shows its path and who acted, and approvals happen in a side panel without leaving the list.

Declining requires a reason, which goes straight to the supervisor's phone. They fix it and resend. Before, this whole conversation happened on WhatsApp, and nothing in the product knew about it.

Approve attendance in a side panel, with the approval chain and whose turn it is
Approve from a side panel, with the approver chain in view.
Decline one record: pick a reason and leave a note for the supervisor
Decline needs a reason the supervisor can act on.
A day shift at one site: approved, pending and declined records with clock-in methods
A day's attendance at one site: approved, pending and declined at a glance.
Productivity records: output per activity by day, with approval status
Productivity: output by worker, ready to turn into pay.

W4 · Payments · the module I own as PM

Pay day, when something goes wrong.

This is where trust is won or lost. Three changes:

  • Pre-flight checks. Account mismatches and unregistered numbers are flagged in the preview, with a “mismatches only” filter, before anything is sent.
  • Coverage and confirmation. The confirm step shows whether the wallet covers the run and asks for an OTP. No surprises after you press pay.
  • Partial failure as a real state. If 375 of 1,250 transfers fail, the page says so at the top, explains the likely cause, keeps the money in the wallet and offers one-click retry.
Payroll draft with a name mismatch flagged before sending
Payroll draft with mismatches flagged before sending.
Pay 1,250 employees: totals, wallet coverage and a 6-digit code
Confirm and pay: coverage, totals and OTP in one dialog.
After
375 transfers failed: the cause, the money still in the wallet, and Retry 375 transfers
Completed with failures. Before, a generic error. Now: how many, why, where the money is, and what to do next.
Import payout sheet: 309 valid rows and 3 errors, row by row
Payout upload errors, row by row.

W5 · Face recognition · web controls

HR sets the rules and sees the evidence.

Face recognition lives on the supervisor's phone (see the mobile case study), but the rules live here. HR sets match thresholds and who can override, with safe defaults from the 2.0.40 matching rules.

Every attendance record keeps its recognition images, so when a worker disputes a shift, HR can look at the photo and settle it.

Face recognition settings: match threshold, lookalike margin, duplicate block and supervisor confirm
Thresholds: 0.82 to match, 0.85–0.88 needs a supervisor, above 0.88 blocks a duplicate.
Attendance record with clock-in and clock-out recognition images
Record detail with clock-in and clock-out recognition images.

W6 · The rest of the app

Same rules, every module.

Leaves: who's away today, a summary and requests with approve and decline
Leaves: requests and balances together.
Wallet: balance, funding, payouts and fees, with transaction history
Wallet: balance, top-ups and where money went.
Deductions: SACCO savings, supplier credit and salary advances
Deductions: loans and advances with remaining balances.
October tax declaration: PAYE, RSSB, maternity and CBHI by employee
Taxes: PAYE, RSSB and CBHI by period, ready to file.
Audit trail: who did what, in plain words
Audit: who did what, readable by a non-engineer.
Create account for an invited admin: illustration on the left, form on the right
Sign-up for an invited admin.

04 · Trade-offs

What these choices cost.

Calm by default

Payroll screens stay quiet so the moments that need attention stand out. Red and orange are reserved for failures and decisions, so admins notice them straight away.

One typeface

DM Sans only. Weight and size carry the hierarchy, which keeps the web app and the mobile app reading as one product.

Partial success

Importing good rows and paying successful transfers adds states to design and build. Worth it: with all-or-nothing, one bad record holds up hundreds of people's pay.

Side panels over pages

Approvals and reviews open beside the list so admins keep their place in a queue of hundreds.

Results

What changed once it shipped.

of transfers fail in a payroll run, down from about 25%
Under 7%
of transfers fail in a payroll run, down from about 25%
payment complaints to support, down from about 46
~10 a month
payment complaints to support, down from about 46
to prepare payroll, down from a weekend
45 min
to prepare payroll, down from a weekend
imports needing a full re-upload, down from 1 in 3
0
imports needing a full re-upload, down from 1 in 3
failed transfers go through on the first retry
9 in 10
failed transfers go through on the first retry
workers paid across 6 sites, about RWF 100M every month
8,500
workers paid across 6 sites, about RWF 100M every month

Before and after the redesign shipped.

05 · Reflection

What I'd tell myself at the start.

Start with the failure states. Designing the declined record, the failed transfer and the bad import first made the happy paths simpler, because every decision had already been tested against what goes wrong.

Payroll runs on trust. Every screen that moves money now answers four questions before the admin acts: how much, to whom, what could fail, and what happens if it does.

Next

  • Measure payroll trust: retry rate after partial failures, and time from payroll created to sent.
  • Hand off as components mapped to the brand variables, so the 800-frame problem doesn't come back.
  • Test with admins on real payroll runs, watching where they still export to check.
Payments: everything in one list
Attendance: today at a glance
Payment sent
Reviewing a leave request
Add employee: the ID fills in the rest
Employee added
Bulk actions
Fund the wallet
Taxes: what's due
Users and roles
Wallet, empty
Sign in
Next case studyFixa Mobile · clock in a crew, signal optional

Working on something people find hard to use?

Let's talkjonmidas27@gmail.com

Replies within a day.