| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399 |
- 'use client';
- import type { RankConfigItem } from '@/types/response/donation/rankConfig';
- import { Checkbox } from '@/components/ui/checkbox';
- import { RANK_PERIODS, RANK_THEMES, NAME_DISPLAY_TYPES, FONT_FAMILIES } from '../constants';
- import type { FormState } from '../types';
- type Props = {
- form: FormState;
- editingItem: RankConfigItem|null;
- saving: boolean;
- onFormChange: <K extends keyof FormState>(field: K, value: FormState[K]) => void;
- onSave: () => void;
- onCancel: () => void;
- };
- /** 색상 입력 (color picker + hex text) */
- function ColorInput({ id, value, onChange }: { id: string; value: string; onChange: (v: string) => void }) {
- return (
- <div className="rank-config__color-field">
- <input
- id={id}
- type="color"
- className="rank-config__color-picker"
- value={value}
- onChange={e => onChange(e.target.value)}
- />
- <input
- type="text"
- className="rank-config__input rank-config__input--color-text"
- value={value}
- onChange={e => onChange(e.target.value)}
- maxLength={7}
- />
- </div>
- );
- }
- export default function RankFormPanel({
- form,
- editingItem,
- saving,
- onFormChange,
- onSave,
- onCancel
- }: Props) {
- return (
- <main className="rank-config__form-panel">
- {/* ── 기본 설정 ────────────────────────────── */}
- <section className="rank-config__section">
- <h3 className="rank-config__section-title">기본 설정</h3>
- <div className="rank-config__section-body">
- <div className="rank-config__field">
- <label htmlFor="rank-title" className="rank-config__field-label">제목</label>
- <input
- id="rank-title"
- type="text"
- className="rank-config__input"
- value={form.title}
- onChange={e => onFormChange('title', e.target.value)}
- placeholder="후원 순위"
- />
- </div>
- <div className="rank-config__field-row">
- <div className="rank-config__field">
- <label htmlFor="rank-theme" className="rank-config__field-label">테마</label>
- <select
- id="rank-theme"
- className="rank-config__select"
- value={form.theme}
- onChange={e => onFormChange('theme', parseInt(e.target.value))}
- >
- {RANK_THEMES.map(t => (
- <option key={t.value} value={t.value}>{t.label}</option>
- ))}
- </select>
- </div>
- <div className="rank-config__field">
- <label htmlFor="rank-period" className="rank-config__field-label">기간</label>
- <select
- id="rank-period"
- className="rank-config__select"
- value={form.period}
- onChange={e => onFormChange('period', parseInt(e.target.value))}
- >
- {RANK_PERIODS.map(p => (
- <option key={p.value} value={p.value}>{p.label}</option>
- ))}
- </select>
- </div>
- </div>
- <div className="rank-config__field">
- <label htmlFor="rank-maxRankCount" className="rank-config__field-label">최대 순위 (명)</label>
- <input
- id="rank-maxRankCount"
- type="number"
- className="rank-config__input"
- min={1}
- max={10}
- value={form.maxRankCount}
- onChange={e => onFormChange('maxRankCount', parseInt(e.target.value) || 5)}
- />
- </div>
- </div>
- </section>
- {/* ── 기간 설정 (사용자 지정일 때만) ────────── */}
- {form.period === 5 && (
- <section className="rank-config__section">
- <h3 className="rank-config__section-title">기간 설정</h3>
- <div className="rank-config__section-body">
- <div className="rank-config__field-row">
- <div className="rank-config__field">
- <label htmlFor="rank-startAt" className="rank-config__field-label">시작일시</label>
- <input
- id="rank-startAt"
- type="text"
- className="rank-config__input"
- value={form.startAt ?? ''}
- onChange={e => onFormChange('startAt', e.target.value || null)}
- placeholder="2026.03.30 14:00"
- maxLength={16}
- />
- </div>
- <div className="rank-config__field">
- <label htmlFor="rank-endAt" className="rank-config__field-label">종료일시</label>
- <input
- id="rank-endAt"
- type="text"
- className="rank-config__input"
- value={form.endAt ?? ''}
- onChange={e => onFormChange('endAt', e.target.value || null)}
- placeholder="2026.03.31 14:00"
- maxLength={16}
- />
- </div>
- </div>
- </div>
- </section>
- )}
- {/* ── 표시 설정 ───────────────────────────── */}
- <section className="rank-config__section">
- <h3 className="rank-config__section-title">표시 설정</h3>
- <div className="rank-config__section-body">
- <div className="rank-config__field">
- <label htmlFor="rank-nameDisplayType" className="rank-config__field-label">이름 유형</label>
- <select
- id="rank-nameDisplayType"
- className="rank-config__select"
- value={form.nameDisplayType}
- onChange={e => onFormChange('nameDisplayType', parseInt(e.target.value))}
- >
- {NAME_DISPLAY_TYPES.map(n => (
- <option key={n.value} value={n.value}>{n.label}</option>
- ))}
- </select>
- </div>
- <div className="rank-config__field">
- <label className="rank-config__checkbox-label">
- <Checkbox
- checked={form.isShowAmount}
- onCheckedChange={v => onFormChange('isShowAmount', !!v)}
- />
- 금액 표시
- </label>
- </div>
- <div className="rank-config__field">
- <label className="rank-config__checkbox-label">
- <Checkbox
- checked={form.isShowDonationCount}
- onCheckedChange={v => onFormChange('isShowDonationCount', !!v)}
- />
- 후원 횟수 표시
- </label>
- </div>
- <div className="rank-config__field">
- <label className="rank-config__checkbox-label">
- <Checkbox
- checked={form.isShowGradeIcon}
- onCheckedChange={v => onFormChange('isShowGradeIcon', !!v)}
- />
- 회원 등급 아이콘 표시
- </label>
- </div>
- <div className="rank-config__field">
- <label className="rank-config__checkbox-label">
- <Checkbox
- checked={form.isShowMemberIcon}
- onCheckedChange={v => onFormChange('isShowMemberIcon', !!v)}
- />
- 사용자 아이콘 표시
- </label>
- </div>
- <div className="rank-config__field">
- <label className="rank-config__checkbox-label">
- <Checkbox
- checked={form.isActive}
- onCheckedChange={v => onFormChange('isActive', !!v)}
- />
- 활성화
- </label>
- </div>
- </div>
- </section>
- {/* ── 제목 폰트 ──────────────────────────────── */}
- <section className="rank-config__section">
- <h3 className="rank-config__section-title">제목 폰트</h3>
- <div className="rank-config__section-body">
- <div className="rank-config__field">
- <label htmlFor="rank-titleFontFamily" className="rank-config__field-label">글꼴</label>
- <select
- id="rank-titleFontFamily"
- className="rank-config__select"
- value={form.titleFontFamily ?? ''}
- onChange={e => onFormChange('titleFontFamily', e.target.value || null)}
- >
- {FONT_FAMILIES.map(f => (
- <option key={f.value} value={f.value}>{f.label}</option>
- ))}
- </select>
- </div>
- <div className="rank-config__field-row">
- <div className="rank-config__field">
- <label htmlFor="rank-titleFontSizePx" className="rank-config__field-label">크기 (px)</label>
- <input
- id="rank-titleFontSizePx"
- type="number"
- className="rank-config__input"
- min={10}
- max={48}
- value={form.titleFontSizePx}
- onChange={e => onFormChange('titleFontSizePx', parseInt(e.target.value) || 18)}
- />
- </div>
- <div className="rank-config__field">
- <label htmlFor="rank-titleFontColor" className="rank-config__field-label">색상</label>
- <ColorInput
- id="rank-titleFontColor"
- value={form.titleFontColor}
- onChange={v => onFormChange('titleFontColor', v)}
- />
- </div>
- </div>
- </div>
- </section>
- {/* ── 순위별 폰트 (details 접이식) ────────── */}
- <section className="rank-config__section">
- <h3 className="rank-config__section-title">순위별 폰트</h3>
- <div className="rank-config__section-body">
- {/* 1등 */}
- <details className="rank-config__details">
- <summary className="rank-config__details-summary">1등 폰트 설정</summary>
- <div className="rank-config__details-body">
- <div className="rank-config__field">
- <label htmlFor="rank-rank1FontFamily" className="rank-config__field-label">글꼴</label>
- <select
- id="rank-rank1FontFamily"
- className="rank-config__select"
- value={form.rank1FontFamily ?? ''}
- onChange={e => onFormChange('rank1FontFamily', e.target.value || null)}
- >
- {FONT_FAMILIES.map(f => (
- <option key={f.value} value={f.value}>{f.label}</option>
- ))}
- </select>
- </div>
- <div className="rank-config__field-row">
- <div className="rank-config__field">
- <label htmlFor="rank-rank1FontSizePx" className="rank-config__field-label">크기 (px)</label>
- <input
- id="rank-rank1FontSizePx"
- type="number"
- className="rank-config__input"
- min={10}
- max={36}
- value={form.rank1FontSizePx}
- onChange={e => onFormChange('rank1FontSizePx', parseInt(e.target.value) || 15)}
- />
- </div>
- <div className="rank-config__field">
- <label htmlFor="rank-rank1FontColor" className="rank-config__field-label">색상</label>
- <ColorInput
- id="rank-rank1FontColor"
- value={form.rank1FontColor}
- onChange={v => onFormChange('rank1FontColor', v)}
- />
- </div>
- </div>
- </div>
- </details>
- {/* 2등 */}
- <details className="rank-config__details">
- <summary className="rank-config__details-summary">2등 폰트 설정</summary>
- <div className="rank-config__details-body">
- <div className="rank-config__field">
- <label htmlFor="rank-rank2FontFamily" className="rank-config__field-label">글꼴</label>
- <select
- id="rank-rank2FontFamily"
- className="rank-config__select"
- value={form.rank2FontFamily ?? ''}
- onChange={e => onFormChange('rank2FontFamily', e.target.value || null)}
- >
- {FONT_FAMILIES.map(f => (
- <option key={f.value} value={f.value}>{f.label}</option>
- ))}
- </select>
- </div>
- <div className="rank-config__field-row">
- <div className="rank-config__field">
- <label htmlFor="rank-rank2FontSizePx" className="rank-config__field-label">크기 (px)</label>
- <input
- id="rank-rank2FontSizePx"
- type="number"
- className="rank-config__input"
- min={10}
- max={36}
- value={form.rank2FontSizePx}
- onChange={e => onFormChange('rank2FontSizePx', parseInt(e.target.value) || 15)}
- />
- </div>
- <div className="rank-config__field">
- <label htmlFor="rank-rank2FontColor" className="rank-config__field-label">색상</label>
- <ColorInput
- id="rank-rank2FontColor"
- value={form.rank2FontColor}
- onChange={v => onFormChange('rank2FontColor', v)}
- />
- </div>
- </div>
- </div>
- </details>
- {/* 3등 */}
- <details className="rank-config__details">
- <summary className="rank-config__details-summary">3등 폰트 설정</summary>
- <div className="rank-config__details-body">
- <div className="rank-config__field">
- <label htmlFor="rank-rank3FontFamily" className="rank-config__field-label">글꼴</label>
- <select
- id="rank-rank3FontFamily"
- className="rank-config__select"
- value={form.rank3FontFamily ?? ''}
- onChange={e => onFormChange('rank3FontFamily', e.target.value || null)}
- >
- {FONT_FAMILIES.map(f => (
- <option key={f.value} value={f.value}>{f.label}</option>
- ))}
- </select>
- </div>
- <div className="rank-config__field-row">
- <div className="rank-config__field">
- <label htmlFor="rank-rank3FontSizePx" className="rank-config__field-label">크기 (px)</label>
- <input
- id="rank-rank3FontSizePx"
- type="number"
- className="rank-config__input"
- min={10}
- max={36}
- value={form.rank3FontSizePx}
- onChange={e => onFormChange('rank3FontSizePx', parseInt(e.target.value) || 15)}
- />
- </div>
- <div className="rank-config__field">
- <label htmlFor="rank-rank3FontColor" className="rank-config__field-label">색상</label>
- <ColorInput
- id="rank-rank3FontColor"
- value={form.rank3FontColor}
- onChange={v => onFormChange('rank3FontColor', v)}
- />
- </div>
- </div>
- </div>
- </details>
- </div>
- </section>
- {/* ── 하단 버튼 ────────────────────────────── */}
- <div className="rank-config__form-footer flex-1 w-full sm:justify-end gap-2">
- <button type="button" className="rank-config__btn flex-1 sm:flex-none" onClick={onCancel}>
- 취소
- </button>
- <button
- type="button"
- className="rank-config__btn rank-config__btn--primary flex-1 sm:flex-none"
- onClick={onSave}
- disabled={saving}
- >
- {saving ? '저장 중...' : (editingItem ? '수정하기' : '등록하기')}
- </button>
- </div>
- </main>
- );
- }
|