/** * 콘텐츠관리 도메인 — 금융쇼츠·금융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 = { 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; 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; }; /** 등록 폼이 처음 보여 주는 키워드 줄 수 — 기획 "기본 2줄 제공". */ export const DEFAULT_KEYWORD_ROWS = 2; /** 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; }