04 — Features by Module

Plain-English screen-by-screen description of what each area of the application looks like and what users do on it. No code references — you can hand this to someone who's never seen the app.

How the app is organized

The app has two shells:

  • Desktop shell — sidebar on the left, top bar with notifications + search + theme + language + user menu, page content in the middle. Used by everyone except pure mobile auditors.
  • Mobile shell — top bar only, no sidebar. Just the screens needed to do an audit on a phone.

Every desktop screen has the same chrome (top bar + side nav). The side nav is filtered by the user's permissions — a user without asset.read doesn't see the "Assets" link at all. The whole nav group hides when none of its items qualify, so the navigation never shows links to inaccessible pages.

flowchart TB
  TOPBAR["Top bar:<br/>brand · search · notif bell · theme · preset · language · user avatar"]
  SIDENAV["Side nav (collapsible):<br/>Dashboard · Operations · Audits ·<br/>Maintenance · Reporting · Master Data ·<br/>Settings · Administration"]
  CONTENT["Page content"]
  TOPBAR -.- SIDENAV
  TOPBAR -.- CONTENT
  SIDENAV -.- CONTENT

Themes: light (default) and dark (toggle in the top bar). Four design presets are available — Aura (default, modern rounded edges), Material (Google Material), Lara (clean professional), Nora (sharp minimal). Each user's choice persists in their browser.

Index

  1. Login & Dashboard
  2. Assets
  3. Master Data
  4. Audits
  5. Transfers
  6. Check-outs
  7. Maintenance
  8. Notifications & Profile
  9. Reports
  10. Documents (cross-cutting)
  11. Settings
  12. Administration
  13. Mobile shell

Login & Dashboard

Login (/login)

The first screen any user sees. Branded background with a centered card containing:

  • Email + password fields
  • A "Remember me" / device-id capture under the hood
  • The language picker and theme toggle are visible on this page so non-English users land in their preferred language before signing in.

On a successful login, the system loads the user's profile + permissions in the background, then routes them to / (desktop shell) or /mobile (if UserType = Mobile).

If the email + password are right but the password change is needed, the system surfaces a "change password" prompt on the same page.

Dashboard (/)

The landing page after login. A grid of summary tiles. Each tile is gated by the matching read permission — a user without audit-assignment.read doesn't see the "Open audits" tile at all.

Typical tiles:

Tile Shows Permission
Welcome Bilingual greeting using the user's full name (always shown)
My open audits Count + "View all" link to /my-audits audit-assignment.read
Pending reviews Number of audit results waiting on the user audit-result.review
Open work orders Count of WOs assigned to the user work-order.read
Pending transfers Submitted transfers awaiting approval asset-transfer.approve
My checked-out assets Count of active check-outs for this user check-out.read
Recent activity Compact feed of the user's latest notifications me.notification.read
Quick links Deep links into the most-used pages (always shown)

Tiles are clickable — going straight to the matching list page with the right filter pre-applied.

If a user has zero permissions beyond the "common reads" (e.g., a stripped-down service account), they'd see only the welcome tile. That's by design.


Assets

The hub of the entire system. Every other module references assets.

Asset list (/assets) — asset.read

A paginated table with these columns:

Column What it shows
Code The system-generated code (e.g., 000123CLS-LAPTOP) printed on the asset's sticker
Name Bilingual — primary or secondary depending on the user's language
Organization The org-chart unit. Shows the leaf name + an info-icon (); hover shows the full chain (HQ › IT Dept › Help Desk)
Location Same shape as Organization. Info-icon hover shows the location chain (Site › Building → Floor → Room)
Classification Same — leaf + chain tooltip (Computers › Laptops › Premium)
Status Color-coded tag (Active = green, Missing = red, Disposed = dark, etc.)
Critical Warning icon if IsCritical is set
Actions Edit (pencil) or View (eye) — icon depends on whether the user has asset.update. Plus a delete (trash) for users with asset.delete.

Filters above the table:

  • Search — type to filter by name or code (debounced 300 ms — there's a tiny delay before the request fires, so typing fast doesn't fire one request per keystroke)
  • Organization picker — hierarchical, with an "Include descendants" checkbox. So picking "IT Dept" with the checkbox shows assets in IT Dept and every team under it.
  • Location picker — same shape
  • Classification picker — same shape
  • Status — single-select dropdown
  • Critical only — toggle

