Skip to content

@ahoo-wang/fetcher-viewer

The @ahoo-wang/fetcher-viewer package provides a React + Ant Design component library for building data viewing interfaces. It includes filter panel components with a typed registry, table components with rich cell renderers, view management with save/load capabilities, a topbar with action items, and a complete FetcherViewer component that ties everything together using Wow CQRS queries.

Installation

bash
pnpm add @ahoo-wang/fetcher-viewer

Component Architecture

mermaid
graph TB
    subgraph sg_1 ["FetcherViewer"]
        direction TB
        FV["FetcherViewer<br>top-level orchestrator"]
        V["Viewer<br>view + table + filters"]
        VT["ViewTable<br>Ant Design Table wrapper"]
        FP["FilterPanel<br>filter composition"]
        TB["TopBar<br>action bar"]
    end

    subgraph sg_2 ["Filter System"]
        direction LR
        TF["TypedFilter<br>type-based dispatch"]
        FR["filterRegistry<br>type -> component map"]
        FF["FilterField<br>id, text, number, select, bool, datetime"]
    end

    subgraph sg_3 ["Cell System"]
        direction LR
        TC["TypedCell<br>type-based dispatch"]
        CR["cellRegistry<br>type -> component map"]
        CELLS["TextCell, TagCell, ImageCell,<br>DateTimeCell, CurrencyCell, ..."]
    end

    subgraph sg_4 ["View Management"]
        direction LR
        VP["ViewPanel<br>view list sidebar"]
        SVM["SaveViewModal<br>create/edit views"]
        VMM["ViewManageModal<br>manage saved views"]
    end

    FV --> V
    V --> VT
    V --> FP
    V --> TB
    FP --> TF
    TF --> FR
    FR --> FF
    VT --> TC
    TC --> CR
    CR --> CELLS
    V --> VP
    VP --> SVM
    VP --> VMM

    style FV fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
    style V fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
    style VT fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
    style FP fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
    style TB fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
    style TF fill:#161b22,stroke:#30363d,color:#e6edf3
    style FR fill:#161b22,stroke:#30363d,color:#e6edf3
    style FF fill:#161b22,stroke:#30363d,color:#e6edf3
    style TC fill:#161b22,stroke:#30363d,color:#e6edf3
    style CR fill:#161b22,stroke:#30363d,color:#e6edf3
    style CELLS fill:#161b22,stroke:#30363d,color:#e6edf3
    style VP fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
    style SVM fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
    style VMM fill:#2d333b,stroke:#6d5dfc,color:#e6edf3

FetcherViewer

The top-level component that orchestrates view definition loading, view state management, data fetching via Wow, and rendering the complete viewer UI.

tsx
import { FetcherViewer } from '@ahoo-wang/fetcher-viewer';

function DataView() {
  return (
    <FetcherViewer
      viewerDefinitionId="order-view"
      pagination={{ pageSize: 20 }}
      enableRowSelection
      primaryAction={{
        label: 'Create Order',
        onClick: () => router.push('/orders/new'),
      }}
    />
  );
}

FetcherViewerProps

PropertyTypeDefaultDescription
viewerDefinitionIdstring(required)ID of the viewer definition to load
ownerIdstring'(0)'Owner ID for data scoping
tenantIdstring'(0)'Tenant ID for data scoping
defaultViewIdstring--Default view to activate
paginationfalse | PaginationProps(required)Pagination configuration or false to disable
actionColumnViewTableActionColumn--Row action column configuration
onClickPrimaryKey(id, record) => void--Click handler for primary key cells
enableRowSelectionbooleanfalseEnable checkbox row selection
enhanceDataSource(data) => data--Transform/enhance fetched data
onSwitchView(view) => void--Callback when view is switched
viewTableSettingViewTableSettingCapable--Table display settings
primaryActionaction object--Primary action button in topbar
secondaryActionsaction array--Secondary action buttons
batchActionsaction array--Batch actions for selected rows

