{{ theme.skipToContentLabel || 'Skip to content' }}

Flow Meter Sidebar Refactor Implementation Plan

For Claude: REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.

Goal: Refactor the Flow Meter page to use a sidebar layout with filters and actions, similar to the Heat Map page.

Architecture: The page will use a flex container with a fixed-width left sidebar (384px on desktop) containing filters and action buttons, and a flexible main content area for site cards, charts, and tables. On mobile, the sidebar stacks above the content.

Tech Stack: React, TypeScript, Tailwind CSS, existing DateRangeSelector component, Iconify icons


Task 1: Create the Flex Container Layout Wrapper

Files:

  • Modify: src/app/(admin)/(pages)/flow-meter/index.tsx:622-626

Step 1: Update the main element to use flex layout

Find the return statement (around line 622) and modify the <main> element structure:

tsx
// BEFORE (line 625):
<main>
  <PageBreadcrumb title="Flow Meter" />

// AFTER:
<main className="relative">
  <PageBreadcrumb title="Flow Meter" />

  <div className="flex flex-col lg:flex-row gap-5 h-[calc(100vh-180px)] min-h-[600px]">
    {/* Sidebar will go here */}

    {/* Main content wrapper */}
    <div className="flex-1 overflow-y-auto">

Step 2: Verify the app compiles

Run: pnpm build:check Expected: Build succeeds (may have warnings, but no errors)

Step 3: Commit

bash
git add src/app/(admin)/(pages)/flow-meter/index.tsx
git commit -m "refactor(flow-meter): add flex container layout wrapper"

Task 2: Create the Sidebar Structure with Header

Files:

  • Modify: src/app/(admin)/(pages)/flow-meter/index.tsx

Step 1: Add the sidebar div after the flex container opening

Insert after <div className="flex flex-col lg:flex-row gap-5 h-[calc(100vh-180px)] min-h-[600px]">:

tsx
{/* Control Panel Sidebar */}
<div className="lg:w-96 w-full max-h-[40vh] lg:max-h-none overflow-y-auto bg-white dark:bg-slate-800 rounded-lg border border-slate-200 dark:border-slate-700 flex-shrink-0">
  <div className="p-6 space-y-6">
    {/* Sidebar Header */}
    <div>
      <h2 className="text-xl font-semibold text-slate-800 dark:text-slate-100 flex items-center gap-2">
        <Icon
          className="w-6 h-6 text-blue-600 dark:text-blue-400"
          icon="solar:drop-bold-duotone"
        />
        Flow Meter Controls
      </h2>
      <p className="text-sm text-slate-600 dark:text-slate-400 mt-1">
        Adjust filters and actions
      </p>
    </div>

    {/* Filters and Actions sections will go here */}
  </div>
</div>

Step 2: Verify the app compiles and renders

Run: pnpm dev Expected: Page loads with empty sidebar visible on the left

Step 3: Commit

bash
git add src/app/(admin)/(pages)/flow-meter/index.tsx
git commit -m "refactor(flow-meter): add sidebar structure with header"

Task 3: Add Data Filters Section to Sidebar

Files:

  • Modify: src/app/(admin)/(pages)/flow-meter/index.tsx

Step 1: Add the Data Filters section inside the sidebar

Insert after the sidebar header div, inside <div className="p-6 space-y-6">:

tsx
{/* Data Filters Section */}
<div className="space-y-4 rounded-lg border border-slate-200 dark:border-slate-700 bg-slate-50 dark:bg-slate-800/50 p-4">
  <h3 className="text-sm font-medium text-slate-700 dark:text-slate-300 flex items-center gap-2">
    <Icon className="w-4 h-4" icon="solar:filter-bold-duotone" />
    Data Filters
  </h3>

  {/* Mine Site Selector */}
  <div>
    <div className="flex items-center gap-2 mb-2">
      <Icon
        className="w-4 h-4 text-slate-600 dark:text-slate-400"
        icon="solar:buildings-2-bold-duotone"
      />
      <label className="text-sm font-medium text-slate-700 dark:text-slate-300">
        Mine Site
      </label>
    </div>
    <select
      className="w-full px-3 py-2 text-sm border border-slate-300 dark:border-slate-600 rounded-lg bg-white dark:bg-slate-900 text-slate-900 dark:text-slate-100 focus:ring-2 focus:ring-primary focus:border-transparent"
      value={selectedSite ?? ""}
      onChange={(e) => {
        setSelectedSite(e.target.value || null);
      }}
    >
      <option value="">All Sites</option>
      {flowMeterEnabledSites.map((site) => (
        <option key={site.id} value={site.name}>
          {site.name}
        </option>
      ))}
    </select>
    {selectedSite && (
      <div className="text-xs text-slate-600 dark:text-slate-400 mt-2">
        Showing data for {selectedSite}
      </div>
    )}
  </div>

  {/* Date Range Selector */}
  <div>
    <div className="flex items-center gap-2 mb-2">
      <Icon
        className="w-4 h-4 text-slate-600 dark:text-slate-400"
        icon="solar:calendar-bold-duotone"
      />
      <label className="text-sm font-medium text-slate-700 dark:text-slate-300">
        Date Range
      </label>
    </div>
    <DateRangeSelector
      dataDateRange={actualDataDateRange}
      value={dateRange}
      onChange={handleDateRangeChange}
    />
  </div>
