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 { FoxBadge, type FoxBadgeColor } from '@fox/core/components/fox-badge';
import { FoxIconButton } from '@fox/core/components/fox-icon-button';
import { FoxStatusIndicator } from '@fox/core/components/fox-status-indicator';
import type { FoxListColumn } from '@fox/core/components/fox-list-container';
import type { CommonCode } from '@/lib/domain/common-code';
import { formatPhoneNumber } from '@/lib/domain/phone-number';
import {
EMPTY_FIELD_PLACEHOLDER,
formatAnswerStatusLabel,
IS_BOARD_CATEGORY_ENABLED,
formatBoardCategoryLabel,
formatFaqTypeLabel,
formatContentExcerpt,
formatInquiryTypeLabel,
formatOptionalValue,
formatVisibilityLabel,
type BoardPost,
type BoardType,
} from '@/lib/domain/board-post';
import { BoardRowActions } from './board-row-actions';
import { InquiryRowActions } from '../inquiries/_components/inquiry-row-actions';
function numberColumn(page: number, pageSize: number): FoxListColumn<BoardPost> {
return {
key: 'no',
header: '번호',
width: 80,
render: (_row, index) => (page - 1) * pageSize + index + 1,
};
}
// 백엔드에 구분 자리가 생기면 `IS_BOARD_CATEGORY_ENABLED`가 이 열을 다시 켠다.
function categoryColumn(): FoxListColumn<BoardPost> {
return {
key: 'category',
header: '구분',
width: 120,
render: (row) =>
row.categoryCode ? (
<FoxBadge type="pastel" color="neutral" size="md">
{formatBoardCategoryLabel(row.categoryCode)}
</FoxBadge>
) : (
EMPTY_FIELD_PLACEHOLDER
),
};
}
function visibilityColumn(): FoxListColumn<BoardPost> {
return {
key: 'isVisible',
header: '사용여부',
width: 120,
render: (row) => (
<FoxStatusIndicator
type={row.isVisible ? 'success' : 'danger'}
label={formatVisibilityLabel(row.isVisible)}
/>
),
};
}
function actionsColumn(
boardType: BoardType,
faqTypes: readonly CommonCode[] = []
): FoxListColumn<BoardPost> {
return {
key: 'actions',
header: '관리',
width: 120,
render: (row) => (
<span onClick={(event) => event.stopPropagation()}>
<BoardRowActions boardType={boardType} post={row} faqTypes={faqTypes} />
</span>
),
};
}
function noticeColumns(page: number, pageSize: number): FoxListColumn<BoardPost>[] {
return [
numberColumn(page, pageSize),
...(IS_BOARD_CATEGORY_ENABLED ? [categoryColumn()] : []),
{ key: 'title', header: '제목', width: 320, emphasis: true },
{
key: 'attachment',
header: '첨부',
width: 100,
render: (row) =>
row.attachmentId ? (
<FoxIconButton
type="default"
size="sm"
icon={<i className="fox-ico fox-ico-Paperclip" aria-hidden="true" />}
label={`${row.title} 첨부파일`}
/>
) : null,
},
{
key: 'authorName',
header: '작성자',
width: 160,
render: (row) => formatOptionalValue(row.authorName),
},
visibilityColumn(),
{
key: 'createdAt',
header: '작성일',
width: 140,
render: (row) => formatOptionalValue(row.createdAt),
},
actionsColumn('notice'),
];
}
function faqColumns(
page: number,
pageSize: number,
faqTypes: readonly CommonCode[]
): FoxListColumn<BoardPost>[] {
const typeOptions = faqTypes.map((code) => ({
value: code.code,
label: code.label,
}));
return [
numberColumn(page, pageSize),
...(IS_BOARD_CATEGORY_ENABLED ? [categoryColumn()] : []),
{
key: 'type',
header: '유형',
width: 120,
// 서버가 조인해 준 이름을 그대로 쓴다. 코드표 조회는 이름이 안 온 행의 보루다.
render: (row) => row.typeName ?? formatFaqTypeLabel(row.typeCode, typeOptions),
},
{ key: 'title', header: '제목', width: 320, emphasis: true },
{
key: 'authorName',
header: '작성자',
width: 160,
render: (row) => formatOptionalValue(row.authorName),
},
{
key: 'createdAt',
header: '등록일',
width: 140,
render: (row) => formatOptionalValue(row.createdAt),
},
visibilityColumn(),
actionsColumn('faq', faqTypes),
];
}
// TODO: BBS_ANS_CD의 실제 코드값이 확인되면 코드값 기준 표로 바꾼다(관리자 권한 배지와 같은 방식).
// 이슈: 코드값을 모르고 코드명도 코드관리 화면에서 바뀔 수 있어 코드명으로 색을 고른다.
// 앞에서부터 먼저 걸리는 것을 쓴다 — 「미처리」가 「처리」보다 앞에 있어야 한다.
const ANSWER_STATUS_BADGE_COLORS: readonly (readonly [string, FoxBadgeColor])[] = [
['미답변', 'warning'],
['미처리', 'warning'],
['완료', 'success'],
['반려', 'danger'],
['보류', 'danger'],
['취소', 'danger'],
['처리', 'information'],
['진행', 'information'],
['접수', 'information'],
['대기', 'warning'],
];
function answerStatusBadgeColor(label: string): FoxBadgeColor {
const matched = ANSWER_STATUS_BADGE_COLORS.find(([keyword]) =>
label.includes(keyword)
);
return matched?.[1] ?? 'neutral';
}
function inquiryColumns(
page: number,
pageSize: number,
answerStatuses: readonly CommonCode[]
): FoxListColumn<BoardPost>[] {
const statusOptions = answerStatuses.map((code) => ({
value: code.code,
label: code.label,
}));
return [
numberColumn(page, pageSize),
{
key: 'type',
header: '질문유형',
width: 120,
// 서버 조인 이름 우선 — 코드표(잠정값)는 이름이 안 온 행의 보루다.
render: (row) => row.typeName ?? formatInquiryTypeLabel(row.typeCode),
},
// 1:1문의는 제목이 없다(내용만 온다) — 내용 발췌가 이 줄의 대표 칸이다.
{
key: 'content',
header: '질문내용',
width: 280,
emphasis: true,
render: (row) => formatContentExcerpt(row.content),
},
{
key: 'authorName',
header: '질문자명',
width: 140,
render: (row) => formatOptionalValue(row.authorName),
},
{
key: 'authorPhoneNumber',
header: '전화번호',
width: 160,
render: (row) => formatOptionalValue(formatPhoneNumber(row.authorPhoneNumber)),
},
{
key: 'createdAt',
header: '질문일',
width: 140,
render: (row) => formatOptionalValue(row.createdAt),
},
{
key: 'answerStatus',
header: '상태',
width: 120,
render: (row) => {
if (!row.answerStatusCode) {
return EMPTY_FIELD_PLACEHOLDER;
}
const label = formatAnswerStatusLabel(row.answerStatusCode, statusOptions);
return (
<FoxBadge type="pastel" color={answerStatusBadgeColor(label)} size="md">
{label}
</FoxBadge>
);
},
},
{
key: 'answeredAt',
header: '답변일',
width: 140,
render: (row) => formatOptionalValue(row.answeredAt),
},
{
key: 'actions',
header: '관리',
width: 160,
render: (row) => (
<InquiryRowActions post={row} answerStatuses={answerStatuses} />
),
},
];
}
export function boardPostColumns(
boardType: BoardType,
page: number,
pageSize: number,
answerStatuses: readonly CommonCode[] = [],
faqTypes: readonly CommonCode[] = []
): FoxListColumn<BoardPost>[] {
if (boardType === 'notice') return noticeColumns(page, pageSize);
if (boardType === 'faq') return faqColumns(page, pageSize, faqTypes);
return inquiryColumns(page, pageSize, answerStatuses);
}