Skip to content

Commit

Permalink
fix table filter issue and put it on the table header
Browse files Browse the repository at this point in the history
  • Loading branch information
mrkarimoff committed Jan 29, 2024
1 parent 27cd7a3 commit 6274ebf
Show file tree
Hide file tree
Showing 5 changed files with 170 additions and 105 deletions.
129 changes: 72 additions & 57 deletions apps/analytics/app/_components/analytics/EventsTable/Columns.tsx
Original file line number Diff line number Diff line change
@@ -1,69 +1,84 @@
"use client";

import { ColumnDef } from "@tanstack/react-table";
import { type Dispatch, type SetStateAction } from "react";
import { DataTableColumnHeader } from "~/components/DataTable/column-header";
import { MetadataDialog } from "~/components/MetadataDialog";
import type { Event } from "~/db/getEvents";
import { type EventType } from "./EventsTable";
import { StackTraceDialog } from "./StackTraceDialog";
import TableFilter from "./TableFilter";

export const columns: ColumnDef<Event>[] = [
{
accessorKey: "type",
header: ({ column }) => (
<DataTableColumnHeader column={column} title="Event" />
),
},
{
accessorKey: "timestamp",
header: ({ column }) => (
<DataTableColumnHeader column={column} title="Timestamp" />
),
cell: ({ row }) => {
return (
<div className="break-all">{row.original.timestamp.toUTCString()}</div>
);
export const getColumns = (
eventTypes: EventType[],
setEventTypes: Dispatch<SetStateAction<EventType[]>>
) => {
const columns: ColumnDef<Event>[] = [
{
accessorKey: "type",
header: ({ column }) => (
<div className="flex space-x-4">
<TableFilter eventTypes={eventTypes} setEventTypes={setEventTypes} />
<DataTableColumnHeader column={column} />
</div>
),
},
},
{
accessorKey: "installationId",
header: ({ column }) => (
<DataTableColumnHeader column={column} title="Installation Id" />
),
cell: ({ row }) => {
return <div className="break-all">{row.original.installationId}</div>;
{
accessorKey: "timestamp",
header: ({ column }) => (
<DataTableColumnHeader column={column} title="Timestamp" />
),
cell: ({ row }) => {
return (
<div className="break-all">
{row.original.timestamp.toUTCString()}
</div>
);
},
},
},
{
accessorKey: "name",
header: ({ column }) => (
<DataTableColumnHeader column={column} title="Name" />
),
},
{
accessorKey: "message",
header: ({ column }) => (
<DataTableColumnHeader column={column} title="Message" />
),
},
{
accessorKey: "stack",
header: "",
cell: ({ row }) =>
row.original.stack && (
<div className="min-w-max">
<StackTraceDialog error={row.original} />
</div>
{
accessorKey: "installationId",
header: ({ column }) => (
<DataTableColumnHeader column={column} title="Installation Id" />
),
cell: ({ row }) => {
return <div className="break-all">{row.original.installationId}</div>;
},
},
{
accessorKey: "name",
header: ({ column }) => (
<DataTableColumnHeader column={column} title="Name" />
),
},
{
accessorKey: "message",
header: ({ column }) => (
<DataTableColumnHeader column={column} title="Message" />
),
},
{
accessorKey: "metadata",
header: "",
cell: ({ row }) => {
return (
<div className="min-w-max">
<MetadataDialog event={row.original} />
</div>
);
},
},
];
{
accessorKey: "stack",
header: "",
cell: ({ row }) =>
row.original.stack && (
<div className="min-w-max">
<StackTraceDialog error={row.original} />
</div>
),
},
{
accessorKey: "metadata",
header: "",
cell: ({ row }) => {
return (
<div className="min-w-max">
<MetadataDialog event={row.original} />
</div>
);
},
},
];

return columns;
};
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,7 @@ import { useEffect, useMemo, useState } from "react";
import { DataTable } from "~/components/DataTable/data-table";
import ExportButton from "~/components/ExportButton";
import { Event } from "~/db/getEvents";
import { columns } from "./Columns";
import TableFilter from "./TableFilter";
import { getColumns } from "./Columns";

export type EventType = {
text: string;
Expand Down Expand Up @@ -34,15 +33,17 @@ export default function EventsTable({ events }: { events: Event[] }) {

return (
<div>
<TableFilter eventTypes={eventTypes} setEventTypes={setEventTypes} />

<div className="flex justify-between items-center mt-2">
<h2>Events</h2>
<ExportButton data={events} filename="events.csv" />
</div>

<div className="mt-2">
<DataTable columns={columns} data={filteredEvents} pagination={true} />
<DataTable
columns={getColumns(eventTypes, setEventTypes)}
data={filteredEvents}
pagination={true}
/>
</div>
</div>
);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
"use client";

import { type Dispatch, type SetStateAction } from "react";
import { Button } from "~/components/ui/button";
import { Checkbox } from "~/components/ui/checkbox";
import {
Expand All @@ -9,53 +10,34 @@ import {
DropdownMenuSeparator,
DropdownMenuTrigger,
} from "~/components/ui/dropdown-menu";
import { EventType } from "./EventsTable";
import { useEffect, useState, type Dispatch, type SetStateAction } from "react";
import { type EventType } from "./EventsTable";

type TableFilterProps = {
eventTypes: EventType[];
setEventTypes: Dispatch<SetStateAction<EventType[]>>;
};

const TableFilter = ({ eventTypes, setEventTypes }: TableFilterProps) => {
const [allSelected, setAllSelected] = useState(true);

useEffect(() => {
const updatedEventTypes = eventTypes.map((t) => ({
...t,
isSelected: allSelected,
}));
setEventTypes(updatedEventTypes);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [allSelected]);

const handleCheckedChange = (value: boolean, currentType: string) => {
const updatedEventTypes = eventTypes.map((t) => {
if (t.text === currentType) {
return { ...t, isSelected: value };
}
return t;
});

// If all event types are selected, set allSelected to true
if (updatedEventTypes.every((t) => t.isSelected)) {
setAllSelected(true);
return;
}

// If no event types are selected, set allSelected to false
if (updatedEventTypes.every((t) => !t.isSelected)) {
setAllSelected(false);
return;
}
const toggleOption = (option: string) => {
setEventTypes((prevState) =>
prevState.map((t) =>
t.text === option ? { ...t, isSelected: !t.isSelected } : t
)
);
};

setEventTypes(updatedEventTypes);
const toggleAllOptions = (isSelected: boolean) => {
setEventTypes((prevState) => prevState.map((t) => ({ ...t, isSelected })));
};

const isAllSelected = eventTypes.every((option) => option.isSelected);

return (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="outline">Event Types</Button>
<Button className="text-sm" size={"sm"} variant="outline">
Type
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent className="w-52 ml-12">
<DropdownMenuLabel>Select events</DropdownMenuLabel>
Expand All @@ -64,11 +46,12 @@ const TableFilter = ({ eventTypes, setEventTypes }: TableFilterProps) => {
<div className="space-y-3">
<label className="text-sm flex items-center gap-3 pl-2 transition-colors hover:bg-muted p-1 rounded-md">
<Checkbox
checked={allSelected}
onCheckedChange={(val) => setAllSelected(Boolean(val))}
checked={isAllSelected}
onCheckedChange={() => toggleAllOptions(!isAllSelected)}
/>
<span>All</span>
</label>
<DropdownMenuSeparator />

{eventTypes.map((type) => (
<label
Expand All @@ -77,9 +60,7 @@ const TableFilter = ({ eventTypes, setEventTypes }: TableFilterProps) => {
>
<Checkbox
checked={type.isSelected}
onCheckedChange={(val) =>
handleCheckedChange(Boolean(val), type.text)
}
onCheckedChange={() => toggleOption(type.text)}
/>
<span>{type.text}</span>
</label>
Expand Down
64 changes: 64 additions & 0 deletions apps/analytics/app/options/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
"use client";

import React, { useState } from "react";

const eventTypes = [
"AppSetup",
"ProtocolInstalled",
"InterviewStarted",
"InterviewCompleted",
"DataExported",
"Error",
];

const DropdownMenu = () => {
const [selectedOptions, setSelectedOptions] = useState(
eventTypes.map((eventType) => ({ text: eventType, isSelected: true }))
);

const toggleOption = (option: string) => {
setSelectedOptions((prevOptions) =>
prevOptions.map((prevOption) =>
prevOption.text === option
? { ...prevOption, isSelected: !prevOption.isSelected }
: prevOption
)
);
};

const toggleAllOptions = (isSelected: boolean) => {
setSelectedOptions((prevOptions) =>
prevOptions.map((prevOption) => ({ ...prevOption, isSelected }))
);
};

const isAllSelected = selectedOptions.every((option) => option.isSelected);

return (
<div className="border-2 p-4">
<label className="text-sm flex items-center gap-3 pl-2 transition-colors hover:bg-muted p-1 rounded-md">
<input
type="checkbox"
checked={isAllSelected}
onChange={() => toggleAllOptions(!isAllSelected)}
/>
All
</label>
{selectedOptions.map((option) => (
<label
className="text-sm flex items-center gap-3 pl-2 transition-colors hover:bg-muted p-1 rounded-md"
key={option.text}
>
<input
type="checkbox"
checked={option.isSelected}
onChange={() => toggleOption(option.text)}
/>
{option.text}
</label>
))}
</div>
);
};

export default DropdownMenu;
12 changes: 8 additions & 4 deletions apps/analytics/components/DataTable/column-header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import { cn } from "~/utils/shadcn";
interface DataTableColumnHeaderProps<TData, TValue>
extends React.HTMLAttributes<HTMLDivElement> {
column: Column<TData, TValue>;
title: string;
title?: string;
}

export function DataTableColumnHeader<TData, TValue>({
Expand All @@ -36,11 +36,15 @@ export function DataTableColumnHeader<TData, TValue>({
>
<span>{title}</span>
{column.getIsSorted() === "desc" ? (
<ArrowDown className="ml-2 h-4 w-4 text-emerald-500" />
<ArrowDown
className={`${title && "ml-2 "}h-4 w-4 text-emerald-500`}
/>
) : column.getIsSorted() === "asc" ? (
<ArrowUp className="ml-2 h-4 w-4 text-emerald-500" />
<ArrowUp
className={`${title && "ml-2"} h-4 w-4 text-emerald-500`}
/>
) : (
<ArrowUpDown className="w-4t ml-2 h-4" />
<ArrowUpDown className={`${title && "ml-2"} w-4t h-4`} />
)}
</Button>
</DropdownMenuTrigger>
Expand Down

0 comments on commit 6274ebf

Please sign in to comment.