From b12d3b81fb228919969e1c89deff8fa4d977740c Mon Sep 17 00:00:00 2001 From: suminb99 Date: Tue, 24 Feb 2026 17:20:46 +0900 Subject: [PATCH 01/15] =?UTF-8?q?#52=20feat:=20=EA=B3=BC=EC=A0=9C=20?= =?UTF-8?q?=EA=B4=80=EB=A0=A8=20API=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/entities/assignment/api/assignmentApi.ts | 49 +++++++++++++++++++- 1 file changed, 48 insertions(+), 1 deletion(-) diff --git a/src/entities/assignment/api/assignmentApi.ts b/src/entities/assignment/api/assignmentApi.ts index 23940b5..e3af901 100644 --- a/src/entities/assignment/api/assignmentApi.ts +++ b/src/entities/assignment/api/assignmentApi.ts @@ -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 => { const response = await privateAxios.get('/assignments/schedule'); return response.data; }; + +// 전체 과제 목록 조회 API +export const getAllAssignments = async (): Promise< + ApiResponse +> => { + 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 => { + 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> => { + const response = await privateAxios.delete(`/assignments/${assignmentId}`); + return response.data; +}; From c17ba820c0e674aa169e1203da70e61e96491a61 Mon Sep 17 00:00:00 2001 From: suminb99 Date: Tue, 24 Feb 2026 17:21:09 +0900 Subject: [PATCH 02/15] =?UTF-8?q?#52=20chore:=20=EC=A3=BC=EC=84=9D=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/entities/course/api/courseApi.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/entities/course/api/courseApi.ts b/src/entities/course/api/courseApi.ts index deae300..a1afec0 100644 --- a/src/entities/course/api/courseApi.ts +++ b/src/entities/course/api/courseApi.ts @@ -2,11 +2,13 @@ import type {ApiResponse} from '@/shared/model/common'; import type {DashboardCourseListResponse} from '@/entities/course/model/types'; import {privateAxios} from '@/shared/api/axiosInstance'; +// 전체 강의 목록 조회 API export const getAllCourses = async (): Promise => { const response = await privateAxios.get('/courses/my'); return response.data; }; +// 강의 삭제 API export const deleteCourse = async ( courseId: number ): Promise> => { From dd44942af0b2b3043010cd8df425ab4f9c0ea31e Mon Sep 17 00:00:00 2001 From: suminb99 Date: Tue, 24 Feb 2026 17:21:51 +0900 Subject: [PATCH 03/15] =?UTF-8?q?#52=20feat:=20=EB=8B=A8=EC=9B=90-?= =?UTF-8?q?=EB=AC=B8=EC=A0=9C=20=EC=97=B0=EA=B2=B0=20=ED=95=B4=EC=A0=9C=20?= =?UTF-8?q?api=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/entities/unit/api/unitApi.ts | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/entities/unit/api/unitApi.ts b/src/entities/unit/api/unitApi.ts index b716386..428720c 100644 --- a/src/entities/unit/api/unitApi.ts +++ b/src/entities/unit/api/unitApi.ts @@ -44,3 +44,14 @@ export const updateUnit = async ( const response = await privateAxios.put(`/units/${unitId}`, unit); return response.data; }; + +// 단원에 등록된 과제 삭제 +export const deleteAssignmentFromUnit = async ( + unitId: number, + assignmentId: number +): Promise> => { + const response = await privateAxios.delete( + `/units/${unitId}/assignments/${assignmentId}` + ); + return response.data; +}; From c3fb10b0cbc5ad9e1a29d29a780e8388ee9954b3 Mon Sep 17 00:00:00 2001 From: suminb99 Date: Tue, 24 Feb 2026 17:22:59 +0900 Subject: [PATCH 04/15] =?UTF-8?q?#52=20feat:=20=EA=B3=BC=EC=A0=9C=20?= =?UTF-8?q?=EA=B4=80=EB=A0=A8=20=EC=BF=BC=EB=A6=AC=20=EC=98=B5=EC=85=98=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../assignment/api/assignmentQueries.ts | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 src/entities/assignment/api/assignmentQueries.ts diff --git a/src/entities/assignment/api/assignmentQueries.ts b/src/entities/assignment/api/assignmentQueries.ts new file mode 100644 index 0000000..d2554d8 --- /dev/null +++ b/src/entities/assignment/api/assignmentQueries.ts @@ -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가 있을 때만 쿼리 실행 + }), +}; From d590227d2d52bad08783e133273613e531c434cb Mon Sep 17 00:00:00 2001 From: suminb99 Date: Tue, 24 Feb 2026 17:23:41 +0900 Subject: [PATCH 05/15] =?UTF-8?q?#52=20refactor:=20query=20=ED=8C=8C?= =?UTF-8?q?=EC=9D=BC=20=EA=B5=AC=EC=A1=B0=20=EA=B0=9C=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/entities/assignment/api/assignmentQueryOptions.ts | 9 --------- src/entities/course/api/courseQueries.ts | 11 +++++++++++ src/entities/course/api/courseQueryOptions.ts | 9 --------- 3 files changed, 11 insertions(+), 18 deletions(-) delete mode 100644 src/entities/assignment/api/assignmentQueryOptions.ts create mode 100644 src/entities/course/api/courseQueries.ts delete mode 100644 src/entities/course/api/courseQueryOptions.ts diff --git a/src/entities/assignment/api/assignmentQueryOptions.ts b/src/entities/assignment/api/assignmentQueryOptions.ts deleted file mode 100644 index 89af95e..0000000 --- a/src/entities/assignment/api/assignmentQueryOptions.ts +++ /dev/null @@ -1,9 +0,0 @@ -import {queryOptions} from '@tanstack/react-query'; -import {getAssignmentSchedules} from './assignmentApi'; - -export default function assignmentQueryOptions() { - return queryOptions({ - queryKey: ['schedules'], - queryFn: getAssignmentSchedules, - }); -} diff --git a/src/entities/course/api/courseQueries.ts b/src/entities/course/api/courseQueries.ts new file mode 100644 index 0000000..2999360 --- /dev/null +++ b/src/entities/course/api/courseQueries.ts @@ -0,0 +1,11 @@ +import {getAllCourses} from './courseApi'; +import {queryOptions} from '@tanstack/react-query'; + +export const courseQueries = { + // 전체 강의 조회 쿼리 옵션 + getAllCourses: () => + queryOptions({ + queryKey: ['courses'], + queryFn: getAllCourses, + }), +}; diff --git a/src/entities/course/api/courseQueryOptions.ts b/src/entities/course/api/courseQueryOptions.ts deleted file mode 100644 index ab5d82f..0000000 --- a/src/entities/course/api/courseQueryOptions.ts +++ /dev/null @@ -1,9 +0,0 @@ -import {getAllCourses} from './courseApi'; -import {queryOptions} from '@tanstack/react-query'; - -export default function courseQueryOptions() { - return queryOptions({ - queryKey: ['courses'], - queryFn: getAllCourses, - }); -} From 46d842f3289179abd3ac55cae419aaba4998fbfc Mon Sep 17 00:00:00 2001 From: suminb99 Date: Tue, 24 Feb 2026 17:23:57 +0900 Subject: [PATCH 06/15] =?UTF-8?q?#52=20feat:=20=EA=B0=95=EC=9D=98=20?= =?UTF-8?q?=EC=82=AD=EC=A0=9C=20=EB=AE=A4=ED=85=8C=EC=9D=B4=EC=85=98=20?= =?UTF-8?q?=EC=98=B5=EC=85=98=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/entities/course/api/courseMutations.ts | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 src/entities/course/api/courseMutations.ts diff --git a/src/entities/course/api/courseMutations.ts b/src/entities/course/api/courseMutations.ts new file mode 100644 index 0000000..0eea045 --- /dev/null +++ b/src/entities/course/api/courseMutations.ts @@ -0,0 +1,9 @@ +import {deleteCourse} from './courseApi'; + +export const courseMutations = { + // 강의 삭제 뮤테이션 옵션 + deleteCourse: { + mutationKey: ['deleteCourse'], + mutationFn: (courseId: number) => deleteCourse(courseId), + }, +}; From b5495f54c664ffb5cd8d2c2ad6ef71c46204d6f5 Mon Sep 17 00:00:00 2001 From: suminb99 Date: Tue, 24 Feb 2026 17:24:28 +0900 Subject: [PATCH 07/15] =?UTF-8?q?#52=20chore:=20barrel=20export=20?= =?UTF-8?q?=ED=8C=8C=EC=9D=BC=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/entities/course/index.ts | 1 - 1 file changed, 1 deletion(-) delete mode 100644 src/entities/course/index.ts diff --git a/src/entities/course/index.ts b/src/entities/course/index.ts deleted file mode 100644 index 6b5cfff..0000000 --- a/src/entities/course/index.ts +++ /dev/null @@ -1 +0,0 @@ -export {getAllCourses, deleteCourse} from './api/courseApi'; From c5b95d91123f4bfe0f7604adc3761f03dfc1da15 Mon Sep 17 00:00:00 2001 From: suminb99 Date: Tue, 24 Feb 2026 17:25:04 +0900 Subject: [PATCH 08/15] =?UTF-8?q?#52=20style:=20=EC=A0=84=EC=B2=B4=20?= =?UTF-8?q?=EB=A0=88=EC=9D=B4=EC=95=84=EC=9B=83=20items-center=20=EC=A0=81?= =?UTF-8?q?=EC=9A=A9=20=EC=B7=A8=EC=86=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/shared/ui/Layout.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/shared/ui/Layout.tsx b/src/shared/ui/Layout.tsx index bf302a7..45913e5 100644 --- a/src/shared/ui/Layout.tsx +++ b/src/shared/ui/Layout.tsx @@ -9,7 +9,7 @@ const Layout = () => { const showHeader = !noHeaderPages.includes(pathname); return ( -
+
{showHeader && (
From 932b8d6008d11f65305bbd2aec8c8b059913a36b Mon Sep 17 00:00:00 2001 From: suminb99 Date: Tue, 24 Feb 2026 17:25:53 +0900 Subject: [PATCH 09/15] =?UTF-8?q?#52=20feat:=20=EA=B3=BC=EC=A0=9C=20?= =?UTF-8?q?=EC=9D=91=EB=8B=B5=20=ED=83=80=EC=9E=85=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/entities/assignment/model/types.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/entities/assignment/model/types.ts b/src/entities/assignment/model/types.ts index 331c549..bdbf0a8 100644 --- a/src/entities/assignment/model/types.ts +++ b/src/entities/assignment/model/types.ts @@ -8,3 +8,8 @@ export interface Assignment { title: string; submittedStatus?: SubmissionStatus; } + +export interface AssignmentsResponse { + count: number; + assignments: Assignment[]; +} From 072672a08ed1e0c0cfff6cf64ae7f370ca0444eb Mon Sep 17 00:00:00 2001 From: suminb99 Date: Tue, 24 Feb 2026 17:27:57 +0900 Subject: [PATCH 10/15] =?UTF-8?q?#52=20refactor:=20=EB=A6=AC=ED=8C=A9?= =?UTF-8?q?=ED=86=A0=EB=A7=81=EB=90=9C=20query/mutation=20=EC=A0=81?= =?UTF-8?q?=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/dashboard/Dashboard.tsx | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/src/pages/dashboard/Dashboard.tsx b/src/pages/dashboard/Dashboard.tsx index e1ca73a..d9acfd6 100644 --- a/src/pages/dashboard/Dashboard.tsx +++ b/src/pages/dashboard/Dashboard.tsx @@ -5,15 +5,15 @@ import AddIcon from '@/assets/svg/addIcon.svg?react'; import ScheduleList from './ui/ScheduleList'; import {Link} from 'react-router-dom'; import {useUserStore} from '@/entities/auth/model/useUserStore'; -import courseQueryOptions from '@/entities/course/api/courseQueryOptions'; import { useMutation, useQueryClient, useSuspenseQueries, } from '@tanstack/react-query'; -import assignmentQueryOptions from '@/entities/assignment/api/assignmentQueryOptions'; -import {deleteCourse} from '@/entities/course'; import {EmptyState} from '@/shared/ui/EmptyState'; +import {courseQueries} from '@/entities/course/api/courseQueries'; +import {courseMutations} from '@/entities/course/api/courseMutations'; +import {assignmentQueries} from '@/entities/assignment/api/assignmentQueries'; const Dashboard = () => { const userType = useUserStore((state) => state.userType); @@ -21,19 +21,22 @@ const Dashboard = () => { // 강의 및 스케쥴 데이터 패칭 const [{data: courses}, {data: schedules}] = useSuspenseQueries({ - queries: [courseQueryOptions(), assignmentQueryOptions()], + queries: [ + courseQueries.getAllCourses(), + assignmentQueries.getAssignmentSchedules(), + ], }); - // 강의 삭제 뮤테이션 + // 강의 삭제 const {mutate} = useMutation({ - mutationFn: (courseId: number) => deleteCourse(courseId), + ...courseMutations.deleteCourse, onSuccess: () => { // 강의 목록 및 스케쥴 목록 갱신 queryClient.invalidateQueries({ - queryKey: courseQueryOptions().queryKey, + queryKey: courseQueries.getAllCourses().queryKey, }); queryClient.invalidateQueries({ - queryKey: assignmentQueryOptions().queryKey, + queryKey: assignmentQueries.getAssignmentSchedules().queryKey, }); alert('강의가 성공적으로 삭제되었습니다.'); }, From e2286644acae29eceb78a503b19ae34a651906c2 Mon Sep 17 00:00:00 2001 From: suminb99 Date: Tue, 24 Feb 2026 17:28:18 +0900 Subject: [PATCH 11/15] =?UTF-8?q?#52=20feat:=20=EA=B3=BC=EC=A0=9C=20?= =?UTF-8?q?=EC=84=A0=ED=83=9D=20=ED=8E=98=EC=9D=B4=EC=A7=80=20API=20?= =?UTF-8?q?=EC=97=B0=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../AssignmentSelectPage.tsx | 30 +++++++++++++------ 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/src/pages/select-assignment/AssignmentSelectPage.tsx b/src/pages/select-assignment/AssignmentSelectPage.tsx index b84b174..5c86b09 100644 --- a/src/pages/select-assignment/AssignmentSelectPage.tsx +++ b/src/pages/select-assignment/AssignmentSelectPage.tsx @@ -1,24 +1,34 @@ 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'; const AssignmentSelectPage = () => { - const {courses} = courseOptionsResponse.response; // /courses/my API 응답 모킹 + const {data: courseList} = useQuery(courseQueries.getAllCourses()); const [selectedAssignments, setSelectedAssignments] = useState([]); // 선택된 문제 ID 목록 + const {courseOptions, handleCourseSelect, selectedCourseId} = useCourseFilter( + courseList?.response.courses ?? [] + ); + const {data: allAssignments} = useQuery( + assignmentQueries.getAllAssignments() + ); + const {data: assignments} = useQuery( + assignmentQueries.getAssignmentsByCourse(selectedCourseId ?? 0) + ); - const {courseOptions, handleCourseSelect} = useCourseFilter(courses); - - // 문제 목록 /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) => { setSelectedAssignments((prev) => { @@ -30,6 +40,8 @@ const AssignmentSelectPage = () => { }); }; + console.log('선택된 강의 ID:', selectedAssignments); + return ( Date: Thu, 26 Feb 2026 16:06:39 +0900 Subject: [PATCH 12/15] =?UTF-8?q?#52=20feat:=20=EB=8B=A8=EC=9B=90=20?= =?UTF-8?q?=ED=8F=BC=20=EC=9E=84=EC=8B=9C=20=EC=A0=80=EC=9E=A5=EC=9D=84=20?= =?UTF-8?q?=EC=9C=84=ED=95=9C=20useUnitStore=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/entities/unit/model/useUnitStore.ts | 51 +++++++++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 src/entities/unit/model/useUnitStore.ts diff --git a/src/entities/unit/model/useUnitStore.ts b/src/entities/unit/model/useUnitStore.ts new file mode 100644 index 0000000..ea0def8 --- /dev/null +++ b/src/entities/unit/model/useUnitStore.ts @@ -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()( + 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; From 2e6b2f48b81014ad762d983cd68ddf1cd89a7558 Mon Sep 17 00:00:00 2001 From: suminb99 Date: Thu, 26 Feb 2026 16:16:27 +0900 Subject: [PATCH 13/15] =?UTF-8?q?#52=20feat:=20=ED=8E=98=EC=9D=B4=EC=A7=80?= =?UTF-8?q?=20=EC=9D=B4=EB=8F=99=20=EB=B0=8F=20=EB=B3=B5=EA=B7=80=20?= =?UTF-8?q?=EC=8B=9C=20=ED=8F=BC=20=EC=83=81=ED=83=9C=20=EC=9C=A0=EC=A7=80?= =?UTF-8?q?=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/unit-editor/UnitEditorPage.tsx | 25 ++++++++--- src/pages/unit-editor/ui/UnitForm.tsx | 55 +++++++++++++++++++----- 2 files changed, 63 insertions(+), 17 deletions(-) diff --git a/src/pages/unit-editor/UnitEditorPage.tsx b/src/pages/unit-editor/UnitEditorPage.tsx index 1e632b9..616354a 100644 --- a/src/pages/unit-editor/UnitEditorPage.tsx +++ b/src/pages/unit-editor/UnitEditorPage.tsx @@ -2,13 +2,14 @@ import {UnitList} from './ui/UnitList'; import {UnitForm} from './ui/UnitForm'; import {useMutation, useQuery, useQueryClient} from '@tanstack/react-query'; import {unitQueries} from '@/entities/unit/api/unitQueries'; -import {useParams} from 'react-router-dom'; +import {useLocation, useParams} from 'react-router-dom'; import {useEffect, useState} from 'react'; import type {Mode} from './model/types'; import {unitMutations} from '@/entities/unit/api/unitMutations'; import type {TUnitFormSchema} from '@/entities/unit/model/types'; import {EmptyState} from '@/shared/ui/EmptyState'; import SurfaceCard from '@/shared/ui/SurfaceCard'; +import useUnitStore from '@/entities/unit/model/useUnitStore'; const UnitEditorPage = () => { const {id} = useParams(); // 강의 ID @@ -18,15 +19,24 @@ const UnitEditorPage = () => { const [currentIndex, setCurrentIndex] = useState(1); const {data: unitList} = useQuery(unitQueries.getUnitList(courseId)); const {data: unit} = useQuery(unitQueries.getUnitDetails(selectedUnitId)); + const {state} = useLocation(); + const {resetStore} = useUnitStore(); useEffect(() => { - if (unitList && unitList?.response.count !== 0 && mode === 'idle') { + if (state?.mode && mode === 'idle') { + setMode(state.mode); + setSelectedUnitId(state.unitId); + setCurrentIndex(state.currentIndex ?? 1); + return; + } + + if (unitList && unitList.response.count !== 0 && mode === 'idle') { setSelectedUnitId(unitList.response.units[0].id); setMode('editing'); // 편집 모드 - } else if (unitList?.response.count === 0 && mode === 'idle') { + } else if (unitList && unitList.response.count === 0 && mode === 'idle') { setMode('creating'); // 생성 모드 } - }, [unitList, mode]); + }, [unitList, mode, state]); const queryClient = useQueryClient(); const invalidateUnitList = () => { @@ -43,6 +53,7 @@ const UnitEditorPage = () => { invalidateUnitList(); setSelectedUnitId(data.response.id); setMode('editing'); // 생성 후 편집 모드로 전환 + resetStore(); // 단원 폼 초기화 alert('새 단원이 성공적으로 생성되었습니다.'); }, onError: (error) => { @@ -69,8 +80,8 @@ const UnitEditorPage = () => { ...unitMutations.deleteUnit, onSuccess: () => { invalidateUnitList(); - setMode('idle'); // 삭제 후 대기 모드로 전환 setSelectedUnitId(null); // 선택된 단원 초기화 + setMode('creating'); alert('단원이 성공적으로 삭제되었습니다.'); }, onError: (error) => { @@ -93,8 +104,8 @@ const UnitEditorPage = () => { }; // 단원 생성 핸들러 - const onCreateUnit = (unit: TUnitFormSchema) => { - addUnit({courseId: courseId, unit}); + const onCreateUnit = (unitForm: TUnitFormSchema) => { + addUnit({courseId, unitForm}); }; // 단원 업데이트 핸들러 diff --git a/src/pages/unit-editor/ui/UnitForm.tsx b/src/pages/unit-editor/ui/UnitForm.tsx index 66cbec5..f50e178 100644 --- a/src/pages/unit-editor/ui/UnitForm.tsx +++ b/src/pages/unit-editor/ui/UnitForm.tsx @@ -7,11 +7,12 @@ import {zodResolver} from '@hookform/resolvers/zod'; import {type UnitFormProps} from '../model/types'; import AddIcon from '@/assets/svg/addIcon.svg?react'; import {EmptyState} from '@/shared/ui/EmptyState'; -// import {useState} from 'react'; import { unitFormSchema, type TUnitFormSchema, } from '@/entities/unit/model/types'; +import {useLocation, useNavigate} from 'react-router-dom'; +import useUnitStore from '@/entities/unit/model/useUnitStore'; export const UnitForm = ({ unit, @@ -21,22 +22,34 @@ export const UnitForm = ({ onUpdateUnit, onDeleteUnit, }: UnitFormProps) => { - // const [assignmentIds, setAssignmentIds] = useState([]); + const location = useLocation(); + const navigate = useNavigate(); + const { + storeFormData, + resetStore, + title: storedTitle, + releaseDate: storedReleaseDate, + dueDate: storedDueDate, + assignments, + } = useUnitStore(); + + const assignmentIds = assignments.map((assignment) => assignment.id); const { register, handleSubmit, reset, + getValues, formState: {errors, isSubmitting}, } = useForm({ resolver: zodResolver(unitFormSchema), values: mode === 'creating' ? { - title: '', - releaseDate: '', - dueDate: '', - assignmentIds: [], + title: storedTitle, + releaseDate: storedReleaseDate, + dueDate: storedDueDate, + assignmentIds: assignmentIds, } : { title: unit?.title || '', @@ -64,9 +77,24 @@ export const UnitForm = ({ // 단원 편집 취소 핸들러 const handleCancel = () => { + resetStore(); // 폼 데이터 초기화 reset(); }; + // 문제 선택 페이지로 이동 핸들러 + const handleAssignmentSelect = () => { + const {title, releaseDate, dueDate} = getValues(); + storeFormData(title, releaseDate, dueDate); + navigate('/admin/assignments/select', { + state: { + mode, + unitId: unit?.id ?? null, + currentIndex: unitIndex, + backPath: location.pathname, + }, + }); + }; + return (
{/* 단원 편집 폼 */} @@ -124,17 +152,24 @@ export const UnitForm = ({

문제 등록

{/* 드래그 앤 드롭 가능한 문제 리스트 */} - {!unit || unit.assignmentCount === 0 ? ( + {mode === 'editing' && unit && unit.assignmentCount > 0 ? ( + + ) : assignments.length > 0 ? ( + + ) : ( 등록된 문제가 없습니다. - ) : ( - )} {/* 문제 연결 버튼 */}
- From 92c4b51a7279b5e2d8764dfad397795e7977cd7a Mon Sep 17 00:00:00 2001 From: suminb99 Date: Thu, 26 Feb 2026 16:17:41 +0900 Subject: [PATCH 14/15] =?UTF-8?q?#52=20feat:=20=EA=B3=BC=EC=A0=9C=20?= =?UTF-8?q?=EC=84=A0=ED=83=9D=20=EB=B0=A9=EC=8B=9D=20id=20->=20Assignment?= =?UTF-8?q?=20=EA=B0=9D=EC=B2=B4=20=EA=B8=B0=EB=B0=98=EC=9C=BC=EB=A1=9C=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../AssignmentSelectPage.tsx | 43 ++++++++++++++----- .../ui/AssignmentListContainer.tsx | 9 ++-- 2 files changed, 38 insertions(+), 14 deletions(-) diff --git a/src/pages/select-assignment/AssignmentSelectPage.tsx b/src/pages/select-assignment/AssignmentSelectPage.tsx index 5c86b09..e437970 100644 --- a/src/pages/select-assignment/AssignmentSelectPage.tsx +++ b/src/pages/select-assignment/AssignmentSelectPage.tsx @@ -6,10 +6,19 @@ 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 navigate = useNavigate(); + const location = useLocation(); const {data: courseList} = useQuery(courseQueries.getAllCourses()); - const [selectedAssignments, setSelectedAssignments] = useState([]); // 선택된 문제 ID 목록 + const {setAssignments, assignments: currentSelectedAssignments} = + useUnitStore(); + const [selectedAssignments, setSelectedAssignments] = useState( + currentSelectedAssignments ?? [] + ); const {courseOptions, handleCourseSelect, selectedCourseId} = useCourseFilter( courseList?.response.courses ?? [] ); @@ -30,17 +39,29 @@ const AssignmentSelectPage = () => { : (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]; }); }; - console.log('선택된 강의 ID:', selectedAssignments); + const returnToPreviousPage = () => { + navigate(location.state?.backPath ?? -1, { + state: { + mode: location.state?.mode, + unitId: location.state?.unitId, + currentIndex: location.state?.currentIndex, + }, + }); + }; + + const handleConfirm = () => { + setAssignments(selectedAssignments); + returnToPreviousPage(); + }; return ( { renderItem={(assignment) => ( a.id === assignment.id)} /> )} /> } - onCancel={() => {}} - onConfirm={() => {}} + onCancel={() => { + returnToPreviousPage(); + }} + onConfirm={handleConfirm} /> ); }; diff --git a/src/pages/select-assignment/ui/AssignmentListContainer.tsx b/src/pages/select-assignment/ui/AssignmentListContainer.tsx index 94eb164..aa67777 100644 --- a/src/pages/select-assignment/ui/AssignmentListContainer.tsx +++ b/src/pages/select-assignment/ui/AssignmentListContainer.tsx @@ -1,3 +1,4 @@ +import type {Assignment} from '@/entities/assignment/model/types'; import type {AssignmentSelectCourse} from '@/entities/course/model/types'; type T = AssignmentSelectCourse['assignments'][number]; @@ -6,7 +7,7 @@ interface AssignmentListContainerProps { items: T[]; renderItem: (item: T) => React.ReactNode; title: string; - onSelect: (id: number) => void; + onSelect: (item: Assignment) => void; } const AssignmentListContainer = ({ @@ -15,16 +16,16 @@ const AssignmentListContainer = ({ renderItem, title, }: AssignmentListContainerProps) => { - const handleSelect = (id: number, event: React.MouseEvent) => { + const handleSelect = (item: Assignment, event: React.MouseEvent) => { event.stopPropagation(); - onSelect(id); + onSelect(item); }; return (

{title}

    {items.map((item) => ( -
  • handleSelect(item.id, e)} key={item.id}> +
  • handleSelect(item, e)} key={item.id}> {renderItem(item)}
  • ))} From d4428d154ec938f2cf2b0ea925609ad038681371 Mon Sep 17 00:00:00 2001 From: suminb99 Date: Thu, 26 Feb 2026 16:18:09 +0900 Subject: [PATCH 15/15] =?UTF-8?q?#52=20refactor:=20=ED=8C=8C=EB=9D=BC?= =?UTF-8?q?=EB=AF=B8=ED=84=B0=EB=AA=85=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/entities/unit/api/unitMutations.ts | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/entities/unit/api/unitMutations.ts b/src/entities/unit/api/unitMutations.ts index a0e6c91..d114344 100644 --- a/src/entities/unit/api/unitMutations.ts +++ b/src/entities/unit/api/unitMutations.ts @@ -5,8 +5,13 @@ export const unitMutations = { // 단원 추가 뮤테이션 옵션 createUnit: { mutationKey: ['createUnit'], - mutationFn: ({courseId, unit}: {courseId: number; unit: TUnitFormSchema}) => - createUnit(courseId, unit), + mutationFn: ({ + courseId, + unitForm, + }: { + courseId: number; + unitForm: TUnitFormSchema; + }) => createUnit(courseId, unitForm), }, // 단원 수정 뮤테이션 옵션