diff --git a/frontend/src/features/liberation/pc/Destiny.jsx b/frontend/src/features/liberation/pc/Destiny.jsx index 76cf049..8a55665 100644 --- a/frontend/src/features/liberation/pc/Destiny.jsx +++ b/frontend/src/features/liberation/pc/Destiny.jsx @@ -1,9 +1,17 @@ import dayjs from 'dayjs' -import { DESTINY_CHAPTERS, DESTINY_QUEST_IMAGE_BASE, formatDate } from '../data' +import { + DESTINY_CHAPTERS, + DESTINY_QUEST_IMAGE_BASE, + DESTINY_BOSSES, + DESTINY_BOSS_IMAGE_BASE, + formatDate, +} from '../data' import { useLiberationStore } from '../store' +import { calcWeekPoints } from '../utils' import ProgressBar from './components/ProgressBar' import QuestSelector from './components/QuestSelector' import PointsInput from './components/PointsInput' +import WeeklyDefault from './components/WeeklyDefault' import DatePicker from '../../../components/common/DatePicker' export default function Destiny() { @@ -13,6 +21,8 @@ export default function Destiny() { const updateSlot = useLiberationStore((s) => s.updateDestinySlot) const setState = (updater) => updateSlot(updater) + const weeklyEarn = calcWeekPoints(state.weekly, DESTINY_BOSSES) + return ( <> {/* 계산 모드 탭 */} @@ -115,6 +125,31 @@ export default function Destiny() { + + {calcMode === 'simple' ? ( + setState((prev) => ({ ...prev, weekly: w }))} + totalWeekly={weeklyEarn} + remaining={0} + mode="simple" + hasScheduler={false} + /> + ) : ( +
+
주차별 계산 준비 중
+
단순 계산 탭을 이용해주세요.
+
+ )} ) } diff --git a/frontend/src/features/liberation/pc/Genesis.jsx b/frontend/src/features/liberation/pc/Genesis.jsx index 5f33f84..d3d2be7 100644 --- a/frontend/src/features/liberation/pc/Genesis.jsx +++ b/frontend/src/features/liberation/pc/Genesis.jsx @@ -3,8 +3,10 @@ import dayjs from 'dayjs' import { GENESIS_CHAPTERS, GENESIS_TOTAL, + WEEKLY_BOSSES, MONTHLY_BOSSES, QUEST_BOSS_IMAGE_BASE, + LIBERATION_BOSS_IMAGE_BASE, formatDate, } from '../data' import { useLiberationStore } from '../store' @@ -193,6 +195,9 @@ export default function Genesis() { setState((prev) => ({ ...prev, weekly: w }))} totalWeekly={headerWeekly} diff --git a/frontend/src/features/liberation/pc/components/WeeklyDefault.jsx b/frontend/src/features/liberation/pc/components/WeeklyDefault.jsx index 6578ed6..e4e3bf2 100644 --- a/frontend/src/features/liberation/pc/components/WeeklyDefault.jsx +++ b/frontend/src/features/liberation/pc/components/WeeklyDefault.jsx @@ -1,7 +1,7 @@ import Select from '../../../../components/common/Select' import Tooltip from '../../../../components/common/Tooltip' import WeeklyScheduler from './WeeklyScheduler' -import { WEEKLY_BOSSES, MONTHLY_BOSSES, LIBERATION_BOSS_IMAGE_BASE, calcPoints } from '../../data' +import { calcPoints } from '../../data' const PARTY_OPTIONS = [1, 2, 3, 4, 5, 6].map((n) => ({ value: n, label: `${n}인` })) const NONE_DIFFICULTY = { key: 'none', label: '격파 불가', points: 0 } @@ -16,7 +16,7 @@ function diffLabel(d, party) { ) } -export function BossRow({ boss, sel, onChange, monthly = false, showDone = true }) { +export function BossRow({ boss, sel, onChange, imageBase, monthly = false, showDone = true }) { const disabled = sel.difficulty === 'none' const difficultyOptions = [NONE_DIFFICULTY, ...boss.difficulties] .map((d) => ({ value: d.key, label: diffLabel(d, sel.party) })) @@ -24,7 +24,7 @@ export function BossRow({ boss, sel, onChange, monthly = false, showDone = true return (
- + {boss.name} @@ -81,7 +81,22 @@ export function BossRow({ boss, sel, onChange, monthly = false, showDone = true ) } -export default function WeeklyDefault({ weekly, onChange, totalWeekly, totalMonthly, remaining, mode = 'simple', startDate, weeks, onChangeWeeks }) { +export default function WeeklyDefault({ + bosses, + monthlyBosses = [], + imageBase, + weekly, + onChange, + totalWeekly, + totalMonthly = 0, + remaining, + mode = 'simple', + startDate, + weeks, + onChangeWeeks, + hasScheduler = true, + label = '주간 보스 설정', +}) { const updateBoss = (key, patch) => { onChange({ ...weekly, bosses: { ...weekly.bosses, [key]: { ...weekly.bosses[key], ...patch } } }) } @@ -99,13 +114,17 @@ export default function WeeklyDefault({ weekly, onChange, totalWeekly, totalMont }} >
-
주간 보스 설정
+
{label}
{mode === 'weekly' ? ( <> {totalWeekly} - + - {totalMonthly} + {monthlyBosses.length > 0 && ( + <> + + + {totalMonthly} + + )} / {(remaining ?? 0).toLocaleString()} @@ -115,9 +134,9 @@ export default function WeeklyDefault({ weekly, onChange, totalWeekly, totalMont
- {mode === 'simple' ? ( + {mode === 'simple' || !hasScheduler ? (
- {WEEKLY_BOSSES.map((boss, i) => ( + {bosses.map((boss, i) => (
0 ? 'border-t' : ''} @@ -127,10 +146,11 @@ export default function WeeklyDefault({ weekly, onChange, totalWeekly, totalMont boss={boss} sel={weekly.bosses[boss.key]} onChange={(patch) => updateBoss(boss.key, patch)} + imageBase={imageBase} />
))} - {MONTHLY_BOSSES.map((boss) => ( + {monthlyBosses.map((boss) => (
diff --git a/frontend/src/features/liberation/store.js b/frontend/src/features/liberation/store.js index a64c6da..9733091 100644 --- a/frontend/src/features/liberation/store.js +++ b/frontend/src/features/liberation/store.js @@ -1,7 +1,7 @@ import { create } from 'zustand' import { persist } from 'zustand/middleware' import dayjs from 'dayjs' -import { WEEKLY_BOSSES, MONTHLY_BOSSES, todayKST } from './data' +import { WEEKLY_BOSSES, MONTHLY_BOSSES, DESTINY_BOSSES, todayKST } from './data' function makeEmptyWeekly() { const bosses = {} @@ -14,6 +14,14 @@ function makeEmptyWeekly() { } } +function makeEmptyDestinyWeekly() { + const bosses = {} + DESTINY_BOSSES.forEach((b) => { + bosses[b.key] = { difficulty: 'none', party: 1, done: false } + }) + return { bosses } +} + function makeInitialSlot() { return { startChapter: 0, @@ -29,6 +37,7 @@ function makeInitialDestinySlot() { startChapter: 0, currentPoints: 0, startDate: dayjs(todayKST()).toISOString(), + weekly: makeEmptyDestinyWeekly(), } } @@ -72,7 +81,20 @@ export const useLiberationStore = create(persist( return { [key]: makeInitialDestinySlot() } }), }), - { name: 'maple-liberation' }, + { + name: 'maple-liberation', + version: 1, + migrate: (persisted) => { + if (!persisted) return persisted + // v0→v1: 데스티니 슬롯에 weekly 필드가 없으면 빈 값으로 채움 + const fill = (slot) => slot ? { ...slot, weekly: slot.weekly || makeEmptyDestinyWeekly() } : slot + return { + ...persisted, + destinySimple: fill(persisted.destinySimple), + destinyWeekly: fill(persisted.destinyWeekly), + } + }, + }, )) -export { makeEmptyWeekly, makeInitialSlot, makeInitialDestinySlot } +export { makeEmptyWeekly, makeEmptyDestinyWeekly, makeInitialSlot, makeInitialDestinySlot } diff --git a/frontend/src/features/liberation/utils.js b/frontend/src/features/liberation/utils.js index b34eaca..90177c9 100644 --- a/frontend/src/features/liberation/utils.js +++ b/frontend/src/features/liberation/utils.js @@ -11,17 +11,17 @@ export function bossEarn(boss, sel) { return calcPoints(d.points, sel.party) } -export function calcWeekPoints(weekData) { +export function calcWeekPoints(weekData, bosses = WEEKLY_BOSSES) { let points = 0 - WEEKLY_BOSSES.forEach((b) => { + bosses.forEach((b) => { points += bossEarn(b, weekData.bosses[b.key]) }) return points } -export function calcDoneEarn(weekData) { +export function calcDoneEarn(weekData, bosses = WEEKLY_BOSSES) { let points = 0 - WEEKLY_BOSSES.forEach((b) => { + bosses.forEach((b) => { const sel = weekData.bosses[b.key] if (sel?.done) points += bossEarn(b, sel) })