v.1.1 순서변경.
홈화면에 업데이트 내역 추가
This commit is contained in:
parent
a28d95e2c6
commit
c1a07ed816
76
package-lock.json
generated
76
package-lock.json
generated
@ -8,6 +8,9 @@
|
|||||||
"name": "frontend",
|
"name": "frontend",
|
||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
"dependencies": {
|
"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/react": "^11.14.0",
|
||||||
"@emotion/styled": "^11.14.0",
|
"@emotion/styled": "^11.14.0",
|
||||||
"@mui/icons-material": "^7.1.0",
|
"@mui/icons-material": "^7.1.0",
|
||||||
@ -348,6 +351,73 @@
|
|||||||
"node": ">=6.9.0"
|
"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": {
|
"node_modules/@emotion/babel-plugin": {
|
||||||
"version": "11.13.5",
|
"version": "11.13.5",
|
||||||
"resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.13.5.tgz",
|
"resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.13.5.tgz",
|
||||||
@ -4774,6 +4844,12 @@
|
|||||||
"typescript": ">=4.8.4"
|
"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": {
|
"node_modules/type-check": {
|
||||||
"version": "0.4.0",
|
"version": "0.4.0",
|
||||||
"resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
|
||||||
|
|||||||
@ -10,6 +10,9 @@
|
|||||||
"preview": "vite preview"
|
"preview": "vite preview"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"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/react": "^11.14.0",
|
||||||
"@emotion/styled": "^11.14.0",
|
"@emotion/styled": "^11.14.0",
|
||||||
"@mui/icons-material": "^7.1.0",
|
"@mui/icons-material": "^7.1.0",
|
||||||
|
|||||||
@ -1,8 +1,8 @@
|
|||||||
import axios from 'axios'
|
import axios from 'axios'
|
||||||
|
|
||||||
const api = axios.create({
|
const api = axios.create({
|
||||||
baseURL: 'https://api.biryu2000.kr',
|
// baseURL: 'https://api.biryu2000.kr',
|
||||||
// baseURL: 'http://localhost:8000',
|
baseURL: 'http://localhost:8000',
|
||||||
})
|
})
|
||||||
|
|
||||||
// 요청 시 토큰 자동 추가
|
// 요청 시 토큰 자동 추가
|
||||||
|
|||||||
@ -1,8 +1,25 @@
|
|||||||
import { Box, Card, CardContent, Typography, Container, Grid } from '@mui/material'
|
import { Box, Card, CardContent, Typography, Container, Grid } from '@mui/material'
|
||||||
import { useEffect, useState } from 'react'
|
import { useEffect, useState } from 'react'
|
||||||
import { useNavigate, Link } from 'react-router-dom'
|
import { useNavigate } from 'react-router-dom'
|
||||||
import api from '../lib/api'
|
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 {
|
interface Character {
|
||||||
id: number
|
id: number
|
||||||
name: string
|
name: string
|
||||||
@ -11,52 +28,111 @@ interface Character {
|
|||||||
combat_power?: number
|
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 (
|
||||||
|
<Grid item xs={12} sm={6} md={4} ref={setNodeRef} style={style}>
|
||||||
|
<Card
|
||||||
|
sx={{
|
||||||
|
cursor: 'pointer',
|
||||||
|
textDecoration: 'none',
|
||||||
|
position: 'relative',
|
||||||
|
}}
|
||||||
|
onClick={() => navigate(`/characters/${character.id}/edit`)}
|
||||||
|
>
|
||||||
|
{/* 드래그 핸들 */}
|
||||||
|
<Box
|
||||||
|
{...attributes}
|
||||||
|
{...listeners}
|
||||||
|
sx={{
|
||||||
|
position: 'absolute',
|
||||||
|
top: 8,
|
||||||
|
right: 8,
|
||||||
|
width: 24,
|
||||||
|
height: 24,
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
backgroundColor: 'rgba(0,0,0,0.1)',
|
||||||
|
borderRadius: '50%',
|
||||||
|
cursor: 'grab',
|
||||||
|
zIndex: 10,
|
||||||
|
}}
|
||||||
|
onClick={(e) => e.stopPropagation()}
|
||||||
|
>
|
||||||
|
<DragIndicatorIcon fontSize="small" />
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<CardContent>
|
||||||
|
<Typography variant="h6">{character.name}</Typography>
|
||||||
|
<Typography color="text.secondary">서버: {character.server || '-'}</Typography>
|
||||||
|
<Typography color="text.secondary">직업: {character.job || '-'}</Typography>
|
||||||
|
<Typography color="text.secondary">
|
||||||
|
전투력: {character.combat_power?.toLocaleString() || '-'}
|
||||||
|
</Typography>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</Grid>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
export default function CharacterList() {
|
export default function CharacterList() {
|
||||||
const [characters, setCharacters] = useState<Character[]>([])
|
const [characters, setCharacters] = useState<Character[]>([])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const fetchCharacters = async () => {
|
api.get('/characters').then((res) => setCharacters(res.data))
|
||||||
try {
|
|
||||||
const response = await api.get('/characters')
|
|
||||||
setCharacters(response.data)
|
|
||||||
} catch (err) {
|
|
||||||
console.error('캐릭터 목록을 불러오는 중 오류 발생:', err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fetchCharacters()
|
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
|
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 (
|
return (
|
||||||
<Container sx={{ mt: 4 }}>
|
<Container sx={{ mt: 4 }}>
|
||||||
<Typography variant="h5" gutterBottom>
|
<Typography variant="h5" gutterBottom>
|
||||||
캐릭터 목록
|
캐릭터 목록
|
||||||
</Typography>
|
</Typography>
|
||||||
|
<DndContext collisionDetection={closestCenter} sensors={sensors} onDragEnd={handleDragEnd}>
|
||||||
|
<SortableContext items={characters.map((c) => c.id)} strategy={verticalListSortingStrategy}>
|
||||||
<Grid container spacing={2}>
|
<Grid container spacing={2}>
|
||||||
{characters.map((char) => (
|
{characters.map((char) => (
|
||||||
<Grid item xs={12} sm={6} md={4} key={char.id} {...({} as any)}>
|
<SortableCharacterCard key={char.id} character={char} />
|
||||||
<Card
|
|
||||||
component={Link}
|
|
||||||
to={`/characters/${char.id}/edit`}
|
|
||||||
sx={{
|
|
||||||
cursor: 'pointer',
|
|
||||||
textDecoration: 'none',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<CardContent>
|
|
||||||
<Typography variant="h6">{char.name}</Typography>
|
|
||||||
<Typography color="text.secondary">서버: {char.server || '-'}</Typography>
|
|
||||||
<Typography color="text.secondary">직업: {char.job || '-'}</Typography>
|
|
||||||
<Typography color="text.secondary">전투력: {char.combat_power?.toLocaleString() || '-'}</Typography>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
</Grid>
|
|
||||||
))}
|
))}
|
||||||
<Grid item xs={12} sm={6} md={4} {...({} as any)}>
|
<Grid item xs={12} sm={6} md={4}>
|
||||||
<Card
|
<Card
|
||||||
component={Link}
|
onClick={() => (window.location.href = '/characters/register')}
|
||||||
to="/characters/register"
|
sx={{
|
||||||
sx={{ height: '100%', display: 'flex', justifyContent: 'center', alignItems: 'center', textDecoration: 'none' }}
|
height: '100%',
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'center',
|
||||||
|
alignItems: 'center',
|
||||||
|
textDecoration: 'none',
|
||||||
|
cursor: 'pointer',
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
<Typography variant="h6" align="center">
|
<Typography variant="h6" align="center">
|
||||||
@ -66,6 +142,8 @@ export default function CharacterList() {
|
|||||||
</Card>
|
</Card>
|
||||||
</Grid>
|
</Grid>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
</SortableContext>
|
||||||
|
</DndContext>
|
||||||
</Container>
|
</Container>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,14 +1,23 @@
|
|||||||
import { Box, Typography, Button } from '@mui/material'
|
import { Box, Typography, Button, Divider } from '@mui/material'
|
||||||
import { useAuth } from '../contexts/AuthContext'
|
import { useAuth } from '../contexts/AuthContext'
|
||||||
import { Link } from 'react-router-dom'
|
import { Link } from 'react-router-dom'
|
||||||
|
|
||||||
export default function Home() {
|
export default function Home() {
|
||||||
const { isLoggedIn } = useAuth()
|
const { isLoggedIn } = useAuth()
|
||||||
|
|
||||||
if (!isLoggedIn) {
|
const updates = [
|
||||||
|
{ date: '2025-05-28', version: 'v1.1', content: '캐릭터 및 숙제 목록에서 순서변경기능 추가' },
|
||||||
|
{ date: '2025-05-28', version: 'v1.1', content: '홈화면에 업데이트 내역 노출' },
|
||||||
|
]
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box sx={{ p: 6, textAlign: 'center' }}>
|
<Box sx={{ p: 4 }}>
|
||||||
<Typography variant="h4" gutterBottom>숙제노기에 오신 걸 환영합니다 📝</Typography>
|
{!isLoggedIn ? (
|
||||||
|
<>
|
||||||
|
<Box sx={{ textAlign: 'center', mb: 6 }}>
|
||||||
|
<Typography variant="h4" gutterBottom>
|
||||||
|
숙제노기에 오신 걸 환영합니다 📝
|
||||||
|
</Typography>
|
||||||
<Typography variant="body1" gutterBottom>
|
<Typography variant="body1" gutterBottom>
|
||||||
이곳은 당신의 마비노기 숙제를 손쉽게 관리하는 웹 앱입니다.
|
이곳은 당신의 마비노기 숙제를 손쉽게 관리하는 웹 앱입니다.
|
||||||
</Typography>
|
</Typography>
|
||||||
@ -16,15 +25,38 @@ export default function Home() {
|
|||||||
로그인 하러 가기
|
로그인 하러 가기
|
||||||
</Button>
|
</Button>
|
||||||
</Box>
|
</Box>
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ✅ 로그인 상태일 때 "컨텐츠 들어갈 자리"만 출력
|
<Box sx={{ mt: 4 }}>
|
||||||
return (
|
<Typography variant="h5" gutterBottom>📌 업데이트 내역</Typography>
|
||||||
<Box sx={{ p: 4 }}>
|
{updates.map((u, idx) => (
|
||||||
<Typography variant="h5" gutterBottom>길드 '노인정'과 함께합니다. 누구마음대로?</Typography>
|
<Box key={idx} sx={{ mb: 1 }}>
|
||||||
|
<Typography variant="body2" color="text.secondary">
|
||||||
|
{u.date} | {u.version}
|
||||||
|
</Typography>
|
||||||
|
<Typography variant="body1">- {u.content}</Typography>
|
||||||
|
</Box>
|
||||||
|
))}
|
||||||
|
<Divider sx={{ mt: 2 }} />
|
||||||
|
</Box>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<Typography variant="h5" gutterBottom>📌 업데이트 내역</Typography>
|
||||||
|
<Box sx={{ mb: 4 }}>
|
||||||
|
{updates.map((u, idx) => (
|
||||||
|
<Box key={idx} sx={{ mb: 1 }}>
|
||||||
|
<Typography variant="body2" color="text.secondary">
|
||||||
|
{u.date} | {u.version}
|
||||||
|
</Typography>
|
||||||
|
<Typography variant="body1">- {u.content}</Typography>
|
||||||
|
</Box>
|
||||||
|
))}
|
||||||
|
<Divider sx={{ mt: 2 }} />
|
||||||
|
</Box>
|
||||||
|
|
||||||
{/* ✅ 이미지 추가 */}
|
<Typography variant="h5" gutterBottom sx={{ mt: 6 }}>
|
||||||
|
길드 '노인정'과 함께합니다. 누구마음대로?
|
||||||
|
</Typography>
|
||||||
<Box sx={{ mt: 4, textAlign: 'center' }}>
|
<Box sx={{ mt: 4, textAlign: 'center' }}>
|
||||||
<img
|
<img
|
||||||
src="/guild.png"
|
src="/guild.png"
|
||||||
@ -32,6 +64,8 @@ export default function Home() {
|
|||||||
style={{ maxWidth: '100%', height: 'auto', borderRadius: '16px' }}
|
style={{ maxWidth: '100%', height: 'auto', borderRadius: '16px' }}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,9 +1,26 @@
|
|||||||
import { Box, Card, CardContent, Typography } from '@mui/material'
|
import { Box, Card, CardContent, Typography } from '@mui/material'
|
||||||
import Grid from '@mui/material/Grid'
|
import Grid from '@mui/material/Grid'
|
||||||
import { Link } from 'react-router-dom'
|
|
||||||
import { useEffect, useState } from 'react'
|
import { useEffect, useState } from 'react'
|
||||||
|
import { useNavigate } from 'react-router-dom'
|
||||||
import api from '../lib/api'
|
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 {
|
interface HomeworkType {
|
||||||
id: number
|
id: number
|
||||||
title: string
|
title: string
|
||||||
@ -12,53 +29,107 @@ interface HomeworkType {
|
|||||||
clear_count: number
|
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 (
|
||||||
|
<Grid item xs={12} sm={6} md={4} ref={setNodeRef} style={style}>
|
||||||
|
<Card
|
||||||
|
sx={{
|
||||||
|
cursor: 'pointer',
|
||||||
|
textDecoration: 'none',
|
||||||
|
position: 'relative',
|
||||||
|
}}
|
||||||
|
onClick={() => navigate(`/homeworks/${homework.id}/edit`)}
|
||||||
|
>
|
||||||
|
<Box
|
||||||
|
{...attributes}
|
||||||
|
{...listeners}
|
||||||
|
sx={{
|
||||||
|
position: 'absolute',
|
||||||
|
top: 8,
|
||||||
|
right: 8,
|
||||||
|
width: 24,
|
||||||
|
height: 24,
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
backgroundColor: 'rgba(0,0,0,0.1)',
|
||||||
|
borderRadius: '50%',
|
||||||
|
cursor: 'grab',
|
||||||
|
zIndex: 10,
|
||||||
|
}}
|
||||||
|
onClick={(e) => e.stopPropagation()}
|
||||||
|
>
|
||||||
|
<DragIndicatorIcon fontSize="small" />
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<CardContent>
|
||||||
|
<Typography variant="h6">{homework.title}</Typography>
|
||||||
|
<Typography>주기: {homework.reset_type}</Typography>
|
||||||
|
<Typography>횟수: {homework.clear_count}</Typography>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</Grid>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
export default function HomeworkList() {
|
export default function HomeworkList() {
|
||||||
const [homeworks, setHomeworks] = useState<HomeworkType[]>([])
|
const [homeworks, setHomeworks] = useState<HomeworkType[]>([])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const fetchHomeworks = async () => {
|
api.get('/homeworks').then((res) => setHomeworks(res.data))
|
||||||
try {
|
|
||||||
const res = await api.get('/homeworks')
|
|
||||||
setHomeworks(res.data)
|
|
||||||
} catch (err) {
|
|
||||||
console.error('숙제 목록을 불러오는 데 실패했습니다.', err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fetchHomeworks()
|
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
|
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 (
|
return (
|
||||||
<Box sx={{ p: 4 }}>
|
<Box sx={{ p: 4 }}>
|
||||||
<Typography variant="h5" gutterBottom>
|
<Typography variant="h5" gutterBottom>
|
||||||
숙제 목록
|
숙제 목록
|
||||||
</Typography>
|
</Typography>
|
||||||
|
<DndContext collisionDetection={closestCenter} sensors={sensors} onDragEnd={handleDragEnd}>
|
||||||
|
<SortableContext items={homeworks.map((h) => h.id)} strategy={verticalListSortingStrategy}>
|
||||||
<Grid container spacing={2}>
|
<Grid container spacing={2}>
|
||||||
{homeworks.map((hw) => (
|
{homeworks.map((hw) => (
|
||||||
<Grid item key={hw.id} xs={12} sm={6} md={4} {...({} as any)}>
|
<SortableHomeworkCard key={hw.id} homework={hw} />
|
||||||
<Card
|
|
||||||
component={Link}
|
|
||||||
to={`/homeworks/${hw.id}/edit`}
|
|
||||||
sx={{ cursor: 'pointer', textDecoration: 'none' }}
|
|
||||||
>
|
|
||||||
<CardContent>
|
|
||||||
<Typography variant="h6">{hw.title}</Typography>
|
|
||||||
<Typography>주기: {hw.reset_type}</Typography>
|
|
||||||
<Typography>횟수: {hw.clear_count}</Typography>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
</Grid>
|
|
||||||
))}
|
))}
|
||||||
<Grid item xs={12} sm={6} md={4} {...({} as any)}>
|
<Grid item xs={12} sm={6} md={4}>
|
||||||
<Card
|
<Card
|
||||||
component={Link}
|
onClick={() => (window.location.href = '/homeworks/register')}
|
||||||
to="/homeworks/register"
|
|
||||||
sx={{
|
sx={{
|
||||||
height: '100%',
|
height: '100%',
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
textDecoration: 'none',
|
textDecoration: 'none',
|
||||||
|
cursor: 'pointer',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
@ -69,6 +140,8 @@ export default function HomeworkList() {
|
|||||||
</Card>
|
</Card>
|
||||||
</Grid>
|
</Grid>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
</SortableContext>
|
||||||
|
</DndContext>
|
||||||
</Box>
|
</Box>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user