데스티니 해방에 현재 진행 상태 섹션 추가

QuestSelector를 chapters/imageBase prop 받도록 일반화한 뒤, Destiny에
시작 날짜/진행 중인 결전/현재 결의 입력 섹션 구현. 결의 입력 max는
20,000 (6챕터 요구량 15,000 여유 포함).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
caadiq 2026-04-21 19:55:16 +09:00
parent 0448b0bfc8
commit d506c022ca
3 changed files with 77 additions and 9 deletions

View file

@ -1,11 +1,17 @@
import { DESTINY_CHAPTERS, DESTINY_QUEST_IMAGE_BASE } from '../data'
import dayjs from 'dayjs'
import { DESTINY_CHAPTERS, DESTINY_QUEST_IMAGE_BASE, formatDate } from '../data'
import { useLiberationStore } from '../store'
import ProgressBar from './components/ProgressBar'
import QuestSelector from './components/QuestSelector'
import PointsInput from './components/PointsInput'
import DatePicker from '../../../components/common/DatePicker'
export default function Destiny() {
const calcMode = useLiberationStore((s) => s.destinyCalcMode)
const setCalcMode = useLiberationStore((s) => s.setDestinyCalcMode)
const state = useLiberationStore((s) => s.destinyCalcMode === 'weekly' ? s.destinyWeekly : s.destinySimple)
const updateSlot = useLiberationStore((s) => s.updateDestinySlot)
const setState = (updater) => updateSlot(updater)
return (
<>
@ -49,6 +55,66 @@ export default function Destiny() {
completionDate={null}
completionColor="var(--destiny-date)"
/>
{/* 현재 진행 상태 입력 */}
<div
className="max-w-3xl mx-auto rounded-2xl border p-6 space-y-4"
style={{
background: 'var(--panel-bg)',
borderColor: 'var(--panel-border)',
boxShadow: 'var(--panel-shadow)',
}}
>
<div className="text-lg font-semibold" style={{ color: 'var(--accent-bright)' }}>현재 진행 상태</div>
<div className="grid gap-3 grid-cols-3">
<div className="space-y-1.5">
<label className="block text-xs" style={{ color: 'var(--text-muted)' }}>시작 날짜</label>
<DatePicker
value={formatDate(state.startDate)}
onChange={(d) => setState((prev) => ({ ...prev, startDate: dayjs(d).toISOString() }))}
/>
</div>
<div className="space-y-1.5">
<label className="block text-xs" style={{ color: 'var(--text-muted)' }}>진행 중인 퀘스트</label>
<QuestSelector
chapters={DESTINY_CHAPTERS}
imageBase={DESTINY_QUEST_IMAGE_BASE}
value={state.startChapter}
onChange={(idx) => setState((prev) => ({ ...prev, startChapter: idx }))}
/>
</div>
<div className="space-y-1.5">
<label className="block text-xs" style={{ color: 'var(--text-muted)' }}>현재 결의</label>
<div
className="flex items-stretch rounded-lg border focus-within:border-[var(--input-border-focus)] hover:border-[var(--input-border-hover)]"
style={{
background: 'var(--input-bg)',
borderColor: 'var(--input-border)',
}}
>
<PointsInput
value={state.currentPoints}
max={20000}
onChange={(n) => setState((prev) => ({ ...prev, currentPoints: n }))}
className="flex-1 min-w-0 bg-transparent px-3 h-12 text-base text-right tabular-nums outline-none"
style={{ color: 'var(--text-strong)' }}
/>
<span
className="flex items-center px-3 text-base border-l select-none tabular-nums"
style={{
borderColor: 'var(--input-border)',
color: 'var(--text-dim)',
}}
>
/ {(DESTINY_CHAPTERS[state.startChapter]?.required ?? 0).toLocaleString()}
</span>
</div>
</div>
</div>
</div>
</>
)
}

View file

@ -155,6 +155,8 @@ export default function Genesis() {
<div className="space-y-1.5">
<label className="block text-xs" style={{ color: 'var(--text-muted)' }}>진행 중인 퀘스트</label>
<QuestSelector
chapters={GENESIS_CHAPTERS}
imageBase={QUEST_BOSS_IMAGE_BASE}
value={state.startChapter}
onChange={(idx) => setState((prev) => ({ ...prev, startChapter: idx }))}
/>

View file

@ -1,12 +1,12 @@
import { useState, useEffect, useRef } from 'react'
import { motion, AnimatePresence } from 'framer-motion'
import { GENESIS_CHAPTERS, QUEST_BOSS_IMAGE_BASE } from '../../data'
/**
* 진행 중인 퀘스트 드롭다운
* - 보스 초상화 + 이름 텍스트
* 진행 중인 퀘스트 드롭다운 (보스 초상화 + 이름)
* @param {Array} chapters - { idx, boss, ... }[]
* @param {string} imageBase - 보스 초상화 S3 base URL
*/
export default function QuestSelector({ value, onChange }) {
export default function QuestSelector({ chapters, imageBase, value, onChange }) {
const [open, setOpen] = useState(false)
const ref = useRef(null)
@ -19,7 +19,7 @@ export default function QuestSelector({ value, onChange }) {
return () => document.removeEventListener('mousedown', handler)
}, [open])
const selected = GENESIS_CHAPTERS[value]
const selected = chapters[value]
return (
<div ref={ref} className="relative">
@ -38,7 +38,7 @@ export default function QuestSelector({ value, onChange }) {
style={{ background: 'var(--surface-nested)' }}
>
<img
src={`${QUEST_BOSS_IMAGE_BASE}/${selected.boss}.webp`}
src={`${imageBase}/${selected.boss}.webp`}
alt=""
className="w-full h-full object-cover"
/>
@ -69,7 +69,7 @@ export default function QuestSelector({ value, onChange }) {
boxShadow: 'var(--popup-shadow)',
}}
>
{GENESIS_CHAPTERS.map((chapter) => {
{chapters.map((chapter) => {
const isSelected = chapter.idx === value
return (
<button
@ -86,7 +86,7 @@ export default function QuestSelector({ value, onChange }) {
style={{ background: 'var(--surface-nested)' }}
>
<img
src={`${QUEST_BOSS_IMAGE_BASE}/${chapter.boss}.webp`}
src={`${imageBase}/${chapter.boss}.webp`}
alt=""
className="w-full h-full object-cover"
/>