o2o-site-AEO/solution/frontend/src/features/dev/mockProps.ts
Mina Choi 23162783f9 [feat] solution/frontend: 붙여넣기 아이템 셋 — 가요 다방·오늘의 한 장·반나절 산책
gunsan_365_story_db.xlsx 365행을 뜯어 보니 고유 주제는 52개이고 한 주제가 7회씩 돈다
(접미사 10개만 회전). 날짜 축으로 카드를 늘어놓으면 이레마다 같은 카드가 돌아온다 —
그래서 묶는 축을 주제로 잡고 날짜는 일력 한 장에만 썼다. 같은 파일 DB_Guide 가 가사·원문
전재를 금지해 가요 스키마에 lyrics 필드를 아예 두지 않았다. 없는 칸은 채울 수 없다.

- canvas/dataSpec.ts: 스키마·예시·프롬프트 단일 표. 레지스트리와 같은 결이라 한 줄을 더하면
  캔버스·[콘텐츠] 탭·프롬프트가 동시에 는다. 파싱은 절대 throw 하지 않는다 — 편집 중인 JSON 은
  늘 깨져 있고 그때 캔버스가 죽으면 고칠 방법이 없다
- shared/types/builder: SectionItem.data 는 파싱본이 아니라 원문 문자열이다. 파싱본만 들면
  JSON 이 깨진 순간 사장님이 쓴 걸 잃는다
- variants/{songs,daily,course}: 턴테이블·일력·승차권. 카드 격자를 쓰지 않고 전부 가로로 넘긴다
- RightTabsPanel: JSON 칸 + 프롬프트 복사/보기·예시 넣기·줄맞춤. 프롬프트에 상호와 주소에서 뽑은
  시·군·구를 박아 내보낸다 — 빈칸을 남기면 못 채우고 그대로 보내고 모델이 엉뚱한 곳을 지어낸다
- dataSpec.locate: JSON.parse 오류가 두 형식이다. position 형만 보면 조각 인용 형에서 위치를
  통째로 잃는다 — 조각을 원문에서 되찾아 센다
- SECTION_DATA_MAX_CHARS: 테마 상한 64KB(site_service._THEME_MAX_BYTES)를 세 섹션이 함께
  넘길 수 있고 거절은 발행 직전에야 드러난다. 화면에서 먼저 끊는다
- industryData: 네 업종에 꺼진 채로 넣는다. 내용 없이 켜져 있으면 발행본에 빈 섹션이 나간다
- index.html: Gugi·Gowun Batang·Nanum Pen Script. 없으면 고딕으로 떨어져 감성이 사라진다

발행 사이트(solution/site)는 아직 variantId 도 data 도 읽지 않는다 — 지금은 빌더 캔버스 전용이다.

tsc·eslint·vite build 통과(frontend·admin), 세 배리에이션 SSR 렌더 확인, 파서 경계 12건 확인
2026-09-01 17:58:58 +09:00

75 lines
3.2 KiB
TypeScript

/**
* 쇼케이스용 목업 props — 배리에이션 하나를 스토어 없이 그리기 위한 최소 입력.
*
* 캔버스(CanvasView)는 빌더 스토어에서 props 를 모으지만, 쇼케이스는 스토어를 건드리면 안 된다 —
* 개발자가 색을 구경하다가 사장님이 편집 중이던 사이트 상태를 바꿔 버리면 곤란하다.
* 그래서 여기서 업종 기본 데이터로 같은 모양의 props 를 직접 만든다.
*/
import type {IndustryType, SectionItem, TemplateItem} from '@o2o/shared';
import {INDUSTRY_CONFIGS} from '@/data/industryData';
import {dataSpecFor} from '@/features/builder/canvas/dataSpec';
import type {SectionRenderProps} from '@/features/builder/canvas/types';
import type {SectionVariant} from '@/features/builder/canvas/types';
import {asTemplate, type TokenGroup} from './tokenGroups';
/** 업종의 첫 템플릿 — 토큰 그룹이 색만 갈아끼울 바탕이 된다. */
export function baseTemplate(industry: IndustryType): TemplateItem {
return INDUSTRY_CONFIGS[industry].templates[0];
}
/**
* 섹션 메타를 만든다.
*
* 업종 기본 섹션 목록에 같은 타입이 있으면 그걸 쓴다(이름·잠금 상태가 실제와 같아진다).
* 없으면 최소 형태로 지어낸다 — 그 업종 메뉴엔 없지만 배리에이션은 존재하는 경우다.
*/
function sectionFor(sectionType: string, industry: IndustryType, variantId: string): SectionItem {
// 붙여넣기 아이템은 JSON 이 없으면 "붙여넣으세요" 안내만 뜬다 — 디자인을 보러 온 화면에선 예시를 얹는다.
const sample = dataSpecFor(sectionType)?.sample;
const found = INDUSTRY_CONFIGS[industry].sections.find((s) => s.type === sectionType);
if (found) return {...found, isEnabled: true, variantId, ...(sample ? {data: sample} : {})};
return {
id: sectionType,
type: sectionType,
name: sectionType,
isLocked: false,
isEnabled: true,
variantId,
...(sample ? {data: sample} : {}),
};
}
/**
* 쇼케이스 전용 자리표시 값.
*
* ★ 실제 업소처럼 보이는 값을 쓰지 않는다. 예전에는 업종 시드('달빛스테이 제주',
* '포레스트 힐 로스터스')를 그대로 썼는데, 그 값들이 캔버스 배리에이션에도
* 폴백으로 새어 사장님 화면에 남의 가게가 떴다. 그래서 시드에서 콘텐츠를 걷어냈고,
* 쇼케이스는 한눈에 가짜인 값을 **여기서만** 들고 간다 — 이 파일은 DEV 라우트
* (`/dev/showcase`, app/router.tsx)에서만 로드된다.
*/
const SHOWCASE_VALUES = {
storeName: '샘플 상호',
location: '샘플 지역',
infoFields: [],
photos: [],
};
/** 배리에이션 하나를 그릴 props 한 벌. */
export function mockPropsFor(
sectionType: string,
variant: SectionVariant,
industry: IndustryType,
group: TokenGroup,
): SectionRenderProps {
return {
section: sectionFor(sectionType, industry, variant.id),
industryId: industry,
...SHOWCASE_VALUES,
template: asTemplate(group, baseTemplate(industry)),
// 쇼케이스는 "고르는" 화면이 아니다 — 선택 링·배지가 뜨면 디자인을 가린다.
isSelected: false,
onSelect: () => {},
};
}