</div>

Step 2: Remove the old DateRangeSelector from main content

Find and remove this block (around line 727-732):

tsx
{/* Date Range Selector */}
<DateRangeSelector
  dataDateRange={actualDataDateRange}
  value={dateRange}
  onChange={handleDateRangeChange}
/>

Step 3: Verify the app compiles and date filter works

Run: pnpm dev Expected: Date range selector appears in sidebar, changing dates updates the data

Step 4: Commit

bash
git add src/app/(admin)/(pages)/flow-meter/index.tsx
git commit -m "refactor(flow-meter): move date range selector to sidebar filters"

Task 4: Add Actions Section to Sidebar

Files:

  • Modify: src/app/(admin)/(pages)/flow-meter/index.tsx

Step 1: Add the Actions section after Data Filters section

Insert after the Data Filters section closing </div>:

tsx
{/* Actions Section */}
<div className="space-y-3 rounded-lg border border-slate-200 dark:border-slate-700 bg-slate-50 dark:bg-slate-800/50 p-4">
  <h3 className="text-sm font-medium text-slate-700 dark:text-slate-300 flex items-center gap-2">
    <Icon className="w-4 h-4" icon="solar:widget-bold-duotone" />
    Actions
  </h3>

  {/* Manage Sites Button */}
  <button
    className="w-full inline-flex items-center justify-center gap-2 px-4 py-2 rounded-lg border border-slate-300 dark:border-slate-600 bg-white dark:bg-slate-800 text-slate-700 dark:text-slate-300 hover:bg-slate-50 dark:hover:bg-slate-700 transition-colors text-sm"
    type="button"
    onClick={() => {
      setShowVisibilityManager(true);
    }}
  >
    <Icon className="w-4 h-4" icon="solar:settings-bold-duotone" />
    Manage Sites
  </button>

  {/* Refresh Data Button */}
  <button
    className="w-full inline-flex items-center justify-center gap-2 px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 disabled:bg-blue-400 disabled:cursor-not-allowed transition-colors text-sm"
    disabled={isRefreshing}
    onClick={() => {
      setIsConfigModalOpen(true);
    }}
  >
    <Icon
      className={`h-4 w-4 ${isRefreshing ? "animate-spin" : ""}`}
      icon={
        isRefreshing
          ? "svg-spinners:ring-resize"
          : "solar:refresh-bold-duotone"
      }
    />
    {isRefreshing ? "Refreshing..." : "Refresh Data"}
  </button>

  {/* Manage Refills Link */}
  <Link
    className="w-full inline-flex items-center justify-center gap-2 px-4 py-2 bg-green-600 text-white rounded-lg hover:bg-green-700 transition-colors text-sm"
    to="/refill-management"
  >
    <Icon className="h-4 w-4" icon="solar:box-bold-duotone" />
    Manage Refills
  </Link>

  {/* Calibration Reminder Link */}
  <Link
    className="w-full inline-flex items-center justify-center gap-2 px-4 py-2 bg-amber-600 text-white rounded-lg hover:bg-amber-700 transition-colors text-sm"
    to="/calibration-reminders"
  >
    <Icon className="h-4 w-4" icon="solar:compass-bold-duotone" />
    Calibration Reminder
  </Link>

  {/* Export Report Button */}
  <button
    className="w-full inline-flex items-center justify-center gap-2 px-4 py-2 bg-primary text-white rounded-lg hover:bg-primary/90 transition-colors text-sm disabled:opacity-50 disabled:cursor-not-allowed"
    disabled={!selectedSiteSummary || selectedSiteSummary.records.length === 0}
    onClick={() => {
      void handleExportAllCharts();
    }}
  >
    <Icon className="h-4 w-4" icon="solar:download-bold-duotone" />
    Export Report
  </button>