Source: packages/viewer/src/fetcherviewer/FetcherViewer.tsx:48-71

Imperative Ref API

FetcherViewer exposes an imperative API via ref for parent-driven control — refresh data, clear selection, or access the current query/view state:

tsx
import { useRef } from 'react';
import { FetcherViewer, type FetcherViewerRef } from '@ahoo-wang/fetcher-viewer';

function DataView() {
  const viewerRef = useRef<FetcherViewerRef>(null);

  const handleRefresh = () => viewerRef.current?.refreshData();
  const handleClearSelection = () => viewerRef.current?.clearSelectedRowKeys();

  return (
    <>
      <button onClick={handleRefresh}>Refresh</button>
      <button onClick={handleClearSelection}>Clear Selection</button>
      <FetcherViewer
        ref={viewerRef}
        viewerDefinitionId="order-view"
        pagination={{ pageSize: 20 }}
      />
    </>
  );
}
MethodReturnsDescription
refreshData()voidRe-fetch the current page data
clearSelectedRowKeys()voidClear all selected row keys
getPageQuery()PagedQuery | undefinedGet the current page query (pagination + condition)
getActiveView()ViewState | undefinedGet the currently active view state
getViewerDefinition()ViewDefinition | undefinedGet the loaded viewer definition

Source: packages/viewer/src/fetcherviewer/FetcherViewer.tsx:40-46

FetcherViewer Data Flow

mermaid
sequenceDiagram
autonumber

    participant FV as FetcherViewer
    participant VD as useViewerDefinition
    participant VV as useViewerViews
    participant FD as useFetchData
    participant API as Wow Server
    participant V as Viewer

    FV->>VD: load definition
    VD->>API: GET /viewer-definitions/{id}
    API-->>VD: ViewDefinition

    FV->>VV: load views
    VV->>API: Query viewer views
    API-->>VV: ViewState[]

    FV->>FD: fetchData(viewDefinition, defaultView)
    FD->>API: POST /snapshot/paged (with conditions)
    API-->>FD: PagedList<RecordType>

    FV->>V: Render Viewer component
    V->>V: ViewTable + FilterPanel + TopBar + ViewPanel

Source: packages/viewer/src/fetcherviewer/FetcherViewer.tsx:82-357

Refresh Event Bus

FetcherViewer and the topbar refresh items (RefreshDataBarItem, AutoRefreshBarItem) communicate through a shared refresh event bus created by useRefreshDataEventBus(subscriberId). FetcherViewer subscribes with its viewerDefinitionId as the subscriber ID and reloads the current page when a REFRESH event with a matching subscriberId arrives; the imperative refreshData() publishes to that same ID.

Handlers are registered under names prefixed with the subscriber ID (${subscriberId}:${handlerName}). On unmount, the hook removes only the handlers registered by that specific hook instance — never the whole subscriber-ID prefix — so multiple components sharing the same viewerDefinitionId (for example a FetcherViewer and its refresh bar items) cannot remove each other's subscriptions.

Source: packages/viewer/src/hooks/useRefreshDataEventBus.ts:29-86, packages/viewer/src/fetcherviewer/FetcherViewer.tsx:254-285

Filter System

Typed Filter Registry

Filters are dispatched by type using a registry pattern. The TypedFilter component looks up the appropriate filter component from the registry:

mermaid
graph LR
    subgraph sg_1 ["filterRegistry"]
        direction TB
        id["id -> IdFilter"]
        text["text -> TextFilter"]
        number["number -> NumberFilter"]
        select["select -> SelectFilter"]
        bool["bool -> BoolFilter"]
        datetime["datetime -> DateTimeFilter"]
    end

    TF["TypedFilter<br>type-based dispatch"] --> filterRegistry

    style TF fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
    style id fill:#161b22,stroke:#30363d,color:#e6edf3
    style text fill:#161b22,stroke:#30363d,color:#e6edf3
    style number fill:#161b22,stroke:#30363d,color:#e6edf3
    style select fill:#161b22,stroke:#30363d,color:#e6edf3
    style bool fill:#161b22,stroke:#30363d,color:#e6edf3
    style datetime fill:#161b22,stroke:#30363d,color:#e6edf3
