import { memo, useMemo } from 'react' import Select from '../../../../components/common/Select' import Tooltip from '../../../../components/common/Tooltip' import { useSymbolStore } from '../../store' import { formatMesoKorean } from '../../../../utils/formatting' import { formatKoreanDate, computeCompletion } from '../../utils' const INPUT_CLASS = "w-full h-10 rounded-md border px-3 text-base text-right tabular-nums outline-none focus:border-[var(--input-border-focus)] hover:border-[var(--input-border-hover)] disabled:opacity-50" const INPUT_STYLE = { background: 'var(--input-bg)', borderColor: 'var(--input-border)', color: 'var(--text-strong)', } function SymbolCard({ symbol, equipped, charId }) { const progress = useSymbolStore((s) => s.progress?.[charId]?.[symbol.id]) const updateSymbol = useSymbolStore((s) => s.updateSymbol) const dailyDone = progress?.dailyDone ?? false const weeklyCount = progress?.weeklyCount ?? 3 const daily = progress?.daily ?? symbol.daily_default const extra = progress?.extra ?? 0 const patch = (p) => charId && updateSymbol(charId, symbol.id, p) const level = progress?.level ?? 0 const growth = progress?.growth ?? 0 const requireGrowth = symbol.levels?.find((l) => l.level === level)?.required_count || 0 const isMax = equipped && level >= symbol.max_level const { remainingSymbols, remainingMeso, arrearMeso } = useMemo(() => { if (!equipped || !symbol.levels?.length) return { remainingSymbols: 0, remainingMeso: 0, arrearMeso: 0 } let sym = 0, meso = 0, arr = 0 let arrLv = level, arrG = growth while (arrLv < symbol.max_level) { const req = symbol.levels.find((l) => l.level === arrLv)?.required_count const cost = symbol.levels.find((l) => l.level === arrLv)?.meso_cost if (req == null || cost == null || arrG < req) break arr += cost arrG -= req arrLv += 1 } let g = growth for (const l of symbol.levels) { if (l.level < level) continue sym += Math.max(l.required_count - g, 0) g = Math.max(g - l.required_count, 0) meso += l.meso_cost } return { remainingSymbols: sym, remainingMeso: meso, arrearMeso: arr } }, [equipped, level, growth, symbol.levels, symbol.max_level]) const reachableLevel = useMemo(() => { if (!equipped || isMax) return level let lv = level let g = growth while (lv < symbol.max_level) { const req = symbol.levels?.find((l) => l.level === lv)?.required_count if (!req || g < req) break g -= req lv += 1 } return lv }, [equipped, isMax, level, growth, symbol.levels, symbol.max_level]) const effectivelyMax = equipped && !isMax && reachableLevel >= symbol.max_level const interactable = equipped && !isMax && !effectivelyMax const { days: daysLeft, date: completeDate } = useMemo(() => { if (!equipped || isMax) return { days: null, date: null } return computeCompletion({ remainingSymbols, daily, weeklyPerWeek: (weeklyCount || 0) * (symbol.weekly_default || 0), extra, dailyDone, }) }, [equipped, isMax, remainingSymbols, daily, weeklyCount, symbol.weekly_default, extra, dailyDone]) return (
{symbol.image_url && ( {symbol.region} )}
{symbol.region}
Lv.{level} / {symbol.max_level}
{equipped && !isMax && !effectivelyMax && ( )}
{/* 진행도 바 */}
{isMax ? ( 성장치 MAX ) : effectivelyMax ? ( 성장치 {growth} (MAX) / {requireGrowth} ) : reachableLevel > level ? ( 성장치 {growth} / {requireGrowth} ) : ( 성장치 {growth} / {requireGrowth} )} {!isMax && !effectivelyMax && ( {requireGrowth ? Math.min(Math.floor((growth / requireGrowth) * 100), 100) : 0}% )}
{/* 획득량 입력 */}
0 ? '0.7fr 1.3fr 1fr' : '1fr 1fr' }} >
patch({ daily: Number(e.target.value.replace(/[^\d]/g, '')) || 0 })} disabled={!interactable} className={INPUT_CLASS} style={INPUT_STYLE} />
{symbol.weekly_default > 0 && (
patch({ extra: Number(e.target.value.replace(/[^\d]/g, '')) || 0 })} disabled={!interactable} className={INPUT_CLASS} style={INPUT_STYLE} />
{/* 정보 */}
{[ { label: '남은 심볼', value: equipped && !isMax && !effectivelyMax ? `${remainingSymbols.toLocaleString()}개` : '-', color: 'var(--text-emphasis)' }, { label: '필요 메소', value: equipped && !isMax ? remainingMeso.toLocaleString() : '-', color: 'var(--warning-text-bright)', tooltip: equipped && !isMax ? formatMesoKorean(remainingMeso) : null }, { label: '체납 메소', value: equipped && !isMax ? arrearMeso.toLocaleString() : '-', color: 'var(--danger-text)', tooltip: equipped && !isMax ? formatMesoKorean(arrearMeso) : null }, { label: '남은 일수', value: equipped && !isMax && !effectivelyMax && daysLeft != null ? `${daysLeft.toLocaleString()}일` : '-', color: 'var(--text-emphasis)' }, { label: '예상 완료일', value: equipped && !isMax && !effectivelyMax && completeDate ? formatKoreanDate(completeDate) : '-', color: equipped && !isMax && !effectivelyMax && completeDate ? 'var(--accent-bright)' : 'var(--text-dim)', strong: true }, ].map((row) => (
{row.label} {row.tooltip ? ( {row.value} ) : ( {row.value} )}
))}
) } export default memo(SymbolCard)