| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199 |
- 'use client';
- import { useState, type CSSProperties } from 'react';
- import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
- import { faPlay } from '@fortawesome/free-solid-svg-icons';
- import {
- DONATION_AMOUNT_MIN, DONATION_AMOUNT_MAX, DONATION_AMOUNT_STEP, DONATION_AMOUNT_DEFAULT,
- ALERT_NAME_MAX_LENGTH, PREVIEW_DEFAULT_NAME
- } from '@/constants/donation';
- import type { FormState } from '../types';
- import { MOCK_RANKING } from '../constants';
- type Props = {
- form: FormState;
- };
- type SimulatedDonation = {
- name: string;
- totalAmount: number;
- donationCount: number;
- };
- /** 순위별 폰트 스타일 반환 (1~3등은 커스텀, 나머지는 기본) */
- const getRankStyle = (rank: number, form: FormState): CSSProperties => {
- if (rank === 1) {
- return {
- '--row-font-family': form.rank1FontFamily || 'inherit',
- '--row-font-size': `${form.rank1FontSizePx}px`,
- '--row-font-color': form.rank1FontColor
- } as CSSProperties;
- }
- if (rank === 2) {
- return {
- '--row-font-family': form.rank2FontFamily || 'inherit',
- '--row-font-size': `${form.rank2FontSizePx}px`,
- '--row-font-color': form.rank2FontColor
- } as CSSProperties;
- }
- if (rank === 3) {
- return {
- '--row-font-family': form.rank3FontFamily || 'inherit',
- '--row-font-size': `${form.rank3FontSizePx}px`,
- '--row-font-color': form.rank3FontColor
- } as CSSProperties;
- }
- return {};
- };
- export default function RankPreviewPanel({ form }: Props) {
- const [previewName, setPreviewName] = useState(PREVIEW_DEFAULT_NAME);
- const [previewAmount, setPreviewAmount] = useState(DONATION_AMOUNT_DEFAULT);
- const [simulatedDonations, setSimulatedDonations] = useState<SimulatedDonation[]>([]);
- // 시뮬레이션 데이터 있으면 그것 사용, 없으면 mock 데이터
- const visibleRanking = simulatedDonations.length > 0
- ? simulatedDonations
- .slice()
- .sort((a, b) => b.totalAmount - a.totalAmount)
- .slice(0, form.maxRankCount)
- .map((d, i) => ({
- rank: i + 1,
- sponsorName: d.name,
- totalAmount: d.totalAmount,
- donationCount: d.donationCount
- }))
- : MOCK_RANKING.slice(0, form.maxRankCount);
- const titleStyle = {
- '--preview-title-font': form.titleFontFamily || 'inherit',
- '--preview-title-size': `${form.titleFontSizePx}px`,
- '--preview-title-color': form.titleFontColor
- } as CSSProperties;
- const handlePreview = () => {
- const name = (previewName || PREVIEW_DEFAULT_NAME).trim();
- const amount = previewAmount || DONATION_AMOUNT_DEFAULT;
- setSimulatedDonations(prev => {
- const existing = prev.find(d => d.name === name);
- if (existing) {
- return prev.map(d => d.name === name
- ? { ...d, totalAmount: d.totalAmount + amount, donationCount: d.donationCount + 1 }
- : d
- );
- }
- return [...prev, { name, totalAmount: amount, donationCount: 1 }];
- });
- };
- const handleReset = () => {
- setSimulatedDonations([]);
- };
- const totalSimulatedAmount = simulatedDonations.reduce((sum, d) => sum + d.totalAmount, 0);
- return (
- <aside className="rank-config__preview-panel">
- <div className="rank-config__widget">
- <div className="rank-config__widget-label">미리보기</div>
- <div className={`rank-config__widget-body rank-config__widget-body--${['basic', 'dark', 'minimal'][form.theme] ?? 'basic'}`}>
- <div className="rank-preview">
- <div className="rank-preview__title" style={titleStyle}>
- {form.title || '후원 순위'}
- </div>
- <div className="rank-preview__list">
- {visibleRanking.map(item => {
- const badgeClass = item.rank <= 3 ? `rank-preview__badge--${item.rank}` : 'rank-preview__badge--default';
- const itemStyle = getRankStyle(item.rank, form);
- return (
- <div key={item.rank} className={`rank-preview__item rank-preview__item--${item.rank}`} style={itemStyle}>
- <div className={`rank-preview__badge ${badgeClass}`}>{item.rank}</div>
- {form.isShowMemberIcon && (
- <div className="rank-preview__member-icon" />
- )}
- {form.isShowGradeIcon && (
- <div className="rank-preview__grade-icon" />
- )}
- <div className="rank-preview__name">
- {item.sponsorName}
- </div>
- <div className="rank-preview__meta">
- {form.isShowAmount && (
- <span className="rank-preview__amount">
- {item.totalAmount.toLocaleString()}원
- </span>
- )}
- {form.isShowDonationCount && (
- <span className="rank-preview__count">
- {item.donationCount}회
- </span>
- )}
- </div>
- </div>
- );
- })}
- {visibleRanking.length === 0 && (
- <div className="rank-preview__empty">순위 데이터 없음</div>
- )}
- </div>
- </div>
- </div>
- </div>
- <fieldset className="rank-config__fieldset">
- <legend className="rank-config__legend">미리보기</legend>
- <div className="rank-config__preview-form">
- <div className="rank-config__field">
- <label htmlFor="rank-preview-amount" className="rank-config__field-label">후원 금액 (원)</label>
- <input
- id="rank-preview-amount"
- type="number"
- className="rank-config__input"
- min={DONATION_AMOUNT_MIN}
- max={DONATION_AMOUNT_MAX}
- step={DONATION_AMOUNT_STEP}
- value={previewAmount}
- onChange={e => setPreviewAmount(parseInt(e.target.value) || DONATION_AMOUNT_DEFAULT)}
- />
- </div>
- <div className="rank-config__field">
- <label htmlFor="rank-preview-name" className="rank-config__field-label">이름</label>
- <input
- id="rank-preview-name"
- type="text"
- className="rank-config__input"
- maxLength={ALERT_NAME_MAX_LENGTH}
- value={previewName}
- onChange={e => setPreviewName(e.target.value)}
- placeholder="후원자 이름"
- />
- </div>
- <div className="rank-config__preview-actions">
- <button
- type="button"
- className="rank-config__btn rank-config__btn--primary rank-config__preview-btn"
- onClick={handlePreview}
- >
- <FontAwesomeIcon icon={faPlay} />
- 미리보기
- </button>
- <button
- type="button"
- className="rank-config__btn"
- onClick={handleReset}
- disabled={simulatedDonations.length === 0}
- >
- 초기화
- </button>
- </div>
- {simulatedDonations.length > 0 && (
- <p className="rank-config__field-hint">
- 후원자 {simulatedDonations.length}명 (총 {totalSimulatedAmount.toLocaleString()}원)
- </p>
- )}
- </div>
- </fieldset>
- </aside>
- );
- }
|