Top-bar actions:

  • Create (asset.create) — opens a dialog with the full asset form
  • Import (asset.import) — uploads an Excel file with bulk asset rows
  • Export (asset.export) — downloads the current filtered view as Excel
  • Print labels — single label or a multi-asset A4 sheet (different layouts available)

Asset detail (/assets/:id)

Tabbed page:

  • Profile — bilingual name + description, image, the four hierarchy pickers (Organization / Location / Classification / Status), critical toggle, condition rating (1-5 stars), acquisition method dropdown (Purchased / Leased / Donated / Transferred / Other), current custodian dropdown, last seen timestamp. Save button at the bottom.
  • Details — secondary fields: serial number, manufacturer, vendor, model, dimensions (inches / weight / color), price + currency, purchase date, PO number, warranty start/end, expected useful life, plus a free-form JSON for custom per-classification fields.
  • History — a single timeline merging status changes, location changes, organization changes, and custody changes. Each entry shows who, when, what kind of change, before → after, and a clickable source link (e.g., the audit, transfer, or work order that caused the change).
  • Documents — file attachments (invoices, photos, warranty PDFs). Drag-and-drop upload, list view, download or delete.

If the user lacks asset.update, the form fields are read-only but they can still see everything.


Master Data

The catalog tables that every other entity references.

Hierarchy entities — Organizations / Locations / Classifications

All three follow the same pattern. Two pages each:

List view (/organizations, /locations, /classifications) — *.read

A flat paged table with hierarchy-aware filters. Users can:

  • Search by name
  • Filter by parent
  • Filter by level (level 1 = roots, level 2 = first children, etc.)
  • Filter by ancestor (every descendant of node X)
  • Toggle "roots only"

Action buttons (gated by *.create, *.update, *.delete, *.import, *.export).

The create / edit dialog has bilingual name + description + image URL fields, a parent picker (optional — null = root), and any entity-specific fields:

Entity Specific fields
Organization Manager (User picker)
Location Location type (Site/Building/Floor/Zone/Room/Rack/Vehicle/Outdoor), latitude, longitude, geofence radius (meters), street address
Classification Default useful life (months), "Requires serial number" flag, "Requires maintenance plan" flag

Tree view (/organizations/tree, etc.) — *.view-tree

A read-only tree visualization of the hierarchy. Nodes can be expanded/collapsed; clicking a node opens its detail.

Useful for:

  • Verifying the hierarchy looks right after a bulk import
  • Visually showing executives the organizational depth
  • Quickly drilling to a specific subtree

Flat entities — Vendors / Manufacturers

No tree, just a list.

Entity Fields shown
Vendor Name (bilingual), contact name, contact email, contact phone, address, tax id, notes
Manufacturer Name (bilingual), website, support email, support phone, notes

Filters by name and contact info. Standard create/edit dialog with all fields. Excel import + export. Soft-delete with confirmation.


Audits

The most complex feature module — eight screens covering plan management, assignment, execution, and review.

Audit plans list (/audit-plans) — audit-plan.read

List of all plans in the system with:

  • Status timeline tag (Draft → Scheduled → InProgress → Completed)
  • Priority tag (Low / Normal / High / Urgent)
  • Resolved asset count
  • Number of assignments
  • Assignees (joined names, comma-separated)

Filters: search, code, status (multi-select), priority (multi-select), assignees (multi-select).

A "Create plan" button leads to the dedicated multi-step page.

Create plan (/audit-plans/new) — audit-plan.create

A wizard-style form:

  • Step 1: Basics — bilingual name, description, priority dropdown.
  • Step 2: Scope rules — repeatable block. Each rule picks a scope type:
    • Location — pick a location, toggle "Include children"
    • Organization — same
    • Classification — same
    • AssetList — paste/select specific asset IDs
    • All — every asset in the system
    • Combined — pick 2 or 3 of (Organization, Location, Classification) — assets must match all set dimensions
  • Preview scope button — shows "X assets resolve to this scope" without saving.
  • Step 3: Review and submit.

Plan detail (/audit-plans/:id)

Top: plan name, status tag, priority tag, scope summary.

Tabs:

  • Scopes — read-only view of the scope rules
  • Assignments — table of AuditAssignment rows. Each row shows assignee, status, submission/review timestamps. Create assignment dialog opens the per-plan assignment form (one active per plan; the dialog is disabled if one already exists). Cancel action per row.

My audits (/my-audits) — audit-assignment.read

The auditor's queue. Filtered to assignments assigned to me. Sort: newest first.

Each row links to the assignment detail page where the work happens.

Assignment detail (/audit-assignments/:id) — audit-assignment.read

