File name
Commit message
Commit date
File name
Commit message
Commit date
File name
Commit message
Commit date
/**
* 콘텐츠관리 도메인 — 금융쇼츠·금융OX퀴즈·4컷금융만화가 공유하는 값과 표기 규칙.
*
* 게시판(`bbs`)과 무관하다. 세 종류는 백엔드 API가 각각 따로 있고 저장되는 테이블도
* 쇼츠·만화(`TB_COM_CNTNTS`)와 퀴즈(`TB_COM_QUIZ`)로 갈린다.
*/
import {
EMPTY_FIELD_PLACEHOLDER,
formatCommonCode,
type CommonCode,
} from '@/lib/domain/common-code';
export const CONTENT_KINDS = ['shorts', 'quiz', 'comic'] as const;
export type ContentKind = (typeof CONTENT_KINDS)[number];
export const CONTENT_KIND_LABEL: Record<ContentKind, string> = {
shorts: '금융쇼츠',
quiz: '금융OX퀴즈',
comic: '4컷금융만화',
};
// TODO: `TB_COM_CNTNTS.CNTNTS_TYPE` — 만화 'T'만 백엔드 매퍼에서 확인했다. 쇼츠는 미확인 잠정값.
export const CONTENT_TYPE_CODE: Record<'shorts' | 'comic', string> = {
shorts: 'V',
comic: 'T',
};
/** 학교 코드 → 그 학교의 학년 목록. 학년이 없는 학교(전체연령 등)는 빈 배열이다. */
export type GradesBySchool = Record<string, readonly CommonCode[]>;
export function gradesOf(
gradesBySchool: GradesBySchool,
schoolCode: string
): readonly CommonCode[] {
return gradesBySchool[schoolCode] ?? [];
}
/** 난이도 — 백엔드 `MngrQuizVo.quizLev` 주석(1:하, 2:중, 3:상)에서 확인한 실제 코드값. */
export const QUIZ_LEVEL_OPTIONS: ReadonlyArray<{ value: string; label: string }> = [
{ value: '3', label: '상' },
{ value: '2', label: '중' },
{ value: '1', label: '하' },
];
export const QUIZ_ANSWER_OPTIONS = ['O', 'X'] as const;
export type QuizAnswer = (typeof QUIZ_ANSWER_OPTIONS)[number];
/** 정답여부에 함께 노출할 문구. 기타를 고르면 직접 입력한다. */
export const ANSWER_MESSAGE_ETC = 'ETC';
// TODO: 기획이 "추후 정의"로 남긴 기본 3종이다. 확정되면 이 표만 교체한다.
export const ANSWER_MESSAGE_OPTIONS: ReadonlyArray<{ value: string; label: string }> = [
{ value: 'MSG1', label: '사용자에게 노출할 문구1' },
{ value: 'MSG2', label: '사용자에게 노출할 문구2' },
{ value: 'MSG3', label: '사용자에게 노출할 문구3' },
{ value: ANSWER_MESSAGE_ETC, label: '기타' },
];
export type ContentKeyword = {
keyword: string;
description: string;
};
/**
* 퀴즈의 `quizTitle`을 키워드 줄로 되돌린다.
*
* 저장할 때 키워드를 쉼표로 이어 한 칸에 넣으므로 **설명은 남지 않는다**(사용자 확정).
* 그래서 수정 화면을 열면 키워드만 채워지고 설명은 빈 칸이며, 그대로 저장하면 설명은 계속
* 비어 있다. 백엔드가 키워드 목록을 받게 되면 이 함수는 필요 없어진다.
*/
export function keywordsFromText(text: string): ContentKeyword[] {
return text
.split(',')
.map((part) => part.trim())
.filter(Boolean)
.map((keyword) => ({ keyword, description: '' }));
}
/** 등록 폼이 처음 보여 주는 키워드 줄 수(사용자 확정 — 종전 2줄에서 1줄로). */
export const DEFAULT_KEYWORD_ROWS = 1;
/** 4컷금융만화의 최소 컷 수 — 기획 "기본 4컷 제공, 4컷 이상 구성도 가능". */
export const MIN_COMIC_CUTS = 4;
export const THUMBNAIL_HELP_TEXT = '권장 사이즈 : 480x270px';
/** 쇼츠·만화 한 건. 백엔드 `MngrCntntsToonVo`(= `CmmCntntsVo`)를 화면 언어로 옮긴 것. */
export type ContentItem = {
cntntsSn: string;
/** 만화의 「콘텐츠명」. 쇼츠 목록에는 열이 없다. */
name: string;
/** 쇼츠의 「제목」. */
title: string;
subject: string;
message: string;
/** 공통코드 `COM_SCHUL_CD` 코드값. */
schoolCode: string;
/** 공통코드 `GRD_CD` 코드값. */
grades: string[];
keywords: ContentKeyword[];
/** 만화의 컷 이미지·쇼츠의 영상이 묶인 첨부 ID. */
attachmentId: string | null;
/** 만화 컷처럼 한 첨부 아래 여러 파일이 있을 때의 파일 번호들. */
cutFileNumbers: number[];
thumbnailFileId: string | null;
isVisible: boolean;
modifiedAt: string | null;
};
/** 금융OX퀴즈 한 건. 백엔드 `MngrQuizVo`. */
export type QuizItem = {
quizSn: string;
/** 백엔드 `quizTitle` — 목록의 「키워드」 열이다(제목이 아니다). */
keywordText: string;
level: string;
question: string;
explanation: string;
answer: string;
points: number | null;
/** 공통코드 `COM_SCHUL_CD` 코드값. */
schoolCode: string;
/** 공통코드 `GRD_CD` 코드값. */
grades: string[];
isVisible: boolean;
modifiedAt: string | null;
};
export function formatSchoolLabel(
code: string,
schools: readonly CommonCode[]
): string {
return code ? formatCommonCode(schools, code) : EMPTY_FIELD_PLACEHOLDER;
}
/** 학년 코드값 목록 → 코드명. 표에 없는 코드는 지어내지 않고 코드 그대로 보여준다. */
export function formatGradesLabel(
grades: readonly string[],
gradeCodes: readonly CommonCode[]
): string {
if (grades.length === 0) {
return EMPTY_FIELD_PLACEHOLDER;
}
return grades.map((grade) => formatCommonCode(gradeCodes, grade)).join(', ');
}
export function formatKeywordsLabel(keywords: readonly ContentKeyword[]): string {
const names = keywords.map((item) => item.keyword).filter(Boolean);
return names.length > 0 ? names.join(', ') : EMPTY_FIELD_PLACEHOLDER;
}
export function formatQuizLevelLabel(value: string): string {
return QUIZ_LEVEL_OPTIONS.find((option) => option.value === value)?.label ?? value;
}
/** 목록의 「비공개」 열 — 기획 라벨 그대로다(값은 공개/비공개). */
export function formatVisibilityLabel(isVisible: boolean): string {
return isVisible ? '공개' : '비공개';
}
export function formatContentText(value: string | null | undefined): string {
return value === null || value === undefined || value === ''
? EMPTY_FIELD_PLACEHOLDER
: value;
}