Frontend - UI Components

React component tree, AG Grid integration, tab system, service layer, and event type mapping for the Historical Summary feature

👤 Aakash Pawar📅 Updated: Apr 1, 2026🏷️ feature

Frontend - UI Components

This page covers the React component tree, AG Grid configuration, the tab system, the service layer, and how the frontend maps user interactions to backend event types.


Component Tree

Entry Point: NewDrillDownView

Source: NewDrillDownView/index.tsx

This component manages the top-level "Summary View / Detailed View / Historical View" tab switching:

  • Renders the "Historical View" tab only if sessionState.enable_operation_summary is truthy
  • Manages shared state: date range, granularity, selected centres (multi-centre labs)
  • Computes auto-granularity via getGranularity(), which selects daily/weekly/monthly based on date range length:
    if (dateDiff <= 14) return "Daily";
    else if (dateDiff <= 62) return "Weekly";
    else return "Monthly";

HistoricalSummary

Source: HistoricalSummary.tsx

This component owns the tab navigation and delegates data display to HistoricalDetails:

State it manages:

  • activeEntityTab - Which entity type is selected (Organization/Referral/Branch/Department/Test)
  • activeCountTab - Which count is selected (Test Count / Sample Count / Bill Count)
  • selectedBreakdown - Optional drill-down selection (Test Wise / Sample Wise)
  • accessFilter - Optional access filter (Accessed / Not Accessed)

Tab System:

There are two levels of tabs:

LevelConfig SourceOptions
Entity Type TabsentityTypeTabs in constants.tsOrganization Wise, Referral Wise, Department Wise, Test Wise, Branch Wise
Count Summary TabscountSummaryTabs in constants.tsTest Count, Sample Count, Bill Count

Some tabs are hidden based on the selected entity type:

entityTypeTabs = [
  { label: "Organization Wise", hideTabs: [] },
  { label: "Referral Wise", hideTabs: [] },
  { label: "Department Wise", hideTabs: ["Sample Count", "Bill Count"] },
  { label: "Test Wise", hideTabs: ["Sample Count", "Bill Count"] },
  { label: "Branch Wise", hideTabs: [] },
];

Department Wise and Test Wise only support Test Count. The backend has no department_sample, department_bill, etc. event types.

Breakdown options (per count type):

historicalBreakdownOptions = {
  "Test Count": [{ label: "Test Wise" }],
  "Sample Count": [{ label: "Sample Wise" }],
};

Bill Count has no breakdown options.

HistoricalDetails

Source: HistoricalDetails.tsx

This component handles:

  1. Data fetching - calls getHistoricalSummaryData() whenever props change
  2. Data transformation - pivots flat API response into AG Grid row format
  3. Synthetic rows - calculates group-level totals and averages
  4. Pinned rows - grand total and grand average pinned to the bottom
  5. AG Grid rendering - configures columns, comparators, and cell renderers

AG Grid Configuration

The grid is configured as a pivot table where:

  • First column is the entity name (org name, referral name, etc.)
  • Dynamic columns are one column per date bucket (e.g., "2026-03-01", "2026-03-08")
  • Pinned bottom rows show Grand Total and Grand Average

Column Generation

Columns are generated dynamically from the API response's date buckets:

// Pseudocode for column generation
const dateColumns = uniqueBuckets.map(bucket => ({
  field: bucket,
  headerName: formatDate(bucket),
  valueGetter: createLeafMetricValueGetter(bucket, summaryTypes),
  cellRenderer: createBucketOrTotalCellRenderer(bucket, normalRowMode, summaryTypes),
  comparator: createSummaryMetricComparator(summaryTypes),
}));

Synthetic Row Types

The grid inserts computed rows after each group:

Row TypesummaryType valueCalculation
Group Total"groupTotal"Sum of all values in the group for each bucket
Group Average"groupAverage"Average of all values in the group for each bucket
Pinned Grand Total"pinnedGrandTotal"Sum across all groups (pinned to bottom)
Pinned Grand Average"pinnedGrandAverage"Average across all groups (pinned to bottom)

Row Sorting

Summary rows are always sorted to the bottom of their group using createSummaryMetricComparator():

const createSummaryMetricComparator = (types) => (valueA, valueB, nodeA, nodeB, isDescending) => {
  const rankA = getSummaryRank(nodeA?.data, types); // 0 = data, 1 = total, 2 = average
  const rankB = getSummaryRank(nodeB?.data, types);
  if (rankA !== rankB) {
    return isDescending ? rankB - rankA : rankA - rankB;
  }
  return (Number(valueA) || 0) - (Number(valueB) || 0);
};

Service Layer

Source: services.ts

getHistoricalSummaryData()

This function builds the API request from the current UI state:

