diff --git a/back/src/controllers/routineController.js b/back/src/controllers/routineController.js index 176b649ed936a4cdf39b7d0874dcbce79035265a..a9538d1150268a7bf048e4464d2dd03e16e10cce 100644 --- a/back/src/controllers/routineController.js +++ b/back/src/controllers/routineController.js @@ -1,13 +1,6 @@ -const express = require('express'); -const router = express.Router(); const minutesToSeconds = require('../utils/timeconvert'); - -//운동 영상 구조 -const Video = require('../models/video'); -//루틴 구조 const Routine = require('../models/routine'); -// 기록할 DB 구조 -const Record = require('../models/records'); +const Video = require('../models/video'); const routineController = { recordRoutine: async (req, res) => { @@ -30,33 +23,30 @@ const routineController = { getRoutine: async (req, res) => { try { const userRoutine = await Routine.find({ - user_id: req.user.user_id, - }).populate('routine_exercises.video'); + user_id: req.user.user_id + }).populate('routine_exercises.video').exec(); + if (userRoutine.length === 0) { - // 아무런 루틴도 없다면 null 로 채워진 루틴 하나를 return - const dummyRoutine = [ - { - routine_id: null, - routine_name: null, - routine_exercises: [ - { - video: { - video_id: null, - video_time: null, - video_tag: null, - }, - }, - ], - }, - ]; + const dummyRoutine = [{ + routine_id: null, + routine_name: null, + routine_exercises: [{ + video: { + video_id: null, + video_time: null, + video_tag: null, + }, + }], + }]; return res.json(dummyRoutine); } + return res.status(200).json(userRoutine); } catch (error) { - console.error(error); + console.error('Routine Error:', error); res.status(500).json({ message: 'Failed to get user routine', - error: error.message, + error: error.message }); } }, @@ -161,6 +151,32 @@ const routineController = { }); } }, + getRoutineVideos: async (req, res) => { + const routine_name = req.query.routine_name; // GET 요청의 쿼리 파라미터 + try { + const routine = await Routine.findOne({ + user_id: req.user.user_id, + routine_name, + }).populate('routine_exercises.video'); + + if (!routine) { + return res.status(404).json({ message: 'Routine not Found' }); + } + + // video 정보만 추출하여 반환 + const videos = routine.routine_exercises + .map(exercise => exercise.video) + .filter(video => video); // null/undefined 필터링 + + res.status(200).json(videos); + } catch (error) { + console.error(error); + res.status(500).json({ + message: 'Failed to get routine videos', + error: error.message, + }); + } + }, }; module.exports = routineController; \ No newline at end of file diff --git a/back/src/models/routine.js b/back/src/models/routine.js index 3d15812f707af6227feac91b486f5f351364100c..692c525be52c029d80040a832756c8dbec96cab7 100644 --- a/back/src/models/routine.js +++ b/back/src/models/routine.js @@ -30,5 +30,4 @@ const routineSchema = new mongoose.Schema( ); const Routine = mongoose.model('Routine', routineSchema); - -module.exports = { Routine }; +module.exports = Routine; diff --git a/front/src/api/routineAPI.js b/front/src/api/routineAPI.js index 438113a13c09b8ec90e889b6a1b942d6e857b9cb..7f77d75d16eb14def1192e4c32ac6b7af0b1aacc 100644 --- a/front/src/api/routineAPI.js +++ b/front/src/api/routineAPI.js @@ -1,9 +1,19 @@ async function fetchWithOptions(url, options) { try { - const response = await fetch(url, options); + const response = await fetch(url, { + ...options, + headers: { + "Authorization": `Bearer ${localStorage.getItem('accessToken')}`, + 'Content-Type': 'application/json', + ...options.headers, + }, + }); + if (!response.ok) { - throw new Error(`HTTP 오류! 상태 코드: ${response.status}`); + const errorData = await response.json(); + throw new Error(errorData.message || `HTTP 오류! 상태 코드: ${response.status}`); } + return await response.json(); } catch (error) { console.error("API 요청 중 에러 발생:", error.message); @@ -11,44 +21,62 @@ async function fetchWithOptions(url, options) { } } -async function addExerciseRecord(record) { - return await fetchWithOptions('/routine/records', { +// 개별 함수들을 export +export const addExerciseRecord = async (record) => { + return await fetchWithOptions('/api/routine/records', { method: 'POST', - headers: { - "Authorization": `Bearer ${localStorage.getItem('accessToken')}`, - 'Content-Type': 'application/json', - }, body: JSON.stringify(record), }); -} +}; -async function getUserRoutines() { - return await fetchWithOptions('/routine', { - method: 'GET', - headers: { - "Authorization": `Bearer ${localStorage.getItem('accessToken')}`, - 'Content-Type': 'application/json', - }, - }); -} +export const getUserRoutines = async () => { + try { + const response = await fetchWithOptions('/api/routine', { + method: 'GET', + }); + + // 빈 루틴도 표시할 수 있도록 변환 + return response.map(routine => ({ + _id: routine._id, + user_id: routine.user_id, + routine_name: routine.routine_name, + routine_exercises: routine.routine_exercises || [], // 빈 배열 처리 + created_at: routine.routine_created_at + })); + } catch (error) { + console.error("루틴 데이터 가져오기 실패:", error); + throw error; + } +}; + +export const getRoutineVideos = async (routineName) => { + try { + const response = await fetchWithOptions(`/api/routine/videos?routine_name=${encodeURIComponent(routineName)}`, { + method: 'GET', + }); + + return response.map(video => ({ + title: video.video_title || '', + duration: video.video_time || 0, + link: video.video_url || '', + thumbnail: video.video_thumbnail || '' + })); + } catch (error) { + console.error("루틴 비디오 가져오기 실패:", error); + return []; // 비디오가 없을 경우 빈 배열 반환 + } +}; -async function getRoutineVideos(routineName) { - return await fetchWithOptions(`/routine/videos?routine_name=${routineName}`, { - method: 'GET', - headers: { - "Authorization": `Bearer ${localStorage.getItem('accessToken')}`, - 'Content-Type': 'application/json', - }, +export const createRoutine = async (routineName) => { + return await fetchWithOptions('/api/routine', { + method: 'POST', + body: JSON.stringify({ routine_name: routineName }), }); -} +}; -async function deleteRoutine(routineName) { - return await fetchWithOptions('/routine', { +export const deleteRoutine = async (routineName) => { + return await fetchWithOptions('/api/routine', { method: 'DELETE', - headers: { - "Authorization": `Bearer ${localStorage.getItem('accessToken')}`, - 'Content-Type': 'application/json', - }, body: JSON.stringify({ routine_name: routineName }), }); } diff --git a/front/src/components/routine/List.jsx b/front/src/components/routine/List.jsx index ed16b331f0ee322e95f96aeea477601f58a0a980..774bf71bec55b8c4db24954ce321ebd75c4fd175 100644 --- a/front/src/components/routine/List.jsx +++ b/front/src/components/routine/List.jsx @@ -1,120 +1,11 @@ import React, { useState, useEffect } from "react"; import "./List.css"; import truncateText from './truncateText'; -import { getUserRoutines, getRoutineVideos, deleteRoutine } from '../../api/routineAPI'; - -const initialRoutines = [ - { - id: 1, - name: "전신 루틴", - exercises: [ - { title: "전신 운동 1", thumbnail: "https://via.placeholder.com/150x100.png?text=Thumbnail1", link: "https://www.youtube.com/watch?v=example1", duration: 300, canceled: false }, - { title: "전신 운동 2", thumbnail: "https://via.placeholder.com/150x100.png?text=Thumbnail2", link: "https://www.youtube.com/watch?v=example2", duration: 420, canceled: false }, - { title: "전신 운동 3", thumbnail: "https://via.placeholder.com/150x100.png?text=Thumbnail3", link: "https://www.youtube.com/watch?v=example3", duration: 350, canceled: false }, - ], - }, - { - id: 2, - name: "상체 루틴", - exercises: [ - { title: "상체 운동 1", thumbnail: "https://via.placeholder.com/150x100.png?text=Thumbnail1", link: "https://www.youtube.com/watch?v=example11", duration: 360, canceled: false }, - { title: "상체 운동 2", thumbnail: "https://via.placeholder.com/150x100.png?text=Thumbnail2", link: "https://www.youtube.com/watch?v=example12", duration: 400, canceled: false }, - { title: "상체 운동 3", thumbnail: "https://via.placeholder.com/150x100.png?text=Thumbnail3", link: "https://www.youtube.com/watch?v=example13", duration: 330, canceled: false }, - { title: "상체 운동 4", thumbnail: "https://via.placeholder.com/150x100.png?text=Thumbnail4", link: "https://www.youtube.com/watch?v=example14", duration: 470, canceled: false }, - { title: "상체 운동 5", thumbnail: "https://via.placeholder.com/150x100.png?text=Thumbnail5", link: "https://www.youtube.com/watch?v=example15", duration: 380, canceled: false }, - { title: "상체 운동 6", thumbnail: "https://via.placeholder.com/150x100.png?text=Thumbnail6", link: "https://www.youtube.com/watch?v=example16", duration: 390, canceled: false }, - { title: "상체 운동 7", thumbnail: "https://via.placeholder.com/150x100.png?text=Thumbnail7", link: "https://www.youtube.com/watch?v=example17", duration: 420, canceled: false }, - { title: "상체 운동 8", thumbnail: "https://via.placeholder.com/150x100.png?text=Thumbnail8", link: "https://www.youtube.com/watch?v=example18", duration: 440, canceled: false }, - { title: "상체 운동 9", thumbnail: "https://via.placeholder.com/150x100.png?text=Thumbnail9", link: "https://www.youtube.com/watch?v=example19", duration: 350, canceled: false }, - { title: "상체 운동 10", thumbnail: "https://via.placeholder.com/150x100.png?text=Thumbnail10", link: "https://www.youtube.com/watch?v=example20", duration: 500, canceled: false }, - ], - }, - { - id: 3, - name: "하체 루틴", - exercises: [ - { title: "하체 운동 1", thumbnail: "https://via.placeholder.com/150x100.png?text=Thumbnail1", link: "https://www.youtube.com/watch?v=example21", duration: 350, canceled: false }, - { title: "하체 운동 2", thumbnail: "https://via.placeholder.com/150x100.png?text=Thumbnail2", link: "https://www.youtube.com/watch?v=example22", duration: 450, canceled: false }, - { title: "하체 운동 3", thumbnail: "https://via.placeholder.com/150x100.png?text=Thumbnail3", link: "https://www.youtube.com/watch?v=example23", duration: 400, canceled: false }, - { title: "하체 운동 4", thumbnail: "https://via.placeholder.com/150x100.png?text=Thumbnail4", link: "https://www.youtube.com/watch?v=example24", duration: 500, canceled: false }, - { title: "하체 운동 5", thumbnail: "https://via.placeholder.com/150x100.png?text=Thumbnail5", link: "https://www.youtube.com/watch?v=example25", duration: 480, canceled: false }, - { title: "하체 운동 6", thumbnail: "https://via.placeholder.com/150x100.png?text=Thumbnail6", link: "https://www.youtube.com/watch?v=example26", duration: 420, canceled: false }, - { title: "하체 운동 7", thumbnail: "https://via.placeholder.com/150x100.png?text=Thumbnail7", link: "https://www.youtube.com/watch?v=example27", duration: 430, canceled: false }, - { title: "하체 운동 8", thumbnail: "https://via.placeholder.com/150x100.png?text=Thumbnail8", link: "https://www.youtube.com/watch?v=example28", duration: 360, canceled: false }, - { title: "하체 운동 9", thumbnail: "https://via.placeholder.com/150x100.png?text=Thumbnail9", link: "https://www.youtube.com/watch?v=example29", duration: 490, canceled: false }, - { title: "하체 운동 10", thumbnail: "https://via.placeholder.com/150x100.png?text=Thumbnail10", link: "https://www.youtube.com/watch?v=example30", duration: 460, canceled: false }, - ], - }, - { - id: 4, - name: "복부 루틴", - exercises: [ - { title: "복부 운동 1", thumbnail: "https://via.placeholder.com/150x100.png?text=Thumbnail1", link: "https://www.youtube.com/watch?v=example31", duration: 400, canceled: false }, - { title: "복부 운동 2", thumbnail: "https://via.placeholder.com/150x100.png?text=Thumbnail2", link: "https://www.youtube.com/watch?v=example32", duration: 300, canceled: false }, - { title: "복부 운동 3", thumbnail: "https://via.placeholder.com/150x100.png?text=Thumbnail3", link: "https://www.youtube.com/watch?v=example33", duration: 350, canceled: false }, - { title: "복부 운동 4", thumbnail: "https://via.placeholder.com/150x100.png?text=Thumbnail4", link: "https://www.youtube.com/watch?v=example34", duration: 380, canceled: false }, - { title: "복부 운동 5", thumbnail: "https://via.placeholder.com/150x100.png?text=Thumbnail5", link: "https://www.youtube.com/watch?v=example35", duration: 320, canceled: false }, - { title: "복부 운동 6", thumbnail: "https://via.placeholder.com/150x100.png?text=Thumbnail6", link: "https://www.youtube.com/watch?v=example36", duration: 370, canceled: false }, - { title: "복부 운동 7", thumbnail: "https://via.placeholder.com/150x100.png?text=Thumbnail7", link: "https://www.youtube.com/watch?v=example37", duration: 390, canceled: false }, - { title: "복부 운동 8", thumbnail: "https://via.placeholder.com/150x100.png?text=Thumbnail8", link: "https://www.youtube.com/watch?v=example38", duration: 430, canceled: false }, - { title: "복부 운동 9", thumbnail: "https://via.placeholder.com/150x100.png?text=Thumbnail9", link: "https://www.youtube.com/watch?v=example39", duration: 480, canceled: false }, - { title: "복부 운동 10", thumbnail: "https://via.placeholder.com/150x100.png?text=Thumbnail10", link: "https://www.youtube.com/watch?v=example40", duration: 500, canceled: false }, - ], - }, - { - id: 5, - name: "유산소 루틴", - exercises: [ - { title: "유산소 운동 1", thumbnail: "https://via.placeholder.com/150x100.png?text=Thumbnail1", link: "https://www.youtube.com/watch?v=example41", duration: 600, canceled: false }, - { title: "유산소 운동 2", thumbnail: "https://via.placeholder.com/150x100.png?text=Thumbnail2", link: "https://www.youtube.com/watch?v=example42", duration: 500, canceled: false }, - { title: "유산소 운동 3", thumbnail: "https://via.placeholder.com/150x100.png?text=Thumbnail3", link: "https://www.youtube.com/watch?v=example43", duration: 550, canceled: false }, - { title: "유산소 운동 4", thumbnail: "https://via.placeholder.com/150x100.png?text=Thumbnail4", link: "https://www.youtube.com/watch?v=example44", duration: 510, canceled: false }, - { title: "유산소 운동 5", thumbnail: "https://via.placeholder.com/150x100.png?text=Thumbnail5", link: "https://www.youtube.com/watch?v=example45", duration: 560, canceled: false }, - { title: "유산소 운동 6", thumbnail: "https://via.placeholder.com/150x100.png?text=Thumbnail6", link: "https://www.youtube.com/watch?v=example46", duration: 530, canceled: false }, - { title: "유산소 운동 7", thumbnail: "https://via.placeholder.com/150x100.png?text=Thumbnail7", link: "https://www.youtube.com/watch?v=example47", duration: 480, canceled: false }, - { title: "유산소 운동 8", thumbnail: "https://via.placeholder.com/150x100.png?text=Thumbnail8", link: "https://www.youtube.com/watch?v=example48", duration: 540, canceled: false }, - { title: "유산소 운동 9", thumbnail: "https://via.placeholder.com/150x100.png?text=Thumbnail9", link: "https://www.youtube.com/watch?v=example49", duration: 490, canceled: false }, - { title: "유산소 운동 10", thumbnail: "https://via.placeholder.com/150x100.png?text=Thumbnail10", link: "https://www.youtube.com/watch?v=example50", duration: 520, canceled: false }, - ], - }, - { - id: 6, - name: "스트레칭 루틴", - exercises: [ - { title: "스트레칭 운동 1", thumbnail: "https://via.placeholder.com/150x100.png?text=Thumbnail1", link: "https://www.youtube.com/watch?v=example51", duration: 300, canceled: false }, - { title: "스트레칭 운동 2", thumbnail: "https://via.placeholder.com/150x100.png?text=Thumbnail2", link: "https://www.youtube.com/watch?v=example52", duration: 400, canceled: false }, - { title: "스트레칭 운동 3", thumbnail: "https://via.placeholder.com/150x100.png?text=Thumbnail3", link: "https://www.youtube.com/watch?v=example53", duration: 350, canceled: false }, - { title: "스트레칭 운동 4", thumbnail: "https://via.placeholder.com/150x100.png?text=Thumbnail4", link: "https://www.youtube.com/watch?v=example54", duration: 300, canceled: false }, - { title: "스트레칭 운동 5", thumbnail: "https://via.placeholder.com/150x100.png?text=Thumbnail5", link: "https://www.youtube.com/watch?v=example55", duration: 380, canceled: false }, - { title: "스트레칭 운동 6", thumbnail: "https://via.placeholder.com/150x100.png?text=Thumbnail6", link: "https://www.youtube.com/watch?v=example56", duration: 420, canceled: false }, - { title: "스트레칭 운동 7", thumbnail: "https://via.placeholder.com/150x100.png?text=Thumbnail7", link: "https://www.youtube.com/watch?v=example57", duration: 440, canceled: false }, - { title: "스트레칭 운동 8", thumbnail: "https://via.placeholder.com/150x100.png?text=Thumbnail8", link: "https://www.youtube.com/watch?v=example58", duration: 450, canceled: false }, - { title: "스트레칭 운동 9", thumbnail: "https://via.placeholder.com/150x100.png?text=Thumbnail9", link: "https://www.youtube.com/watch?v=example59", duration: 500, canceled: false }, - { title: "스트레칭 운동 10", thumbnail: "https://via.placeholder.com/150x100.png?text=Thumbnail10", link: "https://www.youtube.com/watch?v=example60", duration: 480, canceled: false }, - ], - }, - { - id: 7, - name: "등 루틴", - exercises: [ - { title: "업그레이드를 위한 새로운 등운동 [ BACK DAY ]", thumbnail: "https://via.placeholder.com/150x100.png?text=Thumbnail1", link: "https://www.youtube.com/watch?v=f7wFbp9BnFs", duration: 300, canceled: false }, - { title: "뚫고 나오는 등 만들고 싶으면 이거 봐", thumbnail: "https://via.placeholder.com/150x100.png?text=Thumbnail2", link: "https://www.youtube.com/watch?v=8SBBp65Sv3c", duration: 400, canceled: false }, - { title: "Try This Back Exercise | Back & Hamstrings Workout", thumbnail: "https://via.placeholder.com/150x100.png?text=Thumbnail3", link: "https://www.youtube.com/watch?v=nRzAV-CYndA", duration: 350, canceled: false }, - { title: "My Title Winning Back Training", thumbnail: "https://via.placeholder.com/150x100.png?text=Thumbnail4", link: "https://www.youtube.com/watch?v=5dp2FUN3mRQ", duration: 300, canceled: false }, - { title: "Back and Delts Workout", thumbnail: "https://via.placeholder.com/150x100.png?text=Thumbnail5", link: "https://www.youtube.com/watch?v=DjfnFj-50b4", duration: 380, canceled: false }, - { title: "INTENSE Back Workout | Mr. Olympia Derek Lunsford", thumbnail: "https://via.placeholder.com/150x100.png?text=Thumbnail6", link: "https://www.youtube.com/watch?v=HYngFKG5YbY&t=477s", duration: 420, canceled: false }, - { title: "Mr. Olympia BACK WORKOUT", thumbnail: "https://via.placeholder.com/150x100.png?text=Thumbnail7", link: "https://www.youtube.com/watch?v=HqZOWPRyck8&t=134s", duration: 440, canceled: false }, - { title: "등 운동 후 몽둥이질 당한 느낌이 나게 하는 방법들", thumbnail: "https://via.placeholder.com/150x100.png?text=Thumbnail8", link: "https://www.youtube.com/watch?v=OD1JMTLJp-A", duration: 450, canceled: false }, - { title: "등 운동 하는 날 반드시 시청해야 할 영상 ( feat.등 운동 루틴 풀버전 ) 등 운동 하는 날 반드시 시청해야 할 영상 ( feat.등 운동 루틴 풀버전 ) 등 운동 하는 날 반드시 시청해야 할 영상 ( feat.등 운동 루틴 풀버전 ) 등 운동 하는 날 반드시 시청해야 할 영상 ( feat.등 운동 루틴 풀버전 ) 등 운동 하는 날 반드시 시청해야 할 영상 ( feat.등 운동 루틴 풀버전 ) 등 운동 하는 날 반드시 시청해야 할 영상 ( feat.등 운동 루틴 풀버전 ) 등 운동 하는 날 반드시 시청해야 할 영상 ( feat.등 운동 루틴 풀버전 ) ", thumbnail: "https://via.placeholder.com/150x100.png?text=Thumbnail9", link: "https://www.youtube.com/watch?v=naxGvgl9pKg&t=1102s", duration: 500, canceled: false }, - { title: "요즘 유행하는 등 운동 루틴", thumbnail: "https://via.placeholder.com/150x100.png?text=Thumbnail10", link: "https://www.youtube.com/watch?v=XLCtwqECMrs&t=279s", duration: 480, canceled: false }, - ], - }, -]; +import routineAPI from '../../api/routineAPI'; function List({ onRoutineSelect, isActive }) { - - const [routines, setRoutines] = useState(initialRoutines); // 루틴 목록 - const [selectedRoutine, setSelectedRoutine] = useState(null); // 선택한 루틴 + const [routines, setRoutines] = useState([]); + const [selectedRoutine, setSelectedRoutine] = useState(null); const [modify, setModify] = useState(false); const [isModalOpen, setIsModalOpen] = useState(false); @@ -149,161 +40,167 @@ function List({ onRoutineSelect, isActive }) { cursor: 'pointer', }; -const fetchRoutines = async () => { - try { - const data = await getUserRoutines(); - fetchExercises(data); - } catch (err) { - alert(err); - } -}; - -const fetchExercises = async (routines) => { - try { - const updatedRoutines = await Promise.all( - routines.map(async (rt) => { - const data = await getRoutineVideos(rt.name); - const updatedExercises = data.map((exercise) => ({ - ...exercise, - canceled: false, - })); - return { ...rt, exercises: updatedExercises }; - }) - ); - setRoutines(updatedRoutines); - } catch (err) { - alert(err); - } -}; - -useEffect(() => { - fetchRoutines(); -}, []); - -/* - 루틴 목록에서 원하는 루틴 클릭 시 해당 루틴을 selectedRoutine으로 설정하여 - 해당 루틴 운동 목록화면으로 전환 -*/ -const handleRoutineClick = (routine) => { - if (!modify) setSelectedRoutine(routine); + // 루틴 데이터와 비디오 데이터 함께 가져오기 + const fetchRoutines = async () => { + try { + const routineData = await routineAPI.getUserRoutines(); + if (routineData) { + // 각 루틴에 대해 비디오 정보 가져오기 + const routinesWithVideos = await Promise.all( + routineData.map(async (routine) => { + const videos = await routineAPI.getRoutineVideos(routine.routine_name); + return { + ...routine, + exercises: videos.map(video => ({ + title: video.video_title, + duration: video.video_time, + link: video.video_url, + thumbnail: video.video_thumbnail + })) + }; + }) + ); + setRoutines(routinesWithVideos); + } + } catch (err) { + console.error("루틴 데이터 가져오기 실패:", err); + } + }; -}; + useEffect(() => { + fetchRoutines(); + }, []); -/* - 뒤로가기 클릭 시 selectedRoutine 값을 null로 설정하여 루틴 목록화면으로 전환 -*/ -const handleBackClick = () => { - setSelectedRoutine(null); -}; + const handleRoutineClick = (routine) => { + if (!modify) { + const formattedRoutine = { + name: routine.routine_name, + exercises: [] + }; + setSelectedRoutine(formattedRoutine); + onRoutineSelect(formattedRoutine); + } + }; -const handledelete = async (name) => { - try { - await deleteRoutine(name); - setIsModalOpen(false); - } catch (err) { - alert(err); - } -}; + const handleBackClick = () => { + setSelectedRoutine(null); + }; -/* - 토글 클릭 시 해당 운동의 루틴 제외 여부 설정 -*/ -const toggleCancelExercise = (exercise) => { - const updatedExercises = selectedRoutine.exercises.map((ex) => - ex.title === exercise.title ? { ...ex, canceled: !ex.canceled } : ex - ); - setSelectedRoutine({ ...selectedRoutine, exercises: updatedExercises }); -}; + const handledelete = async (name) => { + try { + await routineAPI.deleteRoutine(name); + setIsModalOpen(false); + await fetchRoutines(); // 삭제 후 목록 새로고침 + } catch (err) { + alert(err); + } + }; -return ( - <div id="list-container"> - {!selectedRoutine ? ( - <div className="list-head"> - {/*selectedRoutine이 null로 루틴 목록 화면 표시*/} - <div> - <span>routine</span> - <div className="division-line"></div> - </div> - <div id="list-content"> - <ul> - {routines.map((routine) => ( - <li key={routine.id} onClick={() => handleRoutineClick(routine)}> - <span>{truncateText(routine.name, 10)}</span> - {modify && ( - <button onClick={(e) => { - e.stopPropagation(); // 이벤트 전파 중단 - setIsModalOpen(true); - }}>X</button> + const handleAddRoutine = async () => { + try { + const routineName = prompt("새로운 루틴의 이름을 입력하세요:"); + if (!routineName) return; + + await routineAPI.createRoutine(routineName); + await fetchRoutines(); + } catch (err) { + console.error("루틴 추가 실패:", err); + alert("루틴 추가에 실패했습니다."); + } + }; - )} - {isModalOpen && ( - <div style={modalStyle}> - <div style={modalContentStyle}> - <div> - 루틴을 삭제하겠습니까? - <div> - <button style={buttonStyle} onClick={() => handledelete(routine.title)}>삭제</button> - <button style={buttonStyle} onClick={() => setIsModalOpen(false)}>취소</button> + return ( + <div id="list-container"> + {!selectedRoutine ? ( + <div className="list-head"> + <div> + <span>routine</span> + <div className="division-line"></div> + </div> + <div id="list-content"> + <ul> + {routines && routines.length > 0 ? ( + routines.map((routine) => ( + <li key={routine._id} onClick={() => handleRoutineClick(routine)}> + <span>{truncateText(routine.routine_name, 10)}</span> + {modify && ( + <button onClick={(e) => { + e.stopPropagation(); + setIsModalOpen(true); + }}>X</button> + )} + {isModalOpen && ( + <div style={modalStyle}> + <div style={modalContentStyle}> + <div> + 루틴을 삭제하겠습니까? + <div> + <button style={buttonStyle} onClick={() => handledelete(routine.routine_name)}>삭제</button> + <button style={buttonStyle} onClick={() => setIsModalOpen(false)}>취소</button> + </div> + </div> </div> </div> - </div> - </div> - )} - </li> - ))} - </ul> - {!modify ? (<button className='back' onClick={() => setModify(true)}>수정</button> - ) : (<button className='back' onClick={() => setModify(false)}>뒤로가기</button>)} - </div> - </div> - ) : ( - <div className="list-head"> - {/*selectedRoutine이 설정 되어 운동 목록 화면 표시*/} - <div> - <span>{selectedRoutine.name}</span> - <div className="division-line"></div> + )} + </li> + )) + ) : ( + <li className="no-routine-message"> + <span>루틴이 없습니다. 새로운 루틴을 추가해주세요.</span> + </li> + )} + </ul> + <div className="list-foot"> + {!modify ? ( + <button className='back' onClick={() => setModify(true)}>수정</button> + ) : ( + <> + <button className='add' onClick={handleAddRoutine}>추가</button> + <button className='back' onClick={() => setModify(false)}>뒤로가기</button> + </> + )} + </div> + </div> </div> - <div id="list-content"> - <ul> - {selectedRoutine.exercises.map((exercise, index) => ( - <li key={index}> - <span - style={{ - textDecoration: exercise.canceled ? "line-through" : "none", - }} - > - {truncateText(exercise.title, 11)} - </span> - <button onClick={() => toggleCancelExercise(exercise)}> - {exercise.canceled ? "X" : "O"} - </button> - </li> - ))} - </ul> - <div className="list-foot"> - <button - className="pick" - onClick={() => { - if (!isActive) { - onRoutineSelect({ - ...selectedRoutine, - exercises: selectedRoutine.exercises.filter( - (exercise) => !exercise.canceled - ), - }); - } - }} - > - 선택 - </button> - <button className='back' onClick={handleBackClick}>뒤로가기</button> + ) : ( + <div className="list-head"> + <div> + <span>{selectedRoutine.name}</span> + <div className="division-line"></div> + </div> + <div id="list-content"> + <ul> + {selectedRoutine.exercises && selectedRoutine.exercises.length > 0 ? ( + selectedRoutine.exercises.map((exercise, index) => ( + <li key={index}> + <span>{truncateText(exercise.title, 11)}</span> + </li> + )) + ) : ( + <li className="no-exercise-message"> + <span>운동이 없습니다. 운동을 추가해주세요.</span> + </li> + )} + </ul> + <div className="list-foot"> + <button + className="pick" + onClick={() => { + if (!isActive) { + onRoutineSelect(selectedRoutine); + } + }} + disabled={isActive || selectedRoutine.exercises.length === 0} + > + 선택 + </button> + <button className='back' onClick={handleBackClick}>뒤로가기</button> + </div> </div> </div> - </div> - ) - } - </div > -); + )} + </div> + ); } export default List;