Skip to content

Commit

Permalink
Merge pull request #478 from ryanhopperlowe/ui/feat/models-implementa…
Browse files Browse the repository at this point in the history
…tion

UI feat: Create crud interface for models
  • Loading branch information
ryanhopperlowe authored Nov 8, 2024
2 parents a47cacd + e94427d commit bfc85e6
Show file tree
Hide file tree
Showing 21 changed files with 724 additions and 46 deletions.
41 changes: 19 additions & 22 deletions ui/admin/app/components/composed/DataTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -83,28 +83,7 @@ export function DataTable<TData, TValue>({
rowClassName?.(row.original)
)}
>
{row.getVisibleCells().map((cell) => (
<TableCell
key={cell.id}
className={cn(
"py-4",
classNames?.cell,
{ "cursor-pointer": !!onRowClick }
)}
onClick={() => {
if (
!disableClickPropagation?.(cell)
) {
onRowClick?.(row.original);
}
}}
>
{flexRender(
cell.column.columnDef.cell,
cell.getContext()
)}
</TableCell>
))}
{row.getVisibleCells().map(renderCell)}
</TableRow>
))
) : (
Expand All @@ -124,4 +103,22 @@ export function DataTable<TData, TValue>({
</Table>
</div>
);

function renderCell(cell: Cell<TData, TValue>) {
return (
<TableCell
key={cell.id}
className={cn("py-4", classNames?.cell, {
"cursor-pointer": !!onRowClick,
})}
onClick={() => {
if (!disableClickPropagation?.(cell)) {
onRowClick?.(cell.row.original);
}
}}
>
{flexRender(cell.column.columnDef.cell, cell.getContext())}
</TableCell>
);
}
}
105 changes: 104 additions & 1 deletion ui/admin/app/components/form/controlledInputs.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
import { ReactNode } from "react";
import { ComponentProps, ReactNode } from "react";
import {
Control,
ControllerFieldState,
ControllerRenderProps,
FieldPath,
FieldValues,
FormState,
} from "react-hook-form";

import { cn } from "~/lib/utils";

import { Checkbox } from "~/components/ui/checkbox";
import {
FormControl,
FormDescription,
Expand Down Expand Up @@ -180,6 +182,107 @@ export function ControlledAutosizeTextarea<
);
}

export type ControlledCheckboxProps<
TValues extends FieldValues,
TName extends FieldPath<TValues>,
> = BaseProps<TValues, TName> & ComponentProps<typeof Checkbox>;

export function ControlledCheckbox<
TValues extends FieldValues,
TName extends FieldPath<TValues>,
>({
control,
name,
label,
description,
onCheckedChange,
...checkboxProps
}: ControlledCheckboxProps<TValues, TName>) {
return (
<FormField
control={control}
name={name}
render={({ field, fieldState }) => (
<FormItem>
<div className="flex items-center gap-2">
<FormControl>
<Checkbox
{...field}
{...checkboxProps}
checked={field.value}
onCheckedChange={(value) => {
field.onChange(value);
onCheckedChange?.(value);
}}
className={cn(
getFieldStateClasses(fieldState),
checkboxProps.className
)}
/>
</FormControl>

{label && <FormLabel>{label}</FormLabel>}
</div>

<FormMessage />

{description && (
<FormDescription>{description}</FormDescription>
)}
</FormItem>
)}
/>
);
}

export type ControlledCustomInputProps<
TValues extends FieldValues,
TName extends FieldPath<TValues>,
> = BaseProps<TValues, TName> & {
children: (props: {
field: ControllerRenderProps<TValues, TName>;
fieldState: ControllerFieldState;
formState: FormState<TValues>;
className?: string;
}) => ReactNode;
};

export function ControlledCustomInput<
TValues extends FieldValues,
TName extends FieldPath<TValues>,
>({
control,
name,
label,
description,
children,
}: ControlledCustomInputProps<TValues, TName>) {
return (
<FormField
control={control}
name={name}
render={(args) => (
<FormItem>
{label && <FormLabel>{label}</FormLabel>}

<FormControl>
{children({
...args,
className: getFieldStateClasses(args.fieldState),
})}
</FormControl>

<FormMessage />

{description && (
<FormDescription>{description}</FormDescription>
)}
</FormItem>
)}
/>
);
}

function getFieldStateClasses(fieldState: ControllerFieldState) {
return cn({
"focus-visible:ring-destructive border-destructive": fieldState.invalid,
Expand Down
33 changes: 33 additions & 0 deletions ui/admin/app/components/model/CreateModel.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { PlusIcon } from "lucide-react";
import { useState } from "react";

import { ModelForm } from "~/components/model/ModelForm";
import { Button } from "~/components/ui/button";
import {
Dialog,
DialogContent,
DialogDescription,
DialogTitle,
DialogTrigger,
} from "~/components/ui/dialog";

export function CreateModel() {
const [open, setOpen] = useState(false);

return (
<Dialog open={open} onOpenChange={setOpen}>
<DialogTrigger asChild>
<Button variant="outline" startContent={<PlusIcon />}>
Create Model
</Button>
</DialogTrigger>

<DialogContent>
<DialogTitle>Create Model</DialogTitle>
<DialogDescription hidden>Create Model</DialogDescription>

<ModelForm onSubmit={() => setOpen(false)} />
</DialogContent>
</Dialog>
);
}
54 changes: 54 additions & 0 deletions ui/admin/app/components/model/DeleteModel.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { TrashIcon } from "lucide-react";
import { mutate } from "swr";

import { ModelApiService } from "~/lib/service/api/modelApiService";

import { ConfirmationDialog } from "~/components/composed/ConfirmationDialog";
import { Button } from "~/components/ui/button";
import {
Tooltip,
TooltipContent,
TooltipProvider,
TooltipTrigger,
} from "~/components/ui/tooltip";
import { useAsync } from "~/hooks/useAsync";

type DeleteModelProps = {
id: string;
};

export function DeleteModel(props: DeleteModelProps) {
const deleteModel = useAsync(ModelApiService.deleteModel, {
onSuccess: () => mutate(ModelApiService.getModels.key()),
});

return (
<TooltipProvider>
<Tooltip>
<ConfirmationDialog
title="Are you sure you want to delete this model?"
description="Doing so will break any tools or agents currently using it."
onConfirm={() => deleteModel.execute(props.id)}
confirmProps={{
variant: "destructive",
children: "Delete",
}}
>
<TooltipTrigger asChild>
<Button
size="icon"
variant="ghost"
onClick={(e) => e.stopPropagation()}
disabled={deleteModel.isLoading}
loading={deleteModel.isLoading}
>
<TrashIcon />
</Button>
</TooltipTrigger>
</ConfirmationDialog>

<TooltipContent>Delete Model</TooltipContent>
</Tooltip>
</TooltipProvider>
);
}
Loading

0 comments on commit bfc85e6

Please sign in to comment.