npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@masterteam/dashboard-builder

v0.0.86

Published

Dashboard Builder components and services for Angular 21+ with Tailwind CSS 4. The package supports authoring, saving, loading, and rendering dashboards with charts, header widgets, topbar widgets, metadata-driven data sources, source links, and respon

Downloads

3,015

Readme

@masterteam/dashboard-builder

Dashboard Builder components and services for Angular 21+ with Tailwind CSS 4. The package supports authoring, saving, loading, and rendering dashboards with charts, header widgets, topbar widgets, metadata-driven data sources, source links, and responsive ECharts output.

Features

  • Multilingual dashboard item titles stored as clientConfig.title.en/ar.
  • Header, topbar, and chart item title editing in builder mode.
  • Host-interceptor-owned save messages, with automatic saves marked silent.
  • Registry-driven chart query schemas, including safe preview states for incomplete draft charts.
  • Stacked-bar query fields switch between auto-stack and manual-stack modes so only the relevant stack property selector is displayed and emitted.
  • Quick Manage style changes stay local in the drawer until Apply, with an isolated Preview tab for checking the edited chart config before the dashboard canvas is updated.
  • Chart header icons provide preset-based appearance and placement controls, including independent standalone/group visibility and grouped icon-only mode.
  • Timeline multi-level queries persist per-level extraProperties and still read legacy extras fields. Open timeline detail popovers close when the page, dashboard, or timeline scrolls outside the overlay, while the detail panel's own scrolling remains available.
  • PhaseGate Table uses a dedicated semantic-role editor and sends every configured runtime property in one deduplicated backend projection.
  • Ring Gauge with Formula accepts either valueProperty or formula for each series, exposes no Group By or Extra Properties controls, and renders legacy values[].bars[] responses through the concentric-ring gauge adapter. It keeps each numeric detail visible (including 0) without exposing the generated series key as a gauge label or legend entry.
  • Every GeneralQuery capability that includes formula exposes a visible per-series Show/Hide advanced settings action and a Formula Builder scoped to that series; formula availability is schema-driven rather than chart-specific. It uses the legacy P+4 Dashboard Builder dialect rather than Formula Engine: the original nine function keys and eight punctuation operators compose a compact expression without inter-token whitespace, and selected properties serialize as bare keys such as actual_progress (for example, sum(actual_progress), never sum ( @Current::actual_progress )). Existing scoped or spaced chart formulas are migrated on load, save, and execution; whitespace inside quoted custom values is preserved.
  • Selected-property chips in dashboard query editors can be removed inline.
  • Properties/card-list items normalize null Percentage values to 0 with an empty progress track and never inherit a sibling card's runtime percentage.
  • Duplicated charts receive a new local dashboard identity, omit the source link/component IDs so the next save creates a new backend chart, and persist duplicatedFrom as provenance to the source dashboard identity.
  • Workspace-aware metadata loading for modules and properties.
  • A dedicated chart Filters action separates viewer-facing filter controls and built-in conditions from datasource configuration, using shared mt-* form fields throughout the condition editor.
  • Navigation actions expose a Navigation Target selector. Both page and entity navigation default to the current tab and can explicitly open in a new tab through action.config.navigationTarget. Page navigation replaces only the active dashboard id while preserving host route segments such as manage or dashboards. Entity navigation uses browser-level application navigation so a current-tab action triggered in the control-panel builder can load the app-client workspace route instead of falling through the admin router to All Workspaces.
  • Progress Update cards render activity HTML through ngx-quill's read-only renderer, preserving the rich-text size, color, font, alignment, direction, and semantic formatting configured in the source editor.
  • Persisted dashboards can be deleted from the page-settings drawer after a named, danger-styled confirmation; successful deletion returns the host to its dashboard list while standalone builders keep deletion unavailable.
  • Module multi-select serialized as selectors such as Level:1,2,3.
  • Source-link chaining with previous result fields, relationship fields, and the Parent option exposed to child-level configuration.
  • Distinct aggregation normalization from legacy DisticntCount to DistinctCount.
  • Container-aware ECharts option mapping for compact dashboard grid cells, including shared legend placement for donut, pie, bar, line, stacked bar, and other ECharts-based charts. Pie/donut radius, center, and slice-label geometry stay handler-owned to match the old dashboard builder; the shared EChart wrapper reserves the same chart area as the old wrapper and does not apply pie-specific responsive overrides. Native ECharts legend rendering stays authoritative; the wrapper maps legend dimensions before grid offsets and clamps saved percentage grid values against the current card pixels so stacked bar legends keep reserved space after resize. Donut slice labels never repeat the category name — that stays in the legend and the ring carries the value — and they are shown or hidden by the Show labels toggle (configAsType.label.show), not by whether a legend is visible. Pie legend vertical alignment applies only to left/right legends, so old saved vertical-align values do not move the default bottom legend into the chart body. Compact bar and line charts preserve their configured value-label visibility instead of hiding labels when the card height crosses a responsive breakpoint.

