import { ErrorBoundary, Suspense, type ErrorBoundaryFallbackProps, } from '@suspensive/react'; import { QueryErrorResetBoundary, useSuspenseQuery, } from '@tanstack/react-query'; import { createColumnHelper, getCoreRowModel, getFilteredRowModel, getPaginationRowModel, getSortedRowModel, useReactTable, } from '@tanstack/react-table'; import { useQueryState } from 'nuqs'; import { postQueries, type Post } from '@/entities/post'; import { Badge } from '@/shared/ui/badge'; import { Button } from '@/shared/ui/button'; import { Card, CardContent } from '@/shared/ui/card'; import { Input } from '@/shared/ui/input'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from '@/shared/ui/select'; import { PostCharts } from './PostCharts'; import { PostListPanel } from './PostListPanel'; import { PostStatsCards } from './PostStatsCards'; import { PostTablePagination } from './PostTablePagination'; import { PostTableView } from './PostTableView'; import { filterByAuthor, PAGE_SIZE, postSearchParsers, toAuthorIds, toAvgBodyLength, toEmptyMessage, toNextSort, toSortingState, } from '../model/postSearchParams'; const columnHelper = createColumnHelper(); const columns = [ columnHelper.accessor('id', { header: 'ID' }), columnHelper.accessor('title', { header: '제목' }), columnHelper.accessor('userId', { header: '작성자', cell: (info) => ( 작성자 {info.getValue()} ), }), columnHelper.accessor('body', { header: '내용' }), ]; export const PostDashboard = () => { return ( {({ reset }) => ( }> )} ); }; const DashboardContent = () => { const { data: posts } = useSuspenseQuery(postQueries.list()); const [keyword, setKeyword] = useQueryState('q', postSearchParsers.keyword); const [author, setAuthor] = useQueryState('author', postSearchParsers.author); const [sort, setSort] = useQueryState('sort', postSearchParsers.sort); const [page, setPage] = useQueryState('page', postSearchParsers.page); const authorPosts = filterByAuthor(posts, author); const table = useReactTable({ data: authorPosts, columns, state: { globalFilter: keyword, sorting: toSortingState(sort), pagination: { pageIndex: page - 1, pageSize: PAGE_SIZE }, }, autoResetPageIndex: false, getCoreRowModel: getCoreRowModel(), getFilteredRowModel: getFilteredRowModel(), getSortedRowModel: getSortedRowModel(), getPaginationRowModel: getPaginationRowModel(), }); const handleKeywordChange = (value: string) => { setKeyword(value); setPage(1); }; const handleAuthorChange = (value: string) => { // oxlint-disable-next-line unicorn/no-null -- '전체'는 URL에서 파라미터 제거(null)로 표현한다 setAuthor(value === 'all' ? null : Number(value)); setPage(1); }; const cycleSort = () => { setSort(toNextSort(sort)); }; const authorIds = toAuthorIds(posts); const filteredPosts = table .getFilteredRowModel() .rows.map((row) => row.original); const filteredCount = filteredPosts.length; const avgLength = toAvgBodyLength(filteredPosts); const pageCount = table.getPageCount(); const emptyMessage = toEmptyMessage(filteredCount); return (
{filteredPosts.length > 0 && }
handleKeywordChange(event.target.value)} placeholder="제목·내용으로 검색해요" aria-label="글 검색" className="sm:max-w-sm" />

총 {filteredCount.toLocaleString()}건

{pageCount > 1 && ( { setPage(next); }} /> )}
); }; const DashboardErrorFallback = ({ error, reset, }: ErrorBoundaryFallbackProps) => { return (

대시보드를 불러오지 못했어요.

{error.message}

); }; const DashboardSkeleton = () => { return (
{Array.from({ length: 3 }, (_, index) => (
))}
{Array.from({ length: 2 }, (_, index) => (
))}
{Array.from({ length: 5 }, (_, index) => (
))}
); };