본문 바로가기

TIL

TIL - 운동 기록 루틴 Supabase 연동

운동 루틴 등록 API 구현

import { createClient } from '@/supabase/server';
import { NextRequest, NextResponse } from 'next/server';

export const POST = async (request: NextRequest) => {
  try {
    const supabase = createClient();

    // 사용자 인증 확인
    const {
      data: { user },
      error: authError,
    } = await supabase.auth.getUser();
    if (authError || !user) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });

    const userId = user.id;

    // request body에서 데이터 추출
    const { exerciseData, routineData } = await request.json();
    const { isRoutine } = exerciseData;

    if (!isRoutine || !routineData) {
      return NextResponse.json({ message: '루틴이 아니거나 루틴 데이터가 없습니다.' }, { status: 204 });
    }

    const { endDate, days } = routineData;

    // 필수 필드 검증
    if (!endDate || !days) {
      return NextResponse.json({ error: '필수 필드가 누락되었습니다' }, { status: 400 });
    }

    // Supabase에 데이터 삽입
    const { data, error } = await supabase.from('exerciseRoutines').insert({
      userId,
      endDate,
      days,
      exerciseId: 7,
    });

    if (error) return NextResponse.json({ error: error.message }, { status: 400 });

    return NextResponse.json({ message: '루틴이 성공적으로 등록되었습니다', data }, { status: 200 });
  } catch (e) {
    console.error('루틴 등록 오류:', e);
    return NextResponse.json({ error: '루틴 등록에 실패했습니다' }, { status: 500 });
  }
};

설명:

  • Supabase 클라이언트를 사용하여 사용자 인증을 확인
  • exerciseData와 routineData를 요청 본문에서 추출하고, exerciseData의 isRoutine 값을 확인하여 루틴 여부를 판별
  • routineData에서 필수 필드(endDate, days)를 검증하고, 필수 필드가 누락된 경우 적절한 응답을 반환
  • 데이터를 exerciseRoutines 테이블에 삽입하고, 오류 발생 시 적절한 오류 메시지를 반환

운동 데이터 등록 UI 구현

'use client';

import Button from '@/components/Button';
import axios from 'axios';
import dayjs from 'dayjs';
import { useEffect, useState } from 'react';
import 'swiper/css';
import TestMonthCalendar from './TestMonthCalendar';
import TestWeekCalendar from './TestWeekCalendar';

const TestCalendar = () => {
  const [isMonthView, setIsMonthView] = useState(false);
  const [selectedDate, setSelectedDate] = useState<Date>(new Date());
  const [selectedDays, setSelectedDays] = useState<number[]>([]);
  const [currentDisplayDate, setCurrentDisplayDate] = useState<Date>(new Date());

  const toggleView = () => {
    setIsMonthView(!isMonthView);
  };

  const handleDateSelect = (date: Date) => {
    const today = dayjs().startOf('day');
    const selectedDate = dayjs(date);
    if (selectedDate.isAfter(today) || selectedDate.isSame(today, 'day')) {
      setSelectedDate(date);
      console.log('Selected date:', selectedDate.format('YYYY-MM-DD'));
    } else {
      console.log('마감일은 과거 선택이 불가능합니다.');
    }
  };

  const handleDayClick = (dayIndex: number) => {
    setSelectedDays((prev) => {
      const newSelectedDays = prev.includes(dayIndex) ? prev.filter((d) => d !== dayIndex) : [...prev, dayIndex];
      return [...newSelectedDays].sort((a, b) => a - b);
    });
  };

  useEffect(() => {
    console.log(selectedDays);
  }, [selectedDays]);

  const getMonthYear = (date: Date | null): string => {
    if (!date) return '';
    return date.toLocaleDateString('ko-KR', { year: 'numeric', month: 'long' });
  };

  const handleSubmit = async () => {
    const exerciseData = {
      isRoutine: selectedDays.length > 0, // 선택된 요일이 있으면 루틴으로 간주
    };

    const routineData =
      selectedDays.length > 0
        ? {
            endDate: dayjs(selectedDate).format('YYYY-MM-DD'),
            days: selectedDays,
          }
        : null;

    try {
      const response = await axios.post('/api/exercises/routines', {
        exerciseData,
        routineData,
      });

      console.log('운동 및 루틴 등록 성공:', response.data);
      // 성공 처리 (예: 메시지 표시, 상태 초기화 등)
    } catch (error) {
      console.error('운동 및 루틴 등록 실패:', error);
      // 오류 처리 (예: 오류 메시지 표시)
    }
  };

  return (
    <div className="w-full p-2">
      <header className="flex flex-col gap-2 items-center bg-gray-100 py-2 px-4">
        <button onClick={toggleView} className="bg-blue-500 text-white px-4 py-2 rounded">
          {isMonthView ? '주별 보기' : '월별 보기'}
        </button>
        <h2 className="font-bold">{getMonthYear(currentDisplayDate)}</h2>
      </header>
      {isMonthView ? (
        <TestMonthCalendar
          selectedDate={selectedDate}
          onSelectDate={handleDateSelect}
          onChangeMonth={setCurrentDisplayDate}
          selectedDays={selectedDays}
          onDayClick={handleDayClick}
        />
      ) : (
        <TestWeekCalendar
          selectedDate={selectedDate}
          onSelectDate={handleDateSelect}
          onChangeWeek={setCurrentDisplayDate}
          selectedDays={selectedDays}
          onDayClick={handleDayClick}
        />
      )}
      <Button onClick={handleSubmit}>운동 등록</Button>
    </div>
  );
};

export default TestCalendar;

설명:

  • React 컴포넌트에서 TestCalendar UI를 구현하였으며, 사용자가 날짜를 선택하고 루틴을 설정할 수 있는 기능 추가
  • 사용자가 날짜를 선택하면 selectedDate와 selectedDays 상태 업데이트
  • handleSubmit 함수는 선택된 날짜와 요일을 포함하여 운동 루틴을 등록하는 API 호출
  • 날짜 선택 및 캘린더 뷰 전환 기능 구현

 

추가 사항:

  • 현재의 exerciseId는 하드코딩된 값으로 설정되어 있으며, 향후 다른 route와 통합할 때 동적으로 처리
  • 현재는 운동 기록과 루틴 등록이 별도로 구현되어 있으며, exerciseId 통합을 고려하여 구조를 사전 설계