UI composition and layout
The shell
Two nested components form the frame. AppComponent provides the theme classes and
the outer outlet; MainLayoutComponent provides the page structure.
<!-- app.component.html -->
<div class="preloaderbar active" appPreloader><span class="bar"></span></div>
<div id="app-main" class="app-main full-height" [ngClass]="{ … }">
<router-outlet></router-outlet>
</div>
<!-- shared/layout/main-layout.html -->
<app-stage-info></app-stage-info>
<app-layout-sidenav></app-layout-sidenav>
<section id="page-container" class="app-page-container">
<div class="app-content-wrapper">
<div class="app-content {{ customClasses }}">
<div class="full-height"><router-outlet></router-outlet></div>
</div>
<a class="mat-fab mat-accent d-lg-none" appToggleOffcanvasNav>…</a>
</div>
</section>
<app-search-overlay></app-search-overlay>
Consequences worth knowing:
-
/login,/logoutand/reset-passwordare declared outsideMainLayoutComponent, so they render into the outer outlet with no sidebar, stage banner or search overlay. -
The search overlay is a singleton in the shell, opened by the
appOpenSearchOverlaydirective from the sidebar. -
app-stage-infoshows which environment/back-end build you are pointed at — see versions.
Theme classes
AppComponent binds a dozen classes on #app-main from two sources:
-
ThemeConfig(fromthemeConfigData(env.envName)insrc/app/config.ts) —fixed-header,nav-collapsed,nav-behind,layout-boxed,theme-gray,theme-dark,sidebar-sm,sidebar-lg. -
The
layoutstore slice —crm-fixed,quickview-open,supplier-info-open,supplier-info-close.
ThemeConfig also carries colorOption (a two-digit code driving the sidebar
palette; 11 on prod, 12 elsewhere), sidebarWidth, productLink, and
specialOccasion from shared/utils/functions/check-for-special-occasion.ts —
seasonal theming.
Additionally AppComponent toggles a restricted-access class on document.body
for REST_UW_ACCESS users, which SCSS uses to suppress UI wholesale.
Global stylesheets
Eight SCSS entry points are listed in angular.json, loaded in this order:
styles.scss, bootstrap.scss, font-awesome.scss, layout.scss, loader.scss,
material2-theme.scss, theme.scss, ui.scss, then the two icon fonts.
stylePreprocessorOptions.includePaths adds src/assets, so partials there can be
imported by bare name.
Component SCSS should follow ABEM without prefixes — camelCase blocks and elements with separately-applied modifiers:
<section class="uwCard__subtitle -active -selected -primary">…</section>
See conventions for the full rule and its rationale.
The panels around the outlet
Three regions surround the routed content and are controlled by the layout slice
rather than by routing:
| Region | Behaviour |
|---|---|
Sidebar ( |
Brand, menu, front-end and back-end version labels, logged-in user menu, help
link. Collapsible via |
CRM panel ( |
Four states: |
Supplier info panel |
Three states: |
Route-pattern layout defaults
shared/layout/store/layout.reducers.ts holds a userLayoutConfig keyed by three
URL regexes:
| Pattern | Regex | Defaults |
|---|---|---|
|
|
CRM hidden, supplier info hidden, search visible, actions hidden. |
|
|
CRM fixed, supplier info open, search visible, actions visible. |
|
|
CRM closed, supplier info open, search visible, actions visible. |
LayoutUpdate(urlAfterRedirects) — dispatched from AppComponent on
NavigationEnd — selects the first matching pattern and applies its config, while
also remembering per-pattern user overrides. So "the CRM panel is pinned on the
supplier General tab but merely available elsewhere" is a configuration fact, not
component code.
LayoutService supplies customClasses$ for per-page body classes;
FabHolderNavComponent hosts the floating action buttons.
Table generations
Four table implementations coexist under shared/. Knowing which is which saves a
lot of time.
| Folder | Status and use |
|---|---|
|
Current. The wrapper module to use for new grids. Wraps AG Grid 23.x with the project’s defaults. |
|
AG Grid-based table with header-template rendering
( |
|
Includes |
|
The oldest, Material-table-based implementation. Its |
|
Lightweight non-AG-Grid tables for small fixed lists. |
|
Reusable AG Grid cell renderers shared across grids. |
|
Pagination control used with the non-AG-Grid tables. |
|
For a new grid use |
AG Grid enterprise features are in use, and the licence key is set in
main.ts with an expired ExpiryDate. Expect a console warning; it does not
disable functionality.
Dialogs
shared/dialog/ provides DialogModule + DialogService, with dialog.config.ts
holding shared MatDialogConfig defaults. Purpose-built dialogs sit alongside:
| Component | Use |
|---|---|
|
Generic yes/no confirmation. |
|
Delete confirmation with the item name. |
|
Informational, single-button. |
|
Confirmation that requires a free-text reason — used by supplier lifecycle actions where the reason is persisted. |
|
Shows a computed summary before committing an operation. |
|
The shared "access denied" dialog. |
Domain modals live with their feature, not in shared/ — the ~20 folders under
suppliers/supplier/ such as add-receivable/, add-chargeback/,
cancel-supplier/, transaction-modal/ are each a dialog plus its service.
Notifications are toasts (ngx-toastr), never dialogs, and go through
ErrorVisualizationService for errors. See
error handling.
Forms
Angular reactive forms throughout. Shared form building blocks:
| Component/service | Purpose |
|---|---|
|
Builds form controls from a configuration object — used where the field set is data-driven (report configuration, onboarding templates). |
|
The recurring "editable card" pattern: a titled panel with edit/save/cancel that
submits one |
|
Single-field in-place editing. |
|
File selection for uploads. |
|
Input primitives. |
|
TinyMCE wrapper ( |
|
Display primitives — read-only value pairs, section headers, empty states, and a button with an inline progress state. |
|
Ordering UI (onboarding steps, report columns). |
|
Declarative validation-message display; legacy, used in older forms. |
Filtering and search
| Area | Detail |
|---|---|
|
Reusable filter controls bound to grid query parameters. |
|
A composite panel with |
|
The global search modal, opened by the |
SearchEngineService.pybEncode percent-escapes ?, =, & and + as
~F, ~3D, ~26, ~2B before sending the phrase — a backend-specific encoding.
Do not URL-encode instead; the backend expects this scheme.
The layout slice’s choosenSearch (NEW_SEARCH / OLD_SEARCH) still exists with
CHOOSE_NEW_SEARCH / CHOOSE_OLD_SEARCH actions; NEW_SEARCH is the default and
SearchEngineService.search() has its feature check short-circuited to if (1 === 1).
Directives
18 directives, mostly layout and behaviour glue:
| Directive | Effect |
|---|---|
|
Closes the sidebar on navigation when |
|
The mobile menu FAB. |
|
Sidebar menu behaviour — accordion expansion, chevron injection, active-route highlighting. |
|
Opens the global search overlay. |
|
Structural directive wrapping |
|
Binds the top progress bar to |
|
Shows why a control is disabled — the tooltip pattern used across the supplier workspace where a field is read-only due to status or permission. |
Component base class
shared/component-subscriber/subscriber.component.ts provides SubscriberComponent,
which collects this.subscriptions and unsubscribes in ngOnDestroy.
AppComponent, MainLayoutComponent, ResizeIframeService and many feature
components extend it.
|
Extend |