diff --git a/.gitignore b/.gitignore index d649445..3d53d11 100755 --- a/.gitignore +++ b/.gitignore @@ -43,4 +43,7 @@ tmp/ temp/ *.zip -dist \ No newline at end of file +dist + +*.md +!README.md \ No newline at end of file diff --git a/package.json b/package.json index 0ba9864..d17a100 100644 --- a/package.json +++ b/package.json @@ -6,6 +6,7 @@ "scripts": { "dev": "vite", "build": "tsc -b && vite build", + "build:mock": "tsc -b && vite build --mode mock", "lint": "eslint .", "preview": "vite preview" }, diff --git a/src/hooks/useCourseData.tsx b/src/hooks/useCourseData.tsx index 4a75b40..546cf64 100644 --- a/src/hooks/useCourseData.tsx +++ b/src/hooks/useCourseData.tsx @@ -14,7 +14,34 @@ export function useCourseData(courses: CourseBase[]) { const [remainingTime, setRemainingTime] = useState(0); const [isError, setIsError] = useState(false); + // dev 모드: mock 데이터 로드 + useEffect(() => { + if (!import.meta.env.VITE_MOCK) return; + let cancelled = false; + import('@/mocks/mockData').then(({ mockVods, mockAssigns, mockQuizes }) => { + if (cancelled) return; + setVods(mockVods); + setAssigns(mockAssigns); + setQuizes(mockQuizes); + setRefreshTime(new Date().toLocaleTimeString()); + setRemainingTime(5); + }); + return () => { + cancelled = true; + }; + }, []); + + // dev 모드에서는 실제 fetch를 하지 않음 const updateData = useCallback(async () => { + if (import.meta.env.VITE_MOCK) { + const { mockVods, mockAssigns, mockQuizes } = await import('@/mocks/mockData'); + setVods(mockVods); + setAssigns(mockAssigns); + setQuizes(mockQuizes); + setRefreshTime(new Date().toLocaleTimeString()); + setRemainingTime(0); + return; + } try { setIsError(false); setIsPending(true); diff --git a/src/hooks/useGetCourse.ts b/src/hooks/useGetCourse.ts index 3b908d1..392be39 100644 --- a/src/hooks/useGetCourse.ts +++ b/src/hooks/useGetCourse.ts @@ -10,6 +10,14 @@ export interface UseCourseResult { export const useGetCourses = (): UseCourseResult => { const [courses, setCourses] = useState([]); useEffect(() => { + if (import.meta.env.VITE_MOCK) { + import('@/mocks/mockData').then(({ mockCourses }) => { + setCourses(mockCourses); + saveDataToStorage('courses', JSON.stringify(mockCourses)); + console.info('[Dotbugi] DEV 모드: mock 강의 목록 사용'); + }); + return; + } if (!document) return; const courseData = Array.from(document.querySelectorAll('.course_box')); const data = courseData diff --git a/src/mocks/mockData.ts b/src/mocks/mockData.ts new file mode 100644 index 0000000..16253f3 --- /dev/null +++ b/src/mocks/mockData.ts @@ -0,0 +1,274 @@ +import { CourseBase, Vod, Assign, Quiz } from '@/content/types'; + +function offsetDate(days: number, hours = 0): string { + const d = new Date(); + d.setDate(d.getDate() + days); + d.setHours(d.getHours() + hours); + const pad = (n: number) => String(n).padStart(2, '0'); + return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())} ${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}`; +} + +function makeRange(startDays: number, endDays: number): string { + return `${offsetDate(startDays)} ~ ${offsetDate(endDays)}`; +} + +export const mockCourses: CourseBase[] = [ + { courseId: '1001', courseTitle: '자료구조', prof: '김교수' }, + { courseId: '1002', courseTitle: '운영체제', prof: '이교수' }, + { courseId: '1003', courseTitle: '컴퓨터네트워크', prof: '박교수' }, + { courseId: '1004', courseTitle: '데이터베이스', prof: '최교수' }, +]; + +export const mockVods: Vod[] = [ + // 출석 완료 + 기간 내 + { + courseId: '1001', + courseTitle: '자료구조', + prof: '김교수', + week: 7, + subject: '7주차', + title: '이진 탐색 트리', + url: '#', + range: makeRange(-3, 5), + length: '45:00', + isAttendance: 'O', + weeklyAttendance: '1/1', + }, + // 결석 + 마감 임박 (오늘 안에 끝남) + { + courseId: '1001', + courseTitle: '자료구조', + prof: '김교수', + week: 7, + subject: '7주차', + title: '힙과 우선순위 큐', + url: '#', + range: makeRange(-5, 0, ), + length: '38:00', + isAttendance: 'X', + weeklyAttendance: '0/1', + }, + // 결석 + 기간 여유 + { + courseId: '1002', + courseTitle: '운영체제', + prof: '이교수', + week: 6, + subject: '6주차', + title: '프로세스 동기화', + url: '#', + range: makeRange(-2, 7), + length: '50:00', + isAttendance: 'X', + weeklyAttendance: '0/1', + }, + // 출석 완료 + { + courseId: '1002', + courseTitle: '운영체제', + prof: '이교수', + week: 6, + subject: '6주차', + title: '데드락', + url: '#', + range: makeRange(-2, 7), + length: '42:00', + isAttendance: 'O', + weeklyAttendance: '1/1', + }, + // 결석 + 마감 매우 임박 (몇 시간 남음) + { + courseId: '1003', + courseTitle: '컴퓨터네트워크', + prof: '박교수', + week: 5, + subject: '5주차', + title: 'TCP/IP 프로토콜', + url: '#', + range: makeRange(-6, 0), + length: '55:00', + isAttendance: 'X', + weeklyAttendance: '0/2', + }, + { + courseId: '1003', + courseTitle: '컴퓨터네트워크', + prof: '박교수', + week: 5, + subject: '5주차', + title: 'UDP와 소켓 프로그래밍', + url: '#', + range: makeRange(-6, 0), + length: '35:00', + isAttendance: 'O', + weeklyAttendance: '1/2', + }, + // 출석 + range가 넉넉 + { + courseId: '1004', + courseTitle: '데이터베이스', + prof: '최교수', + week: 4, + subject: '4주차', + title: '정규화 이론', + url: '#', + range: makeRange(-1, 14), + length: '60:00', + isAttendance: 'O', + weeklyAttendance: '1/1', + }, +]; + +export const mockAssigns: Assign[] = [ + // 미제출 + 마감 임박 (내일) + { + courseId: '1001', + courseTitle: '자료구조', + prof: '김교수', + subject: '7주차', + title: '이진 트리 구현 과제', + url: '#', + isSubmit: false, + dueDate: offsetDate(1), + }, + // 미제출 + 마감 3일 뒤 + { + courseId: '1002', + courseTitle: '운영체제', + prof: '이교수', + subject: '6주차', + title: '프로세스 스케줄링 시뮬레이션', + url: '#', + isSubmit: false, + dueDate: offsetDate(3), + }, + // 제출 완료 + 마감 아직 + { + courseId: '1001', + courseTitle: '자료구조', + prof: '김교수', + subject: '6주차', + title: '스택/큐 구현 과제', + url: '#', + isSubmit: true, + dueDate: offsetDate(5), + }, + // 미제출 + 마감 7일 뒤 + { + courseId: '1003', + courseTitle: '컴퓨터네트워크', + prof: '박교수', + subject: '5주차', + title: '소켓 프로그래밍 실습', + url: '#', + isSubmit: false, + dueDate: offsetDate(7), + }, + // 제출 완료 + 마감 지남 (새로고침 전 마감 테스트) + { + courseId: '1004', + courseTitle: '데이터베이스', + prof: '최교수', + subject: '4주차', + title: 'SQL 쿼리 작성', + url: '#', + isSubmit: true, + dueDate: offsetDate(-1), + }, + // 미제출 + 마감 몇 시간 뒤 (긴급) + { + courseId: '1004', + courseTitle: '데이터베이스', + prof: '최교수', + subject: '5주차', + title: 'ERD 설계 과제', + url: '#', + isSubmit: false, + dueDate: offsetDate(0, 3), + }, + // 미제출 + 마감 이미 지남 (새로고침 전 마감 테스트) + { + courseId: '1002', + courseTitle: '운영체제', + prof: '이교수', + subject: '5주차', + title: '메모리 관리 보고서', + url: '#', + isSubmit: false, + dueDate: offsetDate(-2), + }, + // dueDate null 케이스 + { + courseId: '1003', + courseTitle: '컴퓨터네트워크', + prof: '박교수', + subject: '6주차', + title: '네트워크 분석 레포트', + url: '#', + isSubmit: false, + dueDate: null, + }, +]; + +export const mockQuizes: Quiz[] = [ + // 마감 임박 (내일) + { + courseId: '1001', + courseTitle: '자료구조', + prof: '김교수', + subject: '7주차', + title: '트리 구조 퀴즈', + url: '#', + dueDate: offsetDate(1), + }, + // 마감 여유 (5일) + { + courseId: '1002', + courseTitle: '운영체제', + prof: '이교수', + subject: '6주차', + title: '프로세스 관리 퀴즈', + url: '#', + dueDate: offsetDate(5), + }, + // 마감 몇 시간 뒤 (긴급) + { + courseId: '1003', + courseTitle: '컴퓨터네트워크', + prof: '박교수', + subject: '5주차', + title: 'OSI 모델 퀴즈', + url: '#', + dueDate: offsetDate(0, 5), + }, + // 마감 이미 지남 (새로고침 전 마감) + { + courseId: '1004', + courseTitle: '데이터베이스', + prof: '최교수', + subject: '4주차', + title: 'SQL 기초 퀴즈', + url: '#', + dueDate: offsetDate(-1), + }, + // 마감 2주 뒤 + { + courseId: '1004', + courseTitle: '데이터베이스', + prof: '최교수', + subject: '5주차', + title: '정규화 퀴즈', + url: '#', + dueDate: offsetDate(14), + }, + // dueDate null 케이스 + { + courseId: '1001', + courseTitle: '자료구조', + prof: '김교수', + subject: '8주차', + title: '그래프 탐색 퀴즈', + url: '#', + dueDate: null, + }, +]; diff --git a/vite.config.ts b/vite.config.ts index 452c041..0988f39 100755 --- a/vite.config.ts +++ b/vite.config.ts @@ -16,7 +16,7 @@ export default defineConfig(({ mode }) => { react(), tsconfigPaths(), Pages(), - // dev 환경에서는 crx 플러그인을 제외 + // dev 서버에서는 crx 플러그인을 제외 (mock 빌드에서는 포함) !isDev && crx({ manifest,