diff --git a/src/content/StickyPopoverTrigger.tsx b/src/content/StickyPopoverTrigger.tsx index 9c71ac4..daa9eb5 100644 --- a/src/content/StickyPopoverTrigger.tsx +++ b/src/content/StickyPopoverTrigger.tsx @@ -14,7 +14,6 @@ const StickyPopoverTrigger: React.FC<{ children: React.ReactNode }> = ({ childre if (!placeholder) return; const fixedBottom = 20; - const collapsedBottom = 8; const transitionRange = 20; const handleScroll = () => { diff --git a/src/content/components/Assignment.tsx b/src/content/components/Assignment.tsx index 3b10573..99a6589 100644 --- a/src/content/components/Assignment.tsx +++ b/src/content/components/Assignment.tsx @@ -1,5 +1,5 @@ -import { calculateDueDate, calculateRemainingTime, cn, isWithinSevenDays } from '@/lib/utils'; -import { Card, CardContent, CardFooter, CardHeader } from '@/components/ui/card'; +import { calculateDueDate, calculateRemainingTime } from '@/lib/utils'; +import { Card, CardFooter, CardHeader } from '@/components/ui/card'; import { BadgeCheck, Clock, Siren, TriangleAlert } from 'lucide-react'; import { Tooltip } from '@radix-ui/react-tooltip'; import { TooltipContent, TooltipTrigger } from '@/components/ui/tooltip'; @@ -26,7 +26,7 @@ export default function Assignment({ courseData }: Props) { {courseData.map((course, index) => { if (!course) return null; - let isDueDateSame = true; + const isDueDateSame = true; const timeDifference = calculateDueDate(course.dueDate!); return ( diff --git a/src/content/components/FilterBadge.tsx b/src/content/components/FilterBadge.tsx index a32426f..f2b4ed7 100644 --- a/src/content/components/FilterBadge.tsx +++ b/src/content/components/FilterBadge.tsx @@ -1,5 +1,4 @@ import React from 'react'; -import { X } from 'lucide-react'; interface FilterBadgeProps { label: string; diff --git a/src/content/components/QuizTab.tsx b/src/content/components/QuizTab.tsx index 0935562..a420bb3 100644 --- a/src/content/components/QuizTab.tsx +++ b/src/content/components/QuizTab.tsx @@ -26,7 +26,7 @@ export default function QuizTab({ courseData }: Props) { {courseData.map((course, index) => { if (!course) return null; - let isDueDateSame = true; + const isDueDateSame = true; const timeDifference = calculateDueDate(course.dueDate!); return ( diff --git a/src/content/components/Video.tsx b/src/content/components/Video.tsx index 020ca19..26592e0 100644 --- a/src/content/components/Video.tsx +++ b/src/content/components/Video.tsx @@ -4,11 +4,10 @@ import { Vod } from '../types'; import { calculateRemainingTimeByRange, calculateTimeDifference, - cn, formatDateString, isCurrentDateInRange, } from '@/lib/utils'; -import { AlarmClock, BadgeCheck, ChevronDown, ChevronUp, Clock, Siren, TriangleAlert } from 'lucide-react'; +import { BadgeCheck, ChevronDown, ChevronUp, Clock, Siren, TriangleAlert } from 'lucide-react'; import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip'; @@ -126,7 +125,7 @@ export default function Video({ courseData }: Props) { }); const item = vods[0]; - let isDueDateSame = true; + const isDueDateSame = true; const timeDifference = calculateTimeDifference(item.range); const isExpanded = expandedCards[`${item.title}-${index}`] || false; diff --git a/src/hooks/useCourseData.tsx b/src/hooks/useCourseData.tsx index de94422..c54fc26 100644 --- a/src/hooks/useCourseData.tsx +++ b/src/hooks/useCourseData.tsx @@ -1,9 +1,13 @@ import { useState, useEffect, useCallback } from 'react'; -import { Vod, Assign, Quiz, TAB_TYPE } from '@/content/types'; +import { Vod, Assign, Quiz } from '@/content/types'; import { loadDataFromStorage, saveDataToStorage } from '@/lib/storage'; import { requestData } from '@/lib/fetchCourseData'; +import { isCurrentDateByDate, isCurrentDateInRange } from '@/lib/utils'; + +const makeVodKey = (courseId: string, title: string, week: number) => `${courseId}-${title}-${week}`; +const makeAssignKey = (courseId: string, title: string, dueDate: string) => `${courseId}-${title}-${dueDate}`; +const makeQuizKey = (courseId: string, title: string, dueDate: string) => `${courseId}-${title}-${dueDate}`; -// courses 배열을 받아 vod, assign, quiz 데이터를 관리하는 커스텀 훅 export function useCourseData(courses: any[]) { const [vods, setVods] = useState([]); const [assigns, setAssigns] = useState([]); @@ -24,12 +28,9 @@ export function useCourseData(courses: any[]) { const tempAssigns: Assign[] = [...assigns]; const tempQuizes: Quiz[] = [...quizes]; - // Set을 사용하여 중복 방지 (각 데이터 유형별로 title을 기준으로) - const vodSet = new Set(tempVods.map((vod) => `${vod.courseId}-${vod.title}-${vod.range}-vod`)); - const assignSet = new Set( - tempAssigns.map((assign) => `${assign.courseId}-${assign.title}-${assign.dueDate}-assign`) - ); - const quizSet = new Set(tempQuizes.map((quiz) => `${quiz.courseId}-${quiz.title}-${quiz.dueDate}-quiz`)); + const vodSet = new Set(); + const assignSet = new Set(); + const quizSet = new Set(); await Promise.all( courses.map(async (course) => { @@ -37,8 +38,8 @@ export function useCourseData(courses: any[]) { result.vodDataArray.forEach((vodData) => { result.vodAttendanceArray.forEach((vodAttendanceData) => { - const vodKey = `${vodAttendanceData.title}-${vodAttendanceData.week}`; - if (vodAttendanceData.title === vodData.title && vodAttendanceData.week === vodData.week) { + const vodKey = makeVodKey(course.courseId, vodData.title, vodData.week); + if (vodAttendanceData.week === vodData.week) { if (!vodSet.has(vodKey)) { vodSet.add(vodKey); tempVods.push({ @@ -60,8 +61,9 @@ export function useCourseData(courses: any[]) { }); result.assignDataArray.forEach((assignData) => { - if (!assignSet.has(assignData.title)) { - assignSet.add(assignData.title); + const assignKey = makeAssignKey(course.courseId, assignData.title, assignData.dueDate); + if (!assignSet.has(assignKey)) { + assignSet.add(assignKey); tempAssigns.push({ courseId: course.courseId, prof: course.prof, @@ -76,8 +78,9 @@ export function useCourseData(courses: any[]) { }); result.quizDataArray.forEach((quizData) => { - if (!quizSet.has(quizData.title)) { - quizSet.add(quizData.title); + const quizKey = makeQuizKey(course.courseId, quizData.title, quizData.dueDate); + if (!quizSet.has(quizKey)) { + quizSet.add(quizKey); tempQuizes.push({ courseId: course.courseId, prof: course.prof, @@ -142,16 +145,19 @@ export function useCourseData(courses: any[]) { const minutes = (currentTime - parseInt(lastRequestTime, 10)) / (60 * 1000); setRemainingTime(minutes); loadDataFromStorage('vod', (data) => { - // setVods((data as Vod[]).filter((vod) => isCurrentDateInRange(vod.range))); - setVods((data as Vod[]) || []); + if (!data) return; + setVods((data as Vod[]).filter((vod) => isCurrentDateInRange(vod.range))); + // setVods((data as Vod[]) || []); }); loadDataFromStorage('assign', (data) => { - // setAssigns((data as Assign[]).filter((assign) => isCurrentDateByDate(assign.dueDate))); - setAssigns((data as Assign[]) || []); + if (!data) return; + setAssigns((data as Assign[]).filter((assign) => isCurrentDateByDate(assign.dueDate))); + // setAssigns((data as Assign[]) || []); }); loadDataFromStorage('quiz', (data) => { - // setQuizes((data as Quiz[]).filter((quiz) => isCurrentDateByDate(quiz.dueDate))); - setQuizes((data as Quiz[]) || []); + if (!data) return; + setQuizes((data as Quiz[]).filter((quiz) => isCurrentDateByDate(quiz.dueDate))); + // setQuizes((data as Quiz[]) || []); }); } }, [courses, updateData]); diff --git a/src/hooks/useGetCourse.ts b/src/hooks/useGetCourse.ts index ed2199d..6e91c94 100644 --- a/src/hooks/useGetCourse.ts +++ b/src/hooks/useGetCourse.ts @@ -30,8 +30,10 @@ export const useGetCourses = (): UseCouresResult => { (item): item is { courseId: string; courseTitle: string; prof: string } => item !== null && item.courseId !== '' && item.courseTitle !== '' && item.prof !== '' ); + setCourses(data); saveDataToStorage('courses', JSON.stringify(data)); + console.info('[Dotbugi] 강의 목록:', data); }, []); return { courses }; diff --git a/src/lib/fetchCourseData.ts b/src/lib/fetchCourseData.ts index ec9af8c..aa1e1b8 100644 --- a/src/lib/fetchCourseData.ts +++ b/src/lib/fetchCourseData.ts @@ -18,6 +18,7 @@ export const requestData = async (id: string) => { fetchQuiz(QUIZ_LINK), ]); + console.info('[Dotbugi]', vodAttendanceArray, vodDataArray, assignDataArray, quizDataArray); return { vodAttendanceArray, vodDataArray, assignDataArray, quizDataArray }; } catch (error) { console.error('Error while fetching data:', error); diff --git a/src/lib/fetchIndexPage.ts b/src/lib/fetchIndexPage.ts index 6c1879b..c62648a 100644 --- a/src/lib/fetchIndexPage.ts +++ b/src/lib/fetchIndexPage.ts @@ -27,7 +27,7 @@ export const fetchIndexPage = async (link: string) => { .filter((item) => !item.closest('.dimmed')) .map((item) => { const week = index + 1; - const title = item.querySelector('.instancename')?.textContent?.replace('동영상', '').trim() || null; + const title = item.querySelector('.instancename')?.textContent?.trim() || null; const url = item.querySelector('a')?.getAttribute('href') || null; const range = item.querySelector('.text-ubstrap')?.textContent?.trim() || ''; const length = item.querySelector('.text-info')?.textContent?.replace(',', '').trim() || ''; diff --git a/src/lib/fetchVodAttendance.ts b/src/lib/fetchVodAttendance.ts index 78b80dd..601bdbb 100644 --- a/src/lib/fetchVodAttendance.ts +++ b/src/lib/fetchVodAttendance.ts @@ -39,83 +39,86 @@ export const fetchVodAttendance = async (link: string) => { const vods: VodAttendanceData[] = []; let idx = 0; let lastWeeklyAttendance = ''; - rows.forEach((row) => { - const cells = Array.from(row.querySelectorAll('td')); - let colIndex = 0; - const rowData: (string | null)[] = []; - - while (spanTable[colIndex] && spanTable[colIndex]! > 0) { - rowData.push(cellValues[colIndex] || null); - spanTable[colIndex]! -= 1; - colIndex++; - } + try { + const cells = Array.from(row.querySelectorAll('td')); + let colIndex = 0; + const rowData: (string | null)[] = []; - cells.forEach((cell) => { while (spanTable[colIndex] && spanTable[colIndex]! > 0) { rowData.push(cellValues[colIndex] || null); spanTable[colIndex]! -= 1; colIndex++; } - const rowspan = parseInt(cell.getAttribute('rowspan') || '1', 10); - const colspan = parseInt(cell.getAttribute('colspan') || '1', 10); - const cellContent = cell.textContent?.trim() || ''; + cells.forEach((cell) => { + while (spanTable[colIndex] && spanTable[colIndex]! > 0) { + rowData.push(cellValues[colIndex] || null); + spanTable[colIndex]! -= 1; + colIndex++; + } + + const rowspan = parseInt(cell.getAttribute('rowspan') || '1', 10); + const colspan = parseInt(cell.getAttribute('colspan') || '1', 10); + const cellContent = cell.textContent?.trim() || ''; - for (let i = 0; i < colspan; i++) { - rowData.push(cellContent); + for (let i = 0; i < colspan; i++) { + rowData.push(cellContent); - if (rowspan > 1) { - spanTable[colIndex] = rowspan - 1; - cellValues[colIndex] = cellContent; + if (rowspan > 1) { + spanTable[colIndex] = rowspan - 1; + cellValues[colIndex] = cellContent; + } else { + cellValues[colIndex] = null; + } + colIndex++; + } + }); + + while (colIndex < spanTable.length) { + if (spanTable[colIndex] && spanTable[colIndex]! > 0) { + rowData.push(cellValues[colIndex] || null); + spanTable[colIndex]! -= 1; } else { - cellValues[colIndex] = null; + rowData.push(null); } colIndex++; } - }); - while (colIndex < spanTable.length) { - if (spanTable[colIndex] && spanTable[colIndex]! > 0) { - rowData.push(cellValues[colIndex] || null); - spanTable[colIndex]! -= 1; + let weeklyAttendance = + headerMap['weeklyAttendance'] !== undefined ? rowData[headerMap['weeklyAttendance']] || '' : ''; + if (weeklyAttendance) { + lastWeeklyAttendance = weeklyAttendance; } else { - rowData.push(null); + weeklyAttendance = lastWeeklyAttendance; } - colIndex++; - } - - let weeklyAttendance = - headerMap['weeklyAttendance'] !== undefined ? rowData[headerMap['weeklyAttendance']] || '' : ''; - if (weeklyAttendance) { - lastWeeklyAttendance = weeklyAttendance; - } else { - weeklyAttendance = lastWeeklyAttendance; - } - if (weeklyAttendance.includes('일괄출석인정')) weeklyAttendance = 'o'; + if (weeklyAttendance.includes('일괄출석인정')) weeklyAttendance = 'o'; - const title = headerMap['title'] !== undefined ? rowData[headerMap['title']] || '' : ''; - const isAttendance = headerMap['isAttendance'] !== undefined ? rowData[headerMap['isAttendance']] || '' : ''; + const title = headerMap['title'] !== undefined ? rowData[headerMap['title']] || '' : ''; + const isAttendance = headerMap['isAttendance'] !== undefined ? rowData[headerMap['isAttendance']] || '' : ''; - let weekStr = rowData[0] || ''; - if (weekStr !== '' && !isNaN(parseInt(weekStr))) { - idx = parseInt(weekStr); - } else { - weekStr = idx.toString(); - } + let weekStr = rowData[0] || ''; + if (weekStr !== '' && !isNaN(parseInt(weekStr))) { + idx = parseInt(weekStr); + } else { + weekStr = idx.toString(); + } - if (!title || !isAttendance) { - return; + if (!title || !isAttendance) { + return; + } + const week = parseInt(weekStr); + + vods.push({ + title, + isAttendance, + weeklyAttendance, + week, + }); + } catch (error) { + console.error(`[Dotbugi] 영상 강의 조회 오류: ${link} ${row}`, error); } - const week = parseInt(weekStr); - - vods.push({ - title, - isAttendance, - weeklyAttendance, - week, - }); }); return vods; diff --git a/src/option/index.tsx b/src/option/index.tsx index 1bacf30..67d4741 100644 --- a/src/option/index.tsx +++ b/src/option/index.tsx @@ -1,7 +1,7 @@ import React from 'react'; import { createRoot } from 'react-dom/client'; -import { HashRouter as Router, Routes, Route } from 'react-router-dom'; -import 'src/styles/option.css'; +import { HashRouter as Router } from 'react-router-dom'; +import '@/styles/option.css'; import App from './App'; const rootElement = document.getElementById('root'); diff --git a/tsconfig.json b/tsconfig.json index 0d1ba6c..5f4dd36 100755 --- a/tsconfig.json +++ b/tsconfig.json @@ -15,9 +15,10 @@ "jsx": "react-jsx", "baseUrl": ".", "paths": { - "@/*": ["src/*"] + "@/*": ["src/*"], + "src/*": ["src/*"] } }, - "include": ["*/**.ts", "*/**.tsx", "vite.config.ts", "src"], + "include": ["*/**.ts", "*/**.tsx", "vite.config.ts", "*/src"], "exclude": ["node_modules", "dist"] } diff --git a/vite.config.ts b/vite.config.ts index ef52f26..452c041 100755 --- a/vite.config.ts +++ b/vite.config.ts @@ -28,6 +28,7 @@ export default defineConfig(({ mode }) => { resolve: { alias: { '@': path.resolve(__dirname, './src'), + src: path.resolve(__dirname, './src'), }, }, build: {