♻️ refactor

This commit is contained in:
Fred KISSIE
2026-03-31 22:44:18 +02:00
parent a4d8789c20
commit 543542713b
4 changed files with 146 additions and 59 deletions

View File

@@ -1063,7 +1063,7 @@ export function InternalResourceForm({
] ]
) )
} }
enableAutocomplete={true} enableAutocomplete
autocompleteOptions={ autocompleteOptions={
allRoles allRoles
} }

View File

@@ -3,18 +3,9 @@ import type { ListClientsResponse } from "@server/routers/client";
import { useQuery } from "@tanstack/react-query"; import { useQuery } from "@tanstack/react-query";
import { useMemo, useState } from "react"; import { useMemo, useState } from "react";
import { useDebounce } from "use-debounce"; import { useDebounce } from "use-debounce";
import {
Command,
CommandEmpty,
CommandGroup,
CommandInput,
CommandItem,
CommandList
} from "./ui/command";
import { cn } from "@app/lib/cn";
import { CheckIcon } from "lucide-react";
import { useTranslations } from "next-intl"; import { useTranslations } from "next-intl";
import { MultiSelectTags } from "./multi-select-tags";
export type SelectedMachine = Pick< export type SelectedMachine = Pick<
ListClientsResponse["clients"][number], ListClientsResponse["clients"][number],
@@ -57,52 +48,71 @@ export function MachinesSelector({
return allMachines; return allMachines;
}, [machines, selectedMachines, debouncedValue]); }, [machines, selectedMachines, debouncedValue]);
const selectedMachinesIds = new Set( // const selectedMachinesIds = new Set(
selectedMachines.map((m) => m.clientId) // selectedMachines.map((m) => m.clientId)
); // );
return ( return (
<Command shouldFilter={false}> <MultiSelectTags
<CommandInput emptyPlaceholder={t("machineNotFound")}
placeholder={t("machineSearch")} searchPlaceholder={t("machineSearch")}
value={machineSearchQuery} value={selectedMachines.map((m) => ({
onValueChange={setMachineSearchQuery} ...m,
/> text: m.name,
<CommandList> id: m.clientId.toString()
<CommandEmpty>{t("machineNotFound")}</CommandEmpty> }))}
<CommandGroup> onChange={(values) => {
{machinesShown.map((m) => ( onSelectMachines(values);
<CommandItem }}
value={`${m.name}:${m.clientId}`} options={machinesShown.map((m) => ({
key={m.clientId} ...m,
onSelect={() => { id: m.clientId.toString(),
let newMachineClients = []; text: m.name
if (selectedMachinesIds.has(m.clientId)) { }))}
newMachineClients = selectedMachines.filter( onSearch={setMachineSearchQuery}
(mc) => mc.clientId !== m.clientId searchQuery={machineSearchQuery}
); />
} else { // <Command shouldFilter={false}>
newMachineClients = [ // <CommandInput
...selectedMachines, // placeholder={t("machineSearch")}
m // value={machineSearchQuery}
]; // onValueChange={setMachineSearchQuery}
} // />
onSelectMachines(newMachineClients); // <CommandList>
}} // <CommandEmpty>{t("machineNotFound")}</CommandEmpty>
> // <CommandGroup>
<CheckIcon // {machinesShown.map((m) => (
className={cn( // <CommandItem
"mr-2 h-4 w-4", // value={`${m.name}:${m.clientId}`}
selectedMachinesIds.has(m.clientId) // key={m.clientId}
? "opacity-100" // onSelect={() => {
: "opacity-0" // let newMachineClients = [];
)} // if (selectedMachinesIds.has(m.clientId)) {
/> // newMachineClients = selectedMachines.filter(
{`${m.name}`} // (mc) => mc.clientId !== m.clientId
</CommandItem> // );
))} // } else {
</CommandGroup> // newMachineClients = [
</CommandList> // ...selectedMachines,
</Command> // m
// ];
// }
// onSelectMachines(newMachineClients);
// }}
// >
// <CheckIcon
// className={cn(
// "mr-2 h-4 w-4",
// selectedMachinesIds.has(m.clientId)
// ? "opacity-100"
// : "opacity-0"
// )}
// />
// {`${m.name}`}
// </CommandItem>
// ))}
// </CommandGroup>
// </CommandList>
// </Command>
); );
} }

