-
Notifications
You must be signed in to change notification settings - Fork 0
#52 feat: 문제 관리 UI 구현 및 API 연동 #61
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
004a045
df245ef
e7ea6cb
bbe3938
191e891
7bf990a
52b6f14
15eb502
d3ddd54
e06d73e
6550556
15b8a7a
1597f15
9f3fcc6
0d94e1f
4f94358
8c1f4f7
6888d2b
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,53 @@ | ||
| 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`); | ||
| 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,8 @@ | ||
| import {deleteAssignment} from './assignmentApi'; | ||
|
|
||
| export const assignmentMutations = { | ||
| deleteAssignment: { | ||
| mutationKey: ['deleteAssignment'], | ||
| mutationFn: (assignmentId: number) => deleteAssignment(assignmentId), | ||
| }, | ||
| }; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,37 @@ | ||
| import {queryOptions} from '@tanstack/react-query'; | ||
| import { | ||
| getAllAssignments, | ||
| getAssignmentsByCourse, | ||
| getAssignmentSchedules, | ||
| } from './assignmentApi'; | ||
|
|
||
| export const assignmentQueries = { | ||
| // 과제 일정 조회 쿼리 옵션 | ||
| getAssignmentSchedules: () => | ||
| queryOptions({ | ||
| queryKey: ['schedules'], | ||
|
Contributor
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. 흠 이거 쿼리 키 그냥 따로 파일 만들고 얘도 그냥 상수로 여기에 쓸까하는데 어떠세요? |
||
| queryFn: getAssignmentSchedules, | ||
| select: (data) => ({ | ||
| scheduleCount: data.response.count, | ||
| schedules: data.response.schedule, | ||
| }), | ||
| }), | ||
|
|
||
| // 전체 과제 목록 조회 쿼리 옵션 | ||
| getAllAssignments: () => | ||
| queryOptions({ | ||
| queryKey: ['assignments'], | ||
| queryFn: getAllAssignments, | ||
| select: (data) => data.response.assignments, | ||
| }), | ||
|
|
||
| // 강의별 과제 목록 조회 쿼리 옵션 | ||
| getAssignmentsByCourse: (courseId: number) => | ||
| queryOptions({ | ||
| queryKey: ['courses', courseId, 'assignments'], | ||
| queryFn: () => getAssignmentsByCourse(courseId), | ||
| enabled: !!courseId, | ||
| select: (data) => | ||
| data.response.courses.flatMap((course) => course.assignments), | ||
| }), | ||
| }; | ||
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,15 @@ | ||
| import {getAllCourses} from './courseApi'; | ||
| import {queryOptions} from '@tanstack/react-query'; | ||
|
|
||
| export const courseQueries = { | ||
| // 전체 강의 조회 쿼리 옵션 | ||
| getAllCourses: () => | ||
| queryOptions({ | ||
| queryKey: ['courses'], | ||
| queryFn: getAllCourses, | ||
| select: (data) => ({ | ||
| courseCount: data.response.count, | ||
| courses: data.response.courses, | ||
| }), | ||
| }), | ||
| }; |
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 |
|---|---|---|
| @@ -0,0 +1,22 @@ | ||
| import {useQuery} from '@tanstack/react-query'; | ||
| import {assignmentQueries} from '@/entities/assignment/api/assignmentQueries'; | ||
| import type {Assignment} from '@/entities/assignment/model/types'; | ||
|
Comment on lines
+1
to
+3
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: #!/bin/bash
# Check for the typo in folder name and find all references
# Find the folder with typo
echo "=== Searching for folder with typo 'filter-assignmnet' ==="
fd -t d "filter-assignmnet" src/ 2>/dev/null || echo "No 'filter-assignmnet' folder found"
# Find correct spelling folder
echo -e "\n=== Searching for correctly spelled 'filter-assignment' ==="
fd -t d "filter-assignment" src/ 2>/dev/null || echo "No 'filter-assignment' folder found"
# Find all imports referencing the typo path
echo -e "\n=== Finding all references to 'filter-assignmnet' ==="
rg "filter-assignmnet" src/ --type ts --type tsx 2>/dev/null | head -20
# Find all assignment-related folders
echo -e "\n=== Finding all assignment-related folders ==="
fd -t d "filter-assign" src/
# Check the actual file path
echo -e "\n=== Checking if file exists at mentioned path ==="
test -f "src/features/assignment/filter-assignmnet/lib/useAssignmentList.ts" && echo "File exists at typo path" || echo "File NOT found at typo path"Repository: 2025-snowCode/snowCode-Client Length of output: 457 🏁 Script executed: #!/bin/bash
# Check folder contents and verify if it's being used
echo "=== Contents of folder with typo ==="
find src/features/assignment/filter-assignmnet -type f | head -20
echo -e "\n=== Checking for any imports of files from this folder ==="
rg "from.*filter-assignmnet" src/ --type ts --type tsx 2>/dev/null || echo "No imports found"
echo -e "\n=== Checking for useAssignmentList imports (broader search) ==="
rg "useAssignmentList" src/ --type ts --type tsx 2>/dev/null || echo "No useAssignmentList imports found"
echo -e "\n=== Checking folder structure ==="
tree src/features/assignment/filter-assignmnet/ 2>/dev/null || find src/features/assignment/filter-assignmnet -type f -exec echo {} \;Repository: 2025-snowCode/snowCode-Client Length of output: 491 폴더명 오타:
수정사항:
coding guidelines 에서 규정한 kebab-case 명명 규칙을 따르되, 정확한 철자 사용도 중요합니다. 초기 단계에서 수정하는 것이 좋습니다! ✨ 🤖 Prompt for AI Agents |
||
|
|
||
| // 중복 제거 | ||
| const unique = (list: Assignment[]) => | ||
| Array.from(new Map(list.map((a) => [a.id, a])).values()); | ||
|
|
||
| export const useAssignmentList = ( | ||
| selectedCourseId: number | null | ||
| ): Assignment[] => { | ||
| const {data: allAssignments} = useQuery( | ||
| assignmentQueries.getAllAssignments() | ||
| ); | ||
| const {data: assignments} = useQuery( | ||
| assignmentQueries.getAssignmentsByCourse(selectedCourseId ?? 0) | ||
| ); | ||
|
|
||
| return unique( | ||
| selectedCourseId ? (assignments ?? []) : (allAssignments ?? []) | ||
| ); | ||
|
Comment on lines
+12
to
+21
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: cat -n src/features/assignment/filter-assignment/lib/useAssignmentList.tsRepository: 2025-snowCode/snowCode-Client Length of output: 917 🏁 Script executed: # assignmentQueries 정의 확인
find . -type f -name "*.ts" -o -name "*.tsx" | xargs grep -l "assignmentQueries" | head -5Repository: 2025-snowCode/snowCode-Client Length of output: 283 🏁 Script executed: # useQuery 사용 패턴 확인
rg "useQuery" --type ts --type tsx -A 3 | head -50Repository: 2025-snowCode/snowCode-Client Length of output: 100 🏁 Script executed: cat -n src/entities/assignment/api/assignmentQueries.tsRepository: 2025-snowCode/snowCode-Client Length of output: 1178 🏁 Script executed: cat -n package.json | grep -A 2 -B 2 "react-query\|@tanstack"Repository: 2025-snowCode/snowCode-Client Length of output: 493 🏁 Script executed: # Check how assignmentQueries returns query config
rg "getAllAssignments|getAssignmentsByCourse" --type ts -A 5Repository: 2025-snowCode/snowCode-Client Length of output: 4462 🏁 Script executed: # Verify that queryOptions can be spread and extended
rg "useQuery.*\.\.\." --type ts -A 2 | head -20Repository: 2025-snowCode/snowCode-Client Length of output: 55 🏁 Script executed: # Check if there are other examples of spreading queryOptions in the codebase
rg "queryOptions" --type ts -A 3 | grep -E "enabled|spread|\.\.\." | head -20Repository: 2025-snowCode/snowCode-Client Length of output: 210 getAllAssignments 쿼리가 항상 실행되어 불필요한 네트워크 비용이 발생합니다 현재 구현에서 getAllAssignments는 과제 필터 선택 여부와 관계없이 항상 활성화됩니다. getAssignmentsByCourse에는 이미 queryOptions 객체를 spread하여 🔧 제안 diff export const useAssignmentList = (
selectedCourseId: number | null
): Assignment[] => {
- const {data: allAssignments} = useQuery(
- assignmentQueries.getAllAssignments()
- );
- const {data: assignments} = useQuery(
- assignmentQueries.getAssignmentsByCourse(selectedCourseId ?? 0)
- );
+ const {data: allAssignments} = useQuery({
+ ...assignmentQueries.getAllAssignments(),
+ enabled: selectedCourseId === null,
+ });
+ const {data: assignments} = useQuery({
+ ...assignmentQueries.getAssignmentsByCourse(selectedCourseId ?? 0),
+ enabled: selectedCourseId !== null,
+ });
return unique(
- selectedCourseId ? (assignments ?? []) : (allAssignments ?? [])
+ selectedCourseId !== null ? (assignments ?? []) : (allAssignments ?? [])
);
};참고: React Query - Disabling Queries 🤖 Prompt for AI Agents |
||
| }; | ||
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.
지금
assignmentApi.ts
에서 서버 데이터를 가져올 때 TypeScript의 interface만 사용하고 있는데요,
추천하는 구조:
entities/assignment/model/schema.ts (신규 생성)
이미 unit 도메인 쪽에서도 zod를 조금씩 쓰고 계시니까, API 응답 영역까지 확장해 보면 좋을 것 같아요!