v.1.1 순서변경.

홈화면에 업데이트 내역 추가
This commit is contained in:
SR07 2025-05-28 16:43:59 +09:00
parent a28d95e2c6
commit c1a07ed816
6 changed files with 380 additions and 116 deletions

76
package-lock.json generated
View File

@ -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",

View File

@ -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",

View File

@ -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',
})
// 요청 시 토큰 자동 추가

View File

@ -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 (
<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() {
const [characters, setCharacters] = useState<Character[]>([])
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 (
<Container sx={{ mt: 4 }}>
<Typography variant="h5" gutterBottom>
</Typography>
<Grid container spacing={2}>
{characters.map((char) => (
<Grid item xs={12} sm={6} md={4} key={char.id} {...({} as any)}>
<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>
<DndContext collisionDetection={closestCenter} sensors={sensors} onDragEnd={handleDragEnd}>
<SortableContext items={characters.map((c) => c.id)} strategy={verticalListSortingStrategy}>
<Grid container spacing={2}>
{characters.map((char) => (
<SortableCharacterCard key={char.id} character={char} />
))}
<Grid item xs={12} sm={6} md={4}>
<Card
onClick={() => (window.location.href = '/characters/register')}
sx={{
height: '100%',
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
textDecoration: 'none',
cursor: 'pointer',
}}
>
<CardContent>
<Typography variant="h6" align="center">
+
</Typography>
</CardContent>
</Card>
</Grid>
</Grid>
))}
<Grid item xs={12} sm={6} md={4} {...({} as any)}>
<Card
component={Link}
to="/characters/register"
sx={{ height: '100%', display: 'flex', justifyContent: 'center', alignItems: 'center', textDecoration: 'none' }}
>
<CardContent>
<Typography variant="h6" align="center">
+
</Typography>
</CardContent>
</Card>
</Grid>
</Grid>
</SortableContext>
</DndContext>
</Container>
)
}

View File

@ -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 (
<Box sx={{ p: 6, textAlign: 'center' }}>
<Typography variant="h4" gutterBottom> 📝</Typography>
<Typography variant="body1" gutterBottom>
.
</Typography>
<Button variant="contained" component={Link} to="/login">
</Button>
</Box>
)
}
const updates = [
{ date: '2025-05-28', version: 'v1.1', content: '캐릭터 및 숙제 목록에서 순서변경기능 추가' },
{ date: '2025-05-28', version: 'v1.1', content: '홈화면에 업데이트 내역 노출' },
]
// ✅ 로그인 상태일 때 "컨텐츠 들어갈 자리"만 출력
return (
<Box sx={{ p: 4 }}>
<Typography variant="h5" gutterBottom> '노인정' . ?</Typography>
{!isLoggedIn ? (
<>
<Box sx={{ textAlign: 'center', mb: 6 }}>
<Typography variant="h4" gutterBottom>
📝
</Typography>
<Typography variant="body1" gutterBottom>
.
</Typography>
<Button variant="contained" component={Link} to="/login">
</Button>
</Box>
{/* ✅ 이미지 추가 */}
<Box sx={{ mt: 4, textAlign: 'center' }}>
<img
src="/guild.png"
alt="노인정길드"
style={{ maxWidth: '100%', height: 'auto', borderRadius: '16px' }}
/>
</Box>
<Box sx={{ mt: 4 }}>
<Typography variant="h5" gutterBottom>📌 </Typography>
{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>📌 </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' }}>
<img
src="/guild.png"
alt="노인정길드"
style={{ maxWidth: '100%', height: 'auto', borderRadius: '16px' }}
/>
</Box>
</>
)}
</Box>
)
}

View File

@ -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 (
<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() {
const [homeworks, setHomeworks] = useState<HomeworkType[]>([])
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 (
<Box sx={{ p: 4 }}>
<Typography variant="h5" gutterBottom>
</Typography>
<Grid container spacing={2}>
{homeworks.map((hw) => (
<Grid item key={hw.id} xs={12} sm={6} md={4} {...({} as any)}>
<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>
<DndContext collisionDetection={closestCenter} sensors={sensors} onDragEnd={handleDragEnd}>
<SortableContext items={homeworks.map((h) => h.id)} strategy={verticalListSortingStrategy}>
<Grid container spacing={2}>
{homeworks.map((hw) => (
<SortableHomeworkCard key={hw.id} homework={hw} />
))}
<Grid item xs={12} sm={6} md={4}>
<Card
onClick={() => (window.location.href = '/homeworks/register')}
sx={{
height: '100%',
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
textDecoration: 'none',
cursor: 'pointer',
}}
>
<CardContent>
<Typography variant="h6" align="center">
+
</Typography>
</CardContent>
</Card>
</Grid>
</Grid>
))}
<Grid item xs={12} sm={6} md={4} {...({} as any)}>
<Card
component={Link}
to="/homeworks/register"
sx={{
height: '100%',
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
textDecoration: 'none',
}}
>
<CardContent>
<Typography variant="h6" align="center">
+
</Typography>
</CardContent>
</Card>
</Grid>
</Grid>
</SortableContext>
</DndContext>
</Box>
)
}