-
Notifications
You must be signed in to change notification settings - Fork 0
#44 feat: 단원 CRUD 핸들러 버튼 연동 및 적용 #51
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
Changes from all commits
426d344
e9bcab8
8f072b9
2f157d6
20da864
3db20b2
585765b
4b66206
72f148f
71d7c72
f9a7765
dc9afd8
effcb19
c5be347
4c29d4c
8d93737
9364a80
420c7b3
6ae32b4
e0390b5
dd0b2d2
3bd6f5b
d3d758c
e25f7f7
b6cc590
a01b2a7
64a9ec4
0e17edf
4e81014
57f9029
c0e321e
38e8fd7
7584993
94cb995
ca70b6a
2f1ea99
5b292f5
5700500
1b5573c
a1d8cf2
291b3c1
e280297
a0392e9
54c9f37
da1ce82
8bbb2c1
0e754e9
944d864
e3bb94d
2e0944b
2678611
ee62253
40dbbaa
5243442
6c0812c
2a16b75
e021aa3
55ebf7a
96c12c1
067b95a
85e674f
eb927e1
ffb98f9
8500939
2a88f6d
0b37ab3
324f8c3
4144562
9742a09
2548c66
6d7d3ea
6ffba75
b16c8d7
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -91,3 +91,12 @@ export type CourseOptionsResponse = ApiResponse<{ | |
| count: number; | ||
| courses: DashboardCourse[]; | ||
| }>; | ||
|
|
||
| // 단원 조회-생성 페이지 응답 타입 정의 - 단일 단원 | ||
| export type SingleUnitResponse = ApiResponse<Unit>; | ||
|
|
||
| // 단원 조회-생성 페이지 응답 타입 정의 - 전체 단원 목록 | ||
| export type AllUnitsResponse = ApiResponse<{ | ||
| count: number; | ||
| units: Pick<Unit, 'id' | 'title' | 'assignmentCount'>[]; | ||
| }>; | ||
|
Comment on lines
+99
to
+102
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
# Find tsconfig files and examine path aliases
find . -name "tsconfig*.json" -type f | head -10Repository: 2025-snowCode/snowCode-Client Length of output: 129 🏁 Script executed: #!/bin/bash
# Check the tsconfig.json content for path aliases
cat tsconfig.json 2>/dev/null || echo "tsconfig.json not found"Repository: 2025-snowCode/snowCode-Client Length of output: 191 🏁 Script executed: #!/bin/bash
# Search for files using or referencing the Pick<Unit pattern
rg "Pick<Unit" -A 2 -B 2Repository: 2025-snowCode/snowCode-Client Length of output: 337 🏁 Script executed: #!/bin/bash
# Check the actual file mentioned in the review
cat -n src/entities/course/model/types.ts | sed -n '95,110p'Repository: 2025-snowCode/snowCode-Client Length of output: 375 🏁 Script executed: #!/bin/bash
# Find unitMock.ts and check its imports
fd "unitMock.ts" -type fRepository: 2025-snowCode/snowCode-Client Length of output: 243 🏁 Script executed: #!/bin/bash
# Check tsconfig.app.json for path aliases
cat tsconfig.app.jsonRepository: 2025-snowCode/snowCode-Client Length of output: 901 🏁 Script executed: #!/bin/bash
# Find unitMock.ts using find command
find . -name "unitMock.ts"Repository: 2025-snowCode/snowCode-Client Length of output: 113 🏁 Script executed: #!/bin/bash
# Search for any references to `@/models/course` or `@/entities/course`
rg "@/models/course|@/entities/course" -nRepository: 2025-snowCode/snowCode-Client Length of output: 2459 🏁 Script executed: #!/bin/bash
# Search for UnitSummary or similar unit summary types in codebase
rg "UnitSummary|Pick.*Unit.*id.*title.*assignmentCount" -nRepository: 2025-snowCode/snowCode-Client Length of output: 170 🏁 Script executed: #!/bin/bash
# Check if src/models directory exists and what it contains
find src/models -type f 2>/dev/null || echo "src/models directory does not exist"Repository: 2025-snowCode/snowCode-Client Length of output: 108 🏁 Script executed: #!/bin/bash
# Look for index.ts or course.ts in src/models or src/models/course
find . -path "./src/models*" -name "*.ts"Repository: 2025-snowCode/snowCode-Client Length of output: 55 🏁 Script executed: #!/bin/bash
# Check the unitMock.ts file to see the full import context
cat -n src/pages/unit-editor/mock/unitMock.ts | head -20Repository: 2025-snowCode/snowCode-Client Length of output: 661 🏁 Script executed: #!/bin/bash
# Search for any barrel exports (index.ts) that might re-export from entities
rg "export.*from.*@/entities/course" -nRepository: 2025-snowCode/snowCode-Client Length of output: 55
현재 코드에서 여러 파일이
이들 파일에서 부가 개선사항: 🤖 Prompt for AI Agents |
||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,46 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import {privateAxios} from '@/shared/api/axiosInstance'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import type {TUnitFormSchema} from '../model/types'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import type {ApiResponse} from '@/shared/model'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import type {AllUnitsResponse, Unit} from '@/entities/course/model/types'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // 강의별 전체 단원 조회 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| export const getAllUnitsByCourseId = async ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| courseId: number | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ): Promise<AllUnitsResponse> => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const response = await privateAxios.get(`/courses/${courseId}/units`); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return response.data; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // 단일 단원 조회 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| export const getUnitById = async ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| unitId: number | null | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ): Promise<ApiResponse<Unit>> => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
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. 앞에 언급한 zod 활용 응답용 스키마 정의하고 |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const response = await privateAxios.get(`/units/${unitId}`); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return response.data; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+15
to
+20
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.
파라미터 타입이 두 가지 개선 방향이 있습니다: 방향 1 (권장): 파라미터 타입을 방향 2: 함수 내부에서 early return을 통해 방어합니다. 🛡️ 방향 1 — 파라미터 타입 축소 (권장)-export const getUnitById = async (
- unitId: number | null
-): Promise<ApiResponse<Unit>> => {
+export const getUnitById = async (
+ unitId: number
+): Promise<ApiResponse<Unit>> => {
const response = await privateAxios.get(`/units/${unitId}`);
return response.data;
};
queryOptions({
queryKey: [...],
queryFn: () => getUnitById(unitId!),
enabled: unitId !== null,
})🛡️ 방향 2 — 함수 내부 null 가드 export const getUnitById = async (
unitId: number | null
): Promise<ApiResponse<Unit>> => {
+ if (unitId === null) {
+ throw new Error('unitId는 null일 수 없습니다.');
+ }
const response = await privateAxios.get(`/units/${unitId}`);
return response.data;
};📝 Committable suggestion
Suggested change
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // 단원 삭제 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| export const deleteUnit = async ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| unitId: number | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ): Promise<ApiResponse<string>> => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const response = await privateAxios.delete(`/units/${unitId}`); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return response.data; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // 단원 생성 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| export const createUnit = async ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| courseId: number, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| unit: TUnitFormSchema | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ): Promise<ApiResponse<Unit>> => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const response = await privateAxios.post(`/units/${courseId}`, unit); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return response.data; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
suminb99 marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // 단원 수정 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| export const updateUnit = async ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| unitId: number, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| unit: TUnitFormSchema | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ): Promise<ApiResponse<Unit>> => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const response = await privateAxios.put(`/units/${unitId}`, unit); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return response.data; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,24 @@ | ||
| import type {TUnitFormSchema} from '../model/types'; | ||
| import {createUnit, deleteUnit, updateUnit} from './unitApi'; | ||
|
|
||
| export const unitMutations = { | ||
| // 단원 추가 뮤테이션 옵션 | ||
| createUnit: { | ||
| mutationKey: ['createUnit'], | ||
| mutationFn: ({courseId, unit}: {courseId: number; unit: TUnitFormSchema}) => | ||
| createUnit(courseId, unit), | ||
| }, | ||
|
|
||
| // 단원 수정 뮤테이션 옵션 | ||
| updateUnit: { | ||
| mutationKey: ['updateUnit'], | ||
| mutationFn: ({unitId, unit}: {unitId: number; unit: TUnitFormSchema}) => | ||
| updateUnit(unitId, unit), | ||
| }, | ||
|
|
||
| // 단원 삭제 뮤테이션 옵션 | ||
| deleteUnit: { | ||
| mutationKey: ['deleteUnit'], | ||
| mutationFn: (unitId: number) => deleteUnit(unitId), | ||
| }, | ||
| }; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,19 @@ | ||
| import {queryOptions} from '@tanstack/react-query'; | ||
| import {getAllUnitsByCourseId, getUnitById} from './unitApi'; | ||
|
|
||
| export const unitQueries = { | ||
| // 강의별 전체 단원 조회 쿼리 옵션 | ||
| getUnitList: (courseId: number) => | ||
| queryOptions({ | ||
| queryKey: ['units', courseId], | ||
| queryFn: () => getAllUnitsByCourseId(courseId), | ||
| }), | ||
|
|
||
| // 단일 단원 조회 쿼리 옵션 | ||
| getUnitDetails: (unitId: number | null) => | ||
| queryOptions({ | ||
| queryKey: ['units', 'detail', unitId], | ||
| queryFn: () => getUnitById(unitId), | ||
| enabled: !!unitId, | ||
| }), | ||
| }; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,17 @@ | ||
| import {z} from 'zod'; | ||
|
|
||
| // 단원 생성/수정 폼 스키마 | ||
| export const unitFormSchema = z | ||
| .object({ | ||
| title: z.string().min(1, '단원 제목을 입력해주세요.'), | ||
| releaseDate: z.string().min(1, '공개일을 입력해주세요.'), | ||
| dueDate: z.string().min(1, '마감일을 입력해주세요.'), | ||
| assignmentIds: z.array(z.number()).optional(), | ||
| }) | ||
| .refine((data) => data.releaseDate <= data.dueDate, { | ||
| message: '날짜 범위가 올바르지 않습니다.', | ||
| path: ['dueDate'], | ||
| }); | ||
|
|
||
| // 단원 생성/수정 폼 타입 | ||
| export type TUnitFormSchema = z.infer<typeof unitFormSchema>; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -28,6 +28,7 @@ | |
| :root { | ||
| font-family: Pretendard; | ||
| background-color: #f6f5f8; | ||
| box-sizing: border-box; | ||
|
|
||
| font-synthesis: none; | ||
| text-rendering: optimizeLegibility; | ||
|
|
@@ -54,6 +55,7 @@ | |
| --color-badge-red: #ff6f6f; | ||
|
|
||
| --font-coolvetica: 'Coolvetica', sans-serif; | ||
| --shadow-box: 2px 4px 10px 0 rgba(0, 0, 0, 0.05); | ||
| --shadow-card: 0px 0px 14px 0px rgba(223, 219, 240, 0.4); | ||
| --shadow-modal: 4px 4px 7px 0px rgba(0, 0, 0, 0.1); | ||
| --shadow-dropdown: 2px 4px 7px 0px rgba(0, 0, 0, 0.1); | ||
|
|
@@ -90,3 +92,13 @@ | |
| input::placeholder { | ||
| color: var(--color-light-black); | ||
| } | ||
|
|
||
| .input-field { | ||
| @apply bg-white w-[295px] px-[14.6px] py-[10.5px] rounded-[9px] border-[0.9px] border-purple-stroke focus:outline-1 focus:outline-primary; | ||
| } | ||
|
|
||
| @layer base { | ||
| label { | ||
| @apply flex flex-col gap-[12.5px]; | ||
| } | ||
| } | ||
|
Comment on lines
+100
to
+104
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. 전역
전역 적용 대신 특정 컴포넌트 클래스(예: ♻️ 개선 제안-@layer base {
- label {
- `@apply` flex flex-col gap-[12.5px];
- }
-}
+@utility form-label {
+ `@apply` flex flex-col gap-[12.5px];
+}🤖 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.
프로젝트에 이미 zod가 있으니까, 이걸로 API 응답 전용 '스키마'를 만들어보면 좋을 것 같아요!
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.
넵 좋아요!!