diff --git a/src/App.tsx b/src/App.tsx
index 42f76ce..51812e4 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -18,6 +18,7 @@ import CharacterEditPage from './pages/CharacterEditPage'
import GuidePage from './pages/Guide'
import FriendListPage from './pages/FriendListPage'
import FriendRequestsPage from './pages/FriendRequestsPage'
+import FriendCharacterDashboard from './pages/FriendCharacterDashboard'
const darkTheme = createTheme({
palette: {
@@ -49,6 +50,7 @@ function App() {
} />
} />
} />
+ } />
} />
diff --git a/src/pages/CharacterEditPage.tsx b/src/pages/CharacterEditPage.tsx
index 8986223..1bc5db2 100644
--- a/src/pages/CharacterEditPage.tsx
+++ b/src/pages/CharacterEditPage.tsx
@@ -9,7 +9,9 @@ import {
DialogActions,
DialogContent,
DialogContentText,
- DialogTitle
+ DialogTitle,
+ FormControlLabel,
+ Checkbox
} from '@mui/material'
import { useEffect, useState } from 'react'
import { useNavigate, useParams } from 'react-router-dom'
@@ -22,6 +24,7 @@ export default function CharacterEditPage() {
const [name, setName] = useState('')
const [server, setServer] = useState('')
const [combatPower, setCombatPower] = useState('')
+ const [isPublic, setIsPublic] = useState(false)
const [error, setError] = useState('')
const [openConfirm, setOpenConfirm] = useState(false)
@@ -31,6 +34,7 @@ export default function CharacterEditPage() {
setName(res.data.name)
setServer(res.data.server || '')
setCombatPower(String(res.data.combat_power || ''))
+ setIsPublic(Boolean(res.data.is_public))
})
.catch(() => setError('캐릭터 정보를 불러오는 데 실패했습니다.'))
}, [id])
@@ -40,7 +44,8 @@ export default function CharacterEditPage() {
await api.put(`/characters/${id}`, {
name,
server,
- combat_power: Number(combatPower)
+ power: Number(combatPower),
+ is_public: isPublic,
})
navigate('/characters')
} catch {
@@ -82,6 +87,10 @@ export default function CharacterEditPage() {
fullWidth
type="number"
/>
+ setIsPublic(e.target.checked)} />}
+ label="친구에게 노출"
+ />
{error && {error}}
diff --git a/src/pages/FriendCharacterDashboard.tsx b/src/pages/FriendCharacterDashboard.tsx
new file mode 100644
index 0000000..582a19f
--- /dev/null
+++ b/src/pages/FriendCharacterDashboard.tsx
@@ -0,0 +1,102 @@
+import {
+ Box,
+ Typography,
+ Card,
+ CardContent,
+ Grid,
+ Stack,
+ Checkbox,
+ Button
+} from '@mui/material'
+import { useEffect, useState } from 'react'
+import { useNavigate, useParams } from 'react-router-dom'
+import api from '../lib/api'
+
+interface Character {
+ id: number
+ name: string
+ server: string
+}
+
+interface Homework {
+ id: number
+ title: string
+ reset_type: string
+ clear_count: number
+ complete_cnt: number
+}
+
+export default function FriendCharacterDashboard() {
+ const { friend_id } = useParams()
+ const [characters, setCharacters] = useState([])
+ const [homeworks, setHomeworks] = useState>({})
+ const navigate = useNavigate()
+
+ useEffect(() => {
+ const fetchData = async () => {
+ try {
+ const res = await api.get(`/friends/${friend_id}/characters`)
+ setCharacters(res.data)
+
+ const hwResults = await Promise.all(
+ res.data.map((char: Character) =>
+ api
+ .get(`/friends/${friend_id}/characters/${char.id}/homeworks`)
+ .then(r => ({ id: char.id, data: r.data }))
+ )
+ )
+ const map: Record = {}
+ hwResults.forEach(item => {
+ map[item.id] = item.data
+ })
+ setHomeworks(map)
+ } catch (err) {
+ console.error('친구 대시보드 데이터를 불러오는 데 실패했습니다.', err)
+ }
+ }
+ fetchData()
+ }, [friend_id])
+
+ return (
+
+
+
+ 친구 캐릭터 보기
+
+
+ {characters.map(char => (
+
+
+
+
+ {char.server} : {char.name}
+
+
+ {(homeworks[char.id] || []).map(hw => (
+
+
+ {hw.title} ({hw.clear_count}회)
+
+
+ {Array.from({ length: hw.clear_count }).map((_, idx) => (
+
+ ))}
+
+
+ ))}
+
+
+
+
+ ))}
+
+
+ )
+}
diff --git a/src/pages/FriendList.tsx b/src/pages/FriendList.tsx
deleted file mode 100644
index c3c9bab..0000000
--- a/src/pages/FriendList.tsx
+++ /dev/null
@@ -1,12 +0,0 @@
-import { Box, Typography } from '@mui/material'
-
-export default function FriendList() {
- return (
-
-
- 친구 목록
-
- 준비 중...
-
- )
-}
diff --git a/src/pages/FriendListPage.tsx b/src/pages/FriendListPage.tsx
index f8dd699..1a68d09 100644
--- a/src/pages/FriendListPage.tsx
+++ b/src/pages/FriendListPage.tsx
@@ -9,7 +9,6 @@ interface Friend {
}
export default function FriendListPage() {
- const [friendIds, setFriendIds] = useState([])
const [friends, setFriends] = useState([])
const [showDialog, setShowDialog] = useState(false)
const navigate = useNavigate()
@@ -17,13 +16,8 @@ export default function FriendListPage() {
useEffect(() => {
const fetchFriends = async () => {
try {
- const ids: number[] = await api.get('/friends/list').then(res => res.data)
- setFriendIds(ids)
-
- const friendInfos = await Promise.all(
- ids.map(id => api.get(`/users/${id}`).then(res => res.data))
- )
- setFriends(friendInfos)
+ const res = await api.get('/friends/list')
+ setFriends(res.data)
} catch (e) {
console.error('친구 목록 불러오기 실패', e)
}
diff --git a/src/pages/FriendRequests.tsx b/src/pages/FriendRequests.tsx
deleted file mode 100644
index 53de455..0000000
--- a/src/pages/FriendRequests.tsx
+++ /dev/null
@@ -1,12 +0,0 @@
-import { Box, Typography } from '@mui/material'
-
-export default function FriendRequests() {
- return (
-
-
- 요청 관리
-
- 준비 중...
-
- )
-}
diff --git a/src/pages/FriendRequestsPage.tsx b/src/pages/FriendRequestsPage.tsx
index 651b7f7..593c141 100644
--- a/src/pages/FriendRequestsPage.tsx
+++ b/src/pages/FriendRequestsPage.tsx
@@ -5,6 +5,8 @@ interface FriendRequest {
id: number
from_user_id: number
to_user_id: number
+ from_user_email?: string
+ to_user_email?: string
status: 'pending' | 'accepted' | 'rejected' | 'cancelled'
created_at: string
}
@@ -21,11 +23,17 @@ export default function FriendRequestsPage() {
const res = await api.get(url)
setRequests(res.data)
- const userIds = res.data.map((r: FriendRequest) =>
- tab === 'received' ? r.from_user_id : r.to_user_id
- )
const emails = await Promise.all(
- userIds.map(id => api.get(`/users/${id}`).then(res => [id, res.data.email]))
+ res.data.map(async (r: FriendRequest) => {
+ const targetId = tab === 'received' ? r.from_user_id : r.to_user_id
+ const emailFromResponse =
+ tab === 'received' ? r.from_user_email : r.to_user_email
+ if (emailFromResponse) {
+ return [targetId, emailFromResponse] as [number, string]
+ }
+ const user = await api.get(`/users/${targetId}`)
+ return [targetId, user.data.email] as [number, string]
+ })
)
setEmailMap(Object.fromEntries(emails))
}
@@ -35,13 +43,31 @@ export default function FriendRequestsPage() {
const handleRespond = async (id: number, accept: boolean) => {
await api.post(`/friends/requests/${id}/respond`, null, { params: { accept } })
alert(accept ? '친구 요청을 수락했습니다.' : '친구 요청을 거절했습니다.')
- setRequests(requests.filter(r => r.id !== id))
+ const req = requests.find(r => r.id === id)
+ const targetId = req ? (tab === 'received' ? req.from_user_id : req.to_user_id) : null
+ setRequests(prev => prev.filter(r => r.id !== id))
+ if (targetId !== null) {
+ setEmailMap(prev => {
+ const newMap = { ...prev }
+ delete newMap[targetId]
+ return newMap
+ })
+ }
}
const handleCancel = async (id: number) => {
await api.post(`/friends/requests/${id}/cancel`)
alert('요청을 취소했습니다.')
- setRequests(requests.filter(r => r.id !== id))
+ const req = requests.find(r => r.id === id)
+ const targetId = req ? (tab === 'received' ? req.from_user_id : req.to_user_id) : null
+ setRequests(prev => prev.filter(r => r.id !== id))
+ if (targetId !== null) {
+ setEmailMap(prev => {
+ const newMap = { ...prev }
+ delete newMap[targetId]
+ return newMap
+ })
+ }
}
return (
diff --git a/src/pages/HomeworkEditPage.tsx b/src/pages/HomeworkEditPage.tsx
index d535279..88e3aa4 100644
--- a/src/pages/HomeworkEditPage.tsx
+++ b/src/pages/HomeworkEditPage.tsx
@@ -1,6 +1,7 @@
import {
Box, Button, Container, Paper, TextField, Typography, MenuItem,
- Dialog, DialogTitle, DialogContent, DialogContentText, DialogActions
+ Dialog, DialogTitle, DialogContent, DialogContentText, DialogActions,
+ FormControlLabel, Checkbox
} from '@mui/material'
import { useEffect, useState } from 'react'
import { useParams, useNavigate } from 'react-router-dom'
@@ -14,6 +15,7 @@ export default function HomeworkEditPage() {
const [description, setDescription] = useState('')
const [resetType, setResetType] = useState('')
const [clearCount, setClearCount] = useState('')
+ const [isPublic, setIsPublic] = useState(false)
const [error, setError] = useState('')
const [openConfirm, setOpenConfirm] = useState(false)
@@ -25,6 +27,7 @@ export default function HomeworkEditPage() {
setDescription(hw.description || '')
setResetType(hw.reset_type)
setClearCount(String(hw.clear_count || ''))
+ setIsPublic(Boolean(hw.is_public))
})
.catch(() => setError('숙제 정보를 불러오는 데 실패했습니다.'))
}, [id])
@@ -32,10 +35,11 @@ export default function HomeworkEditPage() {
const handleUpdate = async () => {
try {
await api.put(`/homeworks/${id}`, {
- name: title, // ✅ title → name
+ title,
description,
- repeat_type: resetType, // ✅ reset_type → repeat_type
- repeat_count: Number(clearCount) // ✅ clear_count → repeat_count
+ reset_type: resetType,
+ clear_count: Number(clearCount),
+ is_public: isPublic,
})
navigate('/homeworks')
} catch {
@@ -89,6 +93,10 @@ export default function HomeworkEditPage() {
type="number"
fullWidth
/>
+ setIsPublic(e.target.checked)} />}
+ label="친구에게 노출"
+ />
{error && {error}}
diff --git a/src/pages/RegisterCharacter.tsx b/src/pages/RegisterCharacter.tsx
index a4d4093..731f02e 100644
--- a/src/pages/RegisterCharacter.tsx
+++ b/src/pages/RegisterCharacter.tsx
@@ -1,4 +1,4 @@
-import { Box, Button, Container, Paper, TextField, Typography } from '@mui/material'
+import { Box, Button, Container, Paper, TextField, Typography, FormControlLabel, Checkbox } from '@mui/material'
import { useState } from 'react'
import api from '../lib/api'
import { useNavigate } from 'react-router-dom'
@@ -8,6 +8,7 @@ export default function RegisterCharacter() {
const [server, setServer] = useState('')
const [job, setJob] = useState('')
const [power, setPower] = useState('')
+ const [isPublic, setIsPublic] = useState(false)
const navigate = useNavigate()
const handleSubmit = async () => {
@@ -22,6 +23,7 @@ export default function RegisterCharacter() {
server: server || undefined,
job: job || undefined,
combat_power: power ? parseInt(power, 10) : undefined,
+ is_public: isPublic,
})
alert('캐릭터가 성공적으로 등록되었습니다.')
navigate('/characters')
@@ -64,6 +66,10 @@ export default function RegisterCharacter() {
onChange={(e) => setPower(e.target.value)}
fullWidth
/>
+ setIsPublic(e.target.checked)} />}
+ label="친구에게 노출"
+ />
diff --git a/src/pages/RegisterHomework.tsx b/src/pages/RegisterHomework.tsx
index a6a405e..5e2c474 100644
--- a/src/pages/RegisterHomework.tsx
+++ b/src/pages/RegisterHomework.tsx
@@ -4,7 +4,9 @@ import {
Button,
Typography,
MenuItem,
- Container
+ Container,
+ FormControlLabel,
+ Checkbox
} from '@mui/material'
import { useState } from 'react'
import { useNavigate } from 'react-router-dom'
@@ -15,6 +17,8 @@ export default function RegisterHomework() {
const [description, setDescription] = useState('')
const [resetType, setResetType] = useState('')
const [clearCount, setClearCount] = useState(0)
+ const [resetTime, setResetTime] = useState('')
+ const [isPublic, setIsPublic] = useState(false)
const [error, setError] = useState('')
const navigate = useNavigate()
@@ -25,7 +29,9 @@ export default function RegisterHomework() {
title,
description,
reset_type: resetType,
+ reset_time: resetTime,
clear_count: clearCount,
+ is_public: isPublic,
})
navigate('/homeworks')
} catch (err) {
@@ -65,6 +71,14 @@ export default function RegisterHomework() {
+ setResetTime(e.target.value)}
+ />
setClearCount(parseInt(e.target.value) || 0)}
/>
+ setIsPublic(e.target.checked)} />}
+ label="친구에게 노출"
+ />
{error && {error}}