// ──────────────────────────────────────────────────────────────────── // AW-008 · Partner Feature Settings Modal — 640px // ──────────────────────────────────────────────────────────────────── // Local toggle with caption slot const BigToggle = ({ on, disabled }) => (
); // Gradient slider: neutral → warning → error with thumb const RangeBar = ({ value, disabled, max = 50 }) => { const pct = Math.min(100, (value / max) * 100); const clampedValue = Math.max(0, Math.min(max, value)); const valueColor = value > max ? 'var(--error)' : (value >= 25 ? 'var(--warning)' : (value > 0 ? 'var(--n-700)' : 'var(--n-500)')); return (
{/* Tick marks */} {[0, 25, 50].map(t => (
))} {/* Thumb */}
0% 25% 50%
); }; // Small caption inline error const InlineError = ({ children, code }) => (
{children} {code && ({code})}
); // Field row wrapper const FeatField = ({ title, children, caption }) => (
{title && (
{title}
)} {children} {caption && (
{caption}
)}
); // History row const HistoryRow = ({ when, who, change, memo }) => (
{when}
{who}
{change.map((c, i) => ( {c} ))}
{memo &&
{memo}
}
); // Warning banner (SUSPENDED / batch conflict) const FeatBanner = ({ kind, children, code }) => { const style = kind === 'batch' ? { bg: 'var(--error-bg)', border: '#FECACA', icon: 'var(--error)', text: '#991B1B', } : { bg: 'var(--warning-bg)', border: '#FDE68A', icon: 'var(--warning)', text: '#78350F', }; return (
{children} {code && ({code})}
); }; // Diff confirm dialog const FeatConfirmDialog = () => (
설정 변경 확인
{PARTNER_DETAIL.name} · {PARTNER_DETAIL.id}
수동 사용처리
OFF ON
EXPIRED 정산 비율
10.0% 15.0%
변경 내역은 감사 로그에 기록되며 파트너에게 즉시 반영됩니다.
); // Footer const FeatFooter = ({ canSave }) => (
); // Main modal const FeatureSettingsModal = ({ variant = 'pristine' }) => { // Scenarios const isPristine = variant === 'pristine'; const isDirty = variant === 'dirty'; const isConfirm = variant === 'confirm'; const isSuspended = variant === 'suspended'; const isBatch = variant === 'batch-conflict'; const isOutOfRange = variant === 'out-of-range'; const disabled = isSuspended; const manualOn = isPristine ? true : (isDirty || isConfirm || isSuspended ? true : true); const rate = isOutOfRange ? 62.5 : isDirty || isConfirm || isBatch ? 15.0 : 10.0; const dirty = isDirty || isBatch || isOutOfRange; const canSave = dirty && !isOutOfRange && !isSuspended; return ( <> } />
{/* Banners */} {isSuspended && ( 파트너가 SUSPENDED 상태입니다. 운영 재개 전까지 기능 설정을 변경할 수 없습니다. 파트너 운영 재개는 AW-006에서 가능합니다. )} {isBatch && ( 정산 배치가 진행 중입니다. 배치 완료(~18분 남음) 후 다시 시도해 주세요. 현재 변경은 저장되지 않습니다. )} {/* Partner summary strip */}
{PARTNER_DETAIL.name}
{PARTNER_DETAIL.id} · {CAT[PARTNER_DETAIL.cat].label}
{/* Field 1 — Manual redeem */} ON 설정 시 PW-006 / PW-007 메뉴가 해당 파트너에 노출되며, QR 스캔 외 수동 승인이 가능해집니다.} >
수동 사용처리 활성화
{manualOn ? 'ON' : 'OFF'} {isDirty || isConfirm ? ( · 변경됨 (OFF → ON) ) : null}
{/* Field 2 — EXPIRED settlement rate */} 기본 0.0% (정산 제외). 계약 기반으로만 설정 가능하며, 변경 시 다음 정산 사이클부터 반영됩니다.} >
{rate.toFixed(1)} %
50 ? 50 : rate} disabled={disabled}/>
{isOutOfRange && 0.0% ~ 50.0% 범위여야 합니다. 입력값: 62.5%} {(isDirty || isConfirm) && !isOutOfRange && (
변경됨 · 10.0% → 15.0%
)}
{/* Field 3 — Memo */} 계약 근거 메모 · 선택}>