</div>

Step 2: Verify the app compiles and buttons work

Run: pnpm dev Expected: All action buttons appear in sidebar and function correctly

Step 3: Commit

bash
git add src/app/(admin)/(pages)/flow-meter/index.tsx
git commit -m "refactor(flow-meter): add actions section to sidebar"

Task 5: Update Sites Header and Remove Old Buttons

Files:

  • Modify: src/app/(admin)/(pages)/flow-meter/index.tsx

Step 1: Simplify the Sites header row

Find the Sites header section (around line 653-707) and replace it with:

tsx
{/* Site Selection Grid */}
<div className="mb-6">
  <div className="mb-4 flex items-center justify-between">
    <h2 className="text-xl font-semibold text-slate-900 dark:text-slate-100">
      Sites
    </h2>
    <div className="flex items-center gap-2">
      <label className="text-sm text-slate-600 dark:text-slate-400">
        Sort by:
      </label>
      <select
        className="rounded border border-slate-300 dark:border-slate-600 bg-white dark:bg-slate-800 px-3 py-2 text-sm text-slate-900 dark:text-slate-100 hover:bg-slate-50 dark:hover:bg-slate-700 transition-colors"
        value={siteSortBy}
        onChange={(e) => {
          setSiteSortBy(e.target.value as typeof siteSortBy);
        }}
      >
        <option value="name-asc">Name (A-Z)</option>
        <option value="name-desc">Name (Z-A)</option>
        <option value="usage-desc">Usage (High to Low)</option>
        <option value="usage-asc">Usage (Low to High)</option>
        <option value="events-desc">Events (Most to Least)</option>
        <option value="events-asc">Events (Least to Most)</option>
      </select>
    </div>
  </div>

Step 2: Verify the app compiles

Run: pnpm dev Expected: Sites header shows only title and sort dropdown

Step 3: Commit

bash
git add src/app/(admin)/(pages)/flow-meter/index.tsx
git commit -m "refactor(flow-meter): simplify sites header, keep only sort dropdown"

Task 6: Update Site Cards Grid Columns

Files:

  • Modify: src/app/(admin)/(pages)/flow-meter/index.tsx

Step 1: Change the grid columns

Find the site cards grid (around line 708):

tsx
// BEFORE:
<div className="grid grid-cols-1 gap-4 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4">

// AFTER:
<div className="grid grid-cols-1 gap-4 md:grid-cols-1 lg:grid-cols-2 xl:grid-cols-3">

Step 2: Verify the grid layout on different screen sizes

Run: pnpm dev Expected: Cards display in 3 columns on xl, 2 on lg, 1 on md and below

Step 3: Commit

bash
git add src/app/(admin)/(pages)/flow-meter/index.tsx
git commit -m "refactor(flow-meter): adjust site cards grid to 3 columns max"

Task 7: Remove Duplicate Action Buttons from Chart Section

Files:

  • Modify: src/app/(admin)/(pages)/flow-meter/index.tsx

Step 1: Remove the action buttons from the Daily Usage chart header

Find the chart section header (around line 804-843) and simplify it:

tsx
// BEFORE (lines 804-843):
<div className="mb-6">
  <div className="mb-4 flex items-center justify-between">
    <h2 className="text-xl font-semibold text-slate-900 dark:text-slate-100">
      {getChartTitle("Daily Dustloc Usage")}
    </h2>
    <div className="flex items-center gap-3">
      <Link ... Manage Refills />
      <Link ... Calibration Reminder />
      <button ... Export Report />
    </div>
  </div>

// AFTER:
<div className="mb-6">
  <div className="mb-4">
    <h2 className="text-xl font-semibold text-slate-900 dark:text-slate-100">
      {getChartTitle("Daily Dustloc Usage")}
    </h2>
  </div>

Step 2: Verify the app compiles and chart section looks clean

Run: pnpm dev Expected: Chart section shows only the title, no duplicate buttons

Step 3: Commit

bash
git add src/app/(admin)/(pages)/flow-meter/index.tsx
git commit -m "refactor(flow-meter): remove duplicate action buttons from chart section"