Where desktop auditors do the actual work. Two tables:

Expected assets table

One row per asset in the assignment scope. Columns:

  • Asset — code + name + image
  • Expected location — the asset's location at snapshot time
  • Expected organization — same
  • Outcome — dropdown: Found / NotFound / LocationMismatch / OrganizationMismatch
  • Method — dropdown: Qr / Manual
  • Condition — number input 1-5
  • Notes — free-text input

Extra findings table

Initially empty. Auditor clicks "Add extra finding" to add a row for an asset they scanned that wasn't in scope. The row shows:

  • Asset picker — dropdown filtered to assets not in the expected list (the system pre-subtracts the in-scope IDs)
  • Outcome — locked to Extra (static tag, not a picker)
  • Method — locked to Manual (the user typed the code)
  • Condition + Notes inputs same as expected rows

A Submit button at the top finalizes. Disabled until all extra-finding rows have an asset selected.

Pending reviews (/pending-reviews) — audit-result.review

Reviewer's queue. List of audit results awaiting review, filterable by review status (PendingReview / InReview / UnderClarification), submitted date range, and search.

Each row links to the result review page.

Result review (/audit-results/:id) — audit-result.read

The reviewer's working surface. Header shows:

  • Result code, audit plan name + code, assignment code
  • Submitter name + submission timestamp
  • Review status tag

Below: a filterable line table. Columns:

  • Asset code + name
  • Outcome (color-coded tag)
  • Identification method (Qr / Manual; Manual gets a yellow "keyboard" icon to flag weaker auditor confidence)
  • Observed location / organization (if different from expected)
  • Condition rating (stars)
  • Photos — thumbnails inline. Click to enlarge in a lightbox.
  • Status — Approved / Rejected / Modified or "Pending" if no decision yet
  • Actions per row — Approve / Reject / Modify

Clicking Modify opens a dialog with the auditor's observed values pre-filled and editable. The reviewer can override if they have additional knowledge.

A bulk-approve action above the table — multi-select rows, single click to approve all.

When all lines are decided, the assignment auto-flips to Completed and the original auditor gets a notification.


Transfers

Transfer list (/transfers) — asset-transfer.read

Filterable list:

  • Search (code, reason)
  • Status (multi-select)
  • "Mine only" toggle
  • From / to organization + location pickers
  • Requested date range

Each row shows: code, status tag, requester, from/to, line count, requested date.

Buttons: Create, Export.

Transfer detail (/transfers/:id)

Header

Code, status tag, from/to organization + location, requester, requested timestamp, approver/completer + their timestamps.

Reason editor

A rich-text Quill editor while the transfer is in Draft. Read-only after Submit. Supports formatting and inline images.

Lines table

One row per asset:

Column Editable?
Asset (code + name) only while Draft
Received at by Receiver, while InTransit
Received status (Ok / Damaged / Missing) by Receiver
Notes by Receiver

Action bar (top-right)

Context-dependent buttons:

Status Visible to Buttons
Draft Requester Submit, Cancel, Add line, Remove line
Submitted Approver Approve, Reject (opens a reason dialog)
Submitted Requester Cancel
InTransit Receiver Receive line, Receive all, Complete
Completed / Rejected / Cancelled Anyone (none — read only)

Toasts on success ("Transfer submitted", "Transfer approved", etc.). Detailed error messages on validation failures.


Check-outs

A single page (/checkouts) handles the entire flow — no separate detail page.

List

Filters:

  • Status (Active / Returned / Overdue / Lost / Cancelled)
  • Custodian dropdown
  • Asset picker
  • "Mine only" toggle
  • Search
  • Checked-out date range

Each row shows: code, asset code + name, custodian name, checked-out date, expected return date, status. Overdue items are highlighted.

Create check-out (dialog)

Form with:

  • Asset picker — filtered to assets without an active check-out (server-enforced via DB unique index)
  • Custodian dropdown
  • Expected return date picker
  • Purpose textarea

Save creates the check-out, sets the asset's status to OnLoan, and writes the custody history.

Check-in (dialog)

For an active row, the user clicks "Check in":

  • Condition rating (1-5 stars)
  • Return notes

Save flips the row to Returned, restores the asset's previous status.

Cancel

For an active row only. Same restore logic as a check-in but without the condition + notes.


Maintenance

Three sub-pages: plans, requests, work orders.

Maintenance plans (/maintenance-plans) — maintenance-plan.read

