Skip to content

Commit

Permalink
Merge pull request #316 from pennlabs/revert-315-revert-313-feature/c…
Browse files Browse the repository at this point in the history
…alender-frontend

Revert "Revert "Calendar frontend""
  • Loading branch information
benjmnxu authored Aug 25, 2024
2 parents af0bf40 + d349c9d commit 7a18fc2
Show file tree
Hide file tree
Showing 31 changed files with 4,366 additions and 1,810 deletions.
5 changes: 3 additions & 2 deletions backend/Pipfile
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ tblib = "*"
[packages]
dj-database-url = "*"
djangorestframework = "*"
psycopg2 = "*"
psycopg2 = "==2.9.6"
uvloop = "==0.17.0"
sentry-sdk = "*"
django = "==3.1.7"
django-cors-headers = "*"
Expand All @@ -40,7 +41,7 @@ channels = "<3"
channels-redis = "*"
uvicorn = {extras = ["standard"],version = "*"}
gunicorn = "*"
django-scheduler = "*"
django-schedules-ohq = "*"
typing-extensions = "*"
drf-excel = "*"

Expand Down
2,388 changes: 1,293 additions & 1,095 deletions backend/Pipfile.lock

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions backend/ohq/permissions.py
Original file line number Diff line number Diff line change
Expand Up @@ -453,6 +453,7 @@ def has_permission(self, request, view):

course = Course.objects.get(pk=course_pk)
membership = Membership.objects.filter(course=course, user=request.user).first()

if membership is None:
return False
return membership.is_ta
Expand Down
33 changes: 25 additions & 8 deletions backend/ohq/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -481,7 +481,7 @@ class RuleSerializer(serializers.ModelSerializer):

class Meta:
model = Rule
fields = ("frequency",)
fields = ("frequency", "params")


