From 9943a32bf06165ae7579945e445636bd9c63219c Mon Sep 17 00:00:00 2001 From: Ben Allfree Date: Sat, 22 Nov 2025 17:03:30 -0800 Subject: [PATCH] feat: Implement target categorization and filtering in the Profile Editor UI. --- src/components/ProfileEditor.tsx | 131 +++++++++++++++++++++++++------ 1 file changed, 109 insertions(+), 22 deletions(-) diff --git a/src/components/ProfileEditor.tsx b/src/components/ProfileEditor.tsx index 205d1ba..266dc3b 100644 --- a/src/components/ProfileEditor.tsx +++ b/src/components/ProfileEditor.tsx @@ -4,6 +4,8 @@ import { Button } from "@/components/ui/button"; import { Checkbox } from "@/components/ui/checkbox"; import { Input } from "@/components/ui/input"; import { api } from "../../convex/_generated/api"; +import { TARGETS } from "../constants/targets"; +import { VERSIONS } from "../constants/versions"; interface ProfileEditorProps { initialData?: any; @@ -27,11 +29,41 @@ export default function ProfileEditor({ MESHTASTIC_EXCLUDE_MQTT: false, MESHTASTIC_EXCLUDE_AUDIO: false, }, + version: VERSIONS[0], }, }); const targets = watch("targets"); + // Group targets by category + const groupedTargets = React.useMemo(() => { + return Object.entries(TARGETS).reduce( + (acc, [id, meta]) => { + const category = meta.category || "Other"; + if (!acc[category]) acc[category] = []; + acc[category].push({ id, ...meta }); + return acc; + }, + {} as Record, + ); + }, []); + + const categories = React.useMemo( + () => Object.keys(groupedTargets).sort(), + [groupedTargets], + ); + + const [activeCategory, setActiveCategory] = React.useState( + categories[0] || "Heltec", + ); + + // Update active category when categories load if not set + React.useEffect(() => { + if (!activeCategory && categories.length > 0) { + setActiveCategory(categories[0]); + } + }, [categories, activeCategory]); + const toggleTarget = (target: string) => { const current = targets || []; if (current.includes(target)) { @@ -51,6 +83,7 @@ export default function ProfileEditor({ name: data.name, targets: data.targets, config: data.config, + version: data.version, }); } else { await createProfile(data); @@ -63,33 +96,87 @@ export default function ProfileEditor({ onSubmit={handleSubmit(onSubmit)} className="space-y-6 bg-slate-900 p-6 rounded-lg border border-slate-800" > -
- - +
+
+ + +
+
+ + +
-
- {["tbeam", "rak4631", "heltec_v3", "pico"].map((t) => ( -
- toggleTarget(t)} - /> - + {/* ... existing target UI */} + +
+ {/* Category Pills */} +
+ {categories.map((category) => { + const count = groupedTargets[category].filter((t) => + targets?.includes(t.id), + ).length; + const isActive = activeCategory === category; + + return ( + + ); + })} +
+ + {/* Active Category Targets */} +
+
+ {groupedTargets[activeCategory]?.map((item) => ( +
+ toggleTarget(item.id)} + /> + +
+ ))}
- ))} +