List with: code, name, asset count (with the leaf code + "+N" tooltip showing the rest), frequency, next-due date, last-performed date, active toggle.

Create / edit dialog

  • Bilingual name
  • Multi-select asset picker — the plan applies to all selected assets
  • Frequency: Daily / Weekly / Monthly / Quarterly / Yearly / Custom (custom shows a "days" input)
  • Next-due date picker
  • Estimated duration (hours)
  • Default vendor (optional — sets the vendor on every WO this plan generates)
  • Active toggle (deactivate to pause without deleting)

Plan detail (/maintenance-plans/:id)

Header + chip strip of plan-assets + table of historical work orders generated from this plan.

A "Generate work orders" button at the top — disabled until the plan's NextDueDate has been reached, with a tooltip explaining "Not due until ". Clicking it emits one WO per plan-asset and rolls the plan's schedule forward.

Maintenance requests (/maintenance-requests) — maintenance-request.read

Filterable list. Each row: code, status tag, severity tag, asset, summary, reporter, reported date.

Create dialog

  • Asset picker
  • Severity dropdown (Low / Medium / High / Critical)
  • Summary text
  • Description (longer)

Promote action

For a request in Open / UnderReview status, the reviewer clicks Promote. A dialog opens with the WO form pre-filled (asset preset, summary copied). Reviewer adds: type, priority, scheduled start/end, assignee. Save creates the WO and links the request.

Reject action

A reason dialog → status flips to Rejected.

Work orders (/work-orders) — work-order.read

List with: code, status tag, priority tag, type tag, asset code + name, assignee (user OR vendor), scheduled start, summary.

Filters: status, type, priority, asset, assignee, "mine only", date range.

Work order detail (/work-orders/:id)

Header

Code, status, priority, type, asset link, scheduled window, assignee, origin (Plan / Request / Manual — clickable if Plan or Request).

Action bar

Status-aware:

Status Buttons
Open Assign, Cancel
Assigned Start, Cancel
InProgress Complete, Cancel
OnHold Resume, Cancel
Completed / Cancelled (none)

Assign dialog

Pick either a user or a vendor, not both.

Complete dialog

Captures: actual end (defaults to now), downtime hours, labor cost, parts cost, currency, resolution notes. Total cost auto-sums.


Notifications & Profile

Notification bell (top bar, every page)

A bell icon with an unread count badge. Polls the server every 60 seconds for the unread count.

Clicking the bell opens an overlay panel with the latest 10 notifications. Each entry:

  • Severity icon (info / success / warning / error)
  • Plain-text body
  • Relative time ("5 minutes ago")
  • Click → marks as read + navigates to the linked record

Buttons: Mark all as read, View all.

Inbox (/notifications) — me.notification.read

Tabs: All / Unread.

Filterable: by template key (drop-down of all event types), by severity.

Per-row: read/unread indicator, body, source, timestamp, mark-as-read, delete.

Toolbar: Mark all as read, Clear all (with a "keep unread" option).

Preferences (/preferences) — me.notification-preference.update

A grid: each row is one notification template, columns are InApp and Email. Toggles set the user's preference per channel per event.

Default = enabled. Setting all preferences off doesn't break anything; the user just stops getting that template.

Profile (/profile) — me.profile.read

Editable form:

  • Bilingual full name
  • Phone number
  • Preferred language dropdown — drives the entire UI's language for this user
  • Preferred theme dropdown — Light / Dark / System
  • Avatar upload (optional)

A separate Change password section — current password + new password + confirmation.


Reports

A central hub at /reports with tiles for each report. The hub is detailed on its own in 05 — Reporting & Insights.

Six per-page reports:

Report Page What it answers
Asset Inventory /reports/asset-inventory Where is everything right now?
Audit Results /reports/audit-results What did the latest audits find?
Audit History /reports/audit-history Asset-by-asset audit trail across time
Maintenance History /reports/maintenance-history What's been worked on, when, by whom, at what cost
Transfer History /reports/transfer-history Movement of assets between orgs/locations
Checkout Activity /reports/checkout-activity Who's holding what; what's overdue

Every report has the same layout: filterable table with paginated server-side data + Excel/PDF export buttons.


Documents (cross-cutting)

Documents aren't a top-level menu item — they live as a tab/panel inside the entities they're attached to.

The Documents panel appears on:

  • Asset detail
  • Transfer detail
  • Work order detail
  • Maintenance request detail
  • Audit result line (photo evidence)

