-
Notifications
You must be signed in to change notification settings - Fork 0
#52 feat: 문제 선택 페이지 구현 및 과제 API 연동 #57
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: develop
Are you sure you want to change the base?
Changes from all commits
b12d3b8
c17ba82
dd44942
c3fb10b
d590227
46d842f
b5495f5
c5b95d9
932b8d6
072672a
e228664
3c49ba1
2e6b2f4
92c4b51
d4428d1
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,8 +1,55 @@ | ||
| import type {DashboardScheduleListResponse} from '@/entities/course/model/types'; | ||
| import type { | ||
| AssignmentSelectResponse, | ||
| DashboardScheduleListResponse, | ||
| } from '@/entities/course/model/types'; | ||
| import {privateAxios} from '@/shared/api/axiosInstance'; | ||
| import type {ApiResponse} from '@/shared/model'; | ||
| import type {AssignmentsResponse} from '../model/types'; | ||
|
|
||
| // 과제 일정 조회 API | ||
| export const getAssignmentSchedules = | ||
| async (): Promise<DashboardScheduleListResponse> => { | ||
| const response = await privateAxios.get('/assignments/schedule'); | ||
| return response.data; | ||
| }; | ||
|
|
||
| // 전체 과제 목록 조회 API | ||
| export const getAllAssignments = async (): Promise< | ||
| ApiResponse<AssignmentsResponse> | ||
| > => { | ||
| const response = await privateAxios.get< | ||
| ApiResponse<{ | ||
| count: number; | ||
| assignments: {assignmentId: number; title: string}[]; | ||
| }> | ||
| >('/assignments/my'); | ||
| const raw = response.data; | ||
| return { | ||
| ...raw, | ||
| response: { | ||
| count: raw.response.count, | ||
| assignments: raw.response.assignments.map(({assignmentId, title}) => ({ | ||
| id: assignmentId, | ||
| title, | ||
| })), | ||
| }, | ||
| }; | ||
| }; | ||
|
|
||
| // 강의별 과제 목록 조회 API | ||
| export const getAssignmentsByCourse = async ( | ||
| courseId: number | ||
| ): Promise<AssignmentSelectResponse> => { | ||
| const response = await privateAxios.get(`/courses/${courseId}/assignments`); | ||
|
|
||
| console.log('getAssignmentsByCourse API 응답:', response.data); // 응답 데이터 로깅 | ||
| return response.data; | ||
| }; | ||
|
|
||
| // 과제 삭제 API | ||
| export const deleteAssignment = async ( | ||
| assignmentId: number | ||
| ): Promise<ApiResponse<string>> => { | ||
| const response = await privateAxios.delete(`/assignments/${assignmentId}`); | ||
| return response.data; | ||
| }; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,30 @@ | ||
| import {queryOptions} from '@tanstack/react-query'; | ||
| import { | ||
| getAllAssignments, | ||
| getAssignmentsByCourse, | ||
| getAssignmentSchedules, | ||
| } from './assignmentApi'; | ||
|
|
||
| export const assignmentQueries = { | ||
| // 과제 일정 조회 쿼리 옵션 | ||
| getAssignmentSchedules: () => | ||
| queryOptions({ | ||
| queryKey: ['schedules'], | ||
| queryFn: getAssignmentSchedules, | ||
| }), | ||
|
|
||
| // 전체 과제 목록 조회 쿼리 옵션 | ||
| getAllAssignments: () => | ||
| queryOptions({ | ||
| queryKey: ['assignments'], | ||
| queryFn: getAllAssignments, | ||
| }), | ||
|
|
||
| // 강의별 과제 목록 조회 쿼리 옵션 | ||
| getAssignmentsByCourse: (courseId: number) => | ||
| queryOptions({ | ||
| queryKey: ['courses', courseId, 'assignments'], | ||
| queryFn: () => getAssignmentsByCourse(courseId), | ||
| enabled: !!courseId, // courseId가 있을 때만 쿼리 실행 | ||
| }), | ||
| }; |
This file was deleted.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| import {deleteCourse} from './courseApi'; | ||
|
|
||
| export const courseMutations = { | ||
| // 강의 삭제 뮤테이션 옵션 | ||
| deleteCourse: { | ||
| mutationKey: ['deleteCourse'], | ||
| mutationFn: (courseId: number) => deleteCourse(courseId), | ||
| }, | ||
| }; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| import {getAllCourses} from './courseApi'; | ||
| import {queryOptions} from '@tanstack/react-query'; | ||
|
|
||
| export const courseQueries = { | ||
| // 전체 강의 조회 쿼리 옵션 | ||
| getAllCourses: () => | ||
| queryOptions({ | ||
| queryKey: ['courses'], | ||
| queryFn: getAllCourses, | ||
| }), | ||
| }; |
This file was deleted.
This file was deleted.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,51 @@ | ||
| import type {Assignment} from '@/entities/assignment/model/types'; | ||
| import {create} from 'zustand'; | ||
| import {createJSONStorage, persist} from 'zustand/middleware'; | ||
|
|
||
| interface UnitState { | ||
| title: string; | ||
| releaseDate: string; | ||
| dueDate: string; | ||
| assignments: Assignment[]; | ||
| storeFormData: (title: string, releaseDate: string, dueDate: string) => void; | ||
| resetStore: () => void; | ||
| setAssignments: (assignments: Assignment[]) => void; | ||
| } | ||
|
|
||
| export const useUnitStore = create<UnitState>()( | ||
| persist( | ||
| (set) => ({ | ||
| title: '', | ||
| releaseDate: '', | ||
| dueDate: '', | ||
| assignments: [], | ||
|
|
||
| // 단원 폼 임시 저장 | ||
| storeFormData: (title, releaseDate, dueDate) => | ||
| set({ | ||
| title: title, | ||
| releaseDate: releaseDate, | ||
| dueDate: dueDate, | ||
| }), | ||
|
|
||
| // 선택된 과제 ID 저장 | ||
| setAssignments: (assignments) => set({assignments}), | ||
|
|
||
| // 단원 폼 초기화 | ||
| resetStore: () => | ||
| set({title: '', releaseDate: '', dueDate: '', assignments: []}), | ||
| }), | ||
| { | ||
| name: 'unit-session-storage', | ||
| storage: createJSONStorage(() => sessionStorage), | ||
| partialize: (state) => ({ | ||
| title: state.title, | ||
| releaseDate: state.releaseDate, | ||
| dueDate: state.dueDate, | ||
| assignments: state.assignments, | ||
| }), | ||
| } | ||
| ) | ||
| ); | ||
|
|
||
| export default useUnitStore; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,35 +1,68 @@ | ||
| import AssignmentListContainer from './ui/AssignmentListContainer'; | ||
| import {useState} from 'react'; | ||
| import { | ||
| response, | ||
| courseOptionsResponse, | ||
| } from '@/shared/mocks/assignmentSelectResponse'; | ||
| import {useCourseFilter} from '@/features/course/filter-course/lib/useCourseFilter'; | ||
| import {AssignmentPageLayout} from '@/widgets/assignment-page-layout'; | ||
| import ListRow from '@/shared/ui/list-row/ListRow'; | ||
| import {useQuery} from '@tanstack/react-query'; | ||
| import {courseQueries} from '@/entities/course/api/courseQueries'; | ||
| import {assignmentQueries} from '@/entities/assignment/api/assignmentQueries'; | ||
| import useUnitStore from '@/entities/unit/model/useUnitStore'; | ||
| import {useLocation, useNavigate} from 'react-router-dom'; | ||
| import type {Assignment} from '@/entities/assignment/model/types'; | ||
|
|
||
| const AssignmentSelectPage = () => { | ||
| const {courses} = courseOptionsResponse.response; // /courses/my API 응답 모킹 | ||
| const [selectedAssignments, setSelectedAssignments] = useState<number[]>([]); // 선택된 문제 ID 목록 | ||
|
|
||
| const {courseOptions, handleCourseSelect} = useCourseFilter(courses); | ||
| const navigate = useNavigate(); | ||
| const location = useLocation(); | ||
| const {data: courseList} = useQuery(courseQueries.getAllCourses()); | ||
| const {setAssignments, assignments: currentSelectedAssignments} = | ||
| useUnitStore(); | ||
| const [selectedAssignments, setSelectedAssignments] = useState<Assignment[]>( | ||
| currentSelectedAssignments ?? [] | ||
| ); | ||
| const {courseOptions, handleCourseSelect, selectedCourseId} = useCourseFilter( | ||
| courseList?.response.courses ?? [] | ||
| ); | ||
| const {data: allAssignments} = useQuery( | ||
| assignmentQueries.getAllAssignments() | ||
| ); | ||
| const {data: assignments} = useQuery( | ||
| assignmentQueries.getAssignmentsByCourse(selectedCourseId ?? 0) | ||
| ); | ||
|
|
||
| // 문제 목록 /courses/{courseId}/assignments API 응답 모킹 | ||
| const assignmentList = response.response.courses.flatMap( | ||
| const filteredAssignments = assignments?.response.courses.flatMap( | ||
| (course) => course.assignments | ||
| ); | ||
|
|
||
| // 선택된 강의에 따라 보여줄 과제 목록 결정 | ||
| const assignmentList = selectedCourseId | ||
| ? (filteredAssignments ?? []) | ||
| : (allAssignments?.response.assignments ?? []); | ||
|
|
||
| // 문제 선택 핸들러 | ||
| const handleAssignmentSelect = (assignmentId: number) => { | ||
| const handleAssignmentSelect = (assignment: Assignment) => { | ||
| setSelectedAssignments((prev) => { | ||
| if (prev.includes(assignmentId)) { | ||
| return prev.filter((id) => id !== assignmentId); // 선택 해제 | ||
| } else { | ||
| return [...prev, assignmentId]; // 선택 추가 | ||
| if (prev.some((a) => a.id === assignment.id)) { | ||
| return prev.filter((a) => a.id !== assignment.id); | ||
| } | ||
| return [...prev, assignment]; | ||
| }); | ||
| }; | ||
|
|
||
| const returnToPreviousPage = () => { | ||
| navigate(location.state?.backPath ?? -1, { | ||
| state: { | ||
| mode: location.state?.mode, | ||
| unitId: location.state?.unitId, | ||
| currentIndex: location.state?.currentIndex, | ||
| }, | ||
| }); | ||
|
Comment on lines
+52
to
+58
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: cd /tmp && git ls-files | grep -i "AssignmentSelectPage" | head -5Repository: 2025-snowCode/snowCode-Client Length of output: 141 🌐 Web query:
💡 Result: In React Router v7, If you need to send new state, use path navigation instead: Sources: Citations:
🏁 Script executed: fd "AssignmentSelectPage.tsx" --type fRepository: 2025-snowCode/snowCode-Client Length of output: 125 🏁 Script executed: cat -n src/pages/select-assignment/AssignmentSelectPage.tsx 2>/dev/null || find . -name "AssignmentSelectPage.tsx" -type f -exec cat -n {} \;Repository: 2025-snowCode/snowCode-Client Length of output: 3720 Delta 이동 시 state 파라미터가 적용되지 않습니다. 52줄의 해결 방법: path와 delta를 분리하여 처리하세요. 🔧 수정 제안 const returnToPreviousPage = () => {
+ const backPath = location.state?.backPath;
+ const nextState = {
+ mode: location.state?.mode,
+ unitId: location.state?.unitId,
+ currentIndex: location.state?.currentIndex,
+ };
+
+ if (backPath) {
+ navigate(backPath, { state: nextState });
+ return;
+ }
+ navigate(-1);
- navigate(location.state?.backPath ?? -1, {
- state: {
- mode: location.state?.mode,
- unitId: location.state?.unitId,
- currentIndex: location.state?.currentIndex,
- },
- });
};참고: React Router v7 useNavigate API 문서 🤖 Prompt for AI Agents |
||
| }; | ||
|
|
||
| const handleConfirm = () => { | ||
| setAssignments(selectedAssignments); | ||
| returnToPreviousPage(); | ||
| }; | ||
|
|
||
| return ( | ||
| <AssignmentPageLayout | ||
| title='문제 선택' | ||
|
|
@@ -43,13 +76,15 @@ const AssignmentSelectPage = () => { | |
| renderItem={(assignment) => ( | ||
| <ListRow | ||
| title={assignment.title} | ||
| selected={selectedAssignments.includes(assignment.id)} | ||
| selected={selectedAssignments.some((a) => a.id === assignment.id)} | ||
| /> | ||
| )} | ||
| /> | ||
| } | ||
| onCancel={() => {}} | ||
| onConfirm={() => {}} | ||
| onCancel={() => { | ||
| returnToPreviousPage(); | ||
| }} | ||
| onConfirm={handleConfirm} | ||
| /> | ||
| ); | ||
| }; | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
API 응답 디버그 로그는 릴리스 전에 제거해 주세요.
Line 45의
console.log는 운영 환경에서 불필요한 데이터 노출과 콘솔 노이즈를 만듭니다. 필요하면 로컬 디버깅 플래그 기반으로 제한해 주세요.🧼 제안 수정안
- console.log('getAssignmentsByCourse API 응답:', response.data); // 응답 데이터 로깅 return response.data;📝 Committable suggestion
🤖 Prompt for AI Agents