View File

@@ -0,0 +1,77 @@
import type { Ref } from "react";
import {
Command,
CommandEmpty,
CommandGroup,
CommandInput,
CommandItem,
CommandList
} from "./ui/command";
import { cn } from "@app/lib/cn";
import { CheckIcon } from "lucide-react";
export type TagValue = { text: string; id: string };
export type MultiSelectTagsProps<T extends TagValue> = {
emptyPlaceholder: string;
searchPlaceholder: string;
searchQuery?: string;
options: Array<T>;
value: Array<T>;
onChange: (newValue: Array<T>) => void;
onSearch: (query: string) => void;
ref?: Ref<HTMLButtonElement>;
};
export function MultiSelectTags<T extends TagValue>({
emptyPlaceholder,
searchPlaceholder,
searchQuery,
value,
options,
onSearch,
onChange
}: MultiSelectTagsProps<T>) {
const selectedValues = new Set(value.map((v) => v.id));
return (
<Command shouldFilter={false}>
<CommandInput
placeholder={searchPlaceholder}
value={searchQuery}
onValueChange={onSearch}
/>
<CommandList>
<CommandEmpty>{emptyPlaceholder}</CommandEmpty>
<CommandGroup>
{options.map((option) => (
<CommandItem
value={option.id}
key={option.id}
onSelect={() => {
let newValues = [];
if (selectedValues.has(option.id)) {
newValues = value.filter(
(v) => v.id !== option.id
);
} else {
newValues = [...value, option];
}
onChange(newValues);
}}
>
<CheckIcon
className={cn(
"mr-2 h-4 w-4",
selectedValues.has(option.id)
? "opacity-100"
: "opacity-0"
)}
/>
{`${option.text}`}
</CommandItem>
))}
</CommandGroup>
</CommandList>
</Command>
);
}

View File

@@ -522,7 +522,7 @@ export function TagInput({ ref, ...props }: TagInputProps) {
onBlur={handleInputBlur} onBlur={handleInputBlur}
{...inputProps} {...inputProps}
className={cn( className={cn(
"border-0 h-5 bg-transparent focus-visible:ring-0 focus-visible:ring-transparent focus-visible:ring-offset-0 flex-1 w-fit shadow-none inset-shadow-none", "border-0 px-0 h-5 bg-transparent focus-visible:ring-0 focus-visible:ring-transparent focus-visible:ring-offset-0 flex-1 w-fit shadow-none inset-shadow-none",
// className, // className,
styleClasses?.input styleClasses?.input
)} )}
@@ -692,7 +692,7 @@ export function TagInput({ ref, ...props }: TagInputProps) {
onBlur={handleInputBlur} onBlur={handleInputBlur}
{...inputProps} {...inputProps}
className={cn( className={cn(
"border-0 h-5 bg-transparent focus-visible:ring-0 focus-visible:ring-transparent focus-visible:ring-offset-0 flex-1 w-fit shadow-none inset-shadow-none", "border-0 px-0 h-5 bg-transparent focus-visible:ring-0 focus-visible:ring-transparent focus-visible:ring-offset-0 flex-1 w-fit shadow-none inset-shadow-none",
// className, // className,
styleClasses?.input styleClasses?.input
)} )}
@@ -770,7 +770,7 @@ export function TagInput({ ref, ...props }: TagInputProps) {
onBlur={handleInputBlur} onBlur={handleInputBlur}
{...inputProps} {...inputProps}
className={cn( className={cn(
"border-0 h-5 bg-transparent focus-visible:ring-0 focus-visible:ring-transparent focus-visible:ring-offset-0 flex-1 w-fit shadow-none inset-shadow-none", "border-0 px-0 h-5 bg-transparent focus-visible:ring-0 focus-visible:ring-transparent focus-visible:ring-offset-0 flex-1 w-fit shadow-none inset-shadow-none",
// className, // className,
styleClasses?.input styleClasses?.input
)} )}