Skip to content

Commit

Permalink
feat: add the ability to save visible columns in views
Browse files Browse the repository at this point in the history
  • Loading branch information
nainglinnkhant committed Jul 7, 2024
1 parent 953f5ee commit 7017623
Show file tree
Hide file tree
Showing 6 changed files with 136 additions and 15 deletions.
42 changes: 42 additions & 0 deletions src/app/_components/table-instance-provider.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
"use client"

import { createContext, useContext, useState } from "react"
import type { Table } from "@tanstack/react-table"

// eslint-disable-next-line @typescript-eslint/no-explicit-any
interface TableInstanceContextProps<T = any> {
tableInstance: Table<T>
setTableInstance: React.Dispatch<React.SetStateAction<Table<T>>>
}

const TableInstanceContext = createContext<TableInstanceContextProps>({
// eslint-disable-next-line @typescript-eslint/no-explicit-any
tableInstance: {} as Table<any>,
setTableInstance: () => {},
})

export function useTableInstanceContext() {
const context = useContext(TableInstanceContext)
if (!context) {
throw new Error(
"useTableInstanceContext must be used within a TableInstanceProvider"
)
}
return context
}

export function TableInstanceProvider<T>({
table,
children,
}: {
table: Table<T>
children: React.ReactNode
}) {
const [tableInstance, setTableInstance] = useState<Table<T>>(table)

return (
<TableInstanceContext.Provider value={{ tableInstance, setTableInstance }}>
{children}
</TableInstanceContext.Provider>
)
}
23 changes: 13 additions & 10 deletions src/app/_components/tasks-table.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { DataTable } from "@/components/data-table/data-table"

import type { getTasks, getViews } from "../_lib/queries"
import { getPriorityIcon, getStatusIcon } from "../_lib/utils"
import { TableInstanceProvider } from "./table-instance-provider"
import { getColumns } from "./tasks-table-columns"
import { TasksTableFloatingBar } from "./tasks-table-floating-bar"
import { TasksTableToolbarActions } from "./tasks-table-toolbar-actions"
Expand Down Expand Up @@ -76,17 +77,19 @@ export function TasksTable({ tasksPromise, viewsPromise }: TasksTableProps) {
})

return (
<DataTable
table={table}
floatingBar={<TasksTableFloatingBar table={table} />}
>
<DataTableAdvancedToolbar
<TableInstanceProvider table={table}>
<DataTable
table={table}
filterFields={filterFields}
views={views}
floatingBar={<TasksTableFloatingBar table={table} />}
>
<TasksTableToolbarActions table={table} />
</DataTableAdvancedToolbar>
</DataTable>
<DataTableAdvancedToolbar
table={table}
filterFields={filterFields}
views={views}
>
<TasksTableToolbarActions table={table} />
</DataTableAdvancedToolbar>
</DataTable>
</TableInstanceProvider>
)
}
52 changes: 47 additions & 5 deletions src/components/data-table/advanced/data-table-advanced-toolbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import UpdateViewForm from "./views/update-view-form"
import {
calcFilterParams,
calcViewSearchParamsURL,
COLUMNS,
getIsFiltered,
} from "./views/utils"