Task 8: Close the Layout Wrapper Divs

Files:

  • Modify: src/app/(admin)/(pages)/flow-meter/index.tsx

Step 1: Add closing divs before the modals

Find the end of the main content (before the modals, around line 1240) and add:

tsx
        {/* End of main content wrapper */}
        </div>
      {/* End of flex container */}
      </div>

      {/* Scraper Configuration Modal */}
      <ScraperConfigModal

Step 2: Verify the app compiles without errors

Run: pnpm build:check Expected: Build succeeds with no errors

Step 3: Commit

bash
git add src/app/(admin)/(pages)/flow-meter/index.tsx
git commit -m "refactor(flow-meter): close layout wrapper divs correctly"

Task 9: Add Refresh Message to Sidebar

Files:

  • Modify: src/app/(admin)/(pages)/flow-meter/index.tsx

Step 1: Move the refresh message into the sidebar Actions section

Find the refresh message block (around line 628-651) and move it into the Actions section, after the Refresh Data button:

tsx
{/* Refresh Status Message */}
{refreshMessage && (
  <div
    className={`p-2 rounded-md text-xs ${
      refreshMessage.includes("successfully")
        ? "bg-green-50 dark:bg-green-900/20 text-green-700 dark:text-green-400 border border-green-200 dark:border-green-800"
        : "bg-red-50 dark:bg-red-900/20 text-red-700 dark:text-red-400 border border-red-200 dark:border-red-800"
    }`}
  >
    <div className="flex items-center gap-2">
      <Icon
        className="h-3.5 w-3.5 flex-shrink-0"
        icon={
          refreshMessage.includes("successfully")
            ? "solar:check-circle-bold"
            : "solar:danger-circle-bold"
        }
      />
      <p>{refreshMessage}</p>
    </div>
  </div>
)}

Step 2: Remove the old refresh message from the main content area

Delete the original refresh message block that was at the top of the main content.

Step 3: Verify the app compiles and refresh message appears in sidebar

Run: pnpm dev Expected: Refresh message appears in sidebar after triggering refresh

Step 4: Commit

bash
git add src/app/(admin)/(pages)/flow-meter/index.tsx
git commit -m "refactor(flow-meter): move refresh message to sidebar"

Task 10: Final Testing and Cleanup

Files:

  • Modify: src/app/(admin)/(pages)/flow-meter/index.tsx

Step 1: Run linting and fix any issues

Run: pnpm lint:fix Expected: No errors, warnings acceptable

Step 2: Run type checking

Run: pnpm build:check Expected: Build succeeds

Step 3: Manual testing checklist

  • [ ] Sidebar appears on left on desktop (lg+)
  • [ ] Sidebar stacks above content on mobile
  • [ ] Mine Site selector in sidebar works
  • [ ] Date Range selector in sidebar works
  • [ ] All action buttons in sidebar work:
    • [ ] Manage Sites opens modal
    • [ ] Refresh Data opens config modal
    • [ ] Manage Refills navigates to /refill-management
    • [ ] Calibration Reminder navigates to /calibration-reminders
    • [ ] Export Report exports when site has data
  • [ ] Sort dropdown in main area works
  • [ ] Site cards display in 3/2/1 columns correctly
  • [ ] Clicking site cards selects them
  • [ ] Charts and tables display for selected site
  • [ ] Scrolling works independently in sidebar and main content

Step 4: Final commit

bash
git add -A
git commit -m "refactor(flow-meter): complete sidebar layout refactor

- Add left sidebar with Flow Meter Controls header
- Move filters (Mine Site, Date Range) to sidebar
- Move action buttons to sidebar (Manage Sites, Refresh Data, etc.)
- Keep Sort dropdown in main content area
- Adjust site cards grid to 3 columns max
- Responsive layout: sidebar stacks on mobile

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>"

Summary

This plan refactors the Flow Meter page in 10 incremental tasks:

  1. Create flex container layout wrapper
  2. Add sidebar structure with header
  3. Add Data Filters section (Mine Site, Date Range)
  4. Add Actions section (all buttons)
  5. Simplify Sites header (keep only Sort)
  6. Update site cards grid columns
  7. Remove duplicate buttons from chart section
  8. Close layout wrapper divs
  9. Move refresh message to sidebar
  10. Final testing and cleanup

Each task is independently committable and testable.