Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
73 commits
Select commit Hold shift + click to select a range
426d344
#15 feat: svg 아이콘 추가 및 변경
suminb99 Aug 18, 2025
e9bcab8
#15 feat: 단원 개설 페이지 라우트 추가
suminb99 Aug 18, 2025
8f072b9
#15 style: input-field 및 label 공통 스타일 정의
suminb99 Aug 18, 2025
2f157d6
#15 feat: 버튼 secondaryPurple variant 추가
suminb99 Aug 18, 2025
20da864
#15 feat: 단원 개설 페이지 컴포넌트 추가
suminb99 Aug 18, 2025
3db20b2
#15 feat: 단원 개설 페이지 기본 레이아웃 및 UI 구성 요소 구현
suminb99 Aug 18, 2025
585765b
#15 refactor: 버튼 컴포넌트에 color props 전달
suminb99 Aug 18, 2025
4b66206
#15 refactor: 과제 카드 색상 props로 배경색 설정
suminb99 Aug 18, 2025
72f148f
#15 feat: 전역 스타일에 box-sizing: border-box 추가
suminb99 Aug 18, 2025
71d7c72
#15 style: 레이아웃 높이 설정 및 overflow 시 scroll 적용
suminb99 Aug 18, 2025
f9a7765
#15 style: 버튼 컴포넌트 wrapper 패딩값 수정
suminb99 Aug 18, 2025
dc9afd8
#15 chore: dummy 과제 추가
suminb99 Sep 1, 2025
effcb19
Merge branch 'develop' into feat/15-unit-page
suminb99 Feb 8, 2026
c5be347
Merge branch 'refactor/17-assignment-select-update' into feat/15-unit…
suminb99 Feb 8, 2026
4c29d4c
Merge branch 'develop' into feat/15-unit-page
suminb99 Feb 8, 2026
8d93737
style: LabeledInput 흰생 배경 적용
suminb99 Feb 9, 2026
9364a80
refactor: Button.tsx classname explicitly 추가
suminb99 Feb 9, 2026
420c7b3
#15 fix: Assignment 인터페이스의 submittedStatus optional로 변경
suminb99 Feb 9, 2026
6ae32b4
#15 feat: 단원 조회-생성 페이지 더미 응답 데이터 및 타입 추가
suminb99 Feb 9, 2026
e0390b5
#15 feat: 단원 생성 라우트를 admin 하위로 이동 및 CreateUnitPage 적용
suminb99 Feb 9, 2026
dd0b2d2
#15 feat: binIcon public -> src/assets 하위로 이동
suminb99 Feb 9, 2026
3bd6f5b
#15 feat: CreatUnitPage 추가
suminb99 Feb 9, 2026
d3d758c
#15 feat: 단원 편집 섹션 (UnitFormEditor) 추가
suminb99 Feb 9, 2026
e25f7f7
#15 feat: 단원 목록 (UnitList) 추가
suminb99 Feb 9, 2026
b6cc590
#15 chorer: 기존 단원 관련 컴포넌트 및 더미 데이터 제거
suminb99 Feb 9, 2026
a01b2a7
#15 feat: SelectableItem className으로 오버라이딩 가능하도록
suminb99 Feb 9, 2026
64a9ec4
#15 chore: dnd-kit 관련 패키지 설치
suminb99 Feb 9, 2026
0e17edf
#15 style: shadow custom variant 추가
suminb99 Feb 9, 2026
4e81014
#15 feat: 응답 데이터 과제 배열 아이템 추가
suminb99 Feb 9, 2026
57f9029
#15 feat: 문제 등록 섹션 추가
suminb99 Feb 9, 2026
c0e321e
#15 feat: 드래그 및 정렬 가능한 문제 아이템 컴포넌트 추가
suminb99 Feb 9, 2026
38e8fd7
Merge branch 'develop' into feature/38-dashboard-api
suminb99 Feb 11, 2026
7584993
#15 refactor: addIcon 색상 currentColor 적용 및 Button none 사이즈 수정
suminb99 Feb 11, 2026
94cb995
#15 style: 단원 편집 폼 레이아웃 및 스크롤 개선
suminb99 Feb 11, 2026
ca70b6a
Merge branch 'feature/38-dashboard-api' into feature/44-unit-page-api
suminb99 Feb 11, 2026
2f1ea99
#44 feat: 강의별 전체 단원 조회 api
suminb99 Feb 11, 2026
5b292f5
#44 feat: 특정 단원 조회 api
suminb99 Feb 11, 2026
5700500
#44 feat: 단원 삭제 api
suminb99 Feb 11, 2026
1b5573c
#44 feat: 단원 조회 관련 쿼리 옵션스 추가
suminb99 Feb 11, 2026
a1d8cf2
#44 feat: 단원 폼에 zod 스키마 기반 유효성 검증 적용
suminb99 Feb 11, 2026
291b3c1
#44 feat: Button 컴포넌트에 formID prop 추가
suminb99 Feb 11, 2026
e280297
#44 feat: 단원 페이지에 동적 라우팅 및 전체 단원 조회 연동
suminb99 Feb 11, 2026
a0392e9
#44 fix: EmptyState 컴포넌트에서 불필요한 text-center 스타일 제거
suminb99 Feb 11, 2026
54c9f37
#44 feat: 단일 단원 조회 API 반환 타입 수정 및 nullable 처리
suminb99 Feb 11, 2026
da1ce82
#44 feat: 단원 선택 상태 관리 및 단일 단원 조회 API 연동
suminb99 Feb 11, 2026
8bbb2c1
#44 feat: 단원 목록에 선택 상태 UI 및 클릭 핸들러 추가
suminb99 Feb 11, 2026
0e754e9
#44 feat: 단원 폼에 조회 데이터 바인딩 및 문제 등록 섹션 리팩토링
suminb99 Feb 11, 2026
944d864
#44 style: 레이아웃 너비 및 정렬 스타일 조정
suminb99 Feb 13, 2026
e3bb94d
#44 feat: 단원 추가/편집 모드 분리 및 단원 인덱스 관리 기능 구현
suminb99 Feb 13, 2026
2e0944b
#44 refactor: Button 스타일 변수 export 처리 - 외부 사용 가능
suminb99 Feb 13, 2026
2678611
#44 fix: 과제 선택 페이지 정적 경로로 변경
suminb99 Feb 13, 2026
ee62253
#44 feat: 단원 생성 API 및 쿼리 옵션 추가
suminb99 Feb 13, 2026
40dbbaa
#44 feat: 단원 생성 mutation 연동 및 UI 바인딩
suminb99 Feb 13, 2026
5243442
#44 feat: 단원 생성 후 단원 목차 갱신 처리
suminb99 Feb 13, 2026
6c0812c
#44 refactor: 단원 쿼리 옵션을 unitQueries 객체로 통합
suminb99 Feb 13, 2026
2a16b75
#44 refactor: queryKey 네이밍 및 구조 정리
suminb99 Feb 13, 2026
e021aa3
#44 feat: 단원 수정·삭제 API 및 mutation 옵션 추가
suminb99 Feb 22, 2026
55ebf7a
#44 refactor: 단원 페이지 상태 관리 및 CRUD 전체 연동
suminb99 Feb 22, 2026
96c12c1
#44 feat: 단원 CRUD 핸들러 버튼 연동 및 적용
suminb99 Feb 22, 2026
067b95a
#44 fix: barrel export 방식 제거
suminb99 Feb 22, 2026
85e674f
#44 refactor: unit entity 타입 분리 및 파일명 개선
suminb99 Feb 22, 2026
eb927e1
#44 refactor: 단원 에디터 페이지 폴더 구조 및 파일명 개선
suminb99 Feb 22, 2026
ffb98f9
Merge branch 'develop' into feature/44-unit-page-api
suminb99 Feb 22, 2026
8500939
#44 chore: 스타일 파일 분리 및 import 경로 정리
suminb99 Feb 23, 2026
2a88f6d
#44 chore: 단원 페이지 admin 블록 내부로 이동
suminb99 Feb 23, 2026
0b37ab3
#44 fix: unitId 타입 및 dnd-kit 이벤트 타입 명시화
suminb99 Feb 23, 2026
324f8c3
#44 fix: 단원 폼 id unitIndex 기반 설정
suminb99 Feb 23, 2026
4144562
#44 chore: zod import 방식 통일 및 주석 오타 수정
suminb99 Feb 23, 2026
9742a09
#44 fix: 임시 주석 처리
suminb99 Feb 23, 2026
2548c66
#44 fix: 파라미터 타입 수정
suminb99 Feb 23, 2026
6d7d3ea
#44 fix: pnpm-lock.yaml 갱신
suminb99 Feb 23, 2026
6ffba75
#44 fix: 타입 검사 강제 통과
suminb99 Feb 23, 2026
b16c8d7
#44 fix: zod 버전 downgrade
suminb99 Feb 23, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@
"preinstall": "npx only-allow pnpm"
},
"dependencies": {
"@dnd-kit/core": "^6.3.1",
"@dnd-kit/sortable": "^10.0.0",
"@dnd-kit/utilities": "^3.2.2",
"@hookform/resolvers": "^5.2.2",
"@tailwindcss/vite": "^4.1.11",
"@tanstack/react-query": "^5.90.20",
"axios": "^1.13.4",
Expand All @@ -22,6 +26,7 @@
"tailwind-merge": "^3.4.0",
"tailwind-variants": "^3.2.2",
"tailwindcss": "^4.1.11",
"zod": "^3.22.0",
"zustand": "^5.0.10"
},
"devDependencies": {
Expand Down
2,454 changes: 1,127 additions & 1,327 deletions pnpm-lock.yaml

Large diffs are not rendered by default.

7 changes: 6 additions & 1 deletion src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import StudentProfilePage from '@/pages/admin/student/StudentProfilePage';
import KakaoCallbackPage from '@/pages/common/KakaoCallbackPage';
import PrivateRoute from '@/widgets/private-route/ui/PrivateRoute';
import {useSyncUserRole} from '@/features/auth/sync-user-role/model/useSyncUserRole';
import UnitEditorPage from '@/pages/unit-editor/UnitEditorPage';

const AppRoutes = () => {
useSyncUserRole();
Expand Down Expand Up @@ -41,14 +42,18 @@ const AppRoutes = () => {
path='assignments/create'
element={<AssignmentCreatePage />}
/>
<Route path='assignments/:id' element={<AssignmentSelectPage />} />
<Route
path='assignments/select'
element={<AssignmentSelectPage />}
/>
<Route path='courses/:id' element={<CourseOverviewPage />} />
<Route path='courses/create' element={<CourseCreatePage />} />
<Route path='student' element={<StudentManagementPage />} />
<Route
path='student/profile/:studentId'
element={<StudentProfilePage />}
/>
<Route path='units/:id' element={<UnitEditorPage />} />
</Route>
</Route>
</Route>
Expand Down
4 changes: 2 additions & 2 deletions src/assets/svg/addIcon.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
5 changes: 5 additions & 0 deletions src/assets/svg/binIcon.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion src/entities/assignment/model/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,5 @@ import type {SubmissionStatus} from '@/shared/model/common';
export interface Assignment {
id: number;
title: string;
submittedStatus: SubmissionStatus;
submittedStatus?: SubmissionStatus;
}
9 changes: 9 additions & 0 deletions src/entities/course/model/types.ts
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

프로젝트에 이미 zod가 있으니까, 이걸로 API 응답 전용 '스키마'를 만들어보면 좋을 것 같아요!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

프로젝트에 이미 zod가 있으니까, 이걸로 API 응답 전용 '스키마'를 만들어보면 좋을 것 같아요!

넵 좋아요!!

Original file line number Diff line number Diff line change
Expand Up @@ -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
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Find tsconfig files and examine path aliases
find . -name "tsconfig*.json" -type f | head -10

Repository: 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 2

Repository: 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 f

Repository: 2025-snowCode/snowCode-Client

Length of output: 243


🏁 Script executed:

#!/bin/bash
# Check tsconfig.app.json for path aliases
cat tsconfig.app.json

Repository: 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" -n

Repository: 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" -n

Repository: 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 -20

Repository: 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" -n

Repository: 2025-snowCode/snowCode-Client

Length of output: 55


@/models/course 임포트 경로 문제 해결 필요

현재 코드에서 여러 파일이 @/models/course에서 타입을 임포트하고 있지만, tsconfig.app.json의 경로 별칭 설정에 따르면 @/models/coursesrc/models/course로 매핑되며, 이 디렉토리는 존재하지 않습니다. 실제 타입 정의는 src/entities/course/model/types.ts에 있으므로, 다음 파일들의 임포트 경로를 수정해야 합니다:

  • src/pages/unit-editor/mock/unitMock.ts
  • src/entities/unit/api/unitApi.ts
  • src/pages/unit-editor/ui/UnitList.tsx
  • src/pages/unit-editor/model/types.ts

이들 파일에서 @/models/course@/entities/course/model/types로 변경하세요.


부가 개선사항: Pick<Unit, 'id' | 'title' | 'assignmentCount'>UnitSummary 타입으로 추출하는 것도 가독성 향상에 도움이 되겠습니다. 다만 현재 코드베이스에서 이 패턴이 한 곳에서만 사용되므로, 재사용성 측면에서는 우선순위가 낮습니다.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/entities/course/model/types.ts` around lines 99 - 102, Several files
import types from the wrong path '@/models/course' (which maps to non-existent
src/models/course); update the import statements in
src/pages/unit-editor/mock/unitMock.ts, src/entities/unit/api/unitApi.ts,
src/pages/unit-editor/ui/UnitList.tsx, and src/pages/unit-editor/model/types.ts
to import from '@/entities/course/model/types' instead so AllUnitsResponse and
Unit-related types resolve correctly; optionally consider extracting Pick<Unit,
"id" | "title" | "assignmentCount"> into a UnitSummary type near the Unit
definition (e.g., alongside Unit or in types.ts) for clarity, but this is low
priority.

2 changes: 1 addition & 1 deletion src/entities/student/ui/StudentProfile.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import ProfileImage from '@/assets/svg/profileImage.svg?react';
import ChatIcon from '@/assets/svg/chatIcon.svg?react';
import MailIcon from '@/assets/svg/mailIcon.svg?react';
import Button from '@/shared/ui/Button';
import Button from '@/shared/ui/button/Button';

interface StudentProfileProps {
name: string;
Expand Down
2 changes: 1 addition & 1 deletion src/entities/student/ui/StudentTable.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {Checkbox} from '@/shared/ui/Checkbox';
import {Checkbox} from '@/shared/ui/checkbox/Checkbox';
import {ProgressIndicators} from '@/shared/ui/ProgressIndicators';
import {useState, useEffect} from 'react';
import {Link} from 'react-router-dom';
Expand Down
46 changes: 46 additions & 0 deletions src/entities/unit/api/unitApi.ts
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>> => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

앞에 언급한 zod 활용 응답용 스키마 정의하고
unitSchema.parse(response.data)처럼 한 번 걸러서 반환하면 좋을 것 같아요!

const response = await privateAxios.get(`/units/${unitId}`);
return response.data;
};
Comment on lines +15 to +20
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

unitIdnull일 때 /units/null 요청이 발생합니다

파라미터 타입이 number | null이지만, 함수 내부에 null 가드가 없어 실제로 null이 전달되면 /units/null이라는 잘못된 엔드포인트로 요청이 전송됩니다.

두 가지 개선 방향이 있습니다:

방향 1 (권장): 파라미터 타입을 number로 좁히고, 호출부(unitQueries.ts)에서 null 여부를 판단하여 쿼리를 비활성화합니다 (enabled: unitId !== null).

방향 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;
 };

unitQueries.ts 쪽에서는 아래와 같이 enabled 옵션으로 처리:

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

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export const getUnitById = async (
unitId: number | null
): Promise<ApiResponse<Unit>> => {
const response = await privateAxios.get(`/units/${unitId}`);
return response.data;
};
export const getUnitById = async (
unitId: number
): Promise<ApiResponse<Unit>> => {
const response = await privateAxios.get(`/units/${unitId}`);
return response.data;
};
Suggested change
export const getUnitById = async (
unitId: number | null
): Promise<ApiResponse<Unit>> => {
const response = await privateAxios.get(`/units/${unitId}`);
return response.data;
};
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;
};
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/entities/unit/api/unitApi.ts` around lines 15 - 20, The current
getUnitById function accepts unitId: number | null and will call /units/null if
null — fix by either (preferred) narrowing the parameter to number and updating
callers (e.g., unitQueries.ts) to guard the query with enabled: unitId !== null
and call getUnitById(unitId!) from the queryFn, or (alternative) add an internal
null guard in getUnitById that returns an appropriate ApiResponse or throws
before making privateAxios.get when unitId is null; locate the function
getUnitById in unitApi.ts and the query usage in unitQueries.ts to implement one
of these fixes.


// 단원 삭제
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;
};

// 단원 수정
export const updateUnit = async (
unitId: number,
unit: TUnitFormSchema
): Promise<ApiResponse<Unit>> => {
const response = await privateAxios.put(`/units/${unitId}`, unit);
return response.data;
};
24 changes: 24 additions & 0 deletions src/entities/unit/api/unitMutations.ts
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),
},
};
19 changes: 19 additions & 0 deletions src/entities/unit/api/unitQueries.ts
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,
}),
};
17 changes: 17 additions & 0 deletions src/entities/unit/model/types.ts
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>;
12 changes: 12 additions & 0 deletions src/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
:root {
font-family: Pretendard;
background-color: #f6f5f8;
box-sizing: border-box;

font-synthesis: none;
text-rendering: optimizeLegibility;
Expand All @@ -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);
Expand Down Expand Up @@ -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
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