Filter TypeComponentDescription
idIdFilterAggregate ID input
textTextFilterText input with operator selection
numberNumberFilterNumeric input with range support
selectSelectFilterDropdown select with options
boolBoolFilterBoolean toggle/checkbox
datetimeDateTimeFilterDate/time picker with range

Source: packages/viewer/src/filter/filterRegistry.ts:73-83

FilterPanel Components

ComponentDescription
FilterPanelDisplays a set of active filters
EditableFilterPanelAdd/remove filters dynamically
AvailableFilterSelectDropdown to select which filter fields to add
AvailableFilterSelectModalModal version of filter field selection
RemovableTypedFilterIndividual filter with a remove button

Source: packages/viewer/src/filter/panel/

Filter Props

Every filter component receives standard props:

typescript
interface FilterProps {
  field: FilterField;           // { name, label, type, format }
  label?: FilterLabelProps;     // Label display configuration
  operator?: FilterOperatorProps; // Operator selection (null to hide)
  value?: FilterValueProps;     // Value input configuration
  onChange?: (value?: FilterValue) => void; // Change callback
  conditionOptions?: ConditionOptions; // Condition building options
}

Source: packages/viewer/src/filter/types.ts:59-69

Table Cell System

Typed Cell Registry

Table cells are dispatched by type using the same registry pattern as filters:

Cell TypeComponentDescription
textTextCellPlain text display with ellipsis support
tagTagCellSingle Ant Design Tag
tagsTagsCellMultiple Ant Design Tags
datetimeDateTimeCellFormatted date/time display
calendar-timeCalendarTimeCellCalendar-based time display
imageImageCellImage preview with thumbnail
image-groupImageGroupCellGroup of image previews
linkLinkCellClickable link
currencyCurrencyCellFormatted currency display
avatarAvatarCellUser avatar display
primary-keyPrimaryKeyCellClickable primary key cell
actionActionCellSingle action button
actionsActionsCellMultiple action buttons

Source: packages/viewer/src/table/cell/cellRegistry.ts:67-82

CellProps Interface

All cell components receive standardized props:

typescript
interface CellProps<ValueType, RecordType, Attributes> {
  data: {
    value: ValueType;     // The cell value to display
    record: RecordType;   // The full row record
    index: number;        // Row index
  };
  attributes?: Attributes; // Component-specific attributes
}

Source: packages/viewer/src/table/cell/types.ts:100-106

ViewTable

The ViewTable component wraps Ant Design's Table and integrates with the viewer definition to automatically generate columns from view field configurations. It supports:

  • Automatic cell type dispatch via TypedCell
  • Sortable columns
  • Column visibility configuration
  • Table settings panel for field ordering and visibility
  • Row selection
  • Action columns

Source: packages/viewer/src/table/ViewTable.tsx

Row Keys and Selection Stability

ViewTable derives each row's key (used as Ant Design's rowKey) by calling mapToTableRecord(dataSource, primaryKeyFieldName), where primaryKeyFieldName is the name of the field definition marked as primaryKey. Key precedence:

  1. The record's primary key field value — stable across data refreshes, sorting, and paging, so row selection does not drift to the wrong records.
  2. The record's own key property, if present (preserved, never overwritten by the index fallback).
  3. The array index — last resort only; index keys are not stable across data changes.

Source: packages/viewer/src/table/ViewTable.tsx:340-343, packages/viewer/src/utils.ts:111-134

View Management