Installation

pnpm add @masterteam/dashboard-builder

Usage

import { Component } from "@angular/core";
import { DashboardBuilder } from "@masterteam/dashboard-builder";

@Component({
  imports: [DashboardBuilder],
  template: ` <mt-dashboard-builder [services]="['pplus', 'splus']" (onSave)="saveDashboard()" /> `,
})
export class MyComponent {
  saveDashboard(): void {
    // React to a completed explicit save.
  }
}

Metadata Contract

Hosts must provide the dashboard metadata endpoints used by DashboardBuilderService:

  • GET metadata/workspaces returns workspace options: WorkspaceOption { id: string | number; name: string }.
  • GET metadata/modules/tree accepts services, workspaceId, and includeValues. Returned module groups, items, and values may include workspaceId for client-side filtering.
  • POST metadata/modules/properties accepts items and optional workspaceId. Each request item may also include workspaceId.
  • GET Modules?LevelId={levelId} backs the Status filter's Log select. It returns the product module catalog with isActive marking which modules are attached to that level; the builder keeps the active entries whose classification is Log (the enum name or the ordinal 0). Hosts that already own this list can override it with [levelLogs] on ManageFilterOnPage / DynamicFiltersConfig.

When a workspace changes, the builder clears selections and source links that no longer belong to the selected workspace.

Table Query Editing Contract

