File name
Commit message
Commit date
File name
Commit message
Commit date
File name
Commit message
Commit date
File name
Commit message
Commit date
File name
Commit message
Commit date
File name
Commit message
Commit date
'use client';
import { useRouter } from 'next/navigation';
import { useState, type ReactNode } from 'react';
import { useBlockingAction } from '@/app/_hooks/use-blocking-action';
import { FoxButton } from '@fox/core/components/fox-button';
import { FoxIconButton } from '@fox/core/components/fox-icon-button';
import { FoxListContainer } from '@fox/core/components/fox-list-container';
import { FoxSelectText } from '@fox/core/components/fox-select-text';
import { FoxChip } from '@fox/core/components/fox-chip';
import { FoxChipArea } from '@fox/core/components/fox-chip-area';
import type { BoardPost, BoardType } from '@/lib/domain/board-post';
import type { CommonCode } from '@/lib/domain/common-code';
import {
BOARD_PAGE_SIZE_OPTIONS,
BOARD_SEARCH_FIELDS,
FILTER_ALL,
VISIBILITY_FILTER_OPTIONS,
buildBoardPostHref,
type BoardPageSize,
type BoardPostQuery,
} from '@/lib/domain/board-post-query';
import { BoardPostFormModal } from './board-post-form-modal';
import styles from './board-post-list.module.scss';
import { boardPostColumns } from './board-post-columns';
interface BoardPostListProps {
boardType: BoardType;
title: string;
breadcrumb: { label: string; href?: string }[];
items: BoardPost[];
query: BoardPostQuery;
currentPage: number;
totalPages: number;
totalCount: number;
/** 1:1문의처럼 관리자가 글을 쓰지 않는 게시판은 끈다. */
canCreate?: boolean;
/** 행의 "관리" 열을 화면마다 다르게 그려야 할 때. */
extraToolbar?: ReactNode;
/** 공통코드 `BBS_ANS_CD` — 1:1문의 진행상태 표기·선택지. */
answerStatuses?: readonly CommonCode[];
/** 공통코드 `BBS_FAQ_CD` — FAQ의 유형. */
faqTypes?: readonly CommonCode[];
}
const SORT_OPTIONS = [{ value: 'recent', label: '최근등록순' }];
export function BoardPostList({
boardType,
title,
breadcrumb,
items,
query,
currentPage,
totalPages,
totalCount,
canCreate = true,
extraToolbar,
answerStatuses,
faqTypes,
}: BoardPostListProps) {
const router = useRouter();
const [isCreateOpen, setIsCreateOpen] = useState(false);
const [, startRefresh] = useBlockingAction();
function go(patch: Partial<BoardPostQuery>) {
router.replace(buildBoardPostHref(boardType, query, { ...patch, page: 1 }));
}
return (
<>
<FoxListContainer<BoardPost>
title={title}
breadcrumb={breadcrumb}
caption={title}
columns={boardPostColumns(
boardType,
currentPage,
query.pageSize,
answerStatuses,
faqTypes
)}
rows={items}
rowKey={(row) => row.id}
totalCount={totalCount}
emptyMessage="검색 결과가 없습니다. 다른 검색어로 다시 시도해 주세요."
sorts={
<>
<FoxSelectText
size="sm"
ariaLabel="정렬"
options={SORT_OPTIONS}
value="recent"
/>
<FoxSelectText
size="sm"
ariaLabel="페이지 크기"
options={BOARD_PAGE_SIZE_OPTIONS.map((size) => ({
value: String(size),
label: `${size}개씩 보기`,
}))}
value={String(query.pageSize)}
onValueChange={(value) =>
go({ pageSize: Number(value) as BoardPageSize })
}
/>
</>
}
searchHidden={BOARD_SEARCH_FIELDS[boardType].length === 0}
searchFields={BOARD_SEARCH_FIELDS[boardType].map((field) => ({
value: field.value,
label: field.label,
}))}
searchField={query.searchField}
keyword={query.keyword}
toolbarActions={
canCreate ? (
<FoxButton
type="primary"
size="md"
leadingIcon={<i className="fox-ico fox-ico-Plus" aria-hidden="true" />}
label="신규 등록"
onAction={() => setIsCreateOpen(true)}
/>
) : (
extraToolbar
)
}
filter={
<div className={styles.filter}>
<FoxIconButton
type="default"
size="sm"
icon={<i className="fox-ico fox-ico-ArrowsClockwise" aria-hidden="true" />}
label="목록 새로고침"
onAction={() => startRefresh(() => router.refresh())}
/>
<FoxChipArea size="md" label="노출 여부">
{[
{ value: FILTER_ALL, label: '전체' },
...VISIBILITY_FILTER_OPTIONS,
].map((option) => (
<FoxChip
key={option.value || 'all'}
type="action"
label={option.label}
checked={query.visibility === option.value}
onCheckedChange={() => go({ visibility: option.value })}
/>
))}
</FoxChipArea>
</div>
}
page={currentPage}
totalPages={totalPages}
buildHref={(patch) =>
buildBoardPostHref(boardType, query, { page: Number(patch.page ?? 1) })
}
onQueryChange={(patch) => {
if (patch.keyword !== undefined || patch.searchField !== undefined) {
go({
keyword: String(patch.keyword ?? ''),
searchField: String(patch.searchField ?? query.searchField),
});
}
}}
/>
{isCreateOpen && (
<BoardPostFormModal
boardType={boardType}
mode="create"
faqTypes={faqTypes}
onClose={() => setIsCreateOpen(false)}
/>
)}
</>
);
}