Skip to content

Commit

Permalink
fix: created filter and resizable table columns (#1748)
Browse files Browse the repository at this point in the history
* enhance: add resize columns

* enhance: add created table filter

* Update DataTable.tsx

* use daterange instead

* renamed start->createdStart, end->createdEnd
  • Loading branch information
ivyjeong13 authored Feb 14, 2025
1 parent ebff364 commit c505de6
Show file tree
Hide file tree
Showing 10 changed files with 313 additions and 18 deletions.
88 changes: 81 additions & 7 deletions ui/admin/app/components/composed/DataTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,20 @@ import {
useReactTable,
} from "@tanstack/react-table";
import { ListFilterIcon } from "lucide-react";
import { useState } from "react";
import { DateRange } from "react-day-picker";
import { useNavigate } from "react-router";

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

import { ComboBox } from "~/components/composed/ComboBox";
import { Button } from "~/components/ui/button";
import { Calendar } from "~/components/ui/calendar";
import {
Popover,
PopoverContent,
PopoverTrigger,
} from "~/components/ui/popover";
import {
Table,
TableBody,
Expand Down Expand Up @@ -47,6 +56,9 @@ export function DataTable<TData, TValue>({
onCtrlClick,
}: DataTableProps<TData, TValue>) {
const table = useReactTable({
enableColumnResizing: true,
columnResizeMode: "onChange",
columnResizeDirection: "ltr",
data,
columns,
state: { sorting: sort },
Expand All @@ -61,13 +73,33 @@ export function DataTable<TData, TValue>({
<TableRow key={headerGroup.id} className="p-4">
{headerGroup.headers.map((header) => {
return (
<TableHead key={header.id}>
{header.isPlaceholder
? null
: flexRender(
header.column.columnDef.header,
header.getContext()
)}
<TableHead
key={header.id}
style={{ width: header.getSize() }}
className="space-between group relative px-0"
>
<div className="flex h-full w-full items-center justify-between">
{header.isPlaceholder ? null : (
<div className="w-full px-2">
{flexRender(
header.column.columnDef.header,
header.getContext()
)}
</div>
)}
{header.column.getCanResize() && (
<button
onMouseDown={header.getResizeHandler()}
onTouchStart={header.getResizeHandler()}
className={cn(
"mx-2 h-full w-1 cursor-col-resize self-end group-hover:bg-muted-foreground/30",
{
isResizing: header.column.getIsResizing(),
}
)}
></button>
)}
</div>
</TableHead>
);
})}
Expand Down Expand Up @@ -115,6 +147,7 @@ export function DataTable<TData, TValue>({
onRowClick?.(cell.row.original);
}
}}
style={{ width: cell.column.getSize() }}
>
{flexRender(cell.column.columnDef.cell, cell.getContext())}
</TableCell>
Expand Down Expand Up @@ -167,3 +200,44 @@ export const DataTableFilter = ({
/>
);
};

export const DataTableTimeFilter = ({
dateRange,
field,
onSelect,
}: {
dateRange: DateRange;
field: string;
onSelect: (range: DateRange) => void;
}) => {
const [range, setRange] = useState<DateRange | undefined>(dateRange);
return (
<Popover>
<PopoverTrigger asChild>
<Button
variant="text"
endContent={<ListFilterIcon />}
className="w-full p-0"
classNames={{
content: "w-full justify-between",
}}
>
{field}
</Button>
</PopoverTrigger>
<PopoverContent>
<Calendar
mode="range"
selected={range}
onSelect={(range) => {
setRange(range);
if (range?.from && range?.to) {
onSelect(range);
}
}}
initialFocus
/>
</PopoverContent>
</Popover>
);
};
8 changes: 8 additions & 0 deletions ui/admin/app/components/composed/Filters.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ type QueryParams = {
agentId?: string;
userId?: string;
taskId?: string;
created?: string;
};

export function Filters({
Expand Down Expand Up @@ -75,6 +76,13 @@ export function Filters({
value: workflowMap?.get(filters.taskId)?.name ?? filters.taskId,
onRemove: () => updateFilters("taskId"),
},
"created" in filters &&
filters.created && {
key: "created",
label: "Created",
value: new Date(filters.created).toLocaleDateString(),
onRemove: () => updateFilters("created"),
},
].filter((x) => !!x);
}, [navigate, searchParams, agentMap, userMap, workflowMap, url]);

Expand Down
75 changes: 75 additions & 0 deletions ui/admin/app/components/ui/calendar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import { ChevronLeftIcon, ChevronRightIcon } from "lucide-react";
import * as React from "react";
import { DayPicker } from "react-day-picker";

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

import { buttonVariants } from "~/components/ui/button";

export type CalendarProps = React.ComponentProps<typeof DayPicker>;

function Calendar({
className,
classNames,
showOutsideDays = true,
...props
}: CalendarProps) {
return (
<DayPicker
showOutsideDays={showOutsideDays}
className={cn("p-3", className)}
classNames={{
months: "flex flex-col space-y-4 sm:flex-row sm:space-x-4 sm:space-y-0",
month: "space-y-4",
caption: "relative flex items-center justify-center pt-1",
caption_label: "text-sm font-medium",
nav: "flex items-center space-x-1",
nav_button: cn(
buttonVariants({ variant: "outline" }),
"h-7 w-7 bg-transparent p-0 opacity-50 hover:opacity-100"
),
nav_button_previous: "absolute left-1",
nav_button_next: "absolute right-1",
table: "w-full border-collapse space-y-1",
head_row: "flex",
head_cell:
"w-8 rounded-md text-[0.8rem] font-normal text-muted-foreground",
row: "mt-2 flex w-full",
cell: cn(
"relative p-0 text-center text-sm focus-within:relative focus-within:z-20 [&:has([aria-selected])]:bg-accent [&:has([aria-selected].day-outside)]:bg-accent/50 [&:has([aria-selected].day-range-end)]:rounded-r-md",
props.mode === "range"
? "[&:has(>.day-range-end)]:rounded-r-md [&:has(>.day-range-start)]:rounded-l-md first:[&:has([aria-selected])]:rounded-l-md last:[&:has([aria-selected])]:rounded-r-md"
: "[&:has([aria-selected])]:rounded-md"
),
day: cn(
buttonVariants({ variant: "ghost" }),
"h-8 w-8 p-0 font-normal aria-selected:opacity-100"
),
day_range_start: "day-range-start",
day_range_end: "day-range-end",
day_selected:
"bg-primary text-primary-foreground hover:bg-primary hover:text-primary-foreground focus:bg-primary focus:text-primary-foreground",
day_today: "bg-accent text-accent-foreground",
day_outside:
"day-outside text-muted-foreground aria-selected:bg-accent/50 aria-selected:text-muted-foreground",
day_disabled: "text-muted-foreground opacity-50",
day_range_middle:
"aria-selected:bg-accent aria-selected:text-accent-foreground",
day_hidden: "invisible",
...classNames,
}}
components={{
IconLeft: ({ className, ...props }) => (
<ChevronLeftIcon className={cn("h-4 w-4", className)} {...props} />
),
IconRight: ({ className, ...props }) => (
<ChevronRightIcon className={cn("h-4 w-4", className)} {...props} />
),
}}
{...props}
/>
);
}
Calendar.displayName = "Calendar";

export { Calendar };
4 changes: 4 additions & 0 deletions ui/admin/app/lib/service/routeService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ const QuerySchemas = {
userId: z.string().nullish(),
taskId: z.string().nullish(),
from: z.enum(["tasks", "agents", "users"]).nullish().catch(null),
createdStart: z.string().nullish(),
createdEnd: z.string().nullish(),
}),
taskSchema: z.object({
threadId: z.string().nullish(),
Expand All @@ -25,6 +27,8 @@ const QuerySchemas = {
agentId: z.string().nullish(),
userId: z.string().nullish(),
taskId: z.string().nullish(),
createdStart: z.string().nullish(),
createdEnd: z.string().nullish(),
}),
usersSchema: z.object({ userId: z.string().optional() }),
} as const;
Expand Down
22 changes: 22 additions & 0 deletions ui/admin/app/lib/utils/filter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { isAfter, isBefore, isSameDay } from "date-fns";

export const filterByCreatedRange = <T extends { created: string }>(
items: T[],
start: string,
end?: string | null
) => {
const startDate = new Date(start);
const endDate = end ? new Date(end) : undefined;
return items.filter((item) => {
const createdDate = new Date(item.created);

if (endDate) {
const withinStart =
isAfter(createdDate, startDate) || isSameDay(createdDate, startDate);
const withinEnd =
isBefore(createdDate, endDate) || isSameDay(createdDate, endDate);
return withinStart && withinEnd;
}
return isSameDay(createdDate, startDate);
});
};
40 changes: 37 additions & 3 deletions ui/admin/app/routes/_auth.chat-threads._index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,12 @@ import { UserService } from "~/lib/service/api/userService";
import { RouteHandle } from "~/lib/service/routeHandles";
import { RouteQueryParams, RouteService } from "~/lib/service/routeService";
import { timeSince } from "~/lib/utils";
import { filterByCreatedRange } from "~/lib/utils/filter";

import {
DataTable,
DataTableFilter,
DataTableTimeFilter,
useRowNavigate,
} from "~/components/composed/DataTable";
import { Filters } from "~/components/composed/Filters";
Expand Down Expand Up @@ -61,7 +63,8 @@ export default function TaskRuns() {
? value
: $path("/chat-threads/:id", { id: value.id })
);
const { agentId, userId } = useLoaderData<typeof clientLoader>();
const { agentId, userId, createdStart, createdEnd } =
useLoaderData<typeof clientLoader>();

const getThreads = useSWR(...ThreadsService.getThreads.swr({}));
const getAgents = useSWR(...AgentService.getAgents.swr({}));
Expand All @@ -86,8 +89,16 @@ export default function TaskRuns() {
);
}

if (createdStart) {
filteredThreads = filterByCreatedRange(
filteredThreads,
createdStart,
createdEnd
);
}

return filteredThreads;
}, [getThreads.data, agentId, userId]);
}, [getThreads.data, agentId, userId, createdStart, createdEnd]);

