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 type { ReactNode } from 'react';
import { FoxButton } from '@fox/core/components/fox-button';
import { FoxButtonGroup } from '@fox/core/components/fox-button-group';
import { FoxSelectText } from '@fox/core/components/fox-select-text';
import {
FoxListContainer,
type FoxListColumn,
} from '@fox/core/components/fox-list-container';
import { CONTENT_KIND_LABEL, type ContentKind } from '@/lib/domain/content';
import {
CONTENT_PAGE_SIZE_OPTIONS,
CONTENT_SEARCH_FIELDS,
CONTENT_SORT_OPTIONS,
buildContentHref,
contentBreadcrumb,
contentCreatePath,
type ContentPageSize,
type ContentQuery,
type ContentSortOption,
} from '@/lib/domain/content-query';
import { ContentListFilters } from './content-list-filters';
import type { CommonCode } from '@/lib/domain/common-code';
import type { GradesBySchool } from '@/lib/domain/content';
interface ContentListShellProps<T> {
kind: ContentKind;
columns: FoxListColumn<T>[];
rows: T[];
rowKey: (row: T) => string;
query: ContentQuery;
currentPage: number;
totalPages: number;
totalCount: number;
schools: readonly CommonCode[];
gradesBySchool: GradesBySchool;
/** 툴바 오른쪽에 덧붙일 것(금융OX퀴즈의 엑셀 버튼). */
extraActions?: ReactNode;
}
/** 콘텐츠관리 목록 3종이 공유하는 껍데기 — 열만 화면마다 다르다. */
export function ContentListShell<T>({
kind,
columns,
rows,
rowKey,
query,
currentPage,
totalPages,
totalCount,
schools,
gradesBySchool,
extraActions,
}: ContentListShellProps<T>) {
const router = useRouter();
function go(patch: Partial<ContentQuery>) {
router.push(buildContentHref(kind, query, patch));
}
return (
<FoxListContainer<T>
title={`콘텐츠 목록 (${CONTENT_KIND_LABEL[kind]})`}
breadcrumb={contentBreadcrumb(kind)}
caption={`${CONTENT_KIND_LABEL[kind]} 콘텐츠 목록`}
columns={columns}
rows={rows}
rowKey={rowKey}
totalCount={totalCount}
emptyMessage="등록된 콘텐츠가 없습니다."
filter={
<ContentListFilters
query={query}
schools={schools}
gradesBySchool={gradesBySchool}
// 백엔드가 searchLevelCd를 받기만 하고 조건에 쓰지 않아, 골라도 목록이 그대로다.
// SQL에 조건이 생기면 `kind === 'quiz'`로 되돌린다.
withLevel={false}
onChange={go}
/>
}
sorts={
<>
<FoxSelectText
size="sm"
ariaLabel="정렬"
icon={<i className="fox-ico fox-ico-ArrowsDownUp" aria-hidden="true" />}
options={CONTENT_SORT_OPTIONS.map((option) => ({
value: option.value,
label: option.label,
}))}
value={query.sort}
onValueChange={(value) => go({ sort: value as ContentSortOption })}
/>
<FoxSelectText
size="sm"
ariaLabel="페이지 크기"
options={CONTENT_PAGE_SIZE_OPTIONS.map((size) => ({
value: String(size),
label: `${size}개씩 보기`,
}))}
value={String(query.pageSize)}
onValueChange={(value) =>
go({ pageSize: Number(value) as ContentPageSize, page: 1 })
}
/>
</>
}
searchHidden={CONTENT_SEARCH_FIELDS[kind].length === 0}
searchFields={CONTENT_SEARCH_FIELDS[kind].map((field) => ({
value: field.value,
label: field.label,
}))}
searchField={query.searchField}
keyword={query.keyword}
searchPlaceholder="검색어를 입력하세요."
toolbarActions={
<FoxButtonGroup size="md">
{extraActions}
<FoxButton
type="primary"
size="md"
leadingIcon={<i className="fox-ico fox-ico-Plus" aria-hidden="true" />}
label="등록"
onAction={() => router.push(contentCreatePath(kind))}
/>
</FoxButtonGroup>
}
page={currentPage}
totalPages={totalPages}
buildHref={(patch) => buildContentHref(kind, query, patch as Partial<ContentQuery>)}
onQueryChange={go}
/>
);
}