refactor: migrate tickets and users pages to use new dashboard list components
- Refactored TicketsPage to utilize DashboardListPage for improved filtering and data fetching. - Removed unnecessary state management and loading logic in favor of the new list hooks. - Updated user management page to leverage useDashboardPagedList for cleaner pagination and filtering. - Enhanced CRUD page to support new action state management and toolbar customization. - Added reset filters functionality to dashboard list components. - Updated translations for new UI elements and loading states.
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
"use client"
|
||||
|
||||
import type { ReactNode } from "react"
|
||||
import { useState } from "react"
|
||||
import { useCallback, useEffect, useState } from "react"
|
||||
import {
|
||||
closestCenter,
|
||||
DndContext,
|
||||
@@ -111,6 +111,12 @@ export type DashboardCrudRowAction<TItem> = DashboardCrudActionRule<TItem> & {
|
||||
run: (context: DashboardCrudRowActionContext<TItem>) => Promise<void> | void
|
||||
}
|
||||
|
||||
export type DashboardCrudActionState = {
|
||||
onRefresh: () => void
|
||||
onCreate: () => void
|
||||
loading: boolean
|
||||
}
|
||||
|
||||
export type DashboardCrudPageProps<TItem, TPayload> = {
|
||||
filters: DashboardCrudFilter[]
|
||||
columns: DashboardCrudColumn<TItem>[]
|
||||
@@ -142,6 +148,7 @@ export type DashboardCrudPageProps<TItem, TPayload> = {
|
||||
getItemId: (item: TItem) => number
|
||||
createItem: (payload: TPayload) => Promise<unknown>
|
||||
updateItem: (item: TItem, payload: TPayload) => Promise<unknown>
|
||||
canEdit?: (item: TItem) => boolean
|
||||
deleteItem?: (item: TItem) => Promise<unknown>
|
||||
canDelete?: (item: TItem) => boolean
|
||||
deleteConfirm?: false | ConfirmOptions | ((item: TItem) => ConfirmOptions)
|
||||
@@ -156,6 +163,11 @@ export type DashboardCrudPageProps<TItem, TPayload> = {
|
||||
handleLabel: string
|
||||
}
|
||||
pageSize?: number
|
||||
layout?: "page" | "fragment"
|
||||
showToolbar?: boolean
|
||||
showToolbarActions?: boolean
|
||||
tableShellClassName?: string
|
||||
onActionStateChange?: (state: DashboardCrudActionState) => void
|
||||
labels: {
|
||||
refresh: string
|
||||
create: string
|
||||
@@ -185,6 +197,7 @@ export function DashboardCrudPage<TItem, TPayload>({
|
||||
getItemId,
|
||||
createItem,
|
||||
updateItem,
|
||||
canEdit,
|
||||
deleteItem,
|
||||
canDelete,
|
||||
deleteConfirm,
|
||||
@@ -192,6 +205,11 @@ export function DashboardCrudPage<TItem, TPayload>({
|
||||
renderRowActions,
|
||||
sort,
|
||||
pageSize = 20,
|
||||
layout = "page",
|
||||
showToolbar = true,
|
||||
showToolbarActions = true,
|
||||
tableShellClassName,
|
||||
onActionStateChange,
|
||||
labels,
|
||||
}: DashboardCrudPageProps<TItem, TPayload>) {
|
||||
const confirm = useConfirm()
|
||||
@@ -231,16 +249,24 @@ export function DashboardCrudPage<TItem, TPayload>({
|
||||
handleApplyFilters()
|
||||
}
|
||||
|
||||
function openCreateDialog() {
|
||||
const openCreateDialog = useCallback(() => {
|
||||
setEditingItem(null)
|
||||
setDialogOpen(true)
|
||||
}
|
||||
}, [])
|
||||
|
||||
function openEditDialog(item: TItem) {
|
||||
setEditingItem(item)
|
||||
setDialogOpen(true)
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
onActionStateChange?.({
|
||||
onRefresh: () => void loadData(),
|
||||
onCreate: openCreateDialog,
|
||||
loading,
|
||||
})
|
||||
}, [loadData, loading, onActionStateChange, openCreateDialog])
|
||||
|
||||
function handleDialogOpenChange(open: boolean) {
|
||||
if (saving) return
|
||||
if (!open) setEditingItem(null)
|
||||
@@ -379,7 +405,12 @@ export function DashboardCrudPage<TItem, TPayload>({
|
||||
))}
|
||||
<TableCell className="text-right">
|
||||
<ButtonGroup className="ml-auto">
|
||||
<Button variant="outline" size="sm" onClick={() => openEditDialog(item)}>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
disabled={canEdit ? !canEdit(item) : false}
|
||||
onClick={() => openEditDialog(item)}
|
||||
>
|
||||
{labels.edit}
|
||||
</Button>
|
||||
{renderRowActions || rowActions.length > 0 || deleteItem ? (
|
||||
@@ -475,21 +506,23 @@ export function DashboardCrudPage<TItem, TPayload>({
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
const content = (
|
||||
<>
|
||||
<DashboardPage>
|
||||
{showToolbar ? (
|
||||
<DashboardToolbar
|
||||
actions={
|
||||
<>
|
||||
<Button variant="outline" onClick={() => void loadData()} disabled={loading}>
|
||||
<RefreshCwIcon className={loading ? "animate-spin" : undefined} />
|
||||
{labels.refresh}
|
||||
</Button>
|
||||
<Button onClick={openCreateDialog}>
|
||||
<PlusIcon />
|
||||
{labels.create}
|
||||
</Button>
|
||||
</>
|
||||
showToolbarActions ? (
|
||||
<>
|
||||
<Button variant="outline" onClick={() => void loadData()} disabled={loading}>
|
||||
<RefreshCwIcon className={loading ? "animate-spin" : undefined} />
|
||||
{labels.refresh}
|
||||
</Button>
|
||||
<Button onClick={openCreateDialog}>
|
||||
<PlusIcon />
|
||||
{labels.create}
|
||||
</Button>
|
||||
</>
|
||||
) : null
|
||||
}
|
||||
>
|
||||
{filters.map((filter) => {
|
||||
@@ -525,86 +558,104 @@ export function DashboardCrudPage<TItem, TPayload>({
|
||||
{labels.query}
|
||||
</Button>
|
||||
</DashboardToolbar>
|
||||
) : null}
|
||||
|
||||
<DashboardTableShell
|
||||
pagination={
|
||||
<ListPagination
|
||||
page={result.page.page}
|
||||
total={result.page.total}
|
||||
limit={list.limit}
|
||||
loading={loading}
|
||||
onPageChange={list.handlePageChange}
|
||||
onLimitChange={list.handleLimitChange}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<Table>
|
||||
<TableHeader className="bg-muted/40">
|
||||
<TableRow>
|
||||
{sortable ? <TableHead className="w-10" /> : null}
|
||||
{columns.map((column) => (
|
||||
<TableHead key={column.key} className={column.className}>
|
||||
{column.label}
|
||||
</TableHead>
|
||||
))}
|
||||
<TableHead className="w-[92px] text-right">
|
||||
{labels.actions}
|
||||
<DashboardTableShell
|
||||
className={tableShellClassName}
|
||||
pagination={
|
||||
<ListPagination
|
||||
page={result.page.page}
|
||||
total={result.page.total}
|
||||
limit={list.limit}
|
||||
loading={loading}
|
||||
onPageChange={list.handlePageChange}
|
||||
onLimitChange={list.handleLimitChange}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<Table>
|
||||
<TableHeader className="bg-muted/40">
|
||||
<TableRow>
|
||||
{sortable ? <TableHead className="w-10" /> : null}
|
||||
{columns.map((column) => (
|
||||
<TableHead key={column.key} className={column.className}>
|
||||
{column.label}
|
||||
</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
{sortable ? (
|
||||
<DndContext
|
||||
sensors={sensors}
|
||||
collisionDetection={closestCenter}
|
||||
onDragEnd={(event) => void handleSortEnd(event)}
|
||||
))}
|
||||
<TableHead className="w-[92px] text-right">
|
||||
{labels.actions}
|
||||
</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
{sortable ? (
|
||||
<DndContext
|
||||
sensors={sensors}
|
||||
collisionDetection={closestCenter}
|
||||
onDragEnd={(event) => void handleSortEnd(event)}
|
||||
>
|
||||
<SortableContext
|
||||
items={result.results.map((item) => String(getItemId(item)))}
|
||||
strategy={verticalListSortingStrategy}
|
||||
>
|
||||
<SortableContext
|
||||
items={result.results.map((item) => String(getItemId(item)))}
|
||||
strategy={verticalListSortingStrategy}
|
||||
>
|
||||
<TableBody>
|
||||
{result.results.map((item) =>
|
||||
renderTableRow(item, {
|
||||
sortable: true,
|
||||
sortDisabled: sort?.disabled,
|
||||
})
|
||||
)}
|
||||
{renderStateRow()}
|
||||
</TableBody>
|
||||
</SortableContext>
|
||||
</DndContext>
|
||||
) : (
|
||||
<TableBody>
|
||||
{result.results.map((item) => renderTableRow(item))}
|
||||
{renderStateRow()}
|
||||
</TableBody>
|
||||
)}
|
||||
</Table>
|
||||
</DashboardTableShell>
|
||||
</DashboardPage>
|
||||
{form ? (
|
||||
<DashboardCrudFormDialog
|
||||
open={dialogOpen}
|
||||
saving={saving}
|
||||
item={editingItem}
|
||||
itemId={editingItem ? getItemId(editingItem) : null}
|
||||
fields={form.fields}
|
||||
fetchDetail={form.fetchDetail}
|
||||
transformSubmitValues={form.transformSubmitValues}
|
||||
labels={form.labels}
|
||||
onOpenChange={handleDialogOpenChange}
|
||||
onSubmit={handleSubmit}
|
||||
/>
|
||||
) : (
|
||||
renderEditDialog?.({
|
||||
open: dialogOpen,
|
||||
saving,
|
||||
item: editingItem,
|
||||
itemId: editingItem ? getItemId(editingItem) : null,
|
||||
onOpenChange: handleDialogOpenChange,
|
||||
onSubmit: handleSubmit,
|
||||
})
|
||||
)}
|
||||
<TableBody>
|
||||
{result.results.map((item) =>
|
||||
renderTableRow(item, {
|
||||
sortable: true,
|
||||
sortDisabled: sort?.disabled,
|
||||
})
|
||||
)}
|
||||
{renderStateRow()}
|
||||
</TableBody>
|
||||
</SortableContext>
|
||||
</DndContext>
|
||||
) : (
|
||||
<TableBody>
|
||||
{result.results.map((item) => renderTableRow(item))}
|
||||
{renderStateRow()}
|
||||
</TableBody>
|
||||
)}
|
||||
</Table>
|
||||
</DashboardTableShell>
|
||||
</>
|
||||
)
|
||||
|
||||
const dialog = form ? (
|
||||
<DashboardCrudFormDialog
|
||||
open={dialogOpen}
|
||||
saving={saving}
|
||||
item={editingItem}
|
||||
itemId={editingItem ? getItemId(editingItem) : null}
|
||||
fields={form.fields}
|
||||
fetchDetail={form.fetchDetail}
|
||||
transformSubmitValues={form.transformSubmitValues}
|
||||
labels={form.labels}
|
||||
onOpenChange={handleDialogOpenChange}
|
||||
onSubmit={handleSubmit}
|
||||
/>
|
||||
) : (
|
||||
renderEditDialog?.({
|
||||
open: dialogOpen,
|
||||
saving,
|
||||
item: editingItem,
|
||||
itemId: editingItem ? getItemId(editingItem) : null,
|
||||
onOpenChange: handleDialogOpenChange,
|
||||
onSubmit: handleSubmit,
|
||||
})
|
||||
)
|
||||
|
||||
if (layout === "fragment") {
|
||||
return (
|
||||
<>
|
||||
{content}
|
||||
{dialog}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<DashboardPage>{content}</DashboardPage>
|
||||
{dialog}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ export {
|
||||
export { useDashboardCrudFilters } from "./use-dashboard-crud-filters"
|
||||
export type {
|
||||
DashboardCrudColumn,
|
||||
DashboardCrudActionState,
|
||||
DashboardCrudDialogProps,
|
||||
DashboardCrudFilter,
|
||||
DashboardCrudPageProps,
|
||||
|
||||
@@ -48,6 +48,11 @@ export function useDashboardCrudFilters(
|
||||
}))
|
||||
}
|
||||
|
||||
function resetFilters() {
|
||||
setDraftFilters(initialFilters)
|
||||
setAppliedFilters(initialFilters)
|
||||
}
|
||||
|
||||
return {
|
||||
draftFilters,
|
||||
appliedFilters,
|
||||
@@ -55,5 +60,6 @@ export function useDashboardCrudFilters(
|
||||
setDraftFilters,
|
||||
applyFilter,
|
||||
applyFilters,
|
||||
resetFilters,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,6 +54,7 @@ export type DashboardListRenderContext<TItem> = {
|
||||
result: DashboardCrudPageResult<TItem>
|
||||
loading: boolean
|
||||
reload: () => Promise<void>
|
||||
resetFilters: () => void
|
||||
}
|
||||
|
||||
export type DashboardListPageProps<TItem> = {
|
||||
@@ -104,6 +105,7 @@ export function DashboardListPage<TItem>({
|
||||
result: list.result,
|
||||
loading: list.loading,
|
||||
reload: list.loadData,
|
||||
resetFilters: list.resetFilters,
|
||||
}
|
||||
|
||||
function handleFilterKeyDown(event: KeyboardEvent<HTMLInputElement>) {
|
||||
|
||||
@@ -6,4 +6,7 @@ export type {
|
||||
DashboardListPageProps,
|
||||
DashboardListRenderContext,
|
||||
} from "./dashboard-list-page"
|
||||
export type { DashboardPagedListOptions } from "./use-dashboard-paged-list"
|
||||
export type {
|
||||
DashboardPagedListFilter,
|
||||
DashboardPagedListOptions,
|
||||
} from "./use-dashboard-paged-list"
|
||||
|
||||
@@ -39,7 +39,14 @@ export function useDashboardPagedList<TItem>({
|
||||
`${filter.name}:${String(filter.defaultValue)}:${String(filter.allValue)}:${filter.trim ? "1" : "0"}:${filter.valueType ?? ""}`
|
||||
)
|
||||
.join("|")
|
||||
const { draftFilters, appliedFilters, setDraftFilter, applyFilter, applyFilters } =
|
||||
const {
|
||||
draftFilters,
|
||||
appliedFilters,
|
||||
setDraftFilter,
|
||||
applyFilter,
|
||||
applyFilters,
|
||||
resetFilters,
|
||||
} =
|
||||
useDashboardCrudFilters(filters)
|
||||
const [page, setPage] = useState(1)
|
||||
const [limit, setLimit] = useState(pageSize)
|
||||
@@ -100,6 +107,11 @@ export function useDashboardPagedList<TItem>({
|
||||
setPage(1)
|
||||
}
|
||||
|
||||
function resetDraftFilters() {
|
||||
resetFilters()
|
||||
setPage(1)
|
||||
}
|
||||
|
||||
return {
|
||||
draftFilters,
|
||||
setDraftFilter,
|
||||
@@ -115,5 +127,6 @@ export function useDashboardPagedList<TItem>({
|
||||
loadData,
|
||||
handlePageChange,
|
||||
handleLimitChange,
|
||||
resetFilters: resetDraftFilters,
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user