Skip to content

Commit 97bc2ae

Browse files
add filtering on unassigned peers
1 parent f5dce19 commit 97bc2ae

File tree

2 files changed

+95
-14
lines changed

2 files changed

+95
-14
lines changed

src/modules/groups/GroupSelector.tsx

Lines changed: 72 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -22,28 +22,45 @@ import { Group } from "@/interfaces/Group";
2222

2323
interface MultiSelectProps {
2424
values: string[];
25-
onChange: (items: string[]) => void;
25+
exclusiveValue?: string;
26+
onChange: (anyOfValues: string[], exclusiveValue?: string) => void;
2627
disabled?: boolean;
2728
popoverWidth?: "auto" | number;
2829
groups: Group[] | undefined;
30+
unassignedCount?: number;
31+
defaultGroupName?: string;
2932
}
3033
export function GroupSelector({
3134
onChange,
3235
values,
36+
exclusiveValue,
3337
disabled = false,
3438
popoverWidth = 400,
3539
groups,
40+
unassignedCount,
41+
defaultGroupName = "All", //defined as a property, no clue if this value may change in the future
3642
}: MultiSelectProps) {
3743
const searchRef = React.useRef<HTMLInputElement>(null);
3844
const [inputRef, { width }] = useElementSize<HTMLButtonElement>();
3945
const [search, setSearch] = useState("");
4046

41-
const toggle = (code: string) => {
47+
const toggleAnyOf = (code: string) => {
4248
const isSelected = values.find((c) => c == code) != undefined;
4349
if (isSelected) {
44-
onChange && onChange(values.filter((c) => c != code));
50+
onChange && onChange(values.filter((c) => c != code), undefined);
4551
} else {
46-
onChange && onChange([...values, code]);
52+
onChange && onChange([...values, code], undefined);
53+
setSearch("");
54+
}
55+
};
56+
57+
const toggleExclusive = (code: string) => {
58+
const isSelected = exclusiveValue == code;
59+
if (isSelected) {
60+
onChange && onChange([], undefined);
61+
setSearch("");
62+
} else {
63+
onChange && onChange([], code);
4764
setSearch("");
4865
}
4966
};
@@ -63,14 +80,16 @@ export function GroupSelector({
6380
}}
6481
>
6582
<PopoverTrigger asChild={true}>
66-
<Button variant={"secondary"} disabled={disabled} ref={inputRef}>
83+
<Button variant={"secondary"} disabled={disabled} ref={inputRef} className="w-[200px] justify-between">
6784
<FolderGit2 size={16} className={"shrink-0"} />
6885
<div className={"w-full flex justify-between"}>
69-
{values.length > 0 ? (
70-
<div>{values.length} Group(s)</div>
71-
) : (
72-
"All Groups"
73-
)}
86+
{
87+
exclusiveValue != undefined
88+
? ("Unassigned peers")
89+
: values.length > 0
90+
? (`${values.length} Group(s)`)
91+
: ("All Groups")
92+
}
7493
<div className={"pl-2"}>
7594
<ChevronsUpDown size={18} className={"shrink-0"} />
7695
</div>
@@ -133,7 +152,6 @@ export function GroupSelector({
133152
</div>
134153
</div>
135154
</div>
136-
137155
<ScrollArea
138156
className={
139157
"max-h-[380px] overflow-y-auto flex flex-col gap-1 pl-2 py-2 pr-3"
@@ -142,7 +160,48 @@ export function GroupSelector({
142160
<CommandGroup>
143161
<div className={""}>
144162
<div className={"grid grid-cols-1 gap-1"}>
145-
{orderBy(groups, "name")?.map((item) => {
163+
<CommandItem
164+
className={"p-1"}
165+
onSelect={() => {
166+
toggleExclusive(defaultGroupName);
167+
searchRef.current?.focus();
168+
}}
169+
onClick={(e) => e.preventDefault()}
170+
>
171+
<div
172+
className={
173+
"text-neutral-500 dark:text-nb-gray-300 font-medium flex items-center gap-3 py-1 px-1 w-full"
174+
}
175+
>
176+
<Checkbox checked={exclusiveValue == defaultGroupName}/>
177+
<div
178+
className={
179+
"flex justify-between items-center w-full"
180+
}
181+
>
182+
<div
183+
className={
184+
"flex items-center gap-2 whitespace-nowrap text-sm"
185+
}
186+
>
187+
<FolderGit2 size={13} className={"shrink-0"} />
188+
<TextWithTooltip text={"Unassigned peers"} />
189+
</div>
190+
<div
191+
className={
192+
"flex items-center gap-2 text-xs text-nb-gray-200/60"
193+
}
194+
>
195+
<MonitorSmartphoneIcon size={13} />
196+
{unassignedCount} Peer(s)
197+
</div>
198+
</div>
199+
</div>
200+
</CommandItem>
201+
<hr />
202+
{orderBy(groups, "name")
203+
?.filter((group) => group.name != defaultGroupName) // Ignore default group
204+
?.map((item) => {
146205
const value = item?.name || "";
147206
if (value === "") return null;
148207
const isSelected =
@@ -154,7 +213,7 @@ export function GroupSelector({
154213
value={value}
155214
className={"p-1"}
156215
onSelect={() => {
157-
toggle(value);
216+
toggleAnyOf(value);
158217
searchRef.current?.focus();
159218
}}
160219
onClick={(e) => e.preventDefault()}

src/modules/peers/PeersTable.tsx

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,7 @@ const PeersTableColumns: ColumnDef<Peer>[] = [
113113
accessorKey: "group_name_strings",
114114
accessorFn: (peer) => peer.groups?.map((g) => g?.name || "").join(", "),
115115
sortingFn: "text",
116+
filterFn: "equals" // hack to filter on a uniq item value in groups
116117
},
117118
{
118119
id: "group_names",
@@ -226,9 +227,19 @@ export default function PeersTable({ peers, isLoading, headingTarget }: Props) {
226227
],
227228
);
228229

230+
// Caveat: no clue if this name may change in the future
231+
// By default server assign every peers to `All` group
232+
const DEFAULT_GROUP_NAME = "All";
233+
229234
const pendingApprovalCount =
230235
peers?.filter((p) => p.approval_required).length || 0;
231236

237+
// Count peers that are not assigned to other group than the default group
238+
const unassignedCount = peers?.reduce(
239+
(acc, p) => acc + ((p.groups?.length == 1 && p.groups.at(0)?.name == DEFAULT_GROUP_NAME) ? 1: 0),
240+
0
241+
);
242+
232243
const tableGroups =
233244
(uniqBy(
234245
peers?.map((p) => p.groups?.map((g) => g)).flatMap((g) => g),
@@ -440,17 +451,28 @@ export default function PeersTable({ peers, isLoading, headingTarget }: Props) {
440451
.getColumn("group_names")
441452
?.getFilterValue() as string[]) || []
442453
}
443-
onChange={(anyOfValues) => {
454+
exclusiveValue={
455+
(table
456+
.getColumn("group_name_strings")
457+
?.getFilterValue() as string) || undefined
458+
}
459+
onChange={(anyOfValues, exclusiveValue) => {
444460
const normalizedAnyOf = ( anyOfValues.length == 0 ) ? undefined : anyOfValues;
445461
overrideTableFilter( table, [
446462
{
447463
id: "group_names",
448464
value: normalizedAnyOf
449465
},
466+
{
467+
id: "group_name_strings",
468+
value: exclusiveValue
469+
}
450470
]
451471
);
452472
}}
453473
groups={tableGroups}
474+
unassignedCount={unassignedCount}
475+
defaultGroupName={DEFAULT_GROUP_NAME}
454476
/>
455477
)}
456478

0 commit comments

Comments
 (0)