import { useForm, Controller } from 'react-hook-form'; import { zodResolver } from '@hookform/resolvers/zod'; import { z } from 'zod'; import type { Descendant } from 'slate'; import { showToast } from '@/lib/notify'; import { Typography } from '@/components/ui/typography'; import { Button } from '@/components/ui/button'; import { Input } from '@/components/ui/input'; import { Sheet } from '@/components/ui/sheet'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'; import { type NegotiationCard, type CardTab, generateCardCode } from '../types'; import { CardUsageType } from '@/api/generated/model'; import { CARD_USAGE_TYPE_LABEL, CARD_USAGE_TYPE_OPTIONS } from '@/lib/enumLabels'; import type { CardInput } from '../hooks/useCards'; import { CardScriptEditor, deserialize, serializeToText } from '../editor'; const schema = z.object({ isWildcard: z.boolean(), isShared: z.boolean(), usageType: z.number(), code: z.string().trim().min(1, '카드번호는 필수 기입 사항입니다.'), title: z.string().trim().min(1, '카드이름은 필수 기입 사항입니다.'), editorScript: z .custom((v) => Array.isArray(v)) .refine((v) => serializeToText(v).trim().length > 0, '스크립트를 작성해 주십시오.'), status: z.enum(['ACTIVE', 'INACTIVE']), triggerCondition: z.string(), memo: z.string(), }); type FormValues = z.infer; type CardFormSheetProps = { open: boolean; mode: 'create' | 'edit'; card: NegotiationCard | null; // edit 모드 초기값 출처 activeTab: CardTab; // create 시 기본 카드 종류 결정 onCreate: (input: CardInput) => Promise; onUpdate: (id: string, input: CardInput) => Promise; onDelete: (id: string, title: string) => void; onClose: () => void; }; function buildDefaults( mode: 'create' | 'edit', card: NegotiationCard | null, activeTab: CardTab, ): FormValues { if (mode === 'edit' && card) { return { isWildcard: card.isWildcard, isShared: card.isShared, usageType: card.usageType, code: card.code, title: card.title, // 저장된 Slate JSON 우선, 없으면 레거시 평문 script 를 변수 노드로 복원. editorScript: deserialize(card.editorScript, card.scriptPreview), status: card.status, triggerCondition: card.triggerCondition || '', memo: card.memo || '', }; } const wild = activeTab === 'WILD'; return { isWildcard: wild, isShared: false, // 기본: 개인(나만) — 전체 공용은 등록 시 명시 선택 usageType: CardUsageType.COMMON, // 기본: 공통(신규·재 모두) code: generateCardCode(wild), title: '', editorScript: deserialize(), // 새 빈 값(공용 상수 mutate 방지) status: 'ACTIVE', triggerCondition: '', memo: '', }; } // 협상카드/와일드카드 등록·수정 우측 드로어 폼. react-hook-form + zod로 검증. // 상품·협력사 폼과 동일하게 공통 Sheet 셸을 쓴다(우측 슬라이드). // 페이지는 key={mode + card?.id}로 리마운트시켜 초기값을 주입한다. export function CardFormSheet({ open, mode, card, activeTab, onCreate, onUpdate, onDelete, onClose, }: CardFormSheetProps) { const { register, control, handleSubmit, watch, setValue, formState: { errors, isSubmitting }, } = useForm({ resolver: zodResolver(schema), defaultValues: buildDefaults(mode, card, activeTab), }); const isWildcard = watch('isWildcard'); const onValid = async (v: FormValues) => { const input: CardInput = { title: v.title, code: v.code, editorScript: v.editorScript, status: v.status, isWildcard: v.isWildcard, isShared: v.isShared, usageType: v.usageType, triggerCondition: v.triggerCondition, memo: v.memo, }; const kind = v.isWildcard ? '와일드카드' : '협상카드'; try { if (mode === 'create') { await onCreate(input); showToast(`${kind}가 추가되었습니다.`, 'success'); } else if (card) { await onUpdate(card.id, input); showToast('정보가 수정되었습니다.', 'success'); } onClose(); } catch (err) { showToast(err instanceof Error ? err.message : `${kind} 저장 실패`, 'error'); } }; const title = mode === 'create' ? isWildcard ? '신규 와일드카드 등록' : '신규 협상카드 등록' : isWildcard ? '와일드카드 정보 수정' : '협상카드 정보 수정'; return (
{/* Card Type Selection */}
카드 종류 ( )} />
{/* Code */}
카드번호 {errors.code &&

{errors.code.message}

}
{/* 작성자(등록자) — 읽기전용, 편집 시에만. 공용 카드는 작성자 없음. */} {mode === 'edit' && (
작성자 {card?.isShared ? '공용' : card?.creatorName ?? '-'}
)} {/* Status */}
협상 적용 여부 {isWildcard ? ( (
{field.value === 'ACTIVE' ? '적용 승인' : '적용 대기'}
)} /> ) : (
상시 항상 적용
)}
{/* 공개 범위(scope): 개인=나만 / 전체=모두 공용(user_id NULL). 등록 시에만 결정, 수정 시 고정 */}
공개 범위 ( )} />
{/* 카드 용도(usage_type): 공통 / 신규전용 / 재전용 */}
카드 용도 ( )} />
{/* Title */}
카드이름 {errors.title &&

{errors.title.message}

}
{/* Script */}
스크립트 ( )} /> {errors.editorScript && (

{errors.editorScript.message as string}

)}
{/* Wildcard-only fields */} {isWildcard && (
와일드카드 세부조항
사용 조건
메모
)} {/* Footer — 다른 폼 sheet와 동일: 좌측 삭제(edit), 우측 취소/저장 */}
{mode === 'edit' && card && ( )}
); }