({ fields, onRemoveField, onFieldClick }: FieldListPanelProps)
| 10 | } |
| 11 | |
| 12 | const FieldListPanel = ({ fields, onRemoveField, onFieldClick }: FieldListPanelProps) => { |
| 13 | const [search, setSearch] = useState(""); |
| 14 | const [sortBy, setSortBy] = useState<"name" | "area" | "ndvi">("name"); |
| 15 | const [filterCrop, setFilterCrop] = useState<string | null>(null); |
| 16 | const [showFilterMenu, setShowFilterMenu] = useState(false); |
| 17 | |
| 18 | const crops = Array.from(new Set(fields.map((f) => f.crop))); |
| 19 | |
| 20 | const filtered = fields |
| 21 | .filter( |
| 22 | (f) => |
| 23 | (f.name.toLowerCase().includes(search.toLowerCase()) || |
| 24 | f.crop.toLowerCase().includes(search.toLowerCase())) && |
| 25 | (!filterCrop || f.crop === filterCrop) |
| 26 | ) |
| 27 | .sort((a, b) => { |
| 28 | if (sortBy === "area") return b.area - a.area; |
| 29 | if (sortBy === "ndvi") return (b.ndviChange ?? 0) - (a.ndviChange ?? 0); |
| 30 | return a.name.localeCompare(b.name); |
| 31 | }); |
| 32 | |
| 33 | const sortLabel = sortBy === "name" ? "Name" : sortBy === "area" ? "Area" : "NDVI"; |
| 34 | |
| 35 | return ( |
| 36 | <div className="w-[320px] h-full bg-card/95 backdrop-blur-md border-l border-border flex flex-col"> |
| 37 | {/* Header */} |
| 38 | <div className="flex items-center justify-between p-4 border-b border-border"> |
| 39 | <h2 className="text-lg font-semibold text-foreground">Field List</h2> |
| 40 | <div className="flex items-center gap-1"> |
| 41 | <button |
| 42 | title="Edit fields" |
| 43 | className="p-1.5 rounded-md text-muted-foreground hover:text-foreground hover:bg-accent/30 transition-colors" |
| 44 | > |
| 45 | <Pencil className="w-4 h-4" /> |
| 46 | </button> |
| 47 | </div> |
| 48 | </div> |
| 49 | |
| 50 | {/* Search */} |
| 51 | <div className="px-3 pt-3"> |
| 52 | <div className="relative"> |
| 53 | <input |
| 54 | type="text" |
| 55 | placeholder="Search fields…" |
| 56 | value={search} |
| 57 | onChange={(e) => setSearch(e.target.value)} |
| 58 | className="w-full bg-secondary/50 border border-border rounded-lg px-3 py-2 pr-9 text-sm text-foreground placeholder:text-muted-foreground focus:outline-none focus:ring-1 focus:ring-ring" |
| 59 | /> |
| 60 | <Search className="absolute right-3 top-1/2 -translate-y-1/2 w-4 h-4 text-muted-foreground" /> |
| 61 | </div> |
| 62 | </div> |
| 63 | |
| 64 | {/* Filter/Sort/Edit toolbar */} |
| 65 | <div className="flex gap-2 px-3 py-3 relative"> |
| 66 | <div className="relative"> |
| 67 | <button |
| 68 | onClick={() => setShowFilterMenu(!showFilterMenu)} |
| 69 | className={`flex items-center gap-1.5 px-3 py-2 rounded-lg border text-sm transition-colors ${ |
nothing calls this directly
no outgoing calls
no test coverage detected