Skip to Content
FrontendComponents

Components

Key UI components that make up the ETL pipeline interface. All components are client-side ("use client") and built with Mantine v7 and Phosphor Icons.

Styling Conventions

All 8 components (StructureReview, ResultsPanel, ComparisonView, AccuracyFunnel, ProgressStepper, LandingHero, UploadZone, PasswordGate) use semantic theme tokens defined in theme.other instead of hardcoded hex values. The following tokens are available:

TokenValuePurpose
theme.other.inputBg#eceaddCream background for inputs and cards
theme.other.blueAccent#3d8ab5Blue accent for info/unit count
theme.other.goldAccent#c9a227Gold accent for warnings/flagged items
theme.other.brownAccent#6b5c3eBrown accent for table-related labels
theme.other.subtleBorderrgba(61,61,61,0.15)Light borders
theme.other.subtleBgrgba(61,61,61,0.04)Very faint background tint
theme.other.faintBorderrgba(61,61,61,0.1)Table/notification borders

Guideline: Use semantic theme tokens from theme.other.* instead of hardcoded hex values. One-off colors that appear in only one component may remain inline.


ProgressStepper

File: components/ProgressStepper.tsx

Displays the 4-step pipeline progress with a milestone timeline, ETA estimation, and browser notification prompt.

Props

interface ProgressStepperProps { pipelineState: string; // From useEtlPipeline state progress: JobProgress; // From useJobProgress onCancel?: () => void; // Cancel handler }

Steps

The stepper has four labeled steps plus a completion state:

StepLabelDescription
0UploadSending file
1ReadFinding Tables
2ReviewConfirm structure
3ExtractPulling Units
Completed(empty)

Step descriptions use the color-coded entity system: Table is styled with theme.other.brownAccent and Unit with theme.other.blueAccent.

ETA Calculation

The component includes a useHistoricalEta hook that queries up to 20 completed jobs from etl_jobs and runs a simple linear regression to compute a model with baseMs (fixed overhead) and msPerRow (marginal cost per row). The estimated total time is baseMs + msPerRow * total_rows, minus elapsed time.

Fallback estimates when no historical data is available:

  • During analysis: “~1-2 min”
  • During extraction: “~1-3 min”

Elapsed Timer

A useTimer hook tracks wall-clock time while the pipeline is active. Displays formatted elapsed time (e.g., 45s, 1m 30s).

Status Bar

Below the progress bar, a three-column layout shows:

  • Left: Elapsed time
  • Center: Cancel link (while active), “All done!” (on complete), or error message (on failure)
  • Right: ETA remaining

Milestone Timeline

The MilestoneTimeline sub-component renders a Mantine Timeline showing every stage_message the pipeline has emitted. Features:

  • Auto-scrolls to the latest milestone
  • Max height of 240px with overflow scrolling
  • Completed milestones show a checkmark bullet; the current milestone shows a spinning icon
  • Messages are colorized using the entity system (see below)

Browser Notifications

The NotificationPrompt sub-component appears after 5 seconds of active processing (including during the awaiting_review state). It prompts the user to enable browser notifications. When the pipeline completes or enters awaiting_review with notifications enabled, it fires a system notification — either prompting the user to review the structure or showing the Unit count and filename on completion.

Entity Colorization

The colorizeMessage function parses milestone messages and applies color-coded styling to domain entities:

EntityColorExample
Filetheme.other.textPrimary (dark)“Uploading your File…”
Sheet / Sheetstheme.other.greenAccent (green)“Found 3 Sheets with 1,231 rows”
Table / Tablestheme.other.brownAccent (brown)“Found 5 Tables across 3 Sheets
Unit / Unitstheme.other.blueAccent (blue)“Got 18 Units so far…”

ResultsPanel

File: components/ResultsPanel.tsx

Displays the final results after pipeline completion. Shows property info, Unit count, quality badges, accuracy funnel, and a toggle for the comparison view.

Props

interface ResultsPanelProps { progress: JobProgress; jobId: string; onReset: () => void; isDemo?: boolean; }

Layout

The panel is a card with cream background (theme.other.inputBg) containing:

  1. Header — “Results” title with Download and “Process Another” buttons
  2. Property info — Property name, report date (formatted to long date), source filename
  3. Units Extracted — Badge with Unit count (with expected data rows denominator if available, e.g., “106/112”) plus extraction method badge (AI Extracted displayed for all extractions)
  4. Spot Check — Confidence score badge (green >= 80, yellow >= 50, red < 50)
  5. Accuracy — Inline AccuracyFunnel component (280px wide)
  6. Quality — Error count badge (red if > 0, green if 0) and warning count badge (yellow if > 0, green if 0)
  7. Details accordion — Expandable sections for spot-check discrepancies, errors, and warnings (warnings capped at 20 visible with overflow count)
  8. Comparison toggle — Button to show/hide the ComparisonView

Download Flow

In non-demo mode, clicking Download:

  1. Fetches a signed URL from GET /api/download?job_id={jobId}
  2. Downloads the file as a blob (to avoid cross-origin issues with the download attribute)
  3. Creates a temporary <a> element to trigger the browser download
  4. Falls back to filename rent_roll_standardized.xlsx

In demo mode, the download button is disabled.


AccuracyFunnel

File: components/AccuracyFunnel.tsx

A stacked horizontal progress bar showing the breakdown of Unit extraction quality.

Props

interface AccuracyFunnelProps { breakdown: UnitBreakdown; // { successful, flagged, failed, total } }

Segments

SegmentColorLabel
Successful (parsed)theme.other.greenAccent (green)Count of Units parsed cleanly
Flaggedtheme.other.goldAccent (gold)Count of Units with warnings
Failed#c0392b (red)Count of Units that failed extraction

Each segment is proportional to total. Labels inside segments are only shown when the segment exceeds 12% width. Below the bar, a legend shows colored dots with counts and the word “Units” styled in theme.other.blueAccent.

Returns null when total is 0.


ComparisonView

File: components/ComparisonView.tsx

Two-column side-by-side view comparing source spreadsheet cells against cleaned canonical output for each Unit.

Props

interface ComparisonViewProps { jobId: string; isDemo?: boolean; }

Data Loading