Each panel:

  • Lists existing documents (name, file type icon, size, uploaded by, uploaded at)
  • Upload button — opens a file picker
  • Drag and drop anywhere in the panel
  • Per-row download + delete (delete = unlink, the underlying file isn't deleted unless the user has document.delete)

A document can be linked to multiple owners. The same scanned receipt can be attached to both an asset and a maintenance work order, for example.

Document categories: Invoice / Warranty / Manual / Photo / Certificate / AuditEvidence / Other. The category is just a label for filtering — doesn't change behavior.

Photos uploaded during audits get the AuditEvidence category and a special inline thumbnail view in the result review screen.


Settings

Admin-only pages. All under /settings/*.

Hierarchy config (/settings/hierarchy)

For each hierarchical entity (Location, Organization, Classification), the admin defines:

  • How many levels deep
  • Each level's bilingual label (e.g. "Building / Floor / Room")
  • Each level's code-digits width (used in code generation: 2 = "01", 4 = "0001")

Drag-and-drop reorder. Save commits the new shape. Existing entities at deeper-than-allowed levels would need to be addressed first.

App languages (/settings/languages) — app-language.manage

Two dropdowns:

  • Primary language — the language stored in every *Primary column (NamePrimary, FullNamePrimary, etc.). Default English.
  • Secondary language — the language in *Secondary columns. Default Arabic.

Changing these reshapes how the Localize pipe decides which value to show. Existing data isn't translated automatically — the strings already in the DB stay in whatever language they were saved in.

Translations (/settings/translations) — translation.read

Admin override editor for UI labels. The frontend ships with a bundled English+Arabic+9-other-language map (~1000 keys). This page lets the admin override any key in any language without re-deploying.

Layout:

  • Tabs per language
  • Search box
  • Editable grid: key, default value (read-only), override (editable)
  • Save commits per cell. Reset clears the override (reverts to bundled).

Notification templates (/settings/notification-templates) — notification-template.read

Master-detail layout. List on the left (filtered by event key), editor on the right.

For each template:

  • Subject (primary + secondary) — text inputs
  • Body (primary + secondary) — Quill rich-text editors
  • "Insert merge field" toolbar — pulls available fields from the system event catalog and inserts a {{ field.path }} placeholder
  • Preview button — renders with sample merge values so the admin sees what users will receive

Quirk: Quill auto-wraps text in <p> tags and converts spaces to non-breaking spaces. The renderer normalizes both before substituting merge fields, so this is invisible — but if you copy a template's body out for inspection, expect to see HTML.

Email provider (/settings/email) — email-settings.manage

A form for the SMTP configuration the system uses for outbound mail.

Field Notes
Enabled Master switch — disable to stop the worker from sending
Host, Port, SSL Standard SMTP setup
Username / Password Password is encrypted at rest
From address / display name Email "From" header
Reply-to Optional
Redirect-all toggle For staging — every email reroutes to a single address with the original recipient prefixed in the subject
Batch size, poll interval, max attempts Worker tuning

A "Send test email" button drops a one-off message to a chosen address using the current settings — useful when verifying SMTP creds.


Administration

The five admin pages.

Users (/users) — user.read

Standard list with filters: search, isActive, code, email, phone, user type.

Create user dialog

  • Email (unique)
  • Username (unique)
  • Bilingual full name
  • Initial password (visible to admin while typing; never re-displayed after save)
  • Initial role assignments — multi-select from the role list with optional time bounds (ValidFrom / ValidUntil)
  • User type — Interactive (desktop shell) or Mobile (mobile shell)
  • Optional: phone, manager, organization, default location, preferred language, preferred theme, avatar URL
  • IsActive toggle

User detail (/users/:id) — tabbed page

Tab What's there
Profile Same fields as create, edit-mode
Roles Add / remove roles (with time bounds)
Direct permissions Per-user grants and denies. Useful for temporary overrides.
Effective permissions Read-only flat list — what the system actually grants this user after combining all roles + overrides
Sessions Active refresh tokens. Per-row revoke action.

Roles (/roles) — role.read

List of all roles. Built-in + system flags as tags. Member count + permission count columns.

Role detail (/roles/:id)

The recently redesigned permissions editor. Layout:

  • Profile section at the top: bilingual name, bilingual description.
  • Permissions section below.

The permissions UI organizes the catalog by module. Each module is a collapsible card. Inside each card:

  • Module name with a count badge ("3 of 7 selected", color-coded: success / info / secondary)
  • Permission rows in a multi-column grid (2-3 columns on wide screens, 1 column on narrow). Each row shows the permission name, the technical key in monospace, and a checkbox.
  • Selected rows highlight in light blue. Dangerous permissions show a red triangle icon.

Above the cards:

  • Search box — filters across name and key, hides empty modules
  • Expand all / Collapse all
  • Select all visible / Clear all visible — operates on the currently-filtered set, paired with the search

Below the cards:

  • Sticky save bar — disabled until the user has actually changed something. Turns amber with an "Unsaved changes" note + a Discard button when dirty.

Permissions (/permissions) — permission.read

Read-only catalog browser. Permissions grouped by module, color-coded per "verb" (read = blue, create = green, update = amber, delete = red). Search filter. Useful when designing new roles.

Audit log (/audit-log) — audit-log.read

The full system audit log — every change to every business entity, with before/after JSON.

Filters: entity name (dropdown sourced from server), entity ID (paste a Guid), user, action (Created/Updated/Deleted), date range, correlation ID.

Each row expands to show a JSON diff of changed fields.

Login audit (/login-audit) — login-audit.read

Login attempt log — every login (success or failure). Filter by email, user, result (Success / InvalidPassword / LockedOut / Disabled), IP, date range.

Result tag colors: Success = green, others = red/amber. Useful for incident response.


Mobile shell

Three pages live under /mobile. Two of them (profile, notifications) reuse the desktop components.

Mobile home (/mobile)

The mobile auditor's landing page. Card list of their active assignments. Each card shows: plan name, status tag, asset count.

Tapping a card opens the assignment.

Mobile assignment (/mobile/assignment/:id)

The working surface. Header:

  • Plan name, status, asset count, code

A mode picker (segmented control):

  • Scan — embeds the camera scanner (next section)
  • Excel — three-step offline path: Download template → Fill offline → Upload back

Below: a read-only assets table showing the assignment's expected asset list. The auditor's actual scanning happens in the scanner UI.

Scan screen (embedded in assignment)

The most specialized screen in the system.

Top: hierarchical location filter

Cascading dropdowns based on the configured hierarchy. So if Location config is Site → Building → Floor → Room, the auditor sees four dropdowns (or however many levels they have).

The filter options are derived from the assignment's expected-asset chains — auditors only navigate through locations that actually contain assignment assets. Levels with one option auto-collapse.

Buttons: Clear filter (visible only when the user has drilled below the auto-anchored levels).

Middle: camera reader

Full-width camera viewport. Black wrap with a translucent QR-box overlay highlighting where to point.

When a QR is decoded:

  • The matching expected row flips from "pending" to found (green tag) — if the asset's expected location is in the active filter
  • OR to location-mismatch (amber tag) — if the asset is in scope but currently filtered out
  • OR a new extra row is added — if the asset wasn't in the expected list at all

Each scan triggers:

  • Sound — single high beep on found, double low beep on mismatch/extra (mute toggle in the controls)
  • Vibration — single buzz on found, triple buzz on mismatch/extra (where the device supports it)

Below the reader: controls

  • Start/Stop scanning
  • Manual entry — opens a dialog to type the asset's printed code (used when a QR is damaged)
  • Mute / unmute
  • Hide-found toggle (collapses found rows so the auditor sees only what's left)
  • History — opens a dialog showing the last 10 scans for verification

Bottom: rows table

One row per asset (expected + extras), with state tags. Per-row:

  • Edit dialog → condition rating (1-5), notes
  • Photo upload → opens the camera/file picker, attaches to the row

Submit

A confirmation dialog summarizes:

  • Total scanned
  • Found / Missing / Mismatch / Extra counts
  • Soft warning if more than 25% of expected items are still missing (auditor can proceed; the warning is just to catch "did I forget a room?" mistakes)

After submit, the auditor is bounced back to mobile home and a toast confirms.

Auto-save behavior

Every change to the rows or filter selection triggers a debounced save (1 s) to the server. The auditor can:

  • Close the app and resume on the same device
  • Close the app and resume on a different device with the same login
  • Lose internet — keeps scanning; saves catch up when connection returns

The auditor sees a small "Saved Xs ago" indicator. Drafts are cleared after submit so a future re-assignment doesn't pick up stale state.

Mobile profile / notifications

Same screens as desktop, accessed via the user menu. Layla the Mobile Auditor can change her own password, language, theme, notification preferences, just like everyone else.


Where to go next

To learn… See
What each report shows + who uses it 05 — Reporting & Insights
Bilingual rendering and RTL behavior 06 — Localization & RTL