Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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
64 changes: 50 additions & 14 deletions src/content/components/Video.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
import { useState } from 'react';
import { Card, CardContent, CardFooter, CardHeader } from '@/components/ui/card';
import { Vod } from '../types';
import { calculateRemainingTimeByRange, calculateTimeDifference, cn, formatDateString } from '@/lib/utils';
import {
calculateRemainingTimeByRange,
calculateTimeDifference,
cn,
formatDateString,
isCurrentDateInRange,
} from '@/lib/utils';
import { AlarmClock, BadgeCheck, ChevronDown, ChevronUp, Clock, Siren, TriangleAlert } from 'lucide-react';

import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip';
Expand Down Expand Up @@ -48,17 +54,45 @@ export default function Video({ courseData }: Props) {
const isAX = firstA.weeklyAttendance.toUpperCase().startsWith('X');
const isBX = firstB.weeklyAttendance.toUpperCase().startsWith('X');

// X가 있는 항목을 먼저 정렬
if (isAX && !isBX) return -1;
if (!isAX && isBX) return 1;

const rangeStartA = firstA.range.split(' ~ ')[0];
const rangeStartB = firstB.range.split(' ~ ')[0];
const dateA = new Date(rangeStartA);
const dateB = new Date(rangeStartB);

if (dateA < dateB) return -1;
if (dateA > dateB) return 1;
const rangeA = firstA.range;
const rangeB = firstB.range;
const isRangeANull = rangeA === null;
const isRangeBNull = rangeB === null;

// isCurrentDateInRange가 true인 항목을 먼저 정렬 (X와 O 모두)
const isCurrentDateInRangeA = isCurrentDateInRange(firstA.range);
const isCurrentDateInRangeB = isCurrentDateInRange(firstB.range);

if (isAX) {
// X일 때는 isCurrentDateInRange가 true인 항목을 먼저 배치, 그 다음 null
if (isCurrentDateInRangeA && !isCurrentDateInRangeB) return -1;
if (!isCurrentDateInRangeA && isCurrentDateInRangeB) return 1;
if (isRangeANull && !isRangeBNull) return 1;
if (!isRangeANull && isRangeBNull) return -1;
} else {
// O일 때는 isCurrentDateInRange가 true인 항목을 먼저 배치, 그 다음 null, 그 다음 시간순 정렬
if (isCurrentDateInRangeA && !isCurrentDateInRangeB) return -1;
if (!isCurrentDateInRangeA && isCurrentDateInRangeB) return 1;
if (isRangeANull && !isRangeBNull) return 1;
if (!isRangeANull && isRangeBNull) return -1;

// rangeStart 날짜 기준으로 시간순으로 정렬
if (!isRangeANull && !isRangeBNull) {
const rangeStartA = rangeA.split(' ~ ')[0];
const rangeStartB = rangeB.split(' ~ ')[0];
const dateA = new Date(rangeStartA);
const dateB = new Date(rangeStartB);

if (dateA < dateB) return -1;
if (dateA > dateB) return 1;
}
}

// courseTitle로 기본 정렬
if (firstA.courseTitle < firstB.courseTitle) return -1;
if (firstA.courseTitle > firstB.courseTitle) return 1;

Expand All @@ -76,12 +110,14 @@ export default function Video({ courseData }: Props) {
if (isAX && !isBX) return -1;
if (!isAX && isBX) return 1;

const rangeStartA = a.range.split(' ~ ')[0];
const rangeStartB = b.range.split(' ~ ')[0];
const dateA = new Date(rangeStartA);
const dateB = new Date(rangeStartB);
if (dateA < dateB) return -1;
if (dateA > dateB) return 1;
if (a.range && b.range) {
const rangeStartA = a.range.split(' ~ ')[0];
const rangeStartB = b.range.split(' ~ ')[0];
const dateA = new Date(rangeStartA);
const dateB = new Date(rangeStartB);
if (dateA < dateB) return -1;
if (dateA > dateB) return 1;
}

if (a.courseTitle < b.courseTitle) return -1;
if (a.courseTitle > b.courseTitle) return 1;
Expand Down
10 changes: 5 additions & 5 deletions src/content/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ export type VodData = {
subject: string;
title: string;
url: string;
range: string;
range: string | null;
length: string;
};

Expand All @@ -31,7 +31,7 @@ export type AssignData = {
title: string;
url: string;
isSubmit: boolean;
dueDate: string;
dueDate: string | null;
};

export type Quiz = CourseBase & QuizData;
Expand All @@ -40,7 +40,7 @@ export type QuizData = {
subject: string;
title: string;
url: string;
dueDate: string;
dueDate: string | null;
};

export type TimeDifferenceResult = {
Expand All @@ -59,8 +59,8 @@ export interface Item {

export interface Filters {
courseTitles: string[];
attendanceStatuses?: string[];
submitStatuses?: boolean[];
attendanceStatuses?: string[];
submitStatuses?: boolean[];
}

export enum TAB_TYPE {
Expand Down
17 changes: 10 additions & 7 deletions src/hooks/useCalendarEvents.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ export type CalendarEvent = {
type: 'vod' | 'assign' | 'quiz';
title: string;
subject: string;
start: Date;
end: Date;
start: Date | null;
end: Date | null;
};

function useCalendarEvents() {
Expand Down Expand Up @@ -52,12 +52,13 @@ function useCalendarEvents() {
);

return Object.entries(groupedData).map(([key, vodItems]) => {
const [start, end] = vodItems[0].range.split(' ~ ');
const range = vodItems[0].range;
const [start, end] = range ? range.split(' ~ ') : [null, null];
return {
id: key,
type: 'vod',
start: new Date(start.replace(/-/g, '/')),
end: new Date(end.replace(/-/g, '/')),
start: start ? new Date(start.replace(/-/g, '/')) : null,
end: end ? new Date(end.replace(/-/g, '/')) : null,
title: removeSquareBrackets(vodItems[0].courseTitle),
subject: removeSquareBrackets(vodItems[0].subject),
};
Expand All @@ -67,7 +68,8 @@ function useCalendarEvents() {
// assign 데이터 로딩 및 변환
loadEvents<Assign>('assign', (assigns) =>
assigns.map((assign) => {
const normalizedDate = startOfDay(new Date(assign.dueDate));
const dueDate = assign.dueDate;
const normalizedDate = dueDate ? startOfDay(new Date(dueDate)) : null;
return {
id: assign.courseId + assign.title + assign.dueDate,
type: 'assign',
Expand All @@ -82,7 +84,8 @@ function useCalendarEvents() {
// quiz 데이터 로딩 및 변환
loadEvents<Quiz>('quiz', (quizzes) =>
quizzes.map((quiz) => {
const normalizedDate = startOfDay(new Date(quiz.dueDate));
const dueDate = quiz.dueDate;
const normalizedDate = dueDate ? startOfDay(new Date(dueDate)) : null;
return {
id: quiz.courseId + quiz.title + quiz.dueDate,
type: 'quiz',
Expand Down
50 changes: 30 additions & 20 deletions src/hooks/useCourseData.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import { useState, useEffect, useCallback } from 'react';
import { Vod, Assign, Quiz, TAB_TYPE } from '@/content/types';
import { loadDataFromStorage, saveDataToStorage } from '@/lib/storage';
import { requestData } from '@/lib/fetchCourseData';
import { isCurrentDateInRange, isCurrentDateByDate } from '@/lib/utils';

// courses 배열을 받아 vod, assign, quiz 데이터를 관리하는 커스텀 훅
export function useCourseData(courses: any[]) {
Expand All @@ -14,28 +13,34 @@ export function useCourseData(courses: any[]) {
const [remainingTime, setRemainingTime] = useState(0);
const [isError, setIsError] = useState(false);

// updateData 함수를 useCallback으로 선언하여 useEffect 등에서 재사용
const updateData = useCallback(async () => {
try {
setIsError(false);
setIsPending(true);
const currentTime = new Date().getTime();
setVods([]);
setAssigns([]);
setQuizes([]);

const tempVods: Vod[] = [];
const tempAssigns: Assign[] = [];
const tempQuizes: Quiz[] = [];
// 기존 데이터를 유지하면서 새로운 데이터만 추가
const tempVods: Vod[] = [...vods];
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`));

await Promise.all(
courses.map(async (course) => {
const result = await requestData(course.courseId);

result.vodDataArray.forEach((vodData) => {
if (isCurrentDateInRange(vodData.range)) {
result.vodAttendanceArray.forEach((vodAttendanceData) => {
if (vodAttendanceData.title === vodData.title && vodAttendanceData.week === vodData.week) {
result.vodAttendanceArray.forEach((vodAttendanceData) => {
const vodKey = `${vodAttendanceData.title}-${vodAttendanceData.week}`;
if (vodAttendanceData.title === vodData.title && vodAttendanceData.week === vodData.week) {
if (!vodSet.has(vodKey)) {
vodSet.add(vodKey);
tempVods.push({
courseId: course.courseId,
prof: course.prof,
Expand All @@ -50,12 +55,13 @@ export function useCourseData(courses: any[]) {
url: vodData.url,
});
}
});
}
}
});
});

result.assignDataArray.forEach((assignData) => {
if (isCurrentDateByDate(assignData.dueDate)) {
if (!assignSet.has(assignData.title)) {
assignSet.add(assignData.title);
tempAssigns.push({
courseId: course.courseId,
prof: course.prof,
Expand All @@ -70,7 +76,8 @@ export function useCourseData(courses: any[]) {
});

result.quizDataArray.forEach((quizData) => {
if (isCurrentDateByDate(quizData.dueDate)) {
if (!quizSet.has(quizData.title)) {
quizSet.add(quizData.title);
tempQuizes.push({
courseId: course.courseId,
prof: course.prof,
Expand All @@ -94,17 +101,17 @@ export function useCourseData(courses: any[]) {
saveDataToStorage('quiz', tempQuizes);

setRefreshTime(new Date(currentTime).toLocaleTimeString());

setRemainingTime(0);
localStorage.setItem('lastRequestTime', currentTime.toString());
saveDataToStorage('lastRequestTime', currentTime.toString());

setIsPending(false);
} catch (error) {
localStorage.removeItem('lastRequestTime');
setIsError(true);
setIsPending(false);
}
}, [courses]);
}, [courses, vods, assigns, quizes]);

useEffect(() => {
let timer: ReturnType<typeof setTimeout>;
Expand Down Expand Up @@ -135,13 +142,16 @@ 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[]).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[]).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[]).filter((quiz) => isCurrentDateByDate(quiz.dueDate)));
setQuizes((data as Quiz[]) || []);
});
}
}, [courses, updateData]);
Expand Down
26 changes: 14 additions & 12 deletions src/lib/calendarUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,16 +81,18 @@ export async function getCalendarEvents(token: string): Promise<GoogleCalendarEv
* @returns GoogleCalendarEvent 배열
*/
export function convertCalendarEventsToGoogleEvents(events: CalendarEvent[]): GoogleCalendarEvent[] {
return events.map((event) => ({
summary: `${event.title}`,
description: `${event.subject}`,
start: {
dateTime: event.start.toISOString(),
timeZone: 'Asia/Seoul',
},
end: {
dateTime: event.end.toISOString(),
timeZone: 'Asia/Seoul',
},
}));
return events
.filter((event) => event.start !== null && event.end !== null)
.map((event) => ({
summary: `${event.title}`,
description: `${event.subject}`,
start: {
dateTime: event.start!.toISOString(),
timeZone: 'Asia/Seoul',
},
end: {
dateTime: event.end!.toISOString(),
timeZone: 'Asia/Seoul',
},
}));
}
2 changes: 1 addition & 1 deletion src/lib/fetchAssign.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ export const fetchAssign = async (link: string) => {
const isSubmit = row.querySelector(headerMap.isSubmit)?.textContent?.trim() === '미제출' ? false : true;

if (sbj.length !== 0) subject = sbj;
if (!title || !url || !dueDate) return null;
if (!title || !url) return null;
return { subject, title, url, dueDate, isSubmit };
})
.filter((assign) => assign !== null);
Expand Down
2 changes: 1 addition & 1 deletion src/lib/fetchQuiz.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ export const fetchQuiz = async (link: string) => {
url = url.slice(0, index) + 'mod/quiz/' + url.slice(index);
}

if (title && dueDate && url) {
if (title && url) {
return { title, subject, url, dueDate };
}
return null;
Expand Down
16 changes: 13 additions & 3 deletions src/lib/filterData.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ export function filterVods(vods: Vod[], filters: Filters, searchTerm: string, so
return data.sort((a, b) => {
const attendanceA = a.isAttendance.toLowerCase().trim() === 'o';
const attendanceB = b.isAttendance.toLowerCase().trim() === 'o';

if (attendanceA !== attendanceB) {
return attendanceA ? -1 : 1;
}
Expand All @@ -37,7 +38,10 @@ export function filterVods(vods: Vod[], filters: Filters, searchTerm: string, so
case 'title':
return a.title.localeCompare(b.title);
default:
return a.range.localeCompare(b.range);
if (a.range === null && b.range !== null) return attendanceA ? -1 : 1;
if (a.range !== null && b.range === null) return attendanceA ? 1 : -1;
if (a.range === null && b.range === null) return 0;
return (a.range ?? '').localeCompare(b.range ?? '');
}
});
}
Expand Down Expand Up @@ -74,7 +78,10 @@ export function filterAssigns(assigns: Assign[], filters: Filters, searchTerm: s
case 'title':
return a.title.localeCompare(b.title);
default:
return a.dueDate.localeCompare(b.dueDate);
if (a.dueDate === null && b.dueDate !== null) return a.isSubmit ? -1 : 1;
if (a.dueDate !== null && b.dueDate === null) return a.isSubmit ? 1 : -1;
if (a.dueDate === null && b.dueDate === null) return 0;
return (a.dueDate ?? '').localeCompare(b.dueDate ?? '');
}
});
}
Expand Down Expand Up @@ -103,7 +110,10 @@ export function filterQuizes(quizes: Quiz[], filters: Filters, searchTerm: strin
case 'title':
return a.title.localeCompare(b.title);
default:
return a.dueDate.localeCompare(b.dueDate);
if (a.dueDate === null && b.dueDate !== null) return 1;
if (a.dueDate !== null && b.dueDate === null) return -1;
if (a.dueDate === null && b.dueDate === null) return 0;
return (a.dueDate ?? '').localeCompare(b.dueDate ?? '');
}
});
}
Loading