"use client"; /** * MobileWaterHeadgateList — headgate selection screen. * * Visual structure (Apple HIG grouped list): * - Sticky top nav: small back chevron + "Headgates" title + * logout icon on the right. * - Section header ("ACTIVE HEADGATES") with a count chip. * - One tappable card per headgate. Each card has: * - Leading gauge icon (status-tinted) * - Bold headgate name * - Subtitle = "geocode" (notes) OR a derived status line * - Meta row = unit + last-logged relative time * - Trailing chevron * - Subtle threshold badge if high/low thresholds are set * - Pull-to-refresh at the top. * - "No active headgates" empty state with an icon and CTA back to * refresh when the operator has just added a gate. */ import { useCallback } from "react"; import type { FieldHeadgate } from "./types"; import { ChevronRight, Droplet, Gauge, Logout, Pin, Spinner, } from "./icons"; import { MobilePullToRefresh } from "./PullToRefresh"; import { formatRelativeTime, headgateSubtitle } from "./format"; type Props = { /** Optional display name of the logged-in irrigator (for the header). */ irrigatorName?: string; headgates: FieldHeadgate[]; loading: boolean; error: string | null; onSelect: (headgate: FieldHeadgate) => void; onRefresh: () => Promise | void; onLogout: () => void; }; export function MobileWaterHeadgateList({ irrigatorName, headgates, loading, error, onSelect, onRefresh, onLogout, }: Props) { const active = headgates.filter((h) => h.active); return (
{/* ── Top nav ──────────────────────────────────────────────── */}
{/* Placeholder to keep the title centered — the back * chevron would lead to the PIN screen, which the user * got here from. Leaving it as just the title to keep * the nav minimal per HIG. */}

Headgates

{/* ── Scrollable list ─────────────────────────────────────── */}
{irrigatorName && (

Signed in as {irrigatorName}

)} {/* Error banner */} {error && (
⚠️ {error}
)} {/* Section header */}

Active headgates

{active.length}
{loading && active.length === 0 ? ( ) : active.length === 0 ? ( ) : (
    {active.map((hg, i) => (
  • onSelect(hg)} />
  • ))}
)} {/* Footer note */} {active.length > 0 && (

Pull down to refresh the list

)}
); } // ── Headgate card ──────────────────────────────────────────────────────── function HeadgateCard({ headgate, isLast, onClick, }: { headgate: FieldHeadgate; isLast: boolean; onClick: () => void; }) { const subtitle = headgateSubtitle(headgate); const lastUsed = formatRelativeTime(headgate.last_used_at); const isClosed = headgate.status === "closed"; const isMaintenance = headgate.status === "maintenance"; return ( ); } // ── Threshold dot (small indicator on the card row) ──────────────────── function ThresholdDot({ high, }: { high: number | null; low: number | null; }) { return ( {high != null ? "Hi" : "Lo"} ); } // ── Skeleton ──────────────────────────────────────────────────────────── function ListSkeleton() { return ( ); } // ── Empty state ───────────────────────────────────────────────────────── function EmptyState({ onRefresh, refreshing, }: { onRefresh: () => Promise | void; refreshing: boolean; }) { const handleRefresh = useCallback(async () => { if (!refreshing) await onRefresh(); }, [onRefresh, refreshing]); return (

No active headgates

Once your supervisor activates a headgate, it'll show up here. Pull down to check for updates.

); } export default MobileWaterHeadgateList;