File name
Commit message
Commit date
File name
Commit message
Commit date
09-04
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)}
/>
)}
</>
);
}