Remove Flow Meter String Dependency
Date: 2026-01-15
Phase: Frontend Refactoring Phase 2
Related: 20260115000000_add_asset_id_to_flow_meters.sql
Overview
This phase removes the frontend's dependency on the legacy flow_meter string field and switches to using data_assets.display_id for all UI displays. The flow_meter column remains in the database for backward compatibility but is no longer used in the UI.
Changes Made
1. Type Definitions Enhanced
File: src/features/flow-meter/types.ts
export type FlowMeterRecord = {
site: string;
flowMeter: string; // DEPRECATED: Legacy field for backward compatibility
assetId?: string; // Foreign key to data_assets
assetDisplayId?: string; // NEW: Display ID from data_assets table
litresDispensed: number;
dateTimeDispensed: Date;
scrapedAt: Date;
};
export type DustLocRefill = {
// ... other fields
flowMeter?: string; // DEPRECATED: Legacy field
assetId?: string; // Foreign key to data_assets
assetDisplayId?: string; // NEW: Display ID from data_assets
// ... other fields
};2. Database Queries with JOIN
File: src/features/flow-meter/services/databaseService.ts
fetchAllFlowMeterData()
// Before: Simple SELECT *
const { data } = await supabase
.from("data_flow_meters")
.select("*");
// After: JOIN with data_assets to get display_id
const { data } = await supabase
.from("data_flow_meters")
.select(`
*,
asset:data_assets!data_flow_meters_asset_id_fkey(display_id)
`);
// Map to include assetDisplayId
return data.map((row) => ({
...mapFlowMeterRow(row),
assetDisplayId: row.asset?.display_id ?? row.flow_meter,
}));fetchAllRefills()
// Similar JOIN pattern for refills
const { data } = await supabase
.from("ops_dustloc_refills")
.select(`
*,
asset:data_assets!ops_dustloc_refills_asset_id_fkey(display_id)
`);Smart Fallback: If asset.display_id is not available (old records), falls back to flow_meter string.
3. UI Display Updates
All UI components updated to use assetDisplayId with fallback:
// Before
{record.flowMeter}
// After
{record.assetDisplayId || record.flowMeter}Updated Components:
- ✅ Flow Meter page: Event table displays
- ✅ SiteSummaryCard: Flow meter dropdowns
- ✅ AddRefillModal: Available flow meters list
- ✅ EditRefillModal: Available flow meters list
- ✅ All export/print views
4. Filtering Logic Updated
Flow Meter Selection:
// Before: Filter by flowMeter string
records.filter((r) => r.flowMeter === selectedFlowMeter)
// After: Filter by assetDisplayId (with fallback)
records.filter((r) => (r.assetDisplayId || r.flowMeter) === selectedFlowMeter)Unique Values:
// Before: Extract flowMeter strings
Array.from(new Set(records.map((r) => r.flowMeter)))
// After: Extract assetDisplayId (with fallback)
Array.from(new Set(
records.map((r) => r.assetDisplayId || r.flowMeter).filter(Boolean)
))Data Flow
Display Flow (Read)
Database Query
↓
JOIN with data_assets
↓
Get display_id from asset
↓
Map to assetDisplayId
↓
UI displays assetDisplayId
(with flow_meter fallback)Input Flow (Write)
User selects from asset dropdown
↓
Get asset_id (UUID)
↓
Save to database:
- asset_id (primary reference)
- flow_meter (legacy fallback)
↓
Next read will show display_idBackward Compatibility Strategy
The system maintains full backward compatibility through a layered approach:
Layer 1: Database
- ✅ Both
flow_meterandasset_idcolumns exist - ✅ Neither is required (both nullable)
- ✅ Legacy records with only
flow_meterstill work
Layer 2: Data Layer
- ✅ JOIN attempts to get
display_idfromdata_assets - ✅ If JOIN returns null, uses
flow_meteras fallback - ✅ Mapping functions handle both old and new records
Layer 3: UI Layer
- ✅ Display:
assetDisplayId || flowMeter - ✅ Filtering: Works with either field
- ✅ Selection: Prioritizes assets, falls back to strings
Data States Handled
| Database State | Query Result | UI Display |
|---|---|---|
asset_id + asset.display_id | assetDisplayId: "FM-01" | "FM-01" ✅ |
asset_id only (asset deleted) | assetDisplayId: null | Uses flowMeter ⚠️ |
flow_meter only (legacy) | assetDisplayId: null | Uses flowMeter ✅ |
Both asset_id + flow_meter | assetDisplayId: "FM-01" | "FM-01" (prioritizes asset) ✅ |
| Neither (shouldn't happen) | Both null | "-" or "Unknown" ⚠️ |
Benefits
1. Consistent Display Names
- All flow meters show the same name across the system
- Names managed centrally in
data_assets - Easy to update display names globally
2. Rich Metadata Available
- Can access operational status
- Can show last contact time
- Can filter by asset type
- Can show descriptions
3. Future-Proof
- Easily add asset-based features
- Maintenance scheduling integration
- Status-based filtering
- Asset lifecycle tracking
4. Data Integrity
- Foreign keys prevent invalid references
- Cascading deletes handled properly
- Referential integrity enforced
5. Better UX
- Consistent naming
- Can show additional context (status, description)
- Color-coded status indicators (future)
Testing
Automated Tests
- ✅ TypeScript compilation passes
- ✅ Vite build succeeds
- ✅ No type errors
Manual Testing Checklist
Read Operations:
- [ ] Flow meter page displays asset display IDs
- [ ] Old records (flow_meter only) still display correctly
- [ ] New records (asset_id) display asset display_id
- [ ] Flow meter dropdown shows correct names
- [ ] Filtering by flow meter works
- [ ] Site summary cards show correct data
Write Operations:
- [ ] Add refill modal shows asset dropdown
- [ ] Can select asset and submit
- [ ] New refills save with asset_id
- [ ] Edit refill modal works
- [ ] Calibration modal works
Edge Cases:
- [ ] Records with deleted assets (orphaned) still display
- [ ] Mixed data (some with assets, some without) displays correctly
- [ ] Empty/null handling works
- [ ] Filtering with mixed data works
Migration Impact
Database
- ✅ No schema changes (already migrated)
- ✅ Existing data unaffected
- ✅ JOINs are read-only
Frontend
- ✅ All components updated
- ✅ Backward compatibility maintained
- ✅ Build successful
- ✅ No breaking changes
User Impact
- ✨ Users see asset display names instead of arbitrary strings
- ✨ Consistent naming across all views
- ✨ No training required (UI looks the same)
Performance Considerations
JOIN Impact
- Query Complexity: Increased (now includes JOIN)
- Performance: Minimal impact (indexed foreign key)
- Network: Same number of requests
- Client Processing: Same (mapping happens client-side)
Optimization Opportunities
- Caching: Cache asset display names
- Batch Loading: Load all assets once, map client-side
- Materialized View: Pre-join common queries
- Index: Ensure
asset_idcolumns are indexed (✅ done)
Next Steps
Immediate (Optional)
Update other features using flow meters:
- [ ] Dashboard calibration compliance
- [ ] Weekly reports
- [ ] Data freshness cards
Add visual enhancements:
- [ ] Show asset status icon next to name
- [ ] Color-code by operational status
- [ ] Add tooltips with asset details
Medium-term
- Deprecation Notice: Add UI warnings for records without assets
- Migration Tool: Admin UI to map legacy records to assets
- Validation: Require asset selection for new records
Long-term (Phase 3)
- Remove Legacy Field: Once all records migrated
- Make Asset Required: Change
asset_idto NOT NULL - Simplify Code: Remove fallback logic
- Drop Column: Remove
flow_meterstring column
Rollback Plan
If issues arise:
Quick Rollback (Frontend Only)
// Revert to using flowMeter instead of assetDisplayId
{record.flowMeter || record.assetDisplayId}Full Rollback
- Restore previous component files from git
- Remove JOINs from database queries
- System continues to work with legacy strings
No Database Rollback Needed
- Database schema unchanged
- All queries still return
flow_meterfield - Backward compatible
Related Files
Modified files:
src/features/flow-meter/types.ts- AddedassetDisplayIdfieldsrc/features/flow-meter/services/databaseService.ts- Added JOINssrc/app/(admin)/(pages)/flow-meter/index.tsx- Updated displayssrc/features/flow-meter/components/SiteSummaryCard.tsx- Updated filteringsrc/features/flow-meter/components/AddRefillModal.tsx- Updated selectorsrc/features/flow-meter/components/EditRefillModal.tsx- Updated selector
Summary
This phase successfully migrates the UI to use data_assets.display_id while maintaining full backward compatibility with legacy data. The system now:
✅ Displays asset display IDs from centralized source
✅ Maintains backward compatibility with old records
✅ Provides consistent naming across all views
✅ Enables future asset-based features
✅ Preserves all existing functionality
✅ Passes all compilation checks
Status: ✅ Complete and Ready for Testing