chore: merge in finished CalendarGrid

This commit is contained in:
doprz
2024-03-06 15:07:45 -06:00
parent c6a48dd3f6
commit 91d930ee92
7 changed files with 270 additions and 102 deletions

View File

@@ -1,8 +1,8 @@
import { Meta, StoryObj } from '@storybook/react';
import CalendarGrid from 'src/views/components/calendar/CalendarGrid/CalendarGrid';
import { getCourseColors } from 'src/shared/util/colors';
import { CalendarGridCourse } from 'src/views/hooks/useFlattenedCourseSchedule';
import { Status } from 'src/shared/types/Course';
import CalendarGrid from '@views/components/common/CalendarGrid/CalendarGrid';
import { getCourseColors } from '@shared/util/colors';
import { CalendarGridCourse } from '@views/hooks/useFlattenedCourseSchedule';
import { Status } from '@shared/types/Course';
const meta = {
title: 'Components/Common/Calendar',
@@ -22,9 +22,22 @@ export default meta;
const testData: CalendarGridCourse[] = [
{
calendarGridPoint: {
dayIndex: 0,
startIndex: 1,
endIndex: 2,
dayIndex: 4,
startIndex: 10,
endIndex: 11,
},
componentProps: {
courseDeptAndInstr: 'Course 1',
timeAndLocation: '9:00 AM - 10:00 AM, Room 101',
status: Status.OPEN,
colors: getCourseColors('emerald', 500),
},
},
{
calendarGridPoint: {
dayIndex: 2,
startIndex: 5,
endIndex: 6,
},
componentProps: {
courseDeptAndInstr: 'Course 1',
@@ -36,8 +49,8 @@ const testData: CalendarGridCourse[] = [
{
calendarGridPoint: {
dayIndex: 1,
startIndex: 2,
endIndex: 3,
startIndex: 10,
endIndex: 12,
},
componentProps: {
courseDeptAndInstr: 'Course 2',
@@ -46,7 +59,58 @@ const testData: CalendarGridCourse[] = [
colors: getCourseColors('emerald', 500),
},
},
// add more data as needed
{
calendarGridPoint: {
dayIndex: 4,
startIndex: 10,
endIndex: 11,
},
componentProps: {
courseDeptAndInstr: 'Course 1',
timeAndLocation: '9:00 AM - 10:00 AM, Room 101',
status: Status.OPEN,
colors: getCourseColors('emerald', 500),
},
},
{
calendarGridPoint: {
dayIndex: 1,
startIndex: 10,
endIndex: 12,
},
componentProps: {
courseDeptAndInstr: 'Course 2',
timeAndLocation: '10:00 AM - 11:00 AM, Room 102',
status: Status.CLOSED,
colors: getCourseColors('emerald', 500),
},
},
{
calendarGridPoint: {
dayIndex: 1,
startIndex: 10,
endIndex: 12,
},
componentProps: {
courseDeptAndInstr: 'Course 3',
timeAndLocation: '10:00 AM - 11:00 AM, Room 102',
status: Status.CLOSED,
colors: getCourseColors('emerald', 500),
},
},
{
calendarGridPoint: {
dayIndex: 1,
startIndex: 10,
endIndex: 12,
},
componentProps: {
courseDeptAndInstr: 'Course 4',
timeAndLocation: '10:00 AM - 11:00 AM, Room 102',
status: Status.CLOSED,
colors: getCourseColors('emerald', 500),
},
},
];
type Story = StoryObj<typeof meta>;

View File

@@ -36,20 +36,11 @@ const CalendarCourseCell: React.FC<CalendarCourseCellProps> = ({
return (
<div
className={clsx('w-full flex justify-center rounded p-2', fontColor, className)}
className={clsx('h-full w-full flex justify-center rounded p-2 overflow-x-hidden', fontColor, className)}
style={{
backgroundColor: colors.primaryColor,
}}
>
<<<<<<< HEAD
<div className='flex flex-1 flex-col gap-1'>
<Text variant='h1-course' className='leading-[75%]!'>
{courseDeptAndInstr}
</Text>
{timeAndLocation && (
<Text variant='h3-course' className='leading-[75%]!'>
{timeAndLocation}
=======
<div className='flex flex-1 flex-col gap-1 overflow-x-hidden'>
<Text
variant='h1-course'
@@ -64,7 +55,6 @@ const CalendarCourseCell: React.FC<CalendarCourseCellProps> = ({
{`${meeting.getTimeString({ separator: '', capitalize: true })}${
meeting.location ? ` ${meeting.location.building}` : ''
}`}
>>>>>>> 73fe14e (fix calendar course cell spacing)
</Text>
)}
</div>

View File

@@ -14,7 +14,9 @@
.calendarGrid {
display: grid;
grid-template-columns: repeat(6, 1fr);
grid-template-rows: repeat(13, 1fr);
grid-template-rows: repeat(26, 1fr);
width: 100%;
height: 100%;
}
.calendarRow {
@@ -26,6 +28,8 @@
flex-direction: column;
gap: 10px;
position: relative; // Ensuring that child elements can be positioned in relation to this.
min-width: 800px;
min-height: 500px;
}
.day {
@@ -53,7 +57,7 @@
justify-content: space-between;
align-items: flex-start;
flex: 1 0 0;
border-radius: var(--border-radius-none, 0px);
border-radius: 0px;
}
.timeBlock {
@@ -125,3 +129,12 @@
width: 1px;
background-color: grey;
}
.dot {
height: 75%; /* 75% of the container's height */
width: 75%; /* 75% of the container's width */
background-color: #000000; /* Color of the dot */
border-radius: 50%; /* Rounds the corners into a circle */
top: 12.5%; /* Centers the dot vertically */
left: 12.5%; /* Centers the dot horizontally */
}

View File

@@ -1,14 +1,15 @@
import React, { useRef } from 'react';
import * as htmlToImage from 'html-to-image';
import React, { useState, useRef, useEffect } from 'react';
// import html2canvas from 'html2canvas';
import { DAY_MAP } from 'src/shared/types/CourseMeeting';
import { CalendarGridCourse } from 'src/views/hooks/useFlattenedCourseSchedule';
import calIcon from 'src/assets/icons/cal.svg';
/* import calIcon from 'src/assets/icons/cal.svg';
import pngIcon from 'src/assets/icons/png.svg';
*/
import CalendarCell from '../CalendarGridCell/CalendarGridCell';
import CalendarCourseCell from '../CalendarCourseCell/CalendarCourseCell';
import styles from './CalendarGrid.module.scss';
const daysOfWeek = Object.keys(DAY_MAP).filter(key => !['S', 'SU'].includes(key));
/* const daysOfWeek = Object.keys(DAY_MAP).filter(key => !['S', 'SU'].includes(key));
const hoursOfDay = Array.from({ length: 14 }, (_, index) => index + 8);
const grid = [];
for (let i = 0; i < 13; i++) {
@@ -23,7 +24,7 @@ for (let i = 0; i < 13; i++) {
);
row.push(Array.from({ length: 5 }, (_, j) => <CalendarCell key={j} />));
grid.push(row);
}
} */
interface Props {
courseCells: CalendarGridCourse[];
@@ -35,9 +36,13 @@ interface Props {
* @param props
*/
function CalendarGrid({ courseCells, saturdayClass }: React.PropsWithChildren<Props>): JSX.Element {
const [grid, setGrid] = useState([]);
const calendarRef = useRef(null); // Create a ref for the calendar grid
const saveAsPNG = () => {
const daysOfWeek = Object.keys(DAY_MAP).filter(key => !['S', 'SU'].includes(key));
const hoursOfDay = Array.from({ length: 14 }, (_, index) => index + 8);
/* const saveAsPNG = () => {
htmlToImage
.toPng(calendarRef.current, {
backgroundColor: 'white',
@@ -68,23 +73,47 @@ function CalendarGrid({ courseCells, saturdayClass }: React.PropsWithChildren<Pr
.catch(error => {
console.error('oops, something went wrong!', error);
});
}; */
useEffect(() => {
const newGrid = [];
for (let i = 0; i < 13; i++) {
const row = [];
let hour = hoursOfDay[i];
let styleProp = {
gridColumn: '1',
gridRow: `${2 * i + 2}`,
};
row.push(
<div key={hour} className={styles.timeBlock} style={styleProp}>
<div className={styles.timeLabelContainer}>
<p>{(hour % 12 === 0 ? 12 : hour % 12) + (hour < 12 ? ' AM' : ' PM')}</p>
</div>
</div>
);
for (let k = 0; k < 5; k++) {
// let shouldRender = false;
styleProp = {
gridColumn: `${k + 2}`,
gridRow: `${2 * i + 2} / ${2 * i + 4}`,
};
/* let shouldRenderChild = courseCells[iterator]?.calendarGridPoint &&
k === courseCells[iterator].calendarGridPoint.dayIndex && i === courseCells[iterator].calendarGridPoint.startIndex;
let childElement = <div className={styles.dot}/>; */
/* let completeGridCell = shouldRenderChild ? <CalendarCell key={k} children={childElement}/>
: <CalendarCell key={k} />; */
row.push(<CalendarCell key={k} styleProp={styleProp} />);
}
newGrid.push(row);
}
setGrid(newGrid);
}, []);
return (
<div className={styles.calendar}>
<div className={styles.dayLabelContainer} />
{/* Displaying the rest of the calendar */}
<div ref={calendarRef} className={styles.timeAndGrid}>
{/* <div className={styles.timeColumn}>
<div className={styles.timeBlock}></div>
{hoursOfDay.map((hour) => (
<div key={hour} className={styles.timeBlock}>
<div className={styles.timeLabelContainer}>
<p>{hour % 12 === 0 ? 12 : hour % 12} {hour < 12 ? 'AM' : 'PM'}</p>
</div>
</div>
))}
</div> */}
<div className={styles.timeAndGrid}>
<div className={styles.calendarGrid}>
{/* Displaying day labels */}
<div className={styles.timeBlock} />
@@ -93,40 +122,103 @@ function CalendarGrid({ courseCells, saturdayClass }: React.PropsWithChildren<Pr
{day}
</div>
))}
{grid.map((row, index) => (
<React.Fragment key={index}>{row}</React.Fragment>
))}
</div>
</div>
{courseCells.map((block: CalendarGridCourse) => (
{grid.map((row, rowIndex) => row)}
{accountForCourseConflicts(courseCells)}
{/* courseCells.map((block: CalendarGridCourse) => (
<div
key={`${block}`}
style={{
gridColumn: `${block.calendarGridPoint.dayIndex}`,
gridRow: `${block.calendarGridPoint.startIndex} / ${block.calendarGridPoint.endIndex}`,
gridColumn: `${block.calendarGridPoint.dayIndex + 1}`,
gridRow: `${block.calendarGridPoint.startIndex + 1} / ${block.calendarGridPoint.endIndex + 1}`,
}}
>
<CalendarCourseCell
courseDeptAndInstr={block.componentProps.courseDeptAndInstr}
timeAndLocation={block.componentProps.timeAndLocation}
status={block.componentProps.status}
colors={block.componentProps.colors}
/>
</div>
))}
<div className={styles.buttonContainer}>
<div className={styles.divider} /> {/* First divider */}
<button className={styles.calendarButton}>
<img src={calIcon} className={styles.buttonIcon} alt='CAL' />
Save as .CAL
</button>
<div className={styles.divider} /> {/* Second divider */}
<button onClick={saveAsPNG} className={styles.calendarButton}>
<img src={pngIcon} className={styles.buttonIcon} alt='PNG' />
Save as .PNG
</button>
)) */}
</div>
</div>
</div>
);
}
export default CalendarGrid;
function accountForCourseConflicts(courseCells: CalendarGridCourse[]): JSX.Element[] {
// Groups by dayIndex to identify overlaps
const days = courseCells.reduce((acc, cell: CalendarGridCourse) => {
const { dayIndex } = cell.calendarGridPoint;
if (!acc[dayIndex]) {
acc[dayIndex] = [];
}
acc[dayIndex].push(cell);
return acc;
}, {});
// Check for overlaps within each day and adjust gridColumnIndex and totalColumns
Object.values(days).forEach((dayCells: CalendarGridCourse[]) => {
// Sort by start time to ensure proper columnIndex assignment
dayCells.sort((a, b) => a.calendarGridPoint.startIndex - b.calendarGridPoint.startIndex);
dayCells.forEach((cell, _, arr) => {
let columnIndex = 1;
cell.totalColumns = 1;
// Check for overlaps and adjust columnIndex as needed
for (let otherCell of arr) {
if (otherCell !== cell) {
const isOverlapping =
otherCell.calendarGridPoint.startIndex < cell.calendarGridPoint.endIndex &&
otherCell.calendarGridPoint.endIndex > cell.calendarGridPoint.startIndex;
if (isOverlapping) {
console.log('Found overlapping element');
// Adjust columnIndex to not overlap with the otherCell
if (otherCell.gridColumnStart && otherCell.gridColumnStart >= columnIndex) {
columnIndex = otherCell.gridColumnStart + 1;
console.log(columnIndex);
}
cell.totalColumns += 1;
}
}
}
cell.gridColumnStart = columnIndex;
cell.gridColumnEnd = columnIndex + 1;
});
});
return courseCells.map(block => (
<div
key={`${block}`}
style={{
gridColumn: `${block.calendarGridPoint.dayIndex + 1}`,
gridRow: `${block.calendarGridPoint.startIndex + 1} / ${block.calendarGridPoint.endIndex + 1}`,
width: `calc(100% / ${block.totalColumns})`,
marginLeft: `calc(100% * ${(block.gridColumnStart - 1) / block.totalColumns})`,
padding: '0px 10px 4px 0px',
}}
>
<CalendarCourseCell
courseDeptAndInstr={block.componentProps.courseDeptAndInstr}
timeAndLocation={block.componentProps.timeAndLocation}
status={block.componentProps.status}
colors={block.componentProps.colors}
/>
</div>
));
}
/* <div className={styles.buttonContainer}>
<div className={styles.divider} />
<button className={styles.calendarButton}>
<img src={calIcon} className={styles.buttonIcon} alt='CAL' />
Save as .CAL
</button>
<div className={styles.divider} />
<button onClick={saveAsPNG} className={styles.calendarButton}>
<img src={pngIcon} className={styles.buttonIcon} alt='PNG' />
Save as .PNG
</button>
</div> */

View File

@@ -1,7 +1,7 @@
.calendarCell {
display: flex;
width: 213.8px;
height: 44.769px;
width: 100%;
height: 100%;
min-width: 45px;
min-height: 40px;
flex-direction: column;
@@ -11,8 +11,8 @@
}
.hourLine {
width: 213.8px;
width: 100%;
height: 1px;
border-radius: var(--border-radius-none, 0px);
border-radius: 0px;
background: rgba(218, 220, 224, 0.25);
}

View File

@@ -1,14 +1,20 @@
import React from 'react';
import styles from './CalendarGridCell.module.scss';
interface Props {
styleProp: any;
}
/**
* Component representing each 1 hour time block of a calendar
* @param props
*/
const CalendarCell: React.FC = props => (
<div className={styles.calendarCell}>
function CalendarCell({ styleProp }: Props): JSX.Element {
return (
<div className={styles.calendarCell} style={styleProp}>
<div className={styles.hourLine} />
</div>
);
}
export default CalendarCell;

View File

@@ -21,6 +21,9 @@ interface CalendarGridPoint {
export interface CalendarGridCourse {
calendarGridPoint: CalendarGridPoint;
componentProps: CalendarCourseCellProps;
gridColumnStart?: number;
gridColumnEnd?: number;
totalColumns?: number;
}
const convertMinutesToIndex = (minutes: number): number => Math.floor(minutes - 420 / 30);