diff --git a/package-lock.json b/package-lock.json
index 900cb78..80e7396 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -8,6 +8,9 @@
"name": "frontend",
"version": "0.0.0",
"dependencies": {
+ "@dnd-kit/core": "^6.3.1",
+ "@dnd-kit/modifiers": "^9.0.0",
+ "@dnd-kit/sortable": "^10.0.0",
"@emotion/react": "^11.14.0",
"@emotion/styled": "^11.14.0",
"@mui/icons-material": "^7.1.0",
@@ -348,6 +351,73 @@
"node": ">=6.9.0"
}
},
+ "node_modules/@dnd-kit/accessibility": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/@dnd-kit/accessibility/-/accessibility-3.1.1.tgz",
+ "integrity": "sha512-2P+YgaXF+gRsIihwwY1gCsQSYnu9Zyj2py8kY5fFvUM1qm2WA2u639R6YNVfU4GWr+ZM5mqEsfHZZLoRONbemw==",
+ "license": "MIT",
+ "dependencies": {
+ "tslib": "^2.0.0"
+ },
+ "peerDependencies": {
+ "react": ">=16.8.0"
+ }
+ },
+ "node_modules/@dnd-kit/core": {
+ "version": "6.3.1",
+ "resolved": "https://registry.npmjs.org/@dnd-kit/core/-/core-6.3.1.tgz",
+ "integrity": "sha512-xkGBRQQab4RLwgXxoqETICr6S5JlogafbhNsidmrkVv2YRs5MLwpjoF2qpiGjQt8S9AoxtIV603s0GIUpY5eYQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@dnd-kit/accessibility": "^3.1.1",
+ "@dnd-kit/utilities": "^3.2.2",
+ "tslib": "^2.0.0"
+ },
+ "peerDependencies": {
+ "react": ">=16.8.0",
+ "react-dom": ">=16.8.0"
+ }
+ },
+ "node_modules/@dnd-kit/modifiers": {
+ "version": "9.0.0",
+ "resolved": "https://registry.npmjs.org/@dnd-kit/modifiers/-/modifiers-9.0.0.tgz",
+ "integrity": "sha512-ybiLc66qRGuZoC20wdSSG6pDXFikui/dCNGthxv4Ndy8ylErY0N3KVxY2bgo7AWwIbxDmXDg3ylAFmnrjcbVvw==",
+ "license": "MIT",
+ "dependencies": {
+ "@dnd-kit/utilities": "^3.2.2",
+ "tslib": "^2.0.0"
+ },
+ "peerDependencies": {
+ "@dnd-kit/core": "^6.3.0",
+ "react": ">=16.8.0"
+ }
+ },
+ "node_modules/@dnd-kit/sortable": {
+ "version": "10.0.0",
+ "resolved": "https://registry.npmjs.org/@dnd-kit/sortable/-/sortable-10.0.0.tgz",
+ "integrity": "sha512-+xqhmIIzvAYMGfBYYnbKuNicfSsk4RksY2XdmJhT+HAC01nix6fHCztU68jooFiMUB01Ky3F0FyOvhG/BZrWkg==",
+ "license": "MIT",
+ "dependencies": {
+ "@dnd-kit/utilities": "^3.2.2",
+ "tslib": "^2.0.0"
+ },
+ "peerDependencies": {
+ "@dnd-kit/core": "^6.3.0",
+ "react": ">=16.8.0"
+ }
+ },
+ "node_modules/@dnd-kit/utilities": {
+ "version": "3.2.2",
+ "resolved": "https://registry.npmjs.org/@dnd-kit/utilities/-/utilities-3.2.2.tgz",
+ "integrity": "sha512-+MKAJEOfaBe5SmV6t34p80MMKhjvUz0vRrvVJbPT0WElzaOJ/1xs+D+KDv+tD/NE5ujfrChEcshd4fLn0wpiqg==",
+ "license": "MIT",
+ "dependencies": {
+ "tslib": "^2.0.0"
+ },
+ "peerDependencies": {
+ "react": ">=16.8.0"
+ }
+ },
"node_modules/@emotion/babel-plugin": {
"version": "11.13.5",
"resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.13.5.tgz",
@@ -4774,6 +4844,12 @@
"typescript": ">=4.8.4"
}
},
+ "node_modules/tslib": {
+ "version": "2.8.1",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
+ "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
+ "license": "0BSD"
+ },
"node_modules/type-check": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
diff --git a/package.json b/package.json
index e435a0f..532dd90 100644
--- a/package.json
+++ b/package.json
@@ -10,6 +10,9 @@
"preview": "vite preview"
},
"dependencies": {
+ "@dnd-kit/core": "^6.3.1",
+ "@dnd-kit/modifiers": "^9.0.0",
+ "@dnd-kit/sortable": "^10.0.0",
"@emotion/react": "^11.14.0",
"@emotion/styled": "^11.14.0",
"@mui/icons-material": "^7.1.0",
diff --git a/src/lib/api.ts b/src/lib/api.ts
index 9c788fe..7f420d7 100644
--- a/src/lib/api.ts
+++ b/src/lib/api.ts
@@ -1,8 +1,8 @@
import axios from 'axios'
const api = axios.create({
- baseURL: 'https://api.biryu2000.kr',
- // baseURL: 'http://localhost:8000',
+ // baseURL: 'https://api.biryu2000.kr',
+ baseURL: 'http://localhost:8000',
})
// 요청 시 토큰 자동 추가
diff --git a/src/pages/CharacterList.tsx b/src/pages/CharacterList.tsx
index 60a2d38..94a0908 100644
--- a/src/pages/CharacterList.tsx
+++ b/src/pages/CharacterList.tsx
@@ -1,8 +1,25 @@
import { Box, Card, CardContent, Typography, Container, Grid } from '@mui/material'
import { useEffect, useState } from 'react'
-import { useNavigate, Link } from 'react-router-dom'
+import { useNavigate } from 'react-router-dom'
import api from '../lib/api'
+import {
+ DndContext,
+ closestCenter,
+ DragEndEvent,
+ PointerSensor,
+ useSensor,
+ useSensors,
+} from '@dnd-kit/core'
+import {
+ arrayMove,
+ SortableContext,
+ useSortable,
+ verticalListSortingStrategy,
+} from '@dnd-kit/sortable'
+import { CSS } from '@dnd-kit/utilities'
+import DragIndicatorIcon from '@mui/icons-material/DragIndicator'
+
interface Character {
id: number
name: string
@@ -11,61 +28,122 @@ interface Character {
combat_power?: number
}
+function SortableCharacterCard({ character }: { character: Character }) {
+ const navigate = useNavigate()
+ const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({ id: character.id })
+
+ const style = {
+ transform: CSS.Transform.toString(transform),
+ transition,
+ zIndex: isDragging ? 1000 : 'auto',
+ position: 'relative' as const,
+ }
+
+ return (
+
+ navigate(`/characters/${character.id}/edit`)}
+ >
+ {/* 드래그 핸들 */}
+ e.stopPropagation()}
+ >
+
+
+
+
+ {character.name}
+ 서버: {character.server || '-'}
+ 직업: {character.job || '-'}
+
+ 전투력: {character.combat_power?.toLocaleString() || '-'}
+
+
+
+
+ )
+}
+
export default function CharacterList() {
const [characters, setCharacters] = useState([])
useEffect(() => {
- const fetchCharacters = async () => {
- try {
- const response = await api.get('/characters')
- setCharacters(response.data)
- } catch (err) {
- console.error('캐릭터 목록을 불러오는 중 오류 발생:', err)
- }
- }
-
- fetchCharacters()
+ api.get('/characters').then((res) => setCharacters(res.data))
}, [])
+ const sensors = useSensors(useSensor(PointerSensor))
+
+ const handleDragEnd = (event: DragEndEvent) => {
+ const { active, over } = event
+ if (!over || active.id === over.id) return
+
+ const oldIndex = characters.findIndex((c) => c.id === active.id)
+ const newIndex = characters.findIndex((c) => c.id === over.id)
+
+ const newCharacters = arrayMove(characters, oldIndex, newIndex)
+ setCharacters(newCharacters)
+
+ const payload = newCharacters.map((c, index) => ({
+ id: c.id,
+ order: index + 1,
+ }))
+ api.patch('/characters/order', payload)
+ }
+
return (
캐릭터 목록
-
- {characters.map((char) => (
-
-
-
- {char.name}
- 서버: {char.server || '-'}
- 직업: {char.job || '-'}
- 전투력: {char.combat_power?.toLocaleString() || '-'}
-
-
+
+ c.id)} strategy={verticalListSortingStrategy}>
+
+ {characters.map((char) => (
+
+ ))}
+
+ (window.location.href = '/characters/register')}
+ sx={{
+ height: '100%',
+ display: 'flex',
+ justifyContent: 'center',
+ alignItems: 'center',
+ textDecoration: 'none',
+ cursor: 'pointer',
+ }}
+ >
+
+
+ + 캐릭터 추가
+
+
+
+
- ))}
-
-
-
-
- + 캐릭터 추가
-
-
-
-
-
+
+
)
}
diff --git a/src/pages/Home.tsx b/src/pages/Home.tsx
index 6ef2172..5734daa 100644
--- a/src/pages/Home.tsx
+++ b/src/pages/Home.tsx
@@ -1,37 +1,71 @@
-import { Box, Typography, Button } from '@mui/material'
+import { Box, Typography, Button, Divider } from '@mui/material'
import { useAuth } from '../contexts/AuthContext'
import { Link } from 'react-router-dom'
export default function Home() {
const { isLoggedIn } = useAuth()
- if (!isLoggedIn) {
- return (
-
- 숙제노기에 오신 걸 환영합니다 📝
-
- 이곳은 당신의 마비노기 숙제를 손쉽게 관리하는 웹 앱입니다.
-
-
-
- )
- }
+ const updates = [
+ { date: '2025-05-28', version: 'v1.1', content: '캐릭터 및 숙제 목록에서 순서변경기능 추가' },
+ { date: '2025-05-28', version: 'v1.1', content: '홈화면에 업데이트 내역 노출' },
+ ]
- // ✅ 로그인 상태일 때 "컨텐츠 들어갈 자리"만 출력
return (
- 길드 '노인정'과 함께합니다. 누구마음대로?
+ {!isLoggedIn ? (
+ <>
+
+
+ 숙제노기에 오신 걸 환영합니다 📝
+
+
+ 이곳은 당신의 마비노기 숙제를 손쉽게 관리하는 웹 앱입니다.
+
+
+
- {/* ✅ 이미지 추가 */}
-
-
-
+
+ 📌 업데이트 내역
+ {updates.map((u, idx) => (
+
+
+ {u.date} | {u.version}
+
+ - {u.content}
+
+ ))}
+
+
+ >
+ ) : (
+ <>
+ 📌 업데이트 내역
+
+ {updates.map((u, idx) => (
+
+
+ {u.date} | {u.version}
+
+ - {u.content}
+
+ ))}
+
+
+
+
+ 길드 '노인정'과 함께합니다. 누구마음대로?
+
+
+
+
+ >
+ )}
)
}
diff --git a/src/pages/HomeworkList.tsx b/src/pages/HomeworkList.tsx
index 3891940..2a5536b 100644
--- a/src/pages/HomeworkList.tsx
+++ b/src/pages/HomeworkList.tsx
@@ -1,9 +1,26 @@
import { Box, Card, CardContent, Typography } from '@mui/material'
import Grid from '@mui/material/Grid'
-import { Link } from 'react-router-dom'
import { useEffect, useState } from 'react'
+import { useNavigate } from 'react-router-dom'
import api from '../lib/api'
+import {
+ DndContext,
+ closestCenter,
+ DragEndEvent,
+ PointerSensor,
+ useSensor,
+ useSensors,
+} from '@dnd-kit/core'
+import {
+ arrayMove,
+ SortableContext,
+ useSortable,
+ verticalListSortingStrategy,
+} from '@dnd-kit/sortable'
+import { CSS } from '@dnd-kit/utilities'
+import DragIndicatorIcon from '@mui/icons-material/DragIndicator'
+
interface HomeworkType {
id: number
title: string
@@ -12,63 +29,119 @@ interface HomeworkType {
clear_count: number
}
+function SortableHomeworkCard({ homework }: { homework: HomeworkType }) {
+ const navigate = useNavigate()
+ const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({
+ id: homework.id,
+ })
+
+ const style = {
+ transform: CSS.Transform.toString(transform),
+ transition,
+ zIndex: isDragging ? 1000 : 'auto',
+ position: 'relative' as const,
+ }
+
+ return (
+
+ navigate(`/homeworks/${homework.id}/edit`)}
+ >
+ e.stopPropagation()}
+ >
+
+
+
+
+ {homework.title}
+ 주기: {homework.reset_type}
+ 횟수: {homework.clear_count}
+
+
+
+ )
+}
+
export default function HomeworkList() {
const [homeworks, setHomeworks] = useState([])
useEffect(() => {
- const fetchHomeworks = async () => {
- try {
- const res = await api.get('/homeworks')
- setHomeworks(res.data)
- } catch (err) {
- console.error('숙제 목록을 불러오는 데 실패했습니다.', err)
- }
- }
- fetchHomeworks()
+ api.get('/homeworks').then((res) => setHomeworks(res.data))
}, [])
+ const sensors = useSensors(useSensor(PointerSensor))
+
+ const handleDragEnd = (event: DragEndEvent) => {
+ const { active, over } = event
+ if (!over || active.id === over.id) return
+
+ const oldIndex = homeworks.findIndex((h) => h.id === active.id)
+ const newIndex = homeworks.findIndex((h) => h.id === over.id)
+ const newOrder = arrayMove(homeworks, oldIndex, newIndex)
+ setHomeworks(newOrder)
+
+ const payload = newOrder.map((hw, idx) => ({
+ id: hw.id,
+ order: idx + 1,
+ }))
+ api.patch('/homeworks/order', payload)
+ }
+
return (
숙제 목록
-
-
- {homeworks.map((hw) => (
-
-
-
- {hw.title}
- 주기: {hw.reset_type}
- 횟수: {hw.clear_count}
-
-
+
+ h.id)} strategy={verticalListSortingStrategy}>
+
+ {homeworks.map((hw) => (
+
+ ))}
+
+ (window.location.href = '/homeworks/register')}
+ sx={{
+ height: '100%',
+ display: 'flex',
+ justifyContent: 'center',
+ alignItems: 'center',
+ textDecoration: 'none',
+ cursor: 'pointer',
+ }}
+ >
+
+
+ + 숙제 추가
+
+
+
+
- ))}
-
-
-
-
- + 숙제 추가
-
-
-
-
-
+
+
)
}