전역 label 스타일이 기존 레이아웃을 의도치 않게 깨뜨릴 수 있습니다.

@layer base에서 모든 label 요소에 flex flex-col gap-[12.5px]를 적용하면, 체크박스·라디오 버튼처럼 인라인 레이아웃이 필요한 label이나 기존의 가로 배치 폼 레이블 등도 영향을 받습니다. 접근성 측면에서도 스크린 리더 사용자에게 예상치 못한 레이아웃 변화를 줄 수 있습니다.

전역 적용 대신 특정 컴포넌트 클래스(예: .form-label)를 새로 정의하거나, @utility로 추출하는 방식을 권장합니다.

♻️ 개선 제안
-@layer base {
-  label {
-    `@apply` flex flex-col gap-[12.5px];
-  }
-}
+@utility form-label {
+  `@apply` flex flex-col gap-[12.5px];
+}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/index.css` around lines 100 - 104, 전역 `@layer` base의 label 스타일(현재 selector:
label { `@apply` flex flex-col gap-[12.5px]; })이 모든 라벨에 적용되어 레이아웃을 깨뜨리므로 전역
selector를 제거하고 대신 목적별 클래스(.form-label) 또는 유틸리티로 대체하세요; 구체적으로, 삭제할 대상은 label 규칙이며
새 클래스 .form-label { `@apply` flex flex-col gap-[12.5px]; }를 정의한 뒤 폼 컴포넌트에서 label
요소를 .form-label로 교체(또는 기존 컴포넌트에 유틸리티 적용)하여 인라인 레이블(체크박스/라디오 등)에 영향이 가지 않도록
수정하세요.

2 changes: 1 addition & 1 deletion src/pages/admin/assignments/AssignmentCreatePage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import LabeledInput from '@/shared/ui/LabeledInput';
import FileUpload from '@/shared/ui/FileUpload';
import {useState} from 'react';
import LabeledDropdown from '@/shared/ui/LabeledDropdown';
import Button from '@/shared/ui/Button';
import Button from '@/shared/ui/button/Button';
import AddIcon from '@/assets/svg/addIcon.svg?react';

const PUBLIC_OPTIONS = ['공개', '비공개'];
Expand Down
2 changes: 1 addition & 1 deletion src/pages/common/LandingPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import snowCodeEntry from '@/assets/images/snowCode_entry.svg';
import snowCodeStudent from '@/assets/images/snowCode_student.svg';
import snowCodeAdmin from '@/assets/images/snowCode_admin.svg';
import ArrowrightIcon from '@/assets/svg/arrowrightIcon.svg?react';
import Button from '@/shared/ui/Button';
import Button from '@/shared/ui/button/Button';

type HoverState = 'none' | 'student' | 'admin';

Expand Down
2 changes: 1 addition & 1 deletion src/pages/common/UserIdInputPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import {useNavigate, useLocation} from 'react-router-dom';
import SnowCodeEntryMini from '@/assets/images/snowCode_entry_mini.svg';
import kakaoLogo from '@/assets/images/kakao_logo.svg';
import ArrowleftIcon from '@/assets/svg/arrowleftIcon.svg?react';
import Button from '@/shared/ui/Button';
import Button from '@/shared/ui/button/Button';
import {kakaoService} from '@/features/auth/kakao/lib/kakaoService';

export default function UserIdInputPage() {
Expand Down
2 changes: 1 addition & 1 deletion src/pages/course-overview/ui/AssignmentList.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import Badge from '@/shared/ui/Badge';
import Badge from '@/shared/ui/badge/Badge';
import {Link} from 'react-router-dom';
import type {Assignment} from '@/entities/assignment/model/types';

Expand Down
4 changes: 2 additions & 2 deletions src/pages/course-overview/ui/CourseActionsBar.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import Button from '@/shared/ui/Button';
import Button from '@/shared/ui/button/Button';
import {Link} from 'react-router-dom';

// 강의 관리 버튼 바 (관리자 전용)
Expand All @@ -10,7 +10,7 @@ const CourseActionsBar = ({isActiveCourse}: {isActiveCourse: boolean}) => {
학생 목록
</Button>
</Link>
<Link to=''>
<Link to={`/admin/units/3`}>
<Button color={isActiveCourse ? 'outlinePurple' : 'primary'}>
단원 추가
</Button>
Expand Down
2 changes: 1 addition & 1 deletion src/pages/course-overview/ui/UnitItem.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import AssignmentList from './AssignmentList';
import Lock from '@/assets/svg/lock.svg?react';
import Badge from '@/shared/ui/Badge';
import Badge from '@/shared/ui/badge/Badge';
import {formatPeriod} from '@/shared/lib/course';
import type {Unit} from '@/entities/course/model/types';

Expand Down
2 changes: 1 addition & 1 deletion src/pages/dashboard/Dashboard.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import LogoIcon from '@/assets/images/snowCode_logo.svg?react';
import CourseList from './ui/CourseList';
import Button from '@/shared/ui/Button';
import Button from '@/shared/ui/button/Button';
import AddIcon from '@/assets/svg/addIcon.svg?react';
import ScheduleList from './ui/ScheduleList';
import {Link} from 'react-router-dom';
Expand Down
2 changes: 1 addition & 1 deletion src/pages/dashboard/ui/ScheduleCard.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import Badge from '@/shared/ui/Badge';
import Badge from '@/shared/ui/badge/Badge';
import type {Schedule} from '@/entities/course/model/types';

type AssignmentType = Schedule['assignments'][number];
Expand Down
4 changes: 2 additions & 2 deletions src/pages/select-assignment/AssignmentSelectPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import {
} from '@/shared/mocks/assignmentSelectResponse';
import {useCourseFilter} from '@/features/course/filter-course/lib/useCourseFilter';
import {AssignmentPageLayout} from '@/widgets/assignment-page-layout';
import SelectableItem from '@/shared/ui/SelectableItem';
import ListRow from '@/shared/ui/list-row/ListRow';

const AssignmentSelectPage = () => {
const {courses} = courseOptionsResponse.response; // /courses/my API 응답 모킹
Expand Down Expand Up @@ -41,7 +41,7 @@ const AssignmentSelectPage = () => {
title={`${assignmentList.length}문제`}
onSelect={handleAssignmentSelect}
renderItem={(assignment) => (
<SelectableItem
<ListRow
title={assignment.title}
selected={selectedAssignments.includes(assignment.id)}
/>
Expand Down
Loading
Loading