Skip to content

Asset Admin — Distributor Site

Version: 3.3.0
Date: 2026-05-17
Site Key: asset-admin-distributor
Site Family: CRUD-Sites (with embedded Metric pages)
Implementation Repo: webapp-asset-admin-distributor-v2
App URL: https://assets.omnivoltaic.com
Dev Port: 3200
Extends: all-sites.md

Machine-readable intent bundle:

Artifact Path
Site intent docs/intents/sites/asset-admin-distributor.intent.yaml
GraphQL catalog docs/intents/sites/asset-admin-distributor.graphql-catalog.yaml
Themes registry docs/intents/sites/asset-admin-distributor.themes-registry.yaml
Design system (Mosaic) docs/intents/sites/asset-admin-distributor.design-system.yaml
Regeneration contract docs/contracts/crud-scaffold-regeneration.md
Replica parity (exact) docs/contracts/intent-driven-replica-parity.md

Generate assets v2 — intent-driven exact replica

Policy: generationProfile.replicaPolicy: exact — output must match production (https://assets.omnivoltaic.com), not a similar admin app.

There are no codegen scripts. Use the AI prompt with the full intent bundle + webapp-asset-admin-distributor-v2 as the style/structure anchor.

Doc Purpose
AI prompt template Copy-paste instructions for AI
Intent-driven replica parity Routes, layouts, style stack, acceptance checklist
replicaParity in intent YAML productionRouteManifest — every /signin, /dashboards, /thing/item, …

Routes: Sign-in is /signin (not /login). Home / redirects to /dashboards.

Broken styles? Usually missing app/css/style.css, utility-patterns.css, or wrong layout group ((auth) vs (default)). Fix by aligning to reference files — see replica parity contract.

cd oves-sites && npm run validate-crud-intent

Intent-only codegen (no v2 repo)

cd oves-sites && npm run generate-test-site
Doc Purpose
Intent-only codegen How codegen works
Generate test site Commands (port 3201)

Site Purpose

Authenticated operational portal for Omnivoltaic distributors, distributor staff, general agents, super admins, and customers. Full lifecycle management of PayGo asset accounts, fleets, items, customers, personnel, payments, events, and support tickets. Embeds fleet health and device monitoring dashboards powered by live telemetry.

This document is the human-first, page-by-page specification (same discipline as omnivoltaic.md). Together with the intent bundle above, it is sufficient for an AI-assisted rebuild of webapp-asset-admin-distributor-v2 without inferring routes or GraphQL from scratch.


Regeneration Model

app = scaffold(intent, themesRegistry, graphqlCatalog, mosaicShell)
  • Intent declares routes, roles, themes, and bindings.
  • GraphQL catalog declares every operation and owning module.
  • Themes registry maps each theme to reference implementation paths and child components.
  • Mosaic shell supplies layout, sidebar shell, and utility CSS tokens.

Excluded from product: Mosaic demo routes under app/(alternative)/*, ecommerce/*, onboarding, double-sidebar, pay flows — see excludedRoutePatterns in intent.


App Shell

Mosaic design system

Template: mosaic-next (Cruip Mosaic → Next.js App Router)
Policy: docs/governance/style-policy.md (CRUD suite = Mosaic-based)

Element Implementation
Root layout app/layout.tsx — Inter font, Apollo, Auth, Alert providers
Authenticated chrome Sidebar + Header in app/(default)/layout.tsx
Sidebar components/ui/sidebar.tsx — w-64 expanded, violet active states, Lucide-style SVG icons on nav
Header components/ui/header.tsx — search, notifications slot, profile dropdown, theme toggle
Dark mode next-themes via app/theme-provider.tsx
CRUD lists components/table/table.tsx + per-feature tableColumns.tsx / tableActions.tsx
Forms Mosaic .form-input, .form-select, feature use*Form hooks
Fleet charts components/charts/pie-chart.tsx, LastSeenHistogram.tsx, chart-data.ts
Maps @react-google-maps/api + lib/googleMaps.ts

Full component inventory: asset-admin-distributor.design-system.yaml.

Framework

Theme: Next.js App Router + Mosaic template (mosaic-next)

Layouts:

Layout Path Used for
Root app/layout.tsx ApolloProvider (federated client), global CSS
Authenticated app/(default)/layout.tsx Sidebar + main content
Auth app/(auth)/layout.tsx Sign-in, signup, reset password

Sidebar: components/ui/sidebar.tsx — wired menuIds: dashboard, staff, accounts, thing, events, support

CSS tokens (app/css/additional-styles/utility-patterns.css):

  • btn-primary, btn-danger, btn-icon
  • dashboard-select, dashboard-input

Environment

Variable Purpose
NEXT_PUBLIC_GRAPHQL_URL Federated GraphQL (default prod gateway)
NEXT_PUBLIC_THING_GRAPHQL_URL Thing subgraph (map pins, agent items)
NEXT_PUBLIC_DASH_API Historical device data REST
NEXT_PUBLIC_API_BASE_URL REST auth / support
NEXT_PUBLIC_OSRM_URL Route playback in HistoricalAnalysis
NEXT_PUBLIC_GOOGLE_MAPS_API_KEY Device map
NEXT_PUBLIC_BASE_PATH Maps loader prefix
ODOO_BASE_URL, ODOO_X_API_KEY Server-only support integrations

Apollo Clients

Client Module Global provider
Federated lib/apollo-client.ts Yes
Thing lib/apollo-thing-client.ts No — imported in Device Monitoring

Bearer token: localStorage.access_token on both clients.


Authentication

Sign In Page

Route: /signin
Theme: SignInPage
Page family: auth (public)

Implementation: app/(auth)/signin/page.tsx

GraphQL:

  • Mutation: SignInUser (lib/mutations.tssignInUser)

Behavior:

  1. User submits email/password via AuthProvider.signIn.
  2. On success, store access_token, distributorId (_id), user JSON in localStorage.
  3. Decode JWT; attach roleName to user object.
  4. Redirect:
  5. GENERAL AGENT/thing/item
  6. All others → /dashboards

Props (intent):

  • heading: Sign in to Asset Admin
  • submitLabel: Sign in
  • forgotPasswordHref: /reset-password

Sign Up Pages

Routes: /signup, /signup/[...roleToken]
Theme: SignUpPage

Invitation flow uses route token segment for role binding.


Password Reset

Step Route Theme
Request link /reset-password ResetPasswordPage
Set new password /auth/passwordreset/[token] ResetPasswordPage

GraphQL: CheckDistributorEmailForResetPassword, ResetDistributorPassword


Sidebar Navigation

Six production sections (see intent navigation.sidebar).

Section menuId href Roles
Dashboards dashboard /dashboards SUPER_ADMIN, DISTRIBUTOR, DISTRIBUTOR_STAFF, CUSTOMER
Personnel staff DISTRIBUTOR, DISTRIBUTOR_STAFF
→ Distributor Staff distributor_staff /staff/distributor-staff DISTRIBUTOR
→ Agents agents /staff/agents DISTRIBUTOR_STAFF
Accounts accounts DISTRIBUTOR, DISTRIBUTOR_STAFF, GENERAL AGENT
→ Customers /accounts/customers + GENERAL AGENT
→ PayGo /accounts/asset-accounts + GENERAL AGENT
→ Payments /accounts/asset-accounts/payments + GENERAL AGENT
→ Pay Plans /accounts/payment-plans DISTRIBUTOR, DISTRIBUTOR_STAFF
→ Message Templates /accounts/message-template DISTRIBUTOR, DISTRIBUTOR_STAFF
→ Message Groups /accounts/message-group DISTRIBUTOR, DISTRIBUTOR_STAFF
Assets thing DISTRIBUTOR, DISTRIBUTOR_STAFF, GENERAL AGENT
→ Fleets /thing/fleet No GENERAL AGENT
→ Items /thing/item + GENERAL AGENT
Events events /events/items SUPER_ADMIN, DISTRIBUTOR, DISTRIBUTOR_STAFF
Support support /support/tickets All roles incl. CUSTOMER

menuPermissions (runtime canViewMenu) is declared in intent and must match lib/auth.tsx.


Dashboards Page

Route: /dashboards
Theme: DashboardShellPage
Page family: metric
Implementation: app/(default)/dashboards/page.tsx
Allowed roles: SUPER_ADMIN, DISTRIBUTOR, DISTRIBUTOR_STAFF, CUSTOMER (not GENERAL AGENT)

Parent query: GetFleetHealthSummary — batched pagination (itemLimit: 100) until all fleet devices loaded.

Tab shell: role="tab", aria-selected, aria-controls; tabpanels wrap each tab.


Tab: Assets

Theme: FleetHealthDashboard
Component: app/(default)/dashboard/fleet/components/FleetOverviewTab.tsx
Wrapper: app/(default)/dashboards/components/AssetsTab.tsx

KPI / fleet metrics cards

  • Online/offline counts from getFleetHealthSummary
  • Retry buttons use btn-primary on error states
  • Week navigation for code-stats remains visible while loading

Distribution chart (pie)

Component: components/charts/pie-chart.tsx + pie-chart-legend.tsx

Data: getItemStatisticsByItemField (field selector in UI)

Chart behavior:

  • Top 6 slices + Others (gray) via groupChartTailIntoOthers (components/utils/chart-data.ts)
  • Legend: keyboard-focusable buttons; hide/show series synced with chart

Last seen histogram

Component: LastSeenHistogram.tsx

Data: getItemStatisticsByAvatarLastSync

Chart behavior: Tail bars grouped as Others (slate); shared rankSeriesForChartTail

Code statistics (Device Distribution menu)

Route context: /dashboards → Assets tab → Device Distribution

Two Distribution by options:

Menu label Behavior
Code Statistics Original UX: fleet search, item batch search, date range, doughnut + code type list for one batch (getBatchCodeStats).
Code statistics drill-down Table drill-down: time period → fleets by code count → batches → code types (CodeStatsDrillDown.tsx).

Queries: GetFleetCodeStats, GetFleetBatchesForCodeStats, GetBatchCodeStats, getItemBatchesInItemFleet, getAllItemsInItemFleet (itemFleetId top-level arg), GetAllItemFleets, GetItemFleetsForClient

Drill-down flow:

  1. Time period — preset or custom dates; Apply period reloads rankings.
  2. Fleets by code countgetFleetCodeStats; click a row.
  3. Item batchesgetItemBatchesInItemFleet (fallback: paginated items); click a row.
  4. Code types — doughnut + quantities (getBatchCodeStats).

Stat tooltips

Info icon control + role="tooltip" on KPI labels (StatItem)


Tab: Device Monitoring

Theme: DeviceMonitoringDashboard
Component: app/(default)/dashboard/fleet/components/DeviceMonitoringTab.tsx

Layout: Split — device list | map + details | historical analysis panel

Child Path Role
DeviceList DeviceMonitoring/components/DeviceList.tsx Search/filter devices
DeviceMap DeviceMonitoring/components/DeviceMap.tsx Google Maps pins
DeviceDetails DeviceMonitoring/components/DeviceDetails.tsx GATT snapshot panel
HistoricalAnalysis DeviceMonitoring/components/HistoricalAnalysis.tsx Route playback

Data sources by role:

Role Device list source
DISTRIBUTOR / DISTRIBUTOR_STAFF getFleetHealthSummary (federated)
SUPER_ADMIN Thing: SuperAdminFleetMapPins, SuperAdminFleetItemsMapPins
GENERAL AGENT Thing: GetAllAgentItems

Device detail query: getDeviceSnapshotWithGattMeta (federated) — exposes avatar att fields including opid, ccid, ppid when reported.

Historical REST: POST ${NEXT_PUBLIC_DASH_API}/device-data

Map env: NEXT_PUBLIC_GOOGLE_MAPS_API_KEY, OSRM via NEXT_PUBLIC_OSRM_URL

Tab disabled when total_devices === 0 after fleet health load.


Tab: Custom Dashboards

Theme: CustomDashboardBuilder
Component: app/(default)/dashboards/components/CustomDashboardTab.tsx

Persistence: localStorage key dashboardConfig

UI: FloatingActionButton, AddDashboardModal, AddWidgetModal

Metrics REST: app/(default)/dashboards/utils/metrics.ts → Dash API /api/metrics/*


Assets Dashboard (alternate entry)

Route: /dashboard/fleet
Theme: FleetDashboardShellPage
Implementation: app/(default)/dashboard/fleet/page.tsx
Sidebar: Not linked (parallel entry point)

Tab Theme
Overview FleetHealthDashboard
Device Monitoring DeviceMonitoringDashboard
Analytics AnalyticsTab (reports shell)

Same fleet components as /dashboards tabs.


Personnel

Distributor Staff List

Route: /staff/distributor-staff
Theme: RecordListPage
Roles: DISTRIBUTOR

GraphQL:

  • List: GetAllDistributorStaffsForSpecificDistributor
  • Create route: /staff/distributor-staff/addDistributorRegisterDistributorStaff
  • Sub-roles picker: GetAllSubRoles

Module: app/(default)/staff/distributor-staff/


Agents List

Route: /staff/agents
Theme: RecordListPage
Roles: DISTRIBUTOR_STAFF

GraphQL:

  • List: GetAllAgentsForSpecificDistributorStaff
  • Create: DistributorStaffRegisterAgent
  • Permissions: AssignPermissionToAgent, DeletePermissionFromAgent

Module: app/(default)/staff/agents/


Accounts — Customers

Route: /accounts/customers
Theme: RecordListPage
Roles: DISTRIBUTOR, DISTRIBUTOR_STAFF, GENERAL AGENT

GraphQL: GetAllClientCustomers, ClientRegisterCustomer, UpdatePerson, DeletePerson, AssignCustomerToAgent

Route Theme Mode
/accounts/customers/add RecordFormPage create
/accounts/customers/edit/[id] RecordFormPage edit

Module: app/(default)/accounts/customers/


Accounts — PayGo Asset Accounts

Route: /accounts/asset-accounts
Theme: RecordListPage
GraphQL list: GetAllAssetAccountsForClient

Workflow routes

Route Theme step Primary mutations
/accounts/asset-accounts/activate/[id] WorkflowPage activate ActivateAssetAccount, ActivateCreditAccount
/accounts/asset-accounts/pair/[id] WorkflowPage pair PairAssetAccount
/accounts/asset-accounts/link-payplan/[id] WorkflowPage link-payplan UpdateAssetAccount
/accounts/asset-accounts/code-generation/[id] WorkflowPage code-generation GenerateDaysCode, GenerateResetCode, GenerateFreeCode, GenerateOpenToken
/accounts/asset-accounts/code-history/[id] RecordDetailPage GetAllCodeEventsForSpecificItemByDistributor
/accounts/asset-accounts/payments/[id] RecordDetailPage GetAllAssetAccountActivities

Aggregated payments: /accounts/asset-accounts/paymentsGetAllAssetAccountActivities

Module: app/(default)/accounts/asset-accounts/


Accounts — Pay Plans

Routes: /accounts/payment-plans, /add, /edit/[id]
Roles: DISTRIBUTOR, DISTRIBUTOR_STAFF

GraphQL: GetAllPayPlanTemplates, GetSpecificPayPlanTemplate, CreatePayPlan, UpdatePayPlan, DeletePayPlan


Accounts — Messaging

Route Theme GraphQL
/accounts/message-template RecordListPage GetAllMessageTemplates, Update/Delete
/accounts/message-group RecordListPage GetAllMessageGroups, CreateMessageFromTemplate

Assets — Fleets

Route: /thing/fleet
Theme: RecordListPage
Roles: DISTRIBUTOR, DISTRIBUTOR_STAFF

Operations: list, create, edit, delete, assignItems, configureCodeGen

GraphQL: GetAllItemFleets, CreateItemFleet, UpdateItemFleet, DeleteItemFleet, AssignItemToItemFleet, UpdateItemFleetCodeGen, code-stats queries

Special UI: CodeGenSettingsForm, fleet reassignment flows


Assets — Items

Route: /thing/item
Theme: RecordListPage
Roles: DISTRIBUTOR, DISTRIBUTOR_STAFF, GENERAL AGENT

List query by role:

Role Query
DISTRIBUTOR, DISTRIBUTOR_STAFF GetAllClientItems
GENERAL AGENT GetAllAgentItems (Thing client)

Operations: list, assignToAgent, export (CSV via ExportAvatarAttCsv)

GENERAL AGENT: Post-login landing page; no Fleets in sidebar.


Events

Route: /events/items
Theme: RecordListPage (metric family)
GraphQL: GetAllItemEvents
Filters: item, distributor
Roles: SUPER_ADMIN, DISTRIBUTOR, DISTRIBUTOR_STAFF


Support

Routes: /support/tickets, /support/tickets/new
Data source: REST (NEXT_PUBLIC_API_BASE_URL)
Roles: All including CUSTOMER

New ticket form loads product/subscription context from REST.


Avatar Telemetry (cross-cutting)

Thing Avatar shadows Item with GATT buckets att, sts, cmd, dta, dia.

Property Bucket Meaning
opid att Device serial / operator ID
ccid att SIM ICCID
ppid att PAYG identifier
Surface Identifier shown
Fleet list / map oemItemID as device_id from getFleetHealthSummary
Device detail Full snapshot via getDeviceSnapshotWithGattMeta
Item CSV export Includes avatar att columns (e.g. ccid)

Platform avatar CRUD is in Asset Admin — Superadmin, not this app.


API Routes (Next.js)

Route Purpose
app/api/maps/snap-to-roads/route.ts Google Roads snap
app/api/maps/compute-gap-route/route.ts GPS gap routing
app/api/roads/snap/route.ts Road snap utility

Role Matrix (quick reference)

Role Dashboards Accounts Fleets Items Staff Events Support
SUPER_ADMIN Yes No No No No Yes Yes
DISTRIBUTOR Yes Full Yes Yes Distributor staff Yes Yes
DISTRIBUTOR_STAFF Yes Full Yes Yes Agents Yes Yes
GENERAL AGENT No* Customers/PayGo No Yes (landing) No No Yes
CUSTOMER Yes No No No No No Yes

*GENERAL AGENT has dashboard in menuPermissions but /dashboards is not in page allowedRoles; sidebar may show link — guard at route level should match intent.


SUPER_ADMIN vs Superadmin app

Capability This app (v2) Superadmin CRA
Live fleet map Yes No
PayGo operations Client-scoped Global
Avatar entity edit Read-only snapshot Full CRUD
Fleet/batch admin Operational view Full CRUD

See asset-admin-superadmin.md.


Change Log

v3.2.0 — 2026-05-24

  • Intent-driven exact replica policy: replicaParity, productionRouteManifest (32 routes)
  • /signin documented (alias login/signin); layout groups auth vs default
  • docs/contracts/intent-driven-replica-parity.md + strengthened AI prompt

v3.1.0 — 2026-05-17

  • Generation profile: delivery: aiPrompt — rebuild via docs/agent/generate-assets-v2-prompt.md (no codegen scripts)
  • Design-system manifest documents Mosaic output guarantees

v3.0.0 — 2026-05-17

  • Omnivoltaic-style page-by-page specification for full regeneration
  • Intent bundle: graphql catalog, themes registry, pageGraphqlBindings
  • Documented app shell, env, Apollo clients, all production routes
  • Fleet dashboard tabs, chart Others grouping, avatar telemetry, role matrix

v2.0.0 — 2026-05-17

  • Fleet dashboards, role matrix, superadmin cross-link

v1.1.0 — 2026-04-26

  • CRUD intent whitelist model, menuId, route fields

v1.0.0 — 2026-04-26

  • Initial site derived from webapp v2