class EventSerializer(serializers.ModelSerializer):
Expand All @@ -491,7 +491,7 @@ class EventSerializer(serializers.ModelSerializer):
All times are converted to UTC+0
"""

rule = RuleSerializer(required=False)
rule = RuleSerializer(allow_null=True, required=False)
course_id = serializers.IntegerField(required=False)

class Meta:
Expand All @@ -501,6 +501,7 @@ class Meta:
"start",
"end",
"title",
"location",
"description",
"rule",
"end_recurring_period",
Expand All @@ -525,27 +526,43 @@ def update(self, instance, validated_data):
)
or (
"rule" in validated_data
and (rule is None or rule.frequency != validated_data["rule"]["frequency"])
and (
rule is None
or validated_data["rule"] is None
or rule.frequency != validated_data["rule"]["frequency"]
or rule.params != validated_data["rule"]["params"]
)
)
):
if "rule" in validated_data:
rule, _ = Rule.objects.get_or_create(frequency=validated_data["rule"]["frequency"])
if validated_data["rule"] is None:
rule = None
else:
rule, _ = Rule.objects.get_or_create(
frequency=validated_data["rule"]["frequency"],
params=validated_data["rule"]["params"],
)
validated_data.pop("rule")
Occurrence.objects.filter(event=instance).delete()

if "rule" in validated_data:
validated_data.pop("rule")
# can never change course_id, client should create a new event instead
validated_data.pop("course_id")

super().update(instance, validated_data)

instance.rule = rule
instance.save()

Occurrence.objects.filter(event=instance).delete()

return instance

def create(self, validated_data):
course = Course.objects.get(pk=validated_data["course_id"])
rule = None
if "rule" in validated_data:
rule, _ = Rule.objects.get_or_create(frequency=validated_data["rule"]["frequency"])
if "rule" in validated_data and validated_data["rule"] is not None:
rule, _ = Rule.objects.get_or_create(frequency=validated_data["rule"]["frequency"], params = validated_data["rule"]["params"])
validated_data.pop("rule")

validated_data.pop("course_id")
Expand Down Expand Up @@ -573,4 +590,4 @@ class OccurrenceSerializer(serializers.ModelSerializer):

class Meta:
model = Occurrence
fields = ("id", "title", "description", "start", "end", "cancelled", "event")
fields = ("id", "title", "description", "location", "start", "end", "cancelled", "event")
1 change: 1 addition & 0 deletions backend/ohq/views.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from http.client import HTTPResponse
import math
import re
from datetime import datetime, timedelta
Expand Down
134 changes: 134 additions & 0 deletions frontend/components/Calendar/CalendarCommon.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
import { Icon, Modal, SemanticICONS } from "semantic-ui-react";
import React from "react";
import Link from "next/link";
import { Occurrence, UserMembership } from "../../types";
import { dayNames, paramsToDays } from "./calendarUtils";

const IconTextBlock = (props: {
iconName: SemanticICONS;
children: React.JSX.Element;
}) => {
const { iconName, children } = props;

return (
<div
style={{
display: "flex",
flexDirection: "row",
alignItems: "center",
}}
>
<Icon
size="large"
name={iconName}
style={{ marginRight: "10px" }}
/>
{children}
</div>
);
};

export const EventInfoModal = (props: {
occurrence: Occurrence | null;
membership: UserMembership | undefined;
setOccurrence: (occurrence: Occurrence | null) => void;
}) => {
const { occurrence, membership, setOccurrence } = props;

return (
<Modal
size="tiny"
open={occurrence !== null}
onClose={() => setOccurrence(null)}
>
<Modal.Header>
{`${membership?.course.department} ${membership?.course.courseCode}${occurrence?.title}`}
<button
type="button"
style={{
float: "right",
cursor: "pointer",
background: "none",
border: "none",
}}
onClick={() => setOccurrence(null)}
>
<i className="close icon" />
</button>
</Modal.Header>
<Modal.Content>
<Modal.Description>
<IconTextBlock iconName="clock outline">
<span>
{occurrence?.start.toLocaleDateString("en-US", {
weekday: "long",
month: "long",
day: "numeric",
hour: "numeric",
minute: "numeric",
})}{" "}
-{" "}
{occurrence?.start.toDateString() ===
occurrence?.end.toDateString()
? occurrence?.end.toLocaleTimeString("en-US", {
hour: "numeric",
minute: "numeric",
})
: occurrence?.end.toLocaleDateString("en-US", {
weekday: "long",
month: "long",
day: "numeric",
hour: "numeric",
minute: "numeric",
})}
{occurrence?.event.rule && (
<>
<br />
Weekly on{" "}
{paramsToDays(
occurrence.event.rule.params,
0
)
.map((dayNum) => dayNames[dayNum])
.join(", ")}
</>
)}
</span>
</IconTextBlock>
{occurrence?.description && (
<>
<br />
<IconTextBlock iconName="list">
<span style={{ whiteSpace: "pre-wrap" }}>
{occurrence.description}
</span>
</IconTextBlock>
</>
)}
{occurrence?.location && (
<>
<br />
<IconTextBlock iconName="map marker alternate">
<span style={{ whiteSpace: "pre-wrap" }}>
{occurrence.location}
</span>
</IconTextBlock>
</>
)}
<>
<br />
<IconTextBlock iconName="linkify">
<Link
href="/courses/[course]"
as={`/courses/${occurrence?.event.course_id}`}
legacyBehavior
>
Go to queue
</Link>
</IconTextBlock>
</>
</Modal.Description>
</Modal.Content>
</Modal>
);
};
74 changes: 74 additions & 0 deletions frontend/components/Calendar/DashboardCalendar/EventCard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import { Grid, Header, Segment, SemanticCOLORS } from "semantic-ui-react";
import { useState } from "react";
import { Course, Occurrence } from "../../../types";

interface EventCardProps {
occurrence: Occurrence;
course: Course;
color: SemanticCOLORS;
onClick: () => void;
}

const EventCard = (props: EventCardProps) => {
const { occurrence, course, color, onClick } = props;

const startDate = new Date(occurrence.start);
const endDate = new Date(occurrence.end);

const [hover, setHover] = useState(false);

const formatDate = (date: Date) =>
date.toLocaleString("en-US", {
hour: "numeric",
minute: "numeric",
hour12: true,
});

return (
<Segment
color={color as SemanticCOLORS}
style={{
cursor: "pointer",
title: occurrence.description,
}}
raised={hover}
onMouseEnter={() => setHover(true)}
onMouseLeave={() => setHover(false)}
onClick={onClick}
>
{/* TODO: get rid of hardcoded width */}
<Grid style={{ width: "240px" }}>
<Grid.Column width={10}>
<Header
as="h4"
style={{
whiteSpace: "nowrap",
textOverflow: "ellipsis",
overflow: "hidden",
}}
>
{`${course.department} ${course.courseCode}`}
<Header.Subheader
style={{
whiteSpace: "nowrap",
textOverflow: "ellipsis",
overflow: "hidden",
}}
>
{occurrence.title}
</Header.Subheader>
</Header>
</Grid.Column>
<Grid.Column width={6} textAlign="right">
<Header as="h5">
{formatDate(startDate)}
<br />
{formatDate(endDate)}
</Header>
</Grid.Column>
</Grid>
</Segment>
);
};

export default EventCard;
Loading

0 comments on commit 7a18fc2

Please sign in to comment.