The generic Table query editor persists ordered selectedProperties, PivotProperties, AggregationProperties, and an optional lookupProperty. Its ControlValueAccessor treats writeValue as silent model-to-view synchronization: an equivalent value is ignored, and aggregation controls are rebuilt with emitEvent: false. This is required because DataSourceSettings immediately feeds each ngModelChange back into the editor; emitting during that echo creates a synchronous loop that freezes the Properties picker (#1284).

Snapshot Query Contract

Snapshot charts distinguish the selected Level schema from the concrete Level record whose history is requested:

interface SnapshotQuery {
  workspaceSelector?: string | null;
  levelId: number; // schema id used to load record options
  levelDataId: number; // record id sent to GetSnapShotInfo
  snapshotTargetVersion: 2; // prevents schema ids from using the legacy fallback
  timeFrame: "yearly" | "monthly" | "weekly";
  year: number | "{{currentYear}}";
  getAveragePerPeriod?: boolean;
  selectedProperties: string[];
}

After a Level is selected, the builder requests its records through POST fetch/query with:

{
  contextKey: `level:${levelId}`;
  projection: "Card";
  display: {
    areas: ["card"];
  }
}

This authoring request deliberately omits includeState. Record options map directly from data.records[].id/name. Runtime calls POST dashboards/{apiVersion}GetSnapShotInfo/{levelDataId}/{timeFrame}?year={year} with { AvgPerPeriod, dashboardId }; the schema levelId must not be used in that URL. Supported selected-property keys are plannedProgress, actualProgress, budget, baselineProgress, spent, remaining, and paymentPlan.

Axis Label Wrapping Contract

Bar and stacked-bar category labels preserve their original casing and wrap normal spaces, underscores, existing line breaks, and single tokens that exceed the selected character limit. Runtime keeps all generated lines visible and sets hideOverlap: false; the shared ECharts responsive layout still owns the available label width and grid containment.

The persisted controls live under clientConfig.barConfigOverride and clientConfig.stackBarConfigOverride:

{
  axisLabelWrapMode: "characters" | "words" | "none";
  maxCharsPerLine?: 8 | 12 | 16 | 20 | 24 | 30 | 40;
  maxWordsPerLine?: 1 | 2 | 3 | 4 | 5 | 6;
}

Missing mode values use character wrapping, so old dashboards that only saved maxCharsPerLine remain compatible. The default is 20 characters per line; word mode defaults to three words per line. none normalizes separators but keeps the label on one line.

Host Currency Settings Contract

Dashboard currency labels, tooltips, and values read the host's canonical systemSettings.settings local-storage entry. The stored value must be a JSON-encoded array containing a case-sensitive Currency key whose value provides the currency code, localized units, and numeric format:

localStorage.setItem(
  "systemSettings.settings",
  JSON.stringify([
    {
      key: "Currency",
      value: {
        name: "OMR",
        unit: {
          en: "OMR",
          ar: "ريال عماني",
        },
        format: "{0:N3}",
      },
    },
  ]),
);

Runtime currency precedence is:

  1. The valid Currency entry in systemSettings.settings.
  2. The legacy JSON object stored under currency (its unit string).
  3. The built-in SAR unit and legacy number formatting.

Arabic dashboards (including regional language codes such as ar-SA) use value.unit.ar; every other language uses value.unit.en. Supported system formats include decimal/grouping patterns such as {0:#,0.00}, compact thousands such as {0:#,0, K}, and named decimal patterns such as {0:N3}. The host currency format is authoritative; a chart's legacy decimal option does not override its decimal count.

The showcase's opt-in offline dashboard fixture (showcase-mock-dashboard-builder=1) appends the OMR example above only when the settings array has no Currency entry, so an existing host choice remains authoritative.

PhaseGate Table Query Contract

2-table-view does not use the generic table pivot/aggregation query. Its canonical service query is:

{
  rowIdentityProperty: string;
  phaseNameProperty: string;
  phaseIdProperty: string;
  phaseOrderProperty: string;
  phaseStatusProperty: string;
  phaseProgressProperty: string;
  phaseIsCurrentProperty: string;
  phaseIsCompletedProperty: string;
  phaseAllItemsCompletedProperty: string;
  baseProperties: string[];
  properties: string[];
  selectedProperties: string[];
}

baseProperties is the ordered multi-select of visible business columns. Both properties and selectedProperties are rebuilt before save/request as the same deduplicated union of those columns and every non-empty semantic role, so the chart service receives all fields needed by the renderer.

The runtime resolves configured roles by property identity, matching the row object key or wrapper key, normalizedKey, label, or name without depending on response order. Consequently a visible project Status and the phase Status can coexist safely. Dynamic phase headers use the configured phase property's runtime value rather than its schema/metadata label, and an empty phaseProgressProperty does not render a zero-value progress track. Legacy generic-table configs are still read; lookupProperty becomes the phase-status role, with conservative runtime fallbacks until the remaining roles are configured and saved.

Runtime Filter Contract

Dashboard filters have two independent scopes:

  • Page/general filter definitions live in the dashboard configuration's filters collection. The viewer supplies their applied values to every item; request templates consume the keys they reference. ignoreQueryFilter suppresses ambient global query values but retains explicit viewer/dialog filter context for compatibility with saved pages. The viewer opens a default-closed shared MT drawer from its in-flow Manage Filters action, so the host does not need a fixed sidebar or overlay CSS. Apply commits the merged values to the runtime store synchronously, then mirrors them to the URL as the shareable source of truth. Every mounted item observes the forwarded query- param signal and requests data again when it changes, so Apply, Clear, browser navigation, and externally-authored query params update the dashboard without a page reload. The later route echo has the same serialized state and does not duplicate the request.

  • Chart filter definitions live canonically in clientConfig.dynamicFilters. Legacy clientConfig.extraFilters.dynamicFilters is read and migrated when the chart is edited. Runtime values are stored per widget identity and override a page value with the same key for that widget only.

  • Viewer extraFilters are host-owned context (for example, an informative workspace's currentId). They are merged after page and chart values, so a same-key runtime value cannot replace or clear the host's scope.

  • Builder and viewer hosts pass the same client-form metadata through [lookups], [statuses], and [levelsSchema]. Lookup configuration labels are normalized from the host lookup names, runtime Status options accept canonical nested status definitions or legacy flat level statuses, and Status/Phase Gate schema configuration is derived from the shared hierarchical metadata tree's Level module. [levelsSchema] is the host's levels collection; runtime Phase Gate and Schema Settings options read it, and it replaces the legacy pplus.settings localStorage cache for hosts that never wrote one.

  • A Status filter's "Log" options are scoped to the row's selected level, so two Status filters on different levels never share one list. They resolve in this order:

    1. The host [levelLogsResolver], when one is registered. Hosts that already reach the module catalog through their own authenticated client should supply one — it removes every base-URL and permission unknown from the package's side. Without it the package requests GET Modules?LevelId={levelId} itself. Either way the result is cached per level for the session, and a failed load is reported through console.warn so a 401/403/404 is never mistaken for "no logs".

      Note that endpoint returns the WHOLE module catalog and marks the level's own modules with isActive rather than filtering, so a level's log is isActive === true and isLogClassification(classification) (ModuleClassificationEnum.Log; Process is not a log). That endpoint serializes the enum as its name ("Log"), while others emit the ordinal 0 — compare through isLogClassification, never a bare === 0, which silently drops every module and empties the dropdown.

    2. The level's own logs collection, when the host levels schema carries one (the legacy PPlus4 settings contract).

    3. Status metadata: every definition scoped to that level that names a moduleId. This derivation is the exact inverse of the runtime read (getStatuses(logId, 'Log') matches on moduleId), so every offered option resolves to real status values. Labels come from the metadata tree's Module values, falling back to the status property name.

    Sources 2 and 3 need no request, so the select stays populated for hosts that cannot reach the Modules endpoint.

Both drawers edit a private draft. Apply commits and refreshes the relevant scope; closing without Apply discards the draft. Clear replaces only the keys owned by that drawer's field set: optional values are removed, required non-date values remain selected, and static chart conditions plus the other runtime scope are preserved. false and 0 are valid applied values rather than empty values. Drawer ownership follows the viewer/widget lifecycle, so an open body-appended drawer is closed when its owner is destroyed and cannot commit into a stale chart store.

Compatibility key mapping follows the original dashboard builder while keeping each scope's natural runtime types:

  • page/query values serialize multi-selects as comma-delimited strings, users as the id under key plus the display name under <key>Name, checkboxes as "true"/"false", and quarters under <key>S plus <key>E;
  • chart item-store values keep multi-selects as arrays, checkboxes as booleans, users as { userId, displayName } objects (or arrays of those objects), and quarters as the combined start,end value under the base key;
  • Clear owns every compatibility key for its field: users own key and <key>Name, while quarters own key, <key>S, and <key>E.

Request substitution accepts both representations. Page values are merged first and a chart's typed item-store values win only for that widget.

The parent report id (pageId) is the request execution context. A chart's serviceConfig.dashboardId remains its widget/store/cache identity and must not replace the report id. Route query parameters, including a legitimate filter named id, are never used as the report identity.

Dialog actions retain that report id and expose the merged page/chart context both under parentFilter and as flat keys. This keeps current {{parentFilter.year}} templates and legacy {{year}} templates compatible.

Compatibility Notes

  • Saved selectors remain backward-compatible with legacy moduleType/moduleId values and comma-based module ids.
  • Existing multilingual title objects are preserved. Plain string titles are normalized to the active title object shape.
  • Removing a persisted chart group unlinks its saved group container before clearing the local group metadata. Its child charts remain on the dashboard and the deleted empty group cannot return after Save and reload.
  • Style Studio resolves Header and Topbar widgets without a chartTypeId through their registered clientConfig.componentName. This selects their layout-specific controls (without chart legend, label, palette, or number formatting options) and previews draft styles before Apply. Cancel continues to discard the working draft.
  • Style Studio preview/runtime parity includes Header and Topbar icon placement, bar and stacked-bar widths, configured number precision and explicit currency visibility, pie legend type/font controls, bilingual donut center labels, table/timeline legend position and icon shape, and statistic-card icon placement. Stacked-bar totals are emitted only when configAsType.label.showTotalInTop is true; the generic data-label toggle cannot override that dedicated setting.
  • Performance Gauge Card treats the Status wrapper returned in values.*.extraValues as authoritative for the active status key, localized label, and color. Component status ranges still define the gauge scale and segments, and remain the fallback when the response has no status wrapper.
  • The builder does not show save toasts directly. Save response messages belong to the host HTTP interceptor; automatic/internal save requests are sent with noMessage: true.
  • Aggregation requests normalize legacy DisticntCount values to DistinctCount; backends should accept both names and execute distinct-count semantics.
  • Table-card status legends support both grouped and flat label payloads, display legacy label text alongside the color indicator, and expose visibility plus per-entry order/color controls in Style Studio for Table, Dialog Table, and PhaseGate Table items.
  • Table and Dialog Table user cells resolve the canonical photoUrl value before the legacy image and avatar aliases, then load the avatar through the shared authenticated secureImage pipeline instead of binding the backend value directly to <img src>.
  • Manage Item filter value fields treat properties whose normalizedKey is Id as literal identifiers. They do not call the finite-list property-items endpoint for those properties; lookup and status properties continue loading their selectable values normally. Checkbox/boolean properties synthesize localized Yes/No values locally. Existing filters may restore their property key before async property metadata arrives; the field reclassifies that key when metadata resolves and ignores any obsolete remote response, so edit mode retains the boolean Values tab (#1237).
  • ChartDataService.clearCache(dashboardId?) clears LimitedHttpService cache entries by chart item id (serviceConfig.dashboardId) when an id is passed, or clears all chart responses when omitted. Scoped clears also bypass matching in-flight requests so the next fetch cannot reuse stale data after a host create/edit/delete action.
  • DashboardViewer.reloadPage() is a host-facing mutation refresh API: it clears the dashboard chart HTTP cache before triggering all chart items, so calls such as viewer()?.reloadPage() always send fresh backend requests.

Development

# Build only the dashboard-builder package
pnpm build:db

# Focused tests for changed specs
pnpm --filter @masterteam/dashboard-builder test -- --watch=false --include=<path>.spec.ts

Do not use the full @masterteam/components build for routine component work unless the caller explicitly approves it.

License

MIT