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-icondashboard-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.ts→signInUser)
Behavior:
- User submits email/password via
AuthProvider.signIn. - On success, store
access_token,distributorId(_id),userJSON inlocalStorage. - Decode JWT; attach
roleNameto user object. - Redirect:
GENERAL AGENT→/thing/item- 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-primaryon 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:
- Time period — preset or custom dates; Apply period reloads rankings.
- Fleets by code count —
getFleetCodeStats; click a row. - Item batches —
getItemBatchesInItemFleet(fallback: paginated items); click a row. - 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/add→DistributorRegisterDistributorStaff - 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/payments — GetAllAssetAccountActivities
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) /signindocumented (aliaslogin→/signin); layout groups auth vs defaultdocs/contracts/intent-driven-replica-parity.md+ strengthened AI prompt
v3.1.0 — 2026-05-17¶
- Generation profile:
delivery: aiPrompt— rebuild viadocs/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