mermaid
stateDiagram-v2
    [*] --> LoadingDef: FetcherViewer mounts
    LoadingDef --> LoadingViews: Definition loaded
    LoadingViews --> Active: Views loaded, default selected
    Active --> Active: Switch view
    Active --> Active: Create view
    Active --> Active: Edit view
    Active --> Active: Delete view
    Active --> Active: Update filters
    Active --> Active: Change page

    state Active {
        [*] --> Viewing
        Viewing --> FilterEditing: Open filter panel
        FilterEditing --> Viewing: Apply filters
        Viewing --> SaveModal: Save as new view
        SaveModal --> Viewing: View saved
    }

View States

Each view (ViewState) persists:

  • Active filter conditions
  • Sort configuration
  • Column visibility and order
  • View name and type (PRIVATE/SHARED)
  • Default view flag

Views are managed via Wow command operations through ViewCommandClient:

  • createView -- create a new view
  • editView -- update an existing view
  • defaultDeleteAggregate -- delete a view

Source: packages/viewer/src/fetcherviewer/client/view/commandClient.ts

TopBar Components

The topbar provides action items above the data table:

ComponentDescription
TopBarContainer for bar items
BarItemBase bar item component
RefreshDataBarItemManual data refresh button
AutoRefreshBarItemAuto-refresh toggle with interval
FilterBarItemToggle filter panel visibility
FullscreenBarItemToggle fullscreen mode
ColumnHeightBarItemAdjust table row density
DataMonitorBarItemData monitoring indicator
ShareLinkBarItemCopy shareable view link

Source: packages/viewer/src/topbar/

Standalone Components

The viewer package also exports reusable UI components:

ComponentDescription
NumberRangeDual-number input for range filters
RemoteSelectSelect with remote data fetching
TagInputInput for managing tag collections
FullscreenFullscreen container wrapper

Source: packages/viewer/src/components/

Registry Pattern

Both filters and cells use a shared TypedComponentRegistry<T, P> pattern:

typescript
import { TypedComponentRegistry } from '@ahoo-wang/fetcher-viewer';

// Create a custom registry
const myRegistry = TypedComponentRegistry.create<string, MyProps>([
  ['type1', MyComponent1],
  ['type2', MyComponent2],
]);

// Register additional types
myRegistry.register('type3', MyComponent3);

// Look up
const Component = myRegistry.get('type1');

Source: packages/viewer/src/registry/componentRegistry.ts

Storybook Integration

The viewer package includes comprehensive Storybook stories for visual development and testing:

bash
pnpm storybook

Stories are organized alongside their components in stories/ directories:

  • Filter components: filter/stories/, filter/panel/stories/
  • Cell components: table/cell/stories/
  • Table components: table/stories/, table/setting/stories/
  • Topbar components: topbar/stories/
  • Viewer components: viewer/stories/, view/stories/
  • FetcherViewer: fetcherviewer/stories/
  • Standalone components: components/stories/

Key Exports

ExportModuleDescription
FetcherViewerfetcherviewer/Top-level viewer orchestrator
Viewerviewer/View + table + filters composition
Viewview/Single view container
ViewTabletable/Ant Design table with typed cells
TypedFilterfilter/Type-dispatched filter component
filterRegistryfilter/Filter component registry
TypedCelltable/cell/Type-dispatched cell component
cellRegistrytable/cell/Cell component registry
TopBartopbar/Action bar container
FilterPanelfilter/panel/Active filters display
EditableFilterPanelfilter/panel/Dynamic filter editor
ViewPanelviewer/panel/View list sidebar
SaveViewModalviewer/panel/Save/edit view modal
TypedComponentRegistryregistry/Generic typed component registry
NumberRangecomponents/Dual-number range input
RemoteSelectcomponents/Remote data select
TagInputcomponents/Tag management input

Cross-References

  • Wow -- FetcherViewer uses Wow SnapshotQueryClient for data fetching and CommandClient for view management
  • React -- Uses useFetcher, useKeyStorage, useEventSubscription hooks from the React package
  • Fetcher -- The core HTTP client used for all API communication
  • Storage -- KeyStorage used for persisting local default view ID
  • CoSec -- Supports multi-tenant data scoping via owner/tenant IDs

Released under the Apache License 2.0.