| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225 |
- '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 { MOCK_CREW_RANKING } from '../constants';
- import type { FormState } from '../types';
- type Props = {
- form: FormState;
- };
- type SimulatedDonation = {
- name: string;
- totalAmount: number;
- donationCount: number;
- };
- const THEME_NAMES = ['basic', 'dark', 'minimal'];
- export default function CrewWidgetPreviewPanel({ form }: Props)
- {
- const [previewName, setPreviewName] = useState(PREVIEW_DEFAULT_NAME);
- const [previewAmount, setPreviewAmount] = useState(DONATION_AMOUNT_DEFAULT);
- const [simulatedDonations, setSimulatedDonations] = useState<SimulatedDonation[]>([]);
- // 시뮬레이션 데이터 있으면 contributionRate 자동 계산, 없으면 mock
- const visibleItems = simulatedDonations.length > 0
- ? (() => {
- const sorted = simulatedDonations.slice().sort((a, b) => b.totalAmount - a.totalAmount);
- const total = sorted.reduce((sum, d) => sum + d.totalAmount, 0) || 1;
- return sorted.slice(0, form.maxDisplayCount).map((d, i) => ({
- rank: i + 1,
- nickname: d.name,
- totalAmount: d.totalAmount,
- donationCount: d.donationCount,
- contributionRate: (d.totalAmount / total) * 100
- }));
- })()
- : MOCK_CREW_RANKING.slice(0, form.maxDisplayCount);
- const themeName = THEME_NAMES[form.theme] ?? 'basic';
- const getFontStyle = (rank: number): 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 {
- '--row-font-family': form.rowFontFamily || 'inherit',
- '--row-font-size': `${form.rowFontSizePx}px`,
- '--row-font-color': form.rowFontColor
- } as CSSProperties;
- };
- const getBadgeClass = (rank: number) => {
- if (rank <= 3) {
- return `crew-widget-preview__badge--${rank}`;
- }
- return 'crew-widget-preview__badge--default';
- };
- const bodyStyle = { '--preview-bg': form.bgColor } as CSSProperties;
- 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="crew-widget-preview">
- <div className="crew-widget-preview__widget">
- <div className="crew-widget-preview__widget-label">미리보기</div>
- <div
- className={`crew-widget-preview__widget-body crew-widget-preview__widget-body--${themeName}`}
- style={bodyStyle}
- >
- {/* 위젯 제목 */}
- <div className="crew-widget-preview__title" style={titleStyle}>
- {form.title || '크루 순위'}
- </div>
- {/* 컬럼 헤더 */}
- <div className="crew-widget-preview__columns">
- <span className="crew-widget-preview__col-rank">순위</span>
- {form.isShowMemberIcon && <span className="crew-widget-preview__col-icon" />}
- <span className="crew-widget-preview__col-name">이름</span>
- {form.isShowContributionRate && <span className="crew-widget-preview__col-rate">기여도</span>}
- {form.isShowAmount && <span className="crew-widget-preview__col-amount">후원 점수</span>}
- {form.isShowDonationCount && <span className="crew-widget-preview__col-count">건수</span>}
- </div>
- {/* 순위 리스트 */}
- <div className="crew-widget-preview__list">
- {visibleItems.map(item => {
- const style = getFontStyle(item.rank);
- return (
- <div
- key={item.rank}
- className={`crew-widget-preview__item${item.rank <= 3 ? ` crew-widget-preview__item--${item.rank}` : ''}`}
- style={style}
- >
- <span className={`crew-widget-preview__badge ${getBadgeClass(item.rank)}`}>
- {item.rank}
- </span>
- {form.isShowMemberIcon && <span className="crew-widget-preview__member-icon" />}
- <span className="crew-widget-preview__name">{item.nickname}</span>
- {form.isShowContributionRate && (
- <span className="crew-widget-preview__rate">{item.contributionRate.toFixed(1)}%</span>
- )}
- {form.isShowAmount && (
- <span className="crew-widget-preview__amount">{item.totalAmount.toLocaleString()}원</span>
- )}
- {form.isShowDonationCount && (
- <span className="crew-widget-preview__count">{item.donationCount}건</span>
- )}
- </div>
- );
- })}
- {visibleItems.length === 0 && (
- <div className="crew-widget-preview__empty">순위 데이터 없음</div>
- )}
- </div>
- </div>
- </div>
- <fieldset className="crew-widget-preview__fieldset">
- <legend className="crew-widget-preview__legend">미리보기</legend>
- <div className="crew-widget-preview__preview-form">
- <div className="crew-widget-preview__field">
- <label htmlFor="crew-preview-amount" className="crew-widget-preview__field-label">후원 금액 (원)</label>
- <input
- id="crew-preview-amount"
- type="number"
- className="crew-widget-preview__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="crew-widget-preview__field">
- <label htmlFor="crew-preview-name" className="crew-widget-preview__field-label">이름</label>
- <input
- id="crew-preview-name"
- type="text"
- className="crew-widget-preview__input"
- maxLength={ALERT_NAME_MAX_LENGTH}
- value={previewName}
- onChange={e => setPreviewName(e.target.value)}
- placeholder="후원자 이름"
- />
- </div>
- <div className="crew-widget-preview__preview-actions">
- <button
- type="button"
- className="crew-widget-preview__btn crew-widget-preview__btn--primary crew-widget-preview__preview-btn"
- onClick={handlePreview}
- >
- <FontAwesomeIcon icon={faPlay} />
- 미리보기
- </button>
- <button
- type="button"
- className="crew-widget-preview__btn"
- onClick={handleReset}
- disabled={simulatedDonations.length === 0}
- >
- 초기화
- </button>
- </div>
- {simulatedDonations.length > 0 && (
- <p className="crew-widget-preview__field-hint">
- 후원자 {simulatedDonations.length}명 (총 {totalSimulatedAmount.toLocaleString()}원)
- </p>
- )}
- </div>
- </fieldset>
- </aside>
- );
- }
|