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..61b414a --- /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 { + character_id: number + character_name: string + server: string +} + +interface Homework { + 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.character_id}/homeworks`) + .then(r => ({ id: char.character_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.character_name} + + + {(homeworks[char.character_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}}