구조 개편 2단계: features 내부에 pc/ 폴더 생성 + 이동
- features/boss-crystal/pc/: BossCrystal, BossCrystalAdmin, admin/, user/
- features/symbol/pc/: Symbol, SymbolAdmin, admin/
- features/liberation/pc/: Liberation, components/
- store.js, data.js는 feature 루트에 유지 (device 공용)
- registry.js: import.meta.glob 패턴을 './*/\{pc,mobile\}/*.jsx' 로 변경
- getMobileComponent 추가
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
4789c56dfa
commit
b423d0ac82
18 changed files with 62 additions and 56 deletions
|
|
@ -1,10 +1,10 @@
|
|||
import { useEffect, useLayoutEffect } from 'react'
|
||||
import { useQuery, useQueries } from '@tanstack/react-query'
|
||||
import { api } from '../../api/client'
|
||||
import { useLayout } from '../../components/pc/Layout'
|
||||
import { api } from '../../../api/client'
|
||||
import { useLayout } from '../../../components/pc/Layout'
|
||||
import CharacterPanel from './user/CharacterPanel'
|
||||
import BossSelector from './user/BossSelector'
|
||||
import { useBossStore } from './store'
|
||||
import { useBossStore } from '../store'
|
||||
|
||||
const MAX_PER_CHARACTER = 12
|
||||
|
||||
|
|
@ -1,11 +1,11 @@
|
|||
import { useState, useEffect, useRef } from 'react'
|
||||
import { useNavigate, useParams } from 'react-router-dom'
|
||||
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'
|
||||
import { api } from '../../../api/client'
|
||||
import ConfirmDialog from '../../../components/common/ConfirmDialog'
|
||||
import Checkbox from '../../../components/common/Checkbox'
|
||||
import Select from '../../../components/common/Select'
|
||||
import { useAuthStore } from '../../../stores/auth'
|
||||
import { api } from '../../../../api/client'
|
||||
import ConfirmDialog from '../../../../components/common/ConfirmDialog'
|
||||
import Checkbox from '../../../../components/common/Checkbox'
|
||||
import Select from '../../../../components/common/Select'
|
||||
import { useAuthStore } from '../../../../stores/auth'
|
||||
import { DIFFICULTIES, formatMeso, getDifficultyImageUrl } from './constants'
|
||||
|
||||
const PARTY_OPTIONS = [1, 2, 3, 4, 5, 6].map((n) => ({ value: n, label: `${n}인` }))
|
||||
|
|
@ -10,8 +10,8 @@ import {
|
|||
arrayMove,
|
||||
} from '@dnd-kit/sortable'
|
||||
import { CSS } from '@dnd-kit/utilities'
|
||||
import { api } from '../../../api/client'
|
||||
import Tooltip from '../../../components/common/Tooltip'
|
||||
import { api } from '../../../../api/client'
|
||||
import Tooltip from '../../../../components/common/Tooltip'
|
||||
import { DIFFICULTIES, formatMeso, getDifficultyBadgeStyle } from './constants'
|
||||
|
||||
function BossCardContent({ boss, dragging = false }) {
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
import Select from '../../../components/common/Select'
|
||||
import Select from '../../../../components/common/Select'
|
||||
import { DIFFICULTIES, formatMeso } from '../admin/constants'
|
||||
|
||||
const LABEL_EN = { easy: 'EASY', normal: 'NORMAL', hard: 'HARD', chaos: 'CHAOS', extreme: 'EXTREME' }
|
||||
|
|
@ -2,11 +2,11 @@ import { useState } from 'react'
|
|||
import { useMutation } from '@tanstack/react-query'
|
||||
import { Reorder, useDragControls } from 'framer-motion'
|
||||
import { OverlayScrollbarsComponent } from 'overlayscrollbars-react'
|
||||
import { api } from '../../../api/client'
|
||||
import ConfirmDialog from '../../../components/common/ConfirmDialog'
|
||||
import Tooltip from '../../../components/common/Tooltip'
|
||||
import CharacterSuggestDropdown from '../../../components/common/CharacterSuggestDropdown'
|
||||
import { useFitText } from '../../../hooks/useFitText'
|
||||
import { api } from '../../../../api/client'
|
||||
import ConfirmDialog from '../../../../components/common/ConfirmDialog'
|
||||
import Tooltip from '../../../../components/common/Tooltip'
|
||||
import CharacterSuggestDropdown from '../../../../components/common/CharacterSuggestDropdown'
|
||||
import { useFitText } from '../../../../hooks/useFitText'
|
||||
import { DIFFICULTIES, formatMeso, getDifficultyBadgeStyle } from '../admin/constants'
|
||||
|
||||
const MAX_PER_CHARACTER = 12
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
import { useState, useEffect, useLayoutEffect } from 'react'
|
||||
import { useQuery } from '@tanstack/react-query'
|
||||
import dayjs from 'dayjs'
|
||||
import { api } from '../../api/client'
|
||||
import { api } from '../../../api/client'
|
||||
import {
|
||||
GENESIS_CHAPTERS,
|
||||
GENESIS_TOTAL,
|
||||
|
|
@ -10,15 +10,15 @@ import {
|
|||
calcPoints,
|
||||
formatDate,
|
||||
todayKST,
|
||||
} from './data'
|
||||
import { useLiberationStore } from './store'
|
||||
} from '../data'
|
||||
import { useLiberationStore } from '../store'
|
||||
import QuestSelector from './components/QuestSelector'
|
||||
import PointsInput from './components/PointsInput'
|
||||
import ProgressBar from './components/ProgressBar'
|
||||
import WeeklyDefault from './components/WeeklyDefault'
|
||||
import DatePicker from '../../components/common/DatePicker'
|
||||
import ConfirmDialog from '../../components/common/ConfirmDialog'
|
||||
import { useLayout } from '../../components/pc/Layout'
|
||||
import DatePicker from '../../../components/common/DatePicker'
|
||||
import ConfirmDialog from '../../../components/common/ConfirmDialog'
|
||||
import { useLayout } from '../../../components/pc/Layout'
|
||||
|
||||
function makeEmptyWeekly() {
|
||||
const bosses = {}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
import { GENESIS_CHAPTERS, GENESIS_TOTAL, QUEST_BOSS_IMAGE_BASE } from '../data'
|
||||
import { GENESIS_CHAPTERS, GENESIS_TOTAL, QUEST_BOSS_IMAGE_BASE } from '../../data'
|
||||
|
||||
const DOW = ['일', '월', '화', '수', '목', '금', '토']
|
||||
function formatKoreanDate(s) {
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
import { useState, useEffect, useRef } from 'react'
|
||||
import { motion, AnimatePresence } from 'framer-motion'
|
||||
import { GENESIS_CHAPTERS, QUEST_BOSS_IMAGE_BASE } from '../data'
|
||||
import { GENESIS_CHAPTERS, QUEST_BOSS_IMAGE_BASE } from '../../data'
|
||||
|
||||
/**
|
||||
* 진행 중인 퀘스트 드롭다운
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
import Select from '../../../components/common/Select'
|
||||
import Tooltip from '../../../components/common/Tooltip'
|
||||
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 { 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 NONE_DIFFICULTY = { key: 'none', label: '격파 불가', points: 0 }
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
import { useState } from 'react'
|
||||
import { motion, AnimatePresence } from 'framer-motion'
|
||||
import dayjs from 'dayjs'
|
||||
import { LIBERATION_BOSS_IMAGE_BASE, WEEKLY_BOSSES, MONTHLY_BOSSES, calcPoints } from '../data'
|
||||
import { LIBERATION_BOSS_IMAGE_BASE, WEEKLY_BOSSES, MONTHLY_BOSSES, calcPoints } from '../../data'
|
||||
import { BossRow } from './WeeklyDefault'
|
||||
|
||||
function bossEarn(boss, sel) {
|
||||
|
|
@ -1,18 +1,18 @@
|
|||
/**
|
||||
* 기능 자동 등록 시스템
|
||||
*
|
||||
* - features/{kebab-case}/{PascalCase}.jsx : 사용자 페이지
|
||||
* - features/{kebab-case}/{PascalCase}Admin.jsx : 관리자 페이지
|
||||
* - features/{kebab-case}/pc/{PascalCase}.jsx : PC 사용자 페이지
|
||||
* - features/{kebab-case}/pc/{PascalCase}Admin.jsx : PC 관리자 페이지
|
||||
* - features/{kebab-case}/mobile/{PascalCase}.jsx : 모바일 사용자 페이지
|
||||
*
|
||||
* 예시:
|
||||
* /boss-crystal → features/boss-crystal/BossCrystal.jsx
|
||||
* /admin/boss-crystal → features/boss-crystal/BossCrystalAdmin.jsx
|
||||
* /boss-crystal → features/boss-crystal/pc/BossCrystal.jsx
|
||||
* /admin/boss-crystal → features/boss-crystal/pc/BossCrystalAdmin.jsx
|
||||
*/
|
||||
|
||||
import { lazy } from 'react'
|
||||
|
||||
// Vite의 import.meta.glob으로 features 폴더 전체를 스캔
|
||||
const userPages = import.meta.glob('./*/*.jsx')
|
||||
const pages = import.meta.glob('./*/{pc,mobile}/*.jsx')
|
||||
|
||||
function slugToPascal(slug) {
|
||||
return slug
|
||||
|
|
@ -21,33 +21,39 @@ function slugToPascal(slug) {
|
|||
.join('')
|
||||
}
|
||||
|
||||
// 컴포넌트 캐시 - 동일 slug에 대해 항상 같은 컴포넌트 인스턴스 반환
|
||||
// (매 렌더마다 새 lazy() 생성하면 React가 unmount/remount하면서 화면 갱신이 깨짐)
|
||||
const userCache = new Map()
|
||||
const adminCache = new Map()
|
||||
const userPcCache = new Map()
|
||||
const adminPcCache = new Map()
|
||||
const userMobileCache = new Map()
|
||||
|
||||
function loadCached(cache, slug, suffix) {
|
||||
function loadCached(cache, slug, device, suffix) {
|
||||
if (cache.has(slug)) return cache.get(slug)
|
||||
const pascal = slugToPascal(slug)
|
||||
const path = `./${slug}/${pascal}${suffix}.jsx`
|
||||
const loader = userPages[path]
|
||||
const path = `./${slug}/${device}/${pascal}${suffix}.jsx`
|
||||
const loader = pages[path]
|
||||
const component = loader ? lazy(loader) : null
|
||||
cache.set(slug, component)
|
||||
return component
|
||||
}
|
||||
|
||||
/**
|
||||
* slug에 해당하는 사용자 페이지 컴포넌트 반환
|
||||
* slug에 해당하는 PC 사용자 페이지 컴포넌트 반환
|
||||
*/
|
||||
export function getUserComponent(slug) {
|
||||
return loadCached(userCache, slug, '')
|
||||
return loadCached(userPcCache, slug, 'pc', '')
|
||||
}
|
||||
|
||||
/**
|
||||
* slug에 해당하는 관리자 페이지 컴포넌트 반환
|
||||
* slug에 해당하는 관리자 페이지 컴포넌트 반환 (PC 전용)
|
||||
*/
|
||||
export function getAdminComponent(slug) {
|
||||
return loadCached(adminCache, slug, 'Admin')
|
||||
return loadCached(adminPcCache, slug, 'pc', 'Admin')
|
||||
}
|
||||
|
||||
/**
|
||||
* slug에 해당하는 모바일 사용자 페이지 컴포넌트 반환
|
||||
*/
|
||||
export function getMobileComponent(slug) {
|
||||
return loadCached(userMobileCache, slug, 'mobile', '')
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -3,12 +3,12 @@ import { useQuery, useQueries, useMutation } from '@tanstack/react-query'
|
|||
import dayjs from 'dayjs'
|
||||
import utc from 'dayjs/plugin/utc'
|
||||
import timezone from 'dayjs/plugin/timezone'
|
||||
import { api } from '../../api/client'
|
||||
import { useLayout } from '../../components/pc/Layout'
|
||||
import Select from '../../components/common/Select'
|
||||
import Tooltip from '../../components/common/Tooltip'
|
||||
import CharacterSuggestDropdown from '../../components/common/CharacterSuggestDropdown'
|
||||
import { useSymbolStore } from './store'
|
||||
import { api } from '../../../api/client'
|
||||
import { useLayout } from '../../../components/pc/Layout'
|
||||
import Select from '../../../components/common/Select'
|
||||
import Tooltip from '../../../components/common/Tooltip'
|
||||
import CharacterSuggestDropdown from '../../../components/common/CharacterSuggestDropdown'
|
||||
import { useSymbolStore } from '../store'
|
||||
|
||||
dayjs.extend(utc)
|
||||
dayjs.extend(timezone)
|
||||
|
|
@ -1,10 +1,10 @@
|
|||
import { useState, useRef, useEffect } from 'react'
|
||||
import { useNavigate, useParams } from 'react-router-dom'
|
||||
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'
|
||||
import { api } from '../../../api/client'
|
||||
import Select from '../../../components/common/Select'
|
||||
import ConfirmDialog from '../../../components/common/ConfirmDialog'
|
||||
import { useAuthStore } from '../../../stores/auth'
|
||||
import { api } from '../../../../api/client'
|
||||
import Select from '../../../../components/common/Select'
|
||||
import ConfirmDialog from '../../../../components/common/ConfirmDialog'
|
||||
import { useAuthStore } from '../../../../stores/auth'
|
||||
|
||||
const TYPE_OPTIONS = [
|
||||
{ value: '아케인', label: '아케인' },
|
||||
|
|
@ -10,7 +10,7 @@ import {
|
|||
arrayMove,
|
||||
} from '@dnd-kit/sortable'
|
||||
import { CSS } from '@dnd-kit/utilities'
|
||||
import { api } from '../../../api/client'
|
||||
import { api } from '../../../../api/client'
|
||||
|
||||
const TYPE_STYLE = {
|
||||
'아케인': {
|
||||
Loading…
Add table
Reference in a new issue