Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
77 commits
Select commit Hold shift + click to select a range
1eafafd
Add design doc (#6)
fossinating Nov 4, 2024
2dea6f1
Sync upstream (#9)
francinew6 Nov 11, 2024
7185910
Add gear icon to coworking page (#10)
TJOKOLI17 Nov 11, 2024
8749914
Implement Add Hours Flow (#20)
fossinating Nov 20, 2024
26db604
Sync upstream (#23)
fossinating Nov 20, 2024
e5242e9
Merge admin-tab-route into stage (#28)
TJOKOLI17 Nov 21, 2024
8ee9509
Merge operating-hours-frontend-edit-delete into stage (#33)
TJOKOLI17 Dec 4, 2024
716bc13
Reformat and update technical specification document (#34)
ellagonzales Dec 4, 2024
115d609
Modify operating hours entity and model to include recurrence id
fossinating Dec 4, 2024
cd9f001
Merge remote-tracking branch 'origin/stage' into operating-hours-recu…
fossinating Dec 4, 2024
2a8e752
Introduce new draft model for operating hours used for editing and cr…
fossinating Dec 4, 2024
2f32b25
Implement recurrence in create service
fossinating Dec 5, 2024
faaad95
Re-implement frontend recurrence options
fossinating Dec 7, 2024
adabed0
Remove old css block with unbalanced braces
fossinating Dec 7, 2024
8b5f4c8
Show recurrence days when user selects weekly recurrence pattern
fossinating Dec 7, 2024
e1573fe
Add recurrence end date to UI
fossinating Dec 7, 2024
1927af6
Remove duplicate hide method
fossinating Dec 7, 2024
68769bf
Implement end-to-end recurrence creation
fossinating Dec 7, 2024
83b8779
Fix repeat until date not being filled in when editing recurring hours
fossinating Dec 7, 2024
5fdf1f4
Implement daily recurrence
fossinating Dec 7, 2024
a03df6c
Leave a TODO reminder
fossinating Dec 7, 2024
85089a4
Prompt user to delete following operating hours
fossinating Dec 7, 2024
236026a
Fix recurring office hours creating unique recurrence entities for ea…
fossinating Dec 7, 2024
f4fe4c4
Implement backend operating hours cascade delete functionality
fossinating Dec 7, 2024
ae4c7f0
Split recurrences if a single operating hours is deleted in the middle
fossinating Dec 7, 2024
1f88417
Update author tags
fossinating Dec 7, 2024
e7f9b8c
Update sprint01-spec.md (#35)
TJOKOLI17 Dec 8, 2024
7e95dd4
Make frontend submit new object rather than an edited original
fossinating Dec 8, 2024
abb6406
Fix start and end from original operatingHours object being modified …
fossinating Dec 8, 2024
97f7c41
Implement update for recurring operating hours
fossinating Dec 8, 2024
ee17350
Fix recurring hours confirmation window not passing cascade to backend
fossinating Dec 8, 2024
d4a5c22
Fix recurring hours modify confirm dialog saying event instead of ope…
fossinating Dec 8, 2024
04338f2
Fix creation skipping a day when updating end_date in recurrence
fossinating Dec 8, 2024
8e5082c
Fix hours not being deleted on update of recurs_on
fossinating Dec 8, 2024
245e189
Add fall 2024 and spring 2025 to term data
fossinating Dec 8, 2024
00e469f
Make current term end date the default end date for recurrences
fossinating Dec 8, 2024
53d29dc
Validate recurrence input
fossinating Dec 8, 2024
d0d3b17
Remove testing console.log
fossinating Dec 8, 2024
8ca1f8e
No longer split recurrences when deleting single hours
fossinating Dec 8, 2024
7a570db
Make cascade parameter optional on service level update and delete
fossinating Dec 8, 2024
685a2ee
Format open hours editor form for mobile view (#36)
francinew6 Dec 8, 2024
52cce6d
Add tests for creation
fossinating Dec 8, 2024
d675da3
Implement Update Tests
fossinating Dec 8, 2024
32e86ed
Write delete tests
fossinating Dec 8, 2024
ac3abbb
Merge remote-tracking branch 'origin/stage' into operating-hours-recu…
fossinating Dec 8, 2024
eaa4d64
Update author tags
fossinating Dec 8, 2024
9fbbc73
Update technical spec
fossinating Dec 8, 2024
b54482d
Update author tags
fossinating Dec 8, 2024
f6aebc3
Fix a test
fossinating Dec 8, 2024
9e6b3d5
Fix removing recurrence on frontend not being possible
fossinating Dec 8, 2024
18ad6af
Fix removing recurrence not deleting recurrence properly
fossinating Dec 8, 2024
abf50fd
Add error handling for overlapping operating hours
fossinating Jan 16, 2025
9e0f40d
Merge remote-tracking branch 'upstream/main' into update-upstream
fossinating Jan 16, 2025
543808e
Merge branch 'main' of https://github.com/unc-csxl/csxl.unc.edu into …
fossinating Mar 19, 2025
c214d82
Remove authorship, I don't think any of my changes are still here
fossinating Mar 19, 2025
bab9727
Fix extra curly bracket
fossinating Mar 19, 2025
c0669ba
Fix reset_demo
fossinating Mar 19, 2025
86da3e6
Resize calendar elements for mobile
fossinating Mar 26, 2025
c3c74c9
Operating Hours Mobile Support + Cleanup (#52)
fossinating Apr 9, 2025
627f5ad
Sync upstream (#54)
fossinating Apr 9, 2025
ed8efcb
Abstract out calendar component for reuse (#55)
fossinating Apr 9, 2025
2771d07
Fix double import of OperatingHoursCalendar
fossinating May 2, 2025
b525615
[wip] reformat technical spec
fossinating May 2, 2025
010da7a
Merge remote-tracking branch 'upstream/main' into update-upstream
fossinating May 2, 2025
bfb1b5f
Merge branch 'update-upstream' into operating-hours
fossinating May 2, 2025
45a1685
(wip) Rewrite operating hours editor technical spec
fossinating May 3, 2025
5a0c3bf
Fix permissions for updating operating hours
fossinating May 3, 2025
77a9a40
Rewrite Operating Hours Editor technical spec
fossinating May 3, 2025
96e5f4c
Update formatting of operating hours editor
fossinating May 3, 2025
55da4ad
Add clear messaging on overlapping operating hours
fossinating May 4, 2025
c1df1e8
Add basic form validation messages
fossinating May 4, 2025
e977f07
feat: operating hours implementation from team a8
ajaygandecha Aug 17, 2025
ca22d56
chore: re-arrange admin page
ajaygandecha Aug 17, 2025
a6d690d
chore: small tweaks to calendar UI
ajaygandecha Aug 17, 2025
9b27f82
chore: small tweaks to buttons on operating hours editor
ajaygandecha Aug 17, 2025
7de109f
chore: propogate above tweaks to dialog
ajaygandecha Aug 17, 2025
91eed27
chore: ensure dialog width is constant
ajaygandecha Aug 17, 2025
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
1 change: 1 addition & 0 deletions .devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
1561,
1562
],

"customizations": {
"vscode": {
"extensions": [
Expand Down
14 changes: 14 additions & 0 deletions backend/api/coworking/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
from fastapi import Request
from fastapi.responses import JSONResponse
from backend.services.coworking.exceptions import OperatingHoursCannotOverlapException


def _operating_hours_overlap_exception(
request: Request, e: OperatingHoursCannotOverlapException
):
return JSONResponse(status_code=400, content={"message": str(e)})


exception_handlers = [
(OperatingHoursCannotOverlapException, _operating_hours_overlap_exception)
]
25 changes: 18 additions & 7 deletions backend/api/coworking/operating_hours.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,14 @@
from datetime import datetime, timedelta
from typing import Sequence
from fastapi import APIRouter, Depends

from backend.models.coworking.operating_hours import OperatingHoursDraft
from ..authentication import registered_user
from ...models import User
from ...models.coworking import OperatingHours, TimeRange
from ...services.coworking import OperatingHoursService

__authors__ = ["Kris Jordan"]
__authors__ = ["Kris Jordan", "Tobenna Okoli", "David Foss"]
__copyright__ = "Copyright 2023"
__license__ = "MIT"

Expand All @@ -35,23 +37,32 @@ def get_operating_hours(

@api.post("", response_model=OperatingHours, tags=["Coworking"])
def new_operating_hours(
operating_hours_range: TimeRange,
operating_hours: OperatingHoursDraft,
subject: User = Depends(registered_user),
operating_hours_svc: OperatingHoursService = Depends(),
):
"""Create new opening hours for the XL."""
return operating_hours_svc.create(subject, operating_hours)


@api.put("", response_model=OperatingHours, tags=["Coworking"])
def update_operating_hours(
operating_hours: OperatingHoursDraft,
cascade: bool = False,
subject: User = Depends(registered_user),
operating_hours_svc: OperatingHoursService = Depends(),
):
"""Create new opening hours for the XL."""
time_range = TimeRange(
start=operating_hours_range.start, end=operating_hours_range.end
)
return operating_hours_svc.create(subject, time_range)
return operating_hours_svc.update(subject, operating_hours, cascade)


@api.delete("/{id}", tags=["Coworking"])
def delete_operating_hours(
id: int,
cascade: bool = False,
subject: User = Depends(registered_user),
operating_hours_svc: OperatingHoursService = Depends(),
):
"""Delete operating hours for the XL."""
operating_hours = operating_hours_svc.get_by_id(id)
return operating_hours_svc.delete(subject, operating_hours)
return operating_hours_svc.delete(subject, operating_hours, cascade)
56 changes: 51 additions & 5 deletions backend/entities/coworking/operating_hours_entity.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,19 @@
"""Entity for Operating Hours.""" ""

from sqlalchemy import Integer, DateTime, Index
from sqlalchemy import ForeignKey, Integer, DateTime, Index
from sqlalchemy.orm import Mapped, mapped_column, relationship

from backend.entities.coworking.operating_hours_recurrence_entity import (
OperatingHoursRecurrenceEntity,
)
from backend.models.coworking.operating_hours import OperatingHoursDraft
from ..entity_base import EntityBase
from ...models.coworking import OperatingHours
from datetime import datetime
from typing import Self

__authors__ = ["Kris Jordan"]
__copyright__ = "Copyright 2023"
__authors__ = ["Kris Jordan", "David Foss"]
__copyright__ = "Copyright 2024"
__license__ = "MIT"


Expand All @@ -24,12 +29,23 @@ class OperatingHoursEntity(EntityBase):
start: Mapped[datetime] = mapped_column(DateTime, index=True)
end: Mapped[datetime] = mapped_column(DateTime, index=True)

recurrence_id: Mapped[int] = mapped_column(
ForeignKey("coworking__operating_hours_recurrence.id"), nullable=True
)
recurrence: Mapped[OperatingHoursRecurrenceEntity] = relationship()

def to_model(self) -> OperatingHours:
"""Converts the entity to a model.

Returns:
OperatingHours: The model representation of the entity."""
return OperatingHours(id=self.id, start=self.start, end=self.end)
return OperatingHours(
id=self.id,
start=self.start,
end=self.end,
recurrence_id=self.recurrence_id if self.recurrence_id else None,
recurrence=self.recurrence.to_model() if self.recurrence else None,
)

@classmethod
def from_model(cls, model: OperatingHours) -> Self:
Expand All @@ -40,4 +56,34 @@ def from_model(cls, model: OperatingHours) -> Self:

Returns:
Self: The entity (not yet persisted)."""
return cls(id=model.id, start=model.start, end=model.end)
return cls(
id=model.id,
start=model.start,
end=model.end,
recurrence_id=model.recurrence_id if model.recurrence_id else None,
recurrence=(
OperatingHoursRecurrenceEntity.from_model(model.recurrence)
if model.recurrence
else None
),
)

@classmethod
def from_draft(cls, model: OperatingHoursDraft) -> Self:
"""Create an OperatingHoursEntity from a OperatingHoursDraft model.

Args:
model (OperatingHoursDraft): The model to create the entity from.

Returns:
Self: The entity (not yet persisted)."""
return cls(
id=model.id,
start=model.start,
end=model.end,
recurrence=(
OperatingHoursRecurrenceEntity.from_model(model.recurrence)
if model.recurrence
else None
),
)
56 changes: 56 additions & 0 deletions backend/entities/coworking/operating_hours_recurrence_entity.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
"""Entity for Operating Hours Recurrence.""" ""

from sqlalchemy import Integer, DateTime, Index
from sqlalchemy.orm import Mapped, mapped_column

from backend.models.coworking.operating_hours import (
OperatingHoursRecurrence,
)
from ..entity_base import EntityBase
from ...models.coworking import OperatingHours
from datetime import datetime
from typing import Self

__authors__ = ["David Foss"]
__copyright__ = "Copyright 2024"
__license__ = "MIT"


class OperatingHoursRecurrenceEntity(EntityBase):
"""Entity for Operating Hours Recurrence."""

__tablename__ = "coworking__operating_hours_recurrence"
__table_args__ = (
Index("coworking__operating_hours_recurrence_idx", "end_date", unique=False),
)

id: Mapped[int] = mapped_column(Integer, primary_key=True)
end_date: Mapped[datetime] = mapped_column(DateTime(timezone=True), index=True)

# recurs_on saves the days the operating hours recurs on as a bitmask stored as an integer
# Starts with 0 being Monday, following python datetime convention
recurs_on: Mapped[int] = mapped_column(Integer)

def to_model(self) -> OperatingHoursRecurrence:
"""Converts the entity to a model.

Returns:
OperatingHoursRecurrence: The model representation of the entity."""
return OperatingHoursRecurrence(
id=self.id, end_date=self.end_date, recurs_on=self.recurs_on
)

@classmethod
def from_model(cls, model: OperatingHoursRecurrence) -> Self:
"""Create an OperatingHoursRecurrenceEntity from a OperatingHoursRecurrence model.

Args:
model (OperatingHoursRecurrence): The model to create the entity from.

Returns:
Self: The entity (not yet persisted)."""
return cls(
id=model.id,
end_date=model.end_date,
recurs_on=model.recurs_on,
)
6 changes: 4 additions & 2 deletions backend/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@
CourseDataScrapingException,
)

from .services.coworking.exceptions import OperatingHoursCannotOverlapException

__authors__ = ["Kris Jordan"]
__copyright__ = "Copyright 2023"
__license__ = "MIT"
Expand Down Expand Up @@ -153,9 +155,9 @@ def recurring_office_hour_event_exception(


# Add feature-specific exception handling middleware
from .api import events
from .api import events, coworking

feature_exception_handlers = [events.exception_handlers]
feature_exception_handlers = [events.exception_handlers, coworking.exception_handlers]

for feature_exception_handler in feature_exception_handlers:
for exception, handler in feature_exception_handler:
Expand Down
7 changes: 7 additions & 0 deletions backend/models/academics/my_courses.py
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,13 @@ class OfficeHourStatisticsOverview(BaseModel):
history: list[OfficeHourTicketOverview]


class OfficeHourStatisticsOverview(BaseModel):
# add more
average_minutes: int
total_tickets_called: int
history: list[OfficeHourTicketOverview]


class OfficeHourQueueOverview(BaseModel):
id: int
type: str
Expand Down
26 changes: 24 additions & 2 deletions backend/models/coworking/operating_hours.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,37 @@
"""Models open hours of the XL."""


from pydantic import BaseModel
from .time_range import TimeRange
from datetime import datetime, date


__authors__ = ["Kris Jordan, Yuvraj Jain"]
__authors__ = ["Kris Jordan", "Yuvraj Jain", "David Foss"]
__copyright__ = "Copyright 2024"
__license__ = "MIT"


class OperatingHoursRecurrence(BaseModel):
id: int | None = None

end_date: datetime

# Bitmask saved as an int
# Monday is 0, Sunday is 6 (following python datetime standard)
recurs_on: int


class OperatingHours(TimeRange, BaseModel):
"""The operating hours of the XL."""

id: int

recurrence_id: int | None = None
recurrence: OperatingHoursRecurrence | None = None


class OperatingHoursDraft(TimeRange, BaseModel):
"""Data for an operating hours draft (for creation and editing)."""

id: int | None = None

recurrence: OperatingHoursRecurrence | None = None
2 changes: 0 additions & 2 deletions backend/models/office_hours/ticket.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,13 +45,11 @@ class OfficeHoursTicket(NewOfficeHoursTicket):
caller_notes: str = ""
caller_id: int | None


class OfficeHoursTicketCsvRow(BaseModel):
"""
Pydantic model to represent a user's ticket in CSV format, which is used for
exporting ticket data to a CSV file in the ticket statistics feature.
"""

student: str
description: str
type: str
Expand Down
9 changes: 7 additions & 2 deletions backend/script/reset_demo.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@
import subprocess
from sqlalchemy import text
from sqlalchemy.orm import Session

from backend.services.coworking.operating_hours import OperatingHoursService
from backend.services.permission import PermissionService
from ..database import engine
from ..env import getenv
from .. import entities
Expand All @@ -28,7 +31,7 @@
from ..test.services.academics.hiring import hiring_data
from ..test.services.articles import article_data

__authors__ = ["Kris Jordan", "Ajay Gandecha"]
__authors__ = ["Kris Jordan", "Ajay Gandecha", "David Foss"]
__copyright__ = "Copyright 2023"
__license__ = "MIT"

Expand All @@ -55,7 +58,9 @@
permission_data.insert_fake_data(session)
organization_demo_data.insert_fake_data(session)
event_demo_data.insert_fake_data(session)
operating_hours_data.insert_fake_data(session, time)
operating_hours_data.insert_fake_data(
session, time, OperatingHoursService(session, PermissionService(session))
)
seat_data.insert_fake_data(session)
room_data.insert_fake_data(session)
reservation_data.insert_fake_data(session, time)
Expand Down
Loading