Skip to content

Commit

Permalink
feat: advanced sorting (#680)
Browse files Browse the repository at this point in the history
* chore: sync

* feat: add multi-sort ui

* feat: sortable sort items

* feat: better feature flags

* feat: better sorting

* feat: better parsers

* feat: add tsdocs

* feat: update filter type

* chore: sync

* refactor: sorting and header

* feat: better sorting

* feat: validate sort and filter

* feat: update sort

* feat: update sorting list

* feat: regular filters

* feat: update sorting list

* refactor: view dropdown -> combobox

* feat: update sort

* feat: sortable filters

* feat: controlled sorting

* feat: optimize sorting
  • Loading branch information
sadmann7 authored Nov 3, 2024
1 parent 4cea013 commit 73710f9
Show file tree
Hide file tree
Showing 27 changed files with 1,402 additions and 483 deletions.
4 changes: 4 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@
"db:studio": "dotenv drizzle-kit studio"
},
"dependencies": {
"@dnd-kit/core": "^6.1.0",
"@dnd-kit/modifiers": "^7.0.0",
"@dnd-kit/sortable": "^8.0.0",
"@dnd-kit/utilities": "^3.2.2",
"@hookform/resolvers": "^3.9.0",
"@radix-ui/react-checkbox": "^1.1.2",
"@radix-ui/react-dialog": "^1.1.2",
Expand Down
72 changes: 72 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

19 changes: 12 additions & 7 deletions src/app/_components/feature-flags-provider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import * as React from "react"
import { useQueryState } from "nuqs"

import { dataTableConfig, type DataTableConfig } from "@/config/data-table"
import { cn } from "@/lib/utils"
import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group"
import {
Tooltip,
Expand Down Expand Up @@ -65,20 +66,24 @@ export function FeatureFlagsProvider({ children }: FeatureFlagsProviderProps) {
size="sm"
value={featureFlags}
onValueChange={(value: FeatureFlagValue[]) => setFeatureFlags(value)}
className="w-fit"
className="w-fit gap-0"
>
{dataTableConfig.featureFlags.map((flag) => (
{dataTableConfig.featureFlags.map((flag, index) => (
<Tooltip key={flag.value}>
<ToggleGroupItem
value={flag.value}
className="whitespace-nowrap px-3 text-xs"
className={cn(
"gap-2 whitespace-nowrap rounded-none px-3 text-xs data-[state=on]:bg-accent/70 data-[state=on]:hover:bg-accent/90",
{
"rounded-l-sm border-r-0": index === 0,
"rounded-r-sm":
index === dataTableConfig.featureFlags.length - 1,
}
)}
asChild
>
<TooltipTrigger>
<flag.icon
className="mr-2 size-3.5 shrink-0"
aria-hidden="true"
/>
<flag.icon className="size-3.5 shrink-0" aria-hidden="true" />
{flag.label}
</TooltipTrigger>
</ToggleGroupItem>
Expand Down
3 changes: 2 additions & 1 deletion src/app/_components/tasks-table-toolbar-actions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,9 @@ export function TasksTableToolbarActions({
excludeColumns: ["select", "actions"],
})
}
className="gap-2"
>
<DownloadIcon className="mr-2 size-4" aria-hidden="true" />
<DownloadIcon className="size-4" aria-hidden="true" />
Export
</Button>
{/**
Expand Down
21 changes: 11 additions & 10 deletions src/app/_components/tasks-table.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import type {
DataTableRowAction,
} from "@/types"

import { toSentenceCase } from "@/lib/utils"
import { useDataTable } from "@/hooks/use-data-table"
import { DataTable } from "@/components/data-table/data-table"
import { DataTableAdvancedToolbar } from "@/components/data-table/data-table-advanced-toolbar"
Expand Down Expand Up @@ -71,7 +72,7 @@ export function TasksTable({ promises }: TasksTableProps) {
id: "status",
label: "Status",
options: tasks.status.enumValues.map((status) => ({
label: status[0]?.toUpperCase() + status.slice(1),
label: toSentenceCase(status),
value: status,
icon: getStatusIcon(status),
count: statusCounts[status],
Expand All @@ -81,7 +82,7 @@ export function TasksTable({ promises }: TasksTableProps) {
id: "priority",
label: "Priority",
options: tasks.priority.enumValues.map((priority) => ({
label: priority[0]?.toUpperCase() + priority.slice(1),
label: toSentenceCase(priority),
value: priority,
icon: getPriorityIcon(priority),
count: priorityCounts[priority],
Expand Down Expand Up @@ -110,7 +111,7 @@ export function TasksTable({ promises }: TasksTableProps) {
label: "Status",
type: "select",
options: tasks.status.enumValues.map((status) => ({
label: status[0]?.toUpperCase() + status.slice(1),
label: toSentenceCase(status),
value: status,
icon: getStatusIcon(status),
count: statusCounts[status],
Expand All @@ -121,28 +122,28 @@ export function TasksTable({ promises }: TasksTableProps) {
label: "Priority",
type: "multi-select",
options: tasks.priority.enumValues.map((priority) => ({
label: priority[0]?.toUpperCase() + priority.slice(1),
label: toSentenceCase(priority),
value: priority,
icon: getPriorityIcon(priority),
count: priorityCounts[priority],
})),
},
{
id: "createdAt",
label: "Created At",
label: "Created at",
type: "date",
},
]

const advancedFilter = featureFlags.includes("advancedFilter")
const floatingBar = featureFlags.includes("floatingBar")
const enableAdvancedTable = featureFlags.includes("advancedTable")
const enableFloatingBar = featureFlags.includes("floatingBar")

const { table } = useDataTable({
data,
columns,
pageCount,
filterFields,
enableAdvancedFilter: advancedFilter,
enableAdvancedFilter: enableAdvancedTable,
initialState: {
sorting: [{ id: "createdAt", desc: true }],
columnPinning: { right: ["actions"] },
Expand All @@ -157,10 +158,10 @@ export function TasksTable({ promises }: TasksTableProps) {
<DataTable
table={table}
floatingBar={
floatingBar ? <TasksTableFloatingBar table={table} /> : null
enableFloatingBar ? <TasksTableFloatingBar table={table} /> : null
}
>
{advancedFilter ? (
{enableAdvancedTable ? (
<DataTableAdvancedToolbar
table={table}
filterFields={advancedFilterFields}
Expand Down
15 changes: 10 additions & 5 deletions src/app/_lib/queries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,19 +24,17 @@ export async function getTasks(input: GetTasksSchema) {
async () => {
try {
const offset = (input.page - 1) * input.perPage
const { column, order } = input.sort
const fromDate = input.from ? new Date(input.from) : undefined
const toDate = input.to ? new Date(input.to) : undefined
const advancedFilter =
input.flags.includes("advancedFilter") && input.filters.length > 0
const advancedTable = input.flags.includes("advancedTable")

const advancedWhere = filterColumns({
table: tasks,
filters: input.filters,
joinOperator: input.joinOperator,
})

const where = advancedFilter
const where = advancedTable
? advancedWhere
: and(
input.title ? ilike(tasks.title, `%${input.title}%`) : undefined,
Expand All @@ -50,14 +48,21 @@ export async function getTasks(input: GetTasksSchema) {
toDate ? lte(tasks.createdAt, toDate) : undefined
)

const orderBy =
input.sort.length > 0
? input.sort.map((item) =>
item.desc ? desc(tasks[item.id]) : asc(tasks[item.id])
)
: [asc(tasks.createdAt)]

const { data, total } = await db.transaction(async (tx) => {
const data = await tx
.select()
.from(tasks)
.limit(input.perPage)
.offset(offset)
.where(where)
.orderBy(order === "asc" ? asc(tasks[column]) : desc(tasks[column]))
.orderBy(...orderBy)

const total = await tx
.select({
Expand Down
25 changes: 8 additions & 17 deletions src/app/_lib/validations.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { tasks } from "@/db/schema"
import type { FilterOperator, JoinOperator } from "@/types"
import { tasks, type Task } from "@/db/schema"
import {
createSearchParamsCache,
parseAsArrayOf,
Expand All @@ -9,32 +8,24 @@ import {
} from "nuqs/server"
import * as z from "zod"

import { parseAsFilters, parseAsSort } from "@/lib/parsers"

export const filterConditionSchema = z.object({
id: z.string(),
value: z.string(),
operator: z.custom<FilterOperator>(),
joinOperator: z.custom<JoinOperator>(),
})
import { getFiltersStateParser, getSortingStateParser } from "@/lib/parsers"

export const searchParamsCache = createSearchParamsCache({
flags: parseAsArrayOf(z.enum(["advancedFilter", "floatingBar"])).withDefault(
flags: parseAsArrayOf(z.enum(["advancedTable", "floatingBar"])).withDefault(
[]
),
page: parseAsInteger.withDefault(1),
perPage: parseAsInteger.withDefault(10),
sort: parseAsSort(tasks).withDefault({
column: "createdAt",
order: "desc",
}),
sort: getSortingStateParser<Task>().withDefault([
{ id: "createdAt", desc: true },
]),
title: parseAsString.withDefault(""),
status: parseAsArrayOf(z.enum(tasks.status.enumValues)).withDefault([]),
priority: parseAsArrayOf(z.enum(tasks.priority.enumValues)).withDefault([]),
from: parseAsString.withDefault(""),
to: parseAsString.withDefault(""),
// for advanced filter
filters: parseAsFilters(tasks).withDefault([]),
// advanced filter
filters: getFiltersStateParser().withDefault([]),
joinOperator: parseAsStringEnum(["and", "or"]).withDefault("and"),
})

Expand Down
26 changes: 20 additions & 6 deletions src/components/data-table/data-table-advanced-toolbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,21 @@

import * as React from "react"
import type { DataTableAdvancedFilterField } from "@/types"
import type { Table } from "@tanstack/react-table"
import { type Table } from "@tanstack/react-table"

import { cn } from "@/lib/utils"
import { DataTableFilterList } from "@/components/data-table/data-table-filter-list"
import { DataTableSortList } from "@/components/data-table/data-table-sort-list"
import { DataTableViewOptions } from "@/components/data-table/data-table-view-options"

interface DataTableAdvancedToolbarProps<TData>
extends React.HTMLAttributes<HTMLDivElement> {
/**
* The table instance returned from useDataTable hook with pagination, sorting, filtering, etc.
* @type Table<TData>
*/
table: Table<TData>

/**
* An array of filter field configurations for the data table.
* @type DataTableAdvancedFilterField<TData>[]
Expand Down Expand Up @@ -66,11 +72,19 @@ export function DataTableAdvancedToolbar<TData>({
)}
{...props}
>
<DataTableFilterList
filterFields={filterFields}
debounceMs={debounceMs}
shallow={shallow}
/>
<div className="flex items-center gap-2">
<DataTableFilterList
table={table}
filterFields={filterFields}
debounceMs={debounceMs}
shallow={shallow}
/>
<DataTableSortList
table={table}
debounceMs={debounceMs}
shallow={shallow}
/>
</div>
<div className="flex items-center gap-2">
{children}
<DataTableViewOptions table={table} />
Expand Down
Loading

0 comments on commit 73710f9

Please sign in to comment.