// ────────────────────────────────────────────────────────────────────
// 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 }) => (
{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}
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% (정산 제외). 계약 기반으로만 설정 가능 하며, 변경 시 다음 정산 사이클부터 반영됩니다.>}
>
50 ? 50 : rate} disabled={disabled}/>
{isOutOfRange && 0.0% ~ 50.0% 범위여야 합니다. 입력값: 62.5% }
{(isDirty || isConfirm) && !isOutOfRange && (
)}
{/* Field 3 — Memo */}
계약 근거 메모 · 선택 >}>
{(isDirty || isConfirm ? 42 : 0)} / 200
{/* History */}
{isConfirm && }
>
);
};
const numBtn = {
width: 22, height: 16,
background: '#fff', border: '1px solid var(--n-200)',
borderRadius: 3, cursor: 'pointer',
color: 'var(--n-500)', fontSize: 8,
display: 'grid', placeItems: 'center',
};
const FeatureSettingsFrame = ({ variant }) => (
);
Object.assign(window, { FeatureSettingsModal, FeatureSettingsFrame });