  • Demo mode: Uses DEMO_COMPARISON_DATA from useDemoMode (6 hardcoded Montrose Units)
  • Live mode: Fetches from GET /api/comparison?job_id={jobId} with auth headers
  • Prev/Next buttons to step through Units
  • Unit selector dropdown showing all Unit IDs
  • Counter displaying “Unit X of Y” with “Unit” styled in theme.other.blueAccent

Paired-Row Layout

The comparison is rendered as an aligned table with six columns: Source header, Source value, arrow, Canonical field, Canonical value, and tag badge. Each row pairs a source cell with its corresponding canonical field.

  • Row number shown at the top
  • Summary badges above the table show counts of “cleaned” and “inferred” differences
  • Each row is tagged as:
    • direct — source value matches canonical value exactly (no badge shown)
    • cleaned — value was reformatted or normalized (yellow “cleaned” badge, gold-tinted row background)
    • inferred — canonical field has no source column (blue “inferred” badge, blue-tinted row background)
  • Canonical field names have tooltips with descriptions from the FIELD_DESCRIPTIONS dictionary

ComparisonUnit Type

interface ComparisonUnit { unitId: string; sourceRowNumber: number; sourceCells: { column: string; header: string; value: string }[]; canonicalFields: Record<string, unknown>; differences: ComparisonDiff[]; } interface ComparisonDiff { canonicalField: string; sourceColumn: string; sourceValue: string; canonicalValue: unknown; type: "mapped" | "transformed" | "missing"; }

StructureReview

File: components/StructureReview.tsx

Displays the AI-detected column mapping and allows the user to override field assignments before extraction proceeds.

Props

interface StructureReviewProps { structure: { column_mapping?: Record<string, string>; column_headers?: Record<string, string>; header_row?: number; data_start_row?: number; data_end_row?: number; charge_orientation?: string; multi_row_per_unit?: boolean; property_name?: string; report_date?: string; notes?: string; }; onConfirm: (overrides?: Record<string, unknown>) => void; onReanalyze?: () => void; isConfirming?: boolean; }

Canonical Fields

The component defines 36 canonical field options that columns can be mapped to:

unit_id, floor_plan, sqft, beds, baths, unit_status, tenant_name, move_in, move_out, lease_start, lease_end, lease_term_months, is_mtm, market_rent, charge_base_rent, charge_pet, charge_parking, charge_storage, charge_utilities, charge_trash, charge_cable_internet, charge_pest_control, charge_amenity_fee, charge_washer_dryer, charge_package_locker, charge_insurance, charge_deposit_waiver, charge_concession, charge_mtm_fee, charge_employee_discount, charge_subsidy, charge_cam, charge_admin_fees, charge_other, deposit_required, deposit_on_hand, balance, plus (unmapped) (displayed as “Skip”).

Layout

  1. Header — “Review Structure” title with charge orientation badge (e.g., “horizontal layout”)
  2. Description — Instructions to confirm or override the detected mapping
  3. Metadata accordion — Expandable section showing property name, report date, header row, data row range, multi-row flag, and notes
  4. Column mapping table — Three columns: Original Column (badge with column header text), Mapped To, Override (dropdown). Overridden fields show the original with a strikethrough.
  5. Action buttons — “Re-analyze” (optional) and “Confirm & Extract” (or “Apply Overrides & Extract” if overrides exist)

Override Behavior

When the user selects overrides, they are tracked in local state. On confirm:

  • If overrides exist, calls onConfirm({ column_mapping: mergedMapping }) with the original mapping plus overrides applied
  • If selecting (unmapped), removes that column from the mapping
  • If no overrides, calls onConfirm() with no arguments

UploadZone

File: components/UploadZone.tsx

Drag-and-drop file upload component using Mantine’s Dropzone.

Props

interface UploadZoneProps { onDrop: (file: File) => void; loading?: boolean; }

Configuration

SettingValue
Accepted types.xlsx, .xls (MS Excel MIME types), .csv
Max file size10 MB
Max files1
Min height260px

The drop zone has a cream background (theme.other.inputBg) with a green dashed border on drag-accept state. Displays a FileXls icon, a primary prompt, and an accepted formats hint.


LandingHero

File: components/LandingHero.tsx

Public landing page hero section with two CTAs.

Props

interface LandingHeroProps { onPreviewDemo: () => void; }

Layout

Centered stack containing:

  • Title: “Upload Your Rent Roll”
  • Subtitle: Description of the product (max 480px width)
  • Buttons:
    • “Preview Demo” (filled dark, Play icon) — triggers onPreviewDemo which starts the useDemoMode simulation
    • “Try Live Version” (outlined dark, ArrowRight icon) — links to /pipeline (the password-gated live pipeline)

PasswordGate

File: components/PasswordGate.tsx

Session-based authentication wrapper for the live pipeline. Protects the /pipeline route behind a simple password check.

Usage

<PasswordGate> <PipelineApp /> </PasswordGate>

Behavior

  1. On mount, checks sessionStorage for the key rollformat_demo_auth
  2. If found, renders children immediately
  3. If not found, renders a centered password form
  4. On correct password, stores the value in sessionStorage and renders children
  5. On wrong password, shows an inline error message

The session persists for the browser tab lifetime (cleared when the tab closes). Includes a “Back to preview” link that navigates to /.

Last updated on