diff --git a/frontend/src/features/liberation/Liberation.jsx b/frontend/src/features/liberation/Liberation.jsx
index 66af2b6..eba1c99 100644
--- a/frontend/src/features/liberation/Liberation.jsx
+++ b/frontend/src/features/liberation/Liberation.jsx
@@ -1,4 +1,4 @@
-import { useState, useMemo, useEffect } from 'react'
+import { useState, useEffect } from 'react'
import { useQuery } from '@tanstack/react-query'
import dayjs from 'dayjs'
import { api } from '../../api/client'
@@ -8,11 +8,9 @@ import {
WEEKLY_BOSSES,
MONTHLY_BOSSES,
calcPoints,
- addWeeks,
formatDate,
todayKST,
} from './data'
-import WeekCard from './components/WeekCard'
import QuestSelector from './components/QuestSelector'
import PointsInput from './components/PointsInput'
import ProgressBar from './components/ProgressBar'
@@ -23,13 +21,6 @@ import { useLayout } from '../../components/Layout'
const STORAGE_KEY = 'maple-liberation'
-function makeEmptyWeek(startDate) {
- return {
- startDate: dayjs(startDate).toISOString(),
- ...makeEmptyWeekly(),
- }
-}
-
function makeEmptyWeekly() {
const bosses = {}
WEEKLY_BOSSES.forEach((b) => {
@@ -69,9 +60,6 @@ function calcMonthlyEarn(weekData) {
return bossEarn(MONTHLY_BOSSES[0], weekData.blackMage)
}
-function calcMonthlyDoneEarn(weekData) {
- return weekData.blackMage?.done ? bossEarn(MONTHLY_BOSSES[0], weekData.blackMage) : 0
-}
export default function Liberation() {
const { setFullscreen } = useLayout()
@@ -98,8 +86,6 @@ export default function Liberation() {
currentPoints: 0,
startDate: dayjs(todayKST()).toISOString(),
weekly: makeEmptyWeekly(),
- weekOverrides: {},
- weeks: [makeEmptyWeek(todayKST())],
schedulerWeeks: [{ id: 1, config: makeEmptyWeekly() }],
})
@@ -112,11 +98,9 @@ export default function Liberation() {
if (!parsed.calcMode) {
if (!parsed.weekly) parsed.weekly = makeEmptyWeekly()
if (!parsed.startDate) parsed.startDate = dayjs(todayKST()).toISOString()
- if (!parsed.weekOverrides) parsed.weekOverrides = {}
if (!parsed.schedulerWeeks) parsed.schedulerWeeks = [{ id: 1, config: makeEmptyWeekly() }]
return { calcMode: 'simple', simple: parsed, weekly: makeInitialSlot() }
}
- // 새 구조에서 schedulerWeeks 누락 시 채움
;['simple', 'weekly'].forEach((k) => {
if (parsed[k] && !parsed[k].schedulerWeeks) {
parsed[k].schedulerWeeks = [{ id: 1, config: makeEmptyWeekly() }]
@@ -142,46 +126,6 @@ export default function Liberation() {
localStorage.setItem(STORAGE_KEY, JSON.stringify(root))
}, [root])
- // 주차별 계산
- const progressByWeek = useMemo(() => {
- const result = []
- const startConsumedBefore = GENESIS_CHAPTERS
- .slice(0, state.startChapter)
- .reduce((s, c) => s + c.required, 0)
- const currentChapterCap = GENESIS_CHAPTERS[state.startChapter]?.required ?? 0
- const clampedCurrent = Math.min(state.currentPoints, currentChapterCap)
- let totalAccumulated = startConsumedBefore + clampedCurrent
-
- for (const week of state.weeks) {
- const earned = calcWeekPoints(week)
- totalAccumulated += earned
-
- let temp = totalAccumulated
- let chapterIdx = 0
- while (chapterIdx < GENESIS_CHAPTERS.length && temp >= GENESIS_CHAPTERS[chapterIdx].required) {
- temp -= GENESIS_CHAPTERS[chapterIdx].required
- chapterIdx++
- }
-
- const isCompleted = totalAccumulated >= GENESIS_TOTAL
- const chapterInfo = isCompleted
- ? { name: '완료', current: GENESIS_TOTAL, required: GENESIS_TOTAL }
- : {
- name: GENESIS_CHAPTERS[chapterIdx]?.boss || '',
- current: temp,
- required: GENESIS_CHAPTERS[chapterIdx]?.required || 0,
- }
-
- result.push({
- points: earned,
- cumulative: totalAccumulated,
- completed: isCompleted,
- chapterInfo,
- })
- }
- return result
- }, [state])
-
// 포인트 이월 계산: 현재 퀘스트의 required를 초과하면 자동으로 다음 퀘스트로 넘어감
const priorConsumed = GENESIS_CHAPTERS
.slice(0, state.startChapter)
@@ -359,48 +303,12 @@ export default function Liberation() {
const completionDate = computeCompletionDate()
const isDone = completionDate !== null
- const updateWeek = (idx, newWeekData) => {
- setState((prev) => ({
- ...prev,
- weeks: prev.weeks.map((w, i) => (i === idx ? newWeekData : w)),
- }))
- }
-
- const addWeek = () => {
- setState((prev) => {
- const lastWeek = prev.weeks[prev.weeks.length - 1]
- const nextStart = addWeeks(lastWeek.startDate, 1)
- return {
- ...prev,
- weeks: [...prev.weeks, { ...lastWeek, startDate: dayjs(nextStart).toISOString() }],
- }
- })
- }
-
- const removeWeek = (idx) => {
- setState((prev) => ({ ...prev, weeks: prev.weeks.filter((_, i) => i !== idx) }))
- }
-
const [resetOpen, setResetOpen] = useState(false)
const doReset = () => {
setState(makeInitialSlot())
setResetOpen(false)
}
- const setFirstWeekDate = (dateStr) => {
- setState((prev) => {
- const weeks = prev.weeks.map((w, i) => ({
- ...w,
- startDate: dayjs(addWeeks(dateStr, i)).toISOString(),
- }))
- return { ...prev, weeks }
- })
- }
-
- const totalCumulative = progressByWeek[progressByWeek.length - 1]?.cumulative
- || (GENESIS_CHAPTERS.slice(0, state.startChapter).reduce((s, c) => s + c.required, 0) + state.currentPoints)
- const overallProgress = Math.min((totalCumulative / GENESIS_TOTAL) * 100, 100)
-
return (
{/* 해방 종류 탭 */}
diff --git a/frontend/src/features/liberation/components/WeekCard.jsx b/frontend/src/features/liberation/components/WeekCard.jsx
deleted file mode 100644
index 9bd1259..0000000
--- a/frontend/src/features/liberation/components/WeekCard.jsx
+++ /dev/null
@@ -1,134 +0,0 @@
-import Select from '../../../components/Select'
-import Checkbox from '../../../components/Checkbox'
-import Tooltip from '../../../components/Tooltip'
-import { WEEKLY_BOSSES, MONTHLY_BOSSES, BOSS_IMAGE_BASE, calcPoints, formatDate } from '../data'
-
-const PARTY_OPTIONS = [1, 2, 3, 4, 5, 6].map((n) => ({ value: n, label: `${n}인` }))
-
-/**
- * week: { startDate, bosses: { [bossKey]: { enabled, difficulty, party } }, includeBlackMage: {enabled, difficulty, party} }
- */
-export default function WeekCard({ weekNumber, weekData, cumulativePoints, currentChapter, chapterInfo, onChange, weekProgress }) {
- const totalThisWeek = weekProgress.points
- const updateBoss = (bossKey, patch) => {
- const nextBosses = { ...weekData.bosses, [bossKey]: { ...weekData.bosses[bossKey], ...patch } }
- onChange({ ...weekData, bosses: nextBosses })
- }
-
- const updateBlackMage = (patch) => {
- onChange({ ...weekData, blackMage: { ...weekData.blackMage, ...patch } })
- }
-
- return (
-
- {/* 헤더: 주차 번호 + 날짜 + 이번 주 획득 + 누적 */}
-
-
-
{weekNumber}주차
-
{formatDate(weekData.startDate)}
-
-
-
- 획득 +{totalThisWeek}
-
-
- 누적 {cumulativePoints}
-
- {chapterInfo && (
-
- {chapterInfo.name} {chapterInfo.current}/{chapterInfo.required}
-
- )}
-
-
-
- {/* 보스 그리드 */}
-
- {WEEKLY_BOSSES.map((boss) => {
- const sel = weekData.bosses[boss.key] || { enabled: false, difficulty: boss.difficulties[0].key, party: 1 }
- const diff = boss.difficulties.find((d) => d.key === sel.difficulty) || boss.difficulties[0]
- const earned = sel.enabled ? calcPoints(diff.points, sel.party) : 0
-
- return (
-
-
-
updateBoss(boss.key, { enabled: v })}
- size="sm"
- />
-
-
-
- {boss.name}
- {earned > 0 && (
- +{earned}
- )}
-
- {sel.enabled && (
-
- updateBoss(boss.key, { difficulty: v })}
- options={boss.difficulties.map((d) => ({ value: d.key, label: `${d.label} +${d.points}` }))}
- className="flex-1"
- />
- updateBoss(boss.key, { party: v })}
- options={PARTY_OPTIONS}
- className="w-16"
- />
-
- )}
-
- )
- })}
-
- {/* 검은 마법사 (월 1회) */}
- {MONTHLY_BOSSES.map((boss) => {
- const sel = weekData.blackMage || { enabled: false, difficulty: boss.difficulties[0].key, party: 1 }
- const diff = boss.difficulties.find((d) => d.key === sel.difficulty) || boss.difficulties[0]
- const earned = sel.enabled ? calcPoints(diff.points, sel.party) : 0
-
- return (
-
-
-
updateBlackMage({ enabled: v })}
- size="sm"
- />
-
-
-
- {boss.name} 월간
- {earned > 0 && (
- +{earned}
- )}
-
- {sel.enabled && (
-
- updateBlackMage({ difficulty: v })}
- options={boss.difficulties.map((d) => ({ value: d.key, label: `${d.label} +${d.points}` }))}
- className="flex-1"
- />
- updateBlackMage({ party: v })}
- options={PARTY_OPTIONS}
- className="w-16"
- />
-
- )}
-
- )
- })}
-
-
- )
-}
diff --git a/frontend/src/features/liberation/components/WeeklyDefault.jsx b/frontend/src/features/liberation/components/WeeklyDefault.jsx
index c1b11ed..2976f70 100644
--- a/frontend/src/features/liberation/components/WeeklyDefault.jsx
+++ b/frontend/src/features/liberation/components/WeeklyDefault.jsx
@@ -1,4 +1,3 @@
-import { useState } from 'react'
import Select from '../../../components/Select'
import Tooltip from '../../../components/Tooltip'
import WeeklyScheduler from './WeeklyScheduler'
diff --git a/frontend/src/features/liberation/components/WeeklyDesignMocks.jsx b/frontend/src/features/liberation/components/WeeklyDesignMocks.jsx
deleted file mode 100644
index 5cfa053..0000000
--- a/frontend/src/features/liberation/components/WeeklyDesignMocks.jsx
+++ /dev/null
@@ -1,166 +0,0 @@
-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 (
-
-
-
-
- {badge && (
-
- {badge.label}
-
- )}
-
- )
-}
-
-// 주차 편집 영역 (실제 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 (
-
-
- {WEEKLY_BOSSES.map((boss) => (
-
updateBoss(boss.key, patch)}
- showDone={week.current}
- />
- ))}
- {/* 검은 마법사는 항상 표시, 같은 달에 다른 주차에 이미 배정된 경우 비활성 */}
-
-
-
- {monthlyAlreadyAssigned && (
-
- 이번 달 검은 마법사는 다른 주차에 배정되어 있습니다.
-
- )}
-
- {week.custom && (
-
-
- 기본 설정으로 되돌리기
-
-
- )}
-
- )
-}
-
-export default function WeeklyDesignMocks() {
- const [expanded, setExpanded] = useState(3)
-
- return (
-
- {MOCK_WEEKS.map((w) => (
-
-
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"
- >
-
- {w.date}
-
-
- {WEEKLY_BOSSES.map((b) => (
-
- ))}
- {w.monthly && (
-
- )}
-
-
-
-
+{w.earn}
-
누적 {w.cumulative.toLocaleString()}
-
-
-
-
-
-
- {expanded === w.n && (
-
-
-
- )}
-
- ))}
-
- )
-}
diff --git a/frontend/src/features/liberation/data.js b/frontend/src/features/liberation/data.js
index fbba23b..e1ddd53 100644
--- a/frontend/src/features/liberation/data.js
+++ b/frontend/src/features/liberation/data.js
@@ -95,15 +95,6 @@ export function calcPoints(basePoints, partySize) {
return Math.floor(basePoints / partySize)
}
-// 목요일 기준 주차 계산 (KST)
-// 이번 주 목요일 자정 = 이번 주의 시작
-export function getThursdayOfWeek(date) {
- const d = dayjs(date).tz(KST)
- const day = d.day() // 0=일, 4=목
- const diff = (day - 4 + 7) % 7
- return d.subtract(diff, 'day').startOf('day').toDate()
-}
-
import dayjs from 'dayjs'
import utc from 'dayjs/plugin/utc'
import timezone from 'dayjs/plugin/timezone'
@@ -117,10 +108,6 @@ export function formatDate(date) {
return dayjs(date).tz(KST).format('YYYY-MM-DD')
}
-export function addWeeks(date, weeks) {
- return dayjs(date).tz(KST).add(weeks, 'week').toDate()
-}
-
export function todayKST() {
return dayjs().tz(KST).startOf('day').toDate()
}