Expand Down Expand Up @@ -99,13 +100,26 @@ export function DataTableAdvancedToolbar<TData>({

const isFiltered = getIsFiltered(searchParams)

const currentView = views.find(
(view) => view.id === searchParams.get("viewId")
)
const viewId = searchParams.get("viewId")
const currentView = views.find((view) => view.id === viewId)

const columns = table
.getVisibleFlatColumns()
.filter(
(column) =>
typeof column.accessorFn !== "undefined" && column.getCanHide()
)
.map((column) => column.id)
const filterParams = calcFilterParams(selectedOptions, searchParams)

const isUpdated = !isEqual(currentView?.filterParams, filterParams)
const isColumnsUpdated = currentView
? !isEqual(currentView?.columns, columns)
: !isEqual(COLUMNS, columns)

const isDefaultViewUpdated = isFiltered || isColumnsUpdated

const isUpdated =
!isEqual(currentView?.filterParams, filterParams) || isColumnsUpdated

function resetToCurrentView() {
if (!currentView) return
Expand All @@ -114,6 +128,34 @@ export function DataTableAdvancedToolbar<TData>({
router.push(`${pathname}?${searchParamsURL}`)
}

React.useEffect(() => {
if (isColumnsUpdated) {
setOpenFilterBuilder(true)
}
}, [isColumnsUpdated])

// Update visible columns when viewId is changed
React.useEffect(() => {
if (!currentView) {
return table.setColumnVisibility({})
}

table.setColumnVisibility(() => {
const invisibleColumns = COLUMNS.filter(
(column) => !currentView.columns?.includes(column)
)
const newVisibilityState = invisibleColumns.reduce(
(acc, item) => {
acc[item] = false
return acc
},
{} as Partial<Record<(typeof COLUMNS)[number], boolean>>
)
return newVisibilityState
})
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [viewId])

// Update table state when search params are changed
React.useEffect(() => {
const searchParamsObj = Object.fromEntries(searchParams)
Expand Down Expand Up @@ -232,7 +274,7 @@ export function DataTableAdvancedToolbar<TData>({
</Button>
)}

{isFiltered && !currentView && (
{isDefaultViewUpdated && !currentView && (
<CreateViewPopover selectedOptions={selectedOptions} />
)}

Expand Down
16 changes: 16 additions & 0 deletions src/components/data-table/advanced/views/create-view-form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { Button } from "@/components/ui/button"
import { Input } from "@/components/ui/input"
import { Separator } from "@/components/ui/separator"
import { LoaderIcon } from "@/components/loader-icon"
import { useTableInstanceContext } from "@/app/_components/table-instance-provider"
import { createView } from "@/app/_lib/actions"
import type { FilterParams } from "@/app/_lib/validations"

Expand All @@ -31,10 +32,20 @@ export function CreateViewForm({

const nameInputRef = useRef<HTMLInputElement>(null)

const { tableInstance } = useTableInstanceContext()

const [state, formAction] = useFormState(createView, {
message: "",
})

const visibleColumns = tableInstance
.getVisibleFlatColumns()
.filter(
(column) =>
typeof column.accessorFn !== "undefined" && column.getCanHide()
)
.map((column) => column.id)

useEffect(() => {
nameInputRef.current?.focus()
}, [])
Expand Down Expand Up @@ -76,6 +87,11 @@ export function CreateViewForm({
)}

<form action={formAction} className="flex flex-col gap-2 p-2">
<input
type="hidden"
name="columns"
value={JSON.stringify(visibleColumns)}
/>
<input
type="hidden"
name="filterParams"
Expand Down
16 changes: 16 additions & 0 deletions src/components/data-table/advanced/views/update-view-form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { toast } from "sonner"

import { Button } from "@/components/ui/button"
import { LoaderIcon } from "@/components/loader-icon"
import { useTableInstanceContext } from "@/app/_components/table-instance-provider"
import { editView } from "@/app/_lib/actions"
import type { FilterParams } from "@/app/_lib/validations"

Expand All @@ -20,6 +21,16 @@ export default function UpdateViewForm({
}: UpdateViewFormProps) {
const [state, formAction] = useFormState(editView, { message: "" })

const { tableInstance } = useTableInstanceContext()

const visibleColumns = tableInstance
.getVisibleFlatColumns()
.filter(
(column) =>
typeof column.accessorFn !== "undefined" && column.getCanHide()
)
.map((column) => column.id)

useEffect(() => {
if (state.status === "success") {
toast.success(state.message)
Expand All @@ -32,6 +43,11 @@ export default function UpdateViewForm({
<form action={formAction}>
<input type="hidden" name="id" value={currentView.id} />
<input type="hidden" name="name" value={currentView.name} />
<input
type="hidden"
name="columns"
value={JSON.stringify(visibleColumns)}
/>
<input
type="hidden"
name="filterParams"
Expand Down
2 changes: 2 additions & 0 deletions src/components/data-table/advanced/views/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ export const FILTERABLE_FIELDS: (keyof SearchParams)[] = [
"operator",
]

export const COLUMNS = ["title", "status", "priority", "createdAt"] as const

export function calcFilterParams<T = unknown>(
selectedOptions: DataTableFilterOption<T>[],
searchParams: ReadonlyURLSearchParams
Expand Down

0 comments on commit 7017623

Please sign in to comment.