const getHistoricalSummaryData = async (params) => {
  const {
    eventType,     // e.g., "organization_test"
    startDate,     // "2026-03-01"
    endDate,       // "2026-03-14"
    granularity,   // "daily" | "weekly" | "monthly"
    breakdown,     // optional: "true" for detailed view
    secondaryGrouping, // optional: "true" for test-wise
    accessFilter,  // optional: "accessed" | "not_accessed"
  } = params;

  const response = await GET(
    `/api-v3/operation/operation-dashboard/historical-summary/${eventType}`,
    { start_date, end_date, summary_granularity, ... }
  );

  return groupByDimension(response.data.summary, granularity);
};

groupByDimension()

Transforms the flat API response into the row-based structure AG Grid expects:

Input (from API):

[
  { "dimension_a_name": "City Hospital", "ordered_bucket": "2026-03-01", "total_value": 45 },
  { "dimension_a_name": "City Hospital", "ordered_bucket": "2026-03-08", "total_value": 52 },
  { "dimension_a_name": "Metro Clinic",  "ordered_bucket": "2026-03-01", "total_value": 28 }
]

Output (for AG Grid):

[
  { "key": "City Hospital", "dimension_a_id": 101, "2026-03-01": 45, "2026-03-08": 52 },
  { "key": "Metro Clinic",  "dimension_a_id": 102, "2026-03-01": 28, "2026-03-08": 0 }
]

Each ordered_bucket value becomes a column field in the row object.


Event Type Mapping: getEventKey()

Source: helpers.ts

This function translates the UI's tab selections into the backend's event_type string:

// Input: UI tab labels
getEventKey("Test Count", "Organization Wise")  // → "organization_test"
getEventKey("Sample Count", "Referral Wise")    // → "referral_sample"
getEventKey("Bill Count", "Branch Wise")        // → "branch_bill"

The mapping uses two lookup tables:

UI Service LabelMaps to
Test Count"test"
Sample Count"sample"
Bill Count"bill"
UI Group LabelMaps to
Organization Wise"organization"
Referral Wise"referral"
Department Wise"department"
Test Wise"organization" ⚠️
Branch Wise"branch"

Important quirk: "Test Wise" maps to "organization" (not "test"), because the event type format is {entity}_{metric}. If "Test Wise" mapped to "test", combining with "Test Count" would produce "test_test", which isn't a valid event type. The "Test Wise" tab actually means "group by test entity" which is achieved via secondaryGrouping=true on the API. The event key is used just for the primary grouping.

The generated event type is validated against the VALID_EVENT_TYPES array. If it's not valid, the function returns an empty string and the API call is skipped.


Granularity Auto-Selection

Source: getGranularity() in helpers.ts

export const getGranularity = (startDate, endDate) => {
  const dateDiff = endDate.diff(startDate, "days") + 1;
  if (dateDiff <= 14) return { label: "Daily", value: 1 };
  else if (dateDiff <= 62) return { label: "Weekly", value: 2 };
  else return { label: "Monthly", value: 3 };
};

This matches the backend's validation limits exactly: daily allows max 14 days, weekly allows max 62 days. The user can also manually select a different granularity via the dropdown (GRANULARITY_OPTIONS).


Notification Banner

export const getHistoricalViewNotificationText = (labTimeZone) => {
  return i18n.t(
    "All activities related to orders & samples performed today will appear " +
    "in the Historical View on the next day. All data is calculated based on " +
    "the {{timezone}} timezone.",
    { timezone: labTimeZone }
  );
};

This notification is always displayed at the top of the Historical View to set user expectations about data latency.


Export Configuration

The grid supports Excel export with a custom processCellCallback:

totalBilledHistoryDetailsExportConfig = {
  fileName: `Historical Details-Export ${moment().format("YYYY-MM-DD")}`,
  processCellCallback: (params) => {
    if (params?.column?.colId === "key") {
      return dateFormatChange(params?.value);
    }
    return params.value;
  },
};

The key column holds the entity name (organization, referral, etc.). The dateFormatChange() call is a defensive pass-through — it only reformats values that parse as valid dates. Since entity names are never valid date strings, the callback returns them unchanged. This is likely a leftover from an earlier iteration and functions as a no-op in practice.


Source File Index

FilePurpose
NewDrillDownView/index.tsxParent container: date range, view switching, feature flag check
HistoricalSummary.tsxTab navigation, state management, data fetching orchestration
HistoricalDetails.tsxAG Grid rendering, data transformation, synthetic rows
services.tsAPI service layer: request building and response transformation
constants.tsTab definitions, granularity options, breakdown options, export configs
helpers.tsgetEventKey(), getGranularity(), summary row utilities, access filter logic

On this page