해방 주차별 계산 탭 초안
- 계산 모드 탭(단순/주차별)을 상단으로 이동, 각 모드 독립 slot 저장 - 초기화 시 현재 모드 slot만 초기화, 다른 모드는 유지 - 주차별 카드 리스트 + 펼침 편집 영역 목업 - 편집 영역에서 기존 BossRow 재사용 (완료 버튼은 현재 주차에만) - 검은 마법사 행 항상 표시, 같은 달 다른 주차 배정 시 비활성 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
6ca610d014
commit
d1ca41ed4a
3 changed files with 265 additions and 104 deletions
|
|
@ -93,43 +93,46 @@ export default function Liberation() {
|
||||||
staleTime: Infinity,
|
staleTime: Infinity,
|
||||||
})
|
})
|
||||||
|
|
||||||
const [state, setState] = useState(() => {
|
const makeInitialSlot = () => ({
|
||||||
const saved = localStorage.getItem(STORAGE_KEY)
|
|
||||||
if (saved) {
|
|
||||||
try {
|
|
||||||
const parsed = JSON.parse(saved)
|
|
||||||
if (!parsed.weekly) parsed.weekly = makeEmptyWeekly()
|
|
||||||
if (!parsed.startDate) parsed.startDate = dayjs(todayKST()).toISOString()
|
|
||||||
if (!parsed.weekOverrides) parsed.weekOverrides = {}
|
|
||||||
// enabled/'none' 필드 제거 마이그레이션
|
|
||||||
const migrate = (sel, defaultDiff) => {
|
|
||||||
if (!sel) return sel
|
|
||||||
if (!sel.difficulty || sel.difficulty === 'none') sel.difficulty = defaultDiff
|
|
||||||
delete sel.enabled
|
|
||||||
return sel
|
|
||||||
}
|
|
||||||
WEEKLY_BOSSES.forEach((b) => {
|
|
||||||
if (parsed.weekly.bosses?.[b.key]) {
|
|
||||||
parsed.weekly.bosses[b.key] = migrate(parsed.weekly.bosses[b.key], b.difficulties[0].key)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
parsed.weekly.blackMage = migrate(parsed.weekly.blackMage, MONTHLY_BOSSES[0].difficulties[0].key)
|
|
||||||
return parsed
|
|
||||||
} catch { /* ignore */ }
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
startChapter: 0,
|
startChapter: 0,
|
||||||
currentPoints: 0,
|
currentPoints: 0,
|
||||||
startDate: dayjs(todayKST()).toISOString(),
|
startDate: dayjs(todayKST()).toISOString(),
|
||||||
weekly: makeEmptyWeekly(),
|
weekly: makeEmptyWeekly(),
|
||||||
weekOverrides: {},
|
weekOverrides: {},
|
||||||
weeks: [makeEmptyWeek(todayKST())],
|
weeks: [makeEmptyWeek(todayKST())],
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const [root, setRoot] = useState(() => {
|
||||||
|
const saved = localStorage.getItem(STORAGE_KEY)
|
||||||
|
if (saved) {
|
||||||
|
try {
|
||||||
|
const parsed = JSON.parse(saved)
|
||||||
|
// 구버전(단일 slot) → 새 구조로 마이그레이션
|
||||||
|
if (!parsed.calcMode) {
|
||||||
|
if (!parsed.weekly) parsed.weekly = makeEmptyWeekly()
|
||||||
|
if (!parsed.startDate) parsed.startDate = dayjs(todayKST()).toISOString()
|
||||||
|
if (!parsed.weekOverrides) parsed.weekOverrides = {}
|
||||||
|
return { calcMode: 'simple', simple: parsed, weekly: makeInitialSlot() }
|
||||||
|
}
|
||||||
|
return parsed
|
||||||
|
} catch { /* ignore */ }
|
||||||
|
}
|
||||||
|
return { calcMode: 'simple', simple: makeInitialSlot(), weekly: makeInitialSlot() }
|
||||||
|
})
|
||||||
|
|
||||||
|
const calcMode = root.calcMode
|
||||||
|
const state = root[calcMode]
|
||||||
|
const setState = (updater) => {
|
||||||
|
setRoot((prev) => ({
|
||||||
|
...prev,
|
||||||
|
[prev.calcMode]: typeof updater === 'function' ? updater(prev[prev.calcMode]) : updater,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
const setCalcMode = (mode) => setRoot((prev) => ({ ...prev, calcMode: mode }))
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
localStorage.setItem(STORAGE_KEY, JSON.stringify(state))
|
localStorage.setItem(STORAGE_KEY, JSON.stringify(root))
|
||||||
}, [state])
|
}, [root])
|
||||||
|
|
||||||
// 주차별 계산
|
// 주차별 계산
|
||||||
const progressByWeek = useMemo(() => {
|
const progressByWeek = useMemo(() => {
|
||||||
|
|
@ -260,14 +263,7 @@ export default function Liberation() {
|
||||||
|
|
||||||
const [resetOpen, setResetOpen] = useState(false)
|
const [resetOpen, setResetOpen] = useState(false)
|
||||||
const doReset = () => {
|
const doReset = () => {
|
||||||
setState({
|
setState(makeInitialSlot())
|
||||||
startChapter: 0,
|
|
||||||
currentPoints: 0,
|
|
||||||
startDate: dayjs(todayKST()).toISOString(),
|
|
||||||
weekly: makeEmptyWeekly(),
|
|
||||||
weekOverrides: {},
|
|
||||||
weeks: [makeEmptyWeek(todayKST())],
|
|
||||||
})
|
|
||||||
setResetOpen(false)
|
setResetOpen(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -315,6 +311,27 @@ export default function Liberation() {
|
||||||
<div className="text-sm text-gray-500">데스티니 해방 계산기는 준비 중입니다.</div>
|
<div className="text-sm text-gray-500">데스티니 해방 계산기는 준비 중입니다.</div>
|
||||||
</div>
|
</div>
|
||||||
) : (<>
|
) : (<>
|
||||||
|
{/* 계산 모드 탭 */}
|
||||||
|
<div className="max-w-3xl mx-auto flex gap-1 p-1 rounded-xl border border-white/10 bg-gray-950/60">
|
||||||
|
{[
|
||||||
|
{ key: 'simple', label: '단순 계산' },
|
||||||
|
{ key: 'weekly', label: '주차별 계산' },
|
||||||
|
].map((t) => (
|
||||||
|
<button
|
||||||
|
key={t.key}
|
||||||
|
type="button"
|
||||||
|
onClick={() => setCalcMode(t.key)}
|
||||||
|
className={`flex-1 h-10 rounded-lg text-sm font-semibold transition ${
|
||||||
|
calcMode === t.key
|
||||||
|
? 'bg-emerald-500/20 text-emerald-300'
|
||||||
|
: 'text-gray-400 hover:text-gray-200 hover:bg-white/5'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{t.label}
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
<ProgressBar
|
<ProgressBar
|
||||||
startChapter={state.startChapter}
|
startChapter={state.startChapter}
|
||||||
currentPoints={state.currentPoints}
|
currentPoints={state.currentPoints}
|
||||||
|
|
@ -359,6 +376,7 @@ export default function Liberation() {
|
||||||
onChange={(w) => setState((prev) => ({ ...prev, weekly: w }))}
|
onChange={(w) => setState((prev) => ({ ...prev, weekly: w }))}
|
||||||
totalWeekly={weeklyEarn}
|
totalWeekly={weeklyEarn}
|
||||||
totalMonthly={monthlyEarn}
|
totalMonthly={monthlyEarn}
|
||||||
|
mode={calcMode}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div className="max-w-3xl mx-auto flex justify-end">
|
<div className="max-w-3xl mx-auto flex justify-end">
|
||||||
|
|
@ -380,7 +398,7 @@ export default function Liberation() {
|
||||||
onClose={() => setResetOpen(false)}
|
onClose={() => setResetOpen(false)}
|
||||||
onConfirm={doReset}
|
onConfirm={doReset}
|
||||||
title="전체 초기화"
|
title="전체 초기화"
|
||||||
description={'입력한 내용을 모두 초기화하시겠습니까?\n\n시작 날짜, 현재 진행 상태, 주간 보스 설정이 모두 초기값으로 되돌아갑니다.'}
|
description={`${calcMode === 'simple' ? '단순 계산' : '주차별 계산'} 모드의 입력을 모두 초기화하시겠습니까?\n\n시작 날짜, 현재 진행 상태, 주간 보스 설정이 모두 초기값으로 되돌아갑니다.\n다른 모드의 값은 유지됩니다.`}
|
||||||
confirmText="초기화"
|
confirmText="초기화"
|
||||||
destructive
|
destructive
|
||||||
/>
|
/>
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import { useState } from 'react'
|
import { useState } from 'react'
|
||||||
import Select from '../../../components/Select'
|
import Select from '../../../components/Select'
|
||||||
import Tooltip from '../../../components/Tooltip'
|
import Tooltip from '../../../components/Tooltip'
|
||||||
|
import WeeklyDesignMocks from './WeeklyDesignMocks'
|
||||||
import { WEEKLY_BOSSES, MONTHLY_BOSSES, LIBERATION_BOSS_IMAGE_BASE, calcPoints } from '../data'
|
import { WEEKLY_BOSSES, MONTHLY_BOSSES, LIBERATION_BOSS_IMAGE_BASE, calcPoints } from '../data'
|
||||||
|
|
||||||
const PARTY_OPTIONS = [1, 2, 3, 4, 5, 6].map((n) => ({ value: n, label: `${n}인` }))
|
const PARTY_OPTIONS = [1, 2, 3, 4, 5, 6].map((n) => ({ value: n, label: `${n}인` }))
|
||||||
|
|
@ -16,7 +17,7 @@ function diffLabel(d, party) {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function BossRow({ boss, sel, onChange, monthly = false }) {
|
export function BossRow({ boss, sel, onChange, monthly = false, showDone = true }) {
|
||||||
const disabled = sel.difficulty === 'none'
|
const disabled = sel.difficulty === 'none'
|
||||||
const difficultyOptions = [NONE_DIFFICULTY, ...boss.difficulties]
|
const difficultyOptions = [NONE_DIFFICULTY, ...boss.difficulties]
|
||||||
.map((d) => ({ value: d.key, label: diffLabel(d, sel.party) }))
|
.map((d) => ({ value: d.key, label: diffLabel(d, sel.party) }))
|
||||||
|
|
@ -49,6 +50,7 @@ function BossRow({ boss, sel, onChange, monthly = false }) {
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
{showDone && (
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
|
|
@ -63,13 +65,12 @@ function BossRow({ boss, sel, onChange, monthly = false }) {
|
||||||
>
|
>
|
||||||
{sel.done ? '완료' : '미완료'}
|
{sel.done ? '완료' : '미완료'}
|
||||||
</button>
|
</button>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function WeeklyDefault({ weekly, onChange, totalWeekly, totalMonthly }) {
|
export default function WeeklyDefault({ weekly, onChange, totalWeekly, totalMonthly, mode = 'simple' }) {
|
||||||
const [mode, setMode] = useState('simple') // 'simple' | 'weekly'
|
|
||||||
|
|
||||||
const updateBoss = (key, patch) => {
|
const updateBoss = (key, patch) => {
|
||||||
onChange({ ...weekly, bosses: { ...weekly.bosses, [key]: { ...weekly.bosses[key], ...patch } } })
|
onChange({ ...weekly, bosses: { ...weekly.bosses, [key]: { ...weekly.bosses[key], ...patch } } })
|
||||||
}
|
}
|
||||||
|
|
@ -81,15 +82,7 @@ export default function WeeklyDefault({ weekly, onChange, totalWeekly, totalMont
|
||||||
<div className="max-w-3xl mx-auto rounded-2xl border border-white/10 bg-gray-900/60 p-6 space-y-4">
|
<div className="max-w-3xl mx-auto rounded-2xl border border-white/10 bg-gray-900/60 p-6 space-y-4">
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<div className="text-lg font-semibold text-emerald-300">주간 보스 설정</div>
|
<div className="text-lg font-semibold text-emerald-300">주간 보스 설정</div>
|
||||||
<div className="inline-flex rounded-lg border border-white/10 bg-gray-950 p-0.5">
|
<div className="flex items-baseline text-sm text-gray-400 gap-3">
|
||||||
<TabButton active={mode === 'simple'} onClick={() => setMode('simple')}>단순 계산</TabButton>
|
|
||||||
<TabButton active={mode === 'weekly'} onClick={() => setMode('weekly')}>주차별 계산</TabButton>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{mode === 'simple' ? (
|
|
||||||
<>
|
|
||||||
<div className="flex items-baseline justify-end text-sm text-gray-400 gap-3">
|
|
||||||
<span>
|
<span>
|
||||||
주간 획득 <span className="text-emerald-300 font-semibold tabular-nums">+{totalWeekly}</span>
|
주간 획득 <span className="text-emerald-300 font-semibold tabular-nums">+{totalWeekly}</span>
|
||||||
</span>
|
</span>
|
||||||
|
|
@ -97,6 +90,9 @@ export default function WeeklyDefault({ weekly, onChange, totalWeekly, totalMont
|
||||||
월간 획득 <span className="text-amber-300 font-semibold tabular-nums">+{totalMonthly}</span>
|
월간 획득 <span className="text-amber-300 font-semibold tabular-nums">+{totalMonthly}</span>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{mode === 'simple' ? (
|
||||||
<div className="divide-y divide-white/5">
|
<div className="divide-y divide-white/5">
|
||||||
{WEEKLY_BOSSES.map((boss) => (
|
{WEEKLY_BOSSES.map((boss) => (
|
||||||
<BossRow
|
<BossRow
|
||||||
|
|
@ -116,28 +112,9 @@ export default function WeeklyDefault({ weekly, onChange, totalWeekly, totalMont
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</>
|
|
||||||
) : (
|
) : (
|
||||||
<div className="py-12 text-center text-sm text-gray-500">
|
<WeeklyDesignMocks />
|
||||||
주차별 계산 UI 준비 중
|
|
||||||
</div>
|
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function TabButton({ active, onClick, children }) {
|
|
||||||
return (
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={onClick}
|
|
||||||
className={`px-3 h-8 rounded-md text-sm font-medium transition ${
|
|
||||||
active
|
|
||||||
? 'bg-emerald-500/20 text-emerald-300'
|
|
||||||
: 'text-gray-400 hover:text-gray-200'
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
{children}
|
|
||||||
</button>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,166 @@
|
||||||
|
import { useState } from 'react'
|
||||||
|
import { LIBERATION_BOSS_IMAGE_BASE, WEEKLY_BOSSES, MONTHLY_BOSSES } from '../data'
|
||||||
|
import { BossRow } from './WeeklyDefault'
|
||||||
|
|
||||||
|
const DIFF_BADGE = {
|
||||||
|
easy: { label: 'E', color: '#22c55e', border: 'rgba(34,197,94,0.4)', bg: 'rgba(34,197,94,0.15)' },
|
||||||
|
normal: { label: 'N', color: '#60a5fa', border: 'rgba(96,165,250,0.4)', bg: 'rgba(96,165,250,0.15)' },
|
||||||
|
hard: { label: 'H', color: '#f87171', border: 'rgba(248,113,113,0.4)', bg: 'rgba(248,113,113,0.15)' },
|
||||||
|
chaos: { label: 'C', color: '#c084fc', border: 'rgba(192,132,252,0.45)', bg: 'rgba(192,132,252,0.15)' },
|
||||||
|
extreme: { label: 'X', color: '#f59e0b', border: 'rgba(245,158,11,0.5)', bg: 'rgba(245,158,11,0.2)' },
|
||||||
|
}
|
||||||
|
|
||||||
|
// 임시 목업 데이터
|
||||||
|
const MOCK_WEEKS = [
|
||||||
|
{ n: 1, date: '4/14 - 4/16', diffs: { lotus: 'hard', damien: 'hard', lucid: 'hard', will: 'hard', dusk: 'chaos', jinhilla: 'hard', darknell: 'hard' }, monthly: true, earn: 1070, cumulative: 1070, current: true },
|
||||||
|
{ n: 2, date: '4/16 - 4/23', diffs: { lotus: 'hard', damien: 'hard', lucid: 'hard', will: 'hard', dusk: 'chaos', jinhilla: 'hard', darknell: 'hard' }, monthly: false, earn: 470, cumulative: 1540 },
|
||||||
|
{ n: 3, date: '4/23 - 4/30', diffs: { lotus: 'hard', damien: 'hard', lucid: 'normal', will: 'normal', dusk: 'normal', jinhilla: 'hard', darknell: 'hard' }, monthly: false, earn: 315, cumulative: 1855, custom: true },
|
||||||
|
{ n: 4, date: '4/30 - 5/7', diffs: { lotus: 'hard', damien: 'hard', lucid: 'hard', will: 'hard', dusk: 'chaos', jinhilla: 'hard', darknell: 'hard' }, monthly: true, earn: 1070, cumulative: 2925 },
|
||||||
|
{ n: 5, date: '5/7 - 5/14', diffs: { lotus: 'hard', damien: 'hard', lucid: 'hard', will: 'hard', dusk: 'chaos', jinhilla: 'hard', darknell: 'hard' }, monthly: false, earn: 470, cumulative: 3395 },
|
||||||
|
{ n: 6, date: '5/14 - 5/21', diffs: { lotus: 'hard', damien: 'hard', lucid: 'hard', will: 'hard', dusk: 'chaos', jinhilla: 'hard', darknell: 'hard' }, monthly: false, earn: 470, cumulative: 3865 },
|
||||||
|
{ n: 7, date: '5/21 - 5/28', diffs: { lotus: 'hard', damien: 'hard', lucid: 'hard', will: 'hard', dusk: 'chaos', jinhilla: 'hard', darknell: 'hard' }, monthly: false, earn: 470, cumulative: 4335 },
|
||||||
|
{ n: 8, date: '5/28 - 6/4', diffs: { lotus: 'hard', damien: 'hard', lucid: 'hard', will: 'hard', dusk: 'chaos', jinhilla: 'hard', darknell: 'hard' }, monthly: true, earn: 1070, cumulative: 5405 },
|
||||||
|
{ n: 9, date: '6/4 - 6/11', diffs: { lotus: 'hard', damien: 'hard', lucid: 'hard', will: 'hard', dusk: 'chaos', jinhilla: 'hard', darknell: 'hard' }, monthly: false, earn: 470, cumulative: 5875 },
|
||||||
|
{ n: 10, date: '6/11 - 6/18', diffs: { lotus: 'hard', damien: 'hard', lucid: 'hard', will: 'hard', dusk: 'chaos', jinhilla: 'hard', darknell: 'hard' }, monthly: false, earn: 470, cumulative: 6345 },
|
||||||
|
{ n: 11, date: '6/18 - 6/25', diffs: { lotus: 'hard', damien: 'hard', lucid: 'hard', will: 'hard', dusk: 'chaos', jinhilla: 'hard', darknell: 'hard' }, monthly: false, earn: 470, cumulative: 6500 },
|
||||||
|
]
|
||||||
|
|
||||||
|
function BossAvatar({ boss, difficulty, size = 40 }) {
|
||||||
|
const badge = DIFF_BADGE[difficulty]
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col items-center gap-1">
|
||||||
|
<div
|
||||||
|
className="rounded-md overflow-hidden bg-gray-900 border border-white/5"
|
||||||
|
style={{ width: size, height: size }}
|
||||||
|
>
|
||||||
|
<img src={`${LIBERATION_BOSS_IMAGE_BASE}/${boss.image}`} alt={boss.name} className="w-full h-full object-cover" />
|
||||||
|
</div>
|
||||||
|
{badge && (
|
||||||
|
<div
|
||||||
|
className="text-[10px] font-bold leading-none rounded flex items-center justify-center border"
|
||||||
|
style={{ width: 16, height: 16, color: badge.color, background: badge.bg, borderColor: badge.border }}
|
||||||
|
>
|
||||||
|
{badge.label}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 주차 편집 영역 (실제 state 바인딩은 이후 연결)
|
||||||
|
function WeekEditor({ week, monthlyAlreadyAssigned }) {
|
||||||
|
const initial = () => {
|
||||||
|
const bosses = {}
|
||||||
|
WEEKLY_BOSSES.forEach((b) => {
|
||||||
|
bosses[b.key] = { difficulty: week.diffs[b.key] || 'none', party: 1 }
|
||||||
|
})
|
||||||
|
return { bosses, blackMage: { difficulty: week.monthly ? 'hard' : 'none', party: 1 } }
|
||||||
|
}
|
||||||
|
const [config, setConfig] = useState(initial)
|
||||||
|
|
||||||
|
const updateBoss = (key, patch) => {
|
||||||
|
setConfig((prev) => ({ ...prev, bosses: { ...prev.bosses, [key]: { ...prev.bosses[key], ...patch } } }))
|
||||||
|
}
|
||||||
|
const updateBlackMage = (patch) => {
|
||||||
|
if (monthlyAlreadyAssigned) return
|
||||||
|
setConfig((prev) => ({ ...prev, blackMage: { ...prev.blackMage, ...patch } }))
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="space-y-3">
|
||||||
|
<div className="divide-y divide-white/5">
|
||||||
|
{WEEKLY_BOSSES.map((boss) => (
|
||||||
|
<BossRow
|
||||||
|
key={boss.key}
|
||||||
|
boss={boss}
|
||||||
|
sel={config.bosses[boss.key]}
|
||||||
|
onChange={(patch) => updateBoss(boss.key, patch)}
|
||||||
|
showDone={week.current}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
{/* 검은 마법사는 항상 표시, 같은 달에 다른 주차에 이미 배정된 경우 비활성 */}
|
||||||
|
<div className={monthlyAlreadyAssigned ? 'opacity-40 pointer-events-none' : ''}>
|
||||||
|
<BossRow
|
||||||
|
boss={MONTHLY_BOSSES[0]}
|
||||||
|
sel={monthlyAlreadyAssigned ? { difficulty: 'none', party: 1 } : config.blackMage}
|
||||||
|
onChange={updateBlackMage}
|
||||||
|
monthly
|
||||||
|
showDone={week.current}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{monthlyAlreadyAssigned && (
|
||||||
|
<div className="text-[11px] text-amber-400/80 px-3 py-2">
|
||||||
|
이번 달 검은 마법사는 다른 주차에 배정되어 있습니다.
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
{week.custom && (
|
||||||
|
<div className="flex justify-end">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="text-xs text-red-400 hover:text-red-300 transition"
|
||||||
|
>
|
||||||
|
기본 설정으로 되돌리기
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function WeeklyDesignMocks() {
|
||||||
|
const [expanded, setExpanded] = useState(3)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="space-y-2">
|
||||||
|
{MOCK_WEEKS.map((w) => (
|
||||||
|
<div
|
||||||
|
key={w.n}
|
||||||
|
className={`rounded-xl border transition ${
|
||||||
|
w.custom ? 'border-emerald-500/30 bg-emerald-500/[0.03]' : 'border-white/5 bg-gray-950/30'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => setExpanded(expanded === w.n ? null : w.n)}
|
||||||
|
className="w-full flex items-center gap-4 px-4 py-3.5 hover:bg-white/[0.02] transition text-left"
|
||||||
|
>
|
||||||
|
<div className="w-12 text-center shrink-0">
|
||||||
|
<div className="text-[11px] text-gray-500 leading-tight">주차</div>
|
||||||
|
<div className={`text-xl font-extrabold tabular-nums leading-tight ${w.custom ? 'text-emerald-300' : 'text-gray-200'}`}>
|
||||||
|
{w.n}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="text-sm text-gray-400 tabular-nums w-24 shrink-0">{w.date}</div>
|
||||||
|
|
||||||
|
<div className="flex-1 flex items-center gap-2">
|
||||||
|
{WEEKLY_BOSSES.map((b) => (
|
||||||
|
<BossAvatar key={b.key} boss={b} difficulty={w.diffs[b.key]} size={40} />
|
||||||
|
))}
|
||||||
|
{w.monthly && (
|
||||||
|
<BossAvatar boss={MONTHLY_BOSSES[0]} difficulty="hard" size={40} />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="text-right shrink-0">
|
||||||
|
<div className="text-lg font-bold text-emerald-300 tabular-nums leading-tight">+{w.earn}</div>
|
||||||
|
<div className="text-[11px] text-gray-500 tabular-nums">누적 {w.cumulative.toLocaleString()}</div>
|
||||||
|
</div>
|
||||||
|
<svg
|
||||||
|
width="16" height="16" viewBox="0 0 12 12" fill="none"
|
||||||
|
className={`text-gray-500 transition-transform shrink-0 ${expanded === w.n ? 'rotate-180' : ''}`}
|
||||||
|
>
|
||||||
|
<path d="M3 4.5L6 7.5L9 4.5" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
{expanded === w.n && (
|
||||||
|
<div className="border-t border-white/5 px-3 py-3 bg-gray-950/30">
|
||||||
|
<WeekEditor week={w} monthlyAlreadyAssigned={!w.monthly} />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
Loading…
Add table
Reference in a new issue