'use client'; import { useActionState, useEffect, useRef, useState, useTransition, } from 'react'; import { FoxButton } from '@fox/core/components/fox-button'; import { FoxFormLabel } from '@fox/core/components/fox-form-label'; import { FoxHelperText } from '@fox/core/components/fox-helper-text'; import { FoxInput } from '@fox/core/components/fox-input'; import { FoxModal } from '@fox/core/components/fox-modal'; import { FoxRadio } from '@fox/core/components/fox-radio'; import { FoxRadioGroup } from '@fox/core/components/fox-radio-group'; import { FoxSelect } from '@fox/core/components/fox-select'; import { submitFormAction } from '@/app/_hooks/submit-form-action'; import { useFeedback } from '@/app/_hooks/use-feedback'; import { DEFAULT_MERCHANT_DIVISION_CODE, formatOptionalValue, type Merchant, type MerchantCodeTables, } from '@/lib/domain/merchant'; import { ADDRESS_DETAIL_MAX_LENGTH, INITIAL_MERCHANT_FORM_STATE, MEMO_MAX_LENGTH, NAME_MAX_LENGTH, ACCOUNT_NUMBER_MAX_LENGTH, OWNER_MAX_LENGTH, } from '@/lib/domain/merchant-form'; import { saveMerchantAction } from '../_actions'; import { openPostcodeSearch } from '@/app/_components/postcode'; import styles from './merchant-form.module.scss'; interface MerchantFormModalProps { merchant?: Merchant; /** 업종·업태·구분·은행 코드표 — 선택지. */ codeTables: MerchantCodeTables; onClose: () => void; } /** * 가맹몰(가맹점) 등록·수정 모달 — 기획 MCH_002_p·MCH_003_p. 모든 항목을 고칠 수 있다(사용자 확정). * 요청은 백엔드 `MngrVocInsertReqVo`/`MngrVocUpdateReqVo`대로 보낸다 — 지금은 BizPlay(웹캐시)로 * 위임되고 곧 우리 DB 저장으로 바뀐다. */ export function MerchantFormModal({ merchant, codeTables, onClose }: MerchantFormModalProps) { const isEdit = Boolean(merchant); const { showToast } = useFeedback(); const formRef = useRef(null); const [state, formAction, isPending] = useActionState( saveMerchantAction, INITIAL_MERCHANT_FORM_STATE ); const [categoryCode, setCategoryCode] = useState(merchant?.categoryCode ?? ''); const [businessStatusCode, setBusinessStatusCode] = useState(merchant?.businessStatusCode ?? ''); const [divisionCode, setDivisionCode] = useState( merchant?.divisionCode || DEFAULT_MERCHANT_DIVISION_CODE ); const [bankCode, setBankCode] = useState(''); // 조회 응답에 useYn이 없어 기존 가맹점은 값을 모른다 — 그때는 어느 쪽도 고르지 않는다. // 신규 등록만 「노출」로 시작한다. const [isVisible, setIsVisible] = useState( merchant === undefined ? true : merchant.isVisible ); // 주소·우편번호는 검색으로 채우거나 직접 고친다. 좌표는 검색 결과만 채운다. const [address, setAddress] = useState(merchant?.address ?? ''); const [postalCode, setPostalCode] = useState(merchant?.postalCode ?? ''); const [latitude, setLatitude] = useState(merchant?.latitude ?? ''); const [longitude, setLongitude] = useState(merchant?.longitude ?? ''); const [isSearching, startSearching] = useTransition(); useEffect(() => { if (state.status === 'success') { showToast({ variant: 'success', message: isEdit ? '가맹점을 수정했습니다.' : '가맹점을 등록했습니다.', }); onClose(); return; } // 칸별 오류는 해당 입력칸에 그대로 두고, 폼 전체에 걸린 사유만 토스트로 알린다 — // 긴 폼이라 맨 아래 문구는 스크롤 밖에 있으면 보이지 않는다. if (state.status === 'error' && state.message) { showToast({ variant: 'danger', message: state.message }); } }, [state, showToast, onClose, isEdit]); const errors = state.status === 'error' ? (state.errors ?? {}) : {}; /** 기획 ③ 주소 검색 연동 — 고른 주소로 우편번호·좌표까지 채운다. */ function searchAddress() { startSearching(async () => { let picked; try { picked = await openPostcodeSearch(); } catch { showToast({ variant: 'danger', message: '주소 검색을 불러오지 못했습니다. 잠시 후 다시 시도해 주세요.', }); return; } if (!picked) { return; } setAddress(picked.address); setPostalCode(picked.zonecode); setLatitude(picked.latitude); setLongitude(picked.longitude); }); } return ( formRef.current?.requestSubmit()} /> } >
submitFormAction(event, formAction)} className={styles.fields} > {/* 등록 시에는 코드가 아직 없다 — 저장할 때 서버가 만든다. */} {isEdit && (
)} {/* 메모·등록일은 백엔드에 컬럼이 없어 저장되지 않는다. 기획 시안에 있는 항목이라 화면에서는 지우지 않고, 값이 없으면 `-`로 보여준다(사용자 확정). 브랜드명은 폼에서 뺐다(사용자 요청). */}
({ value: code.code, label: code.label }))} value={categoryCode} onValueChange={setCategoryCode} placeholder="선택" error={Boolean(errors.categoryCode)} hint={errors.categoryCode} /> ({ value: code.code, label: code.label }))} value={businessStatusCode} onValueChange={setBusinessStatusCode} placeholder="선택" />
({ value: code.code, label: code.label }))} value={divisionCode} onValueChange={setDivisionCode} placeholder="선택" />
주소
{errors.address && ( )}
위도 / 경도
정산 계좌
({ value: code.code, label: code.label }))} value={bankCode} onValueChange={setBankCode} placeholder="은행 선택" />
사용여부 setIsVisible(true)} /> setIsVisible(false)} />
{isEdit && (
등록일 / 최종수정일
)}
); }