const agentMap = useMemo(
() => new Map(getAgents.data?.map((agent) => [agent.id, agent])),
Expand Down Expand Up @@ -160,6 +171,8 @@ export default function TaskRuns() {
$path("/chat-threads", {
agentId: value,
...(userId && { userId }),
...(createdStart && { createdStart }),
...(createdEnd && { createdEnd }),
})
);
}}
Expand All @@ -183,6 +196,8 @@ export default function TaskRuns() {
$path("/chat-threads", {
userId: value,
...(agentId && { agentId }),
...(createdStart && { createdStart }),
...(createdEnd && { createdEnd }),
})
);
}}
Expand All @@ -191,7 +206,26 @@ export default function TaskRuns() {
}),
columnHelper.accessor("created", {
id: "created",
header: "Created",
header: ({ column }) => (
<DataTableTimeFilter
key={column.id}
field="Created"
dateRange={{
from: createdStart ? new Date(createdStart) : undefined,
to: createdEnd ? new Date(createdEnd) : undefined,
}}
onSelect={(range) => {
navigate.internal(
$path("/chat-threads", {
createdStart: range.from?.toDateString(),
createdEnd: range.to?.toDateString(),
...(agentId && { agentId }),
...(userId && { userId }),
})
);
}}
/>
),
cell: (info) => (
<p>{timeSince(new Date(info.row.original.created))} ago</p>
),
Expand Down
Loading

0 comments on commit c505de6

Please sign in to comment.