/** * 콘텐츠관리 도메인 — 금융쇼츠·금융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: '하' }, ]; /** * 정답여부에 함께 노출할 문구. 선택지는 정답마다 다른 공통코드 그룹에서 온다 — * `QUIZ_ANSWER_O` · `QUIZ_ANSWER_X`(사용자 확정). 「기타」만 코드가 아니라 직접 입력을 * 여는 자리라 프론트가 붙인다. */ export const ANSWER_MESSAGE_ETC = 'ETC'; /** 정답여부 한 줄이 갖는 노출 문구. 정답 선택지마다 하나씩 둔다. */ export type QuizAnswerMessage = { /** 어느 정답에 붙는 문구인지 — `QUIZ_ANSWER` 상세코드의 코드명. */ answer: string; /** 정답별 문구 그룹의 상세코드값, 또는 `ANSWER_MESSAGE_ETC`. */ code: string; /** `code`가 기타일 때만 쓰는 직접 입력 문구. */ text: string; }; export type ContentKeyword = { keyword: string; description: string; }; /** 등록 폼이 처음 보여 주는 키워드 줄 수(사용자 확정 — 종전 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 = { cntntsId: string; /** 만화의 「콘텐츠명」. 쇼츠 목록에는 열이 없다. */ name: string; /** 쇼츠의 「제목」. */ title: string; subject: string; message: string; /** 공통코드 `COM_SCHUL_CD` 코드값. */ schoolCode: string; /** 공통코드 `GRD_CD` 코드값. */ grades: string[]; keywords: ContentKeyword[]; /** 만화의 컷 이미지·쇼츠의 영상이 묶인 첨부 ID. */ attachmentId: string | null; /** 첨부의 원본 파일명. 조회로 연 화면이 붙어 있는 파일을 보여 줄 때 쓴다. */ attachmentFileName: string | null; /** 만화 컷처럼 한 첨부 아래 여러 파일이 있을 때의 파일 번호들. */ cutFileNumbers: number[]; thumbnailFileId: string | null; isVisible: boolean; modifiedAt: string | null; }; /** 금융OX퀴즈 한 건. 백엔드 `MngrQuizVo`. */ export type QuizItem = { quizId: string; /** 백엔드 `keywordList` — 목록의 「키워드」 열이다. 항목마다 설명을 갖는다. */ keywords: ContentKeyword[]; level: string; question: string; explanation: string; answer: string; /** 공통코드 `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; }