Skip to content

Commit 3c22c8c

Browse files
authored
Feature/current time cursor (#288)
1 parent f917d03 commit 3c22c8c

File tree

12 files changed

+336
-100
lines changed

12 files changed

+336
-100
lines changed

app/routers/dayview.py

Lines changed: 60 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,15 @@
11
from bisect import bisect_left
22
from datetime import datetime, timedelta
3-
from typing import Iterator, Optional, Tuple, Union
3+
from typing import Dict, Iterator, Optional, Tuple, Union
44

5-
from fastapi import APIRouter, Depends, HTTPException, Request, status
5+
from fastapi import APIRouter, Depends, HTTPException, Request
66

77
from app.database.models import Event, User
88
from app.dependencies import get_db, templates
99
from app.internal import international_days, zodiac
10+
from app.internal.security.dependencies import current_user
11+
12+
# from app.internal.security.schema import CurrentUser
1013
from app.routers.user import get_all_user_events
1114

1215
router = APIRouter()
@@ -26,6 +29,50 @@ class DivAttributes:
2629
CLASS_SIZES = ("title-size-tiny", "title-size-xsmall", "title-size-small")
2730
LENGTH_SIZE_STEP = (30, 45, 90)
2831

32+
def _minutes_position(self, minutes: int) -> Dict[str, int]:
33+
"""
34+
Provides info about the minutes value.
35+
Returns a Dict that contains-
36+
'minutes position': calculates the number of grid bar quarters
37+
that the minutes value covers (from 1 to 4).
38+
'min_deviation': calculates the 'spare' minutes left out
39+
of a grid bar quarter.
40+
(used to indicate the accurate current time)
41+
"""
42+
min_minutes = self.MIN_MINUTES
43+
max_minutes = self.MAX_MINUTES
44+
for i in range(self.GRID_BAR_QUARTER, self.FULL_GRID_BAR + 1):
45+
if min_minutes < minutes <= max_minutes:
46+
minute_deviation = minutes - (i - 1) * self.MAX_MINUTES
47+
return {"min_position": i, "min_deviation": minute_deviation}
48+
min_minutes = max_minutes
49+
max_minutes += self.MAX_MINUTES
50+
51+
def _get_position(self, time: datetime) -> int:
52+
grid_hour_position = time.hour * self.FULL_GRID_BAR
53+
grid_minutes_modifier = self._minutes_position(time.minute)
54+
if grid_minutes_modifier is None:
55+
grid_minutes_modifier = 0
56+
else:
57+
grid_minutes_modifier = grid_minutes_modifier["min_position"]
58+
return grid_hour_position + grid_minutes_modifier + self.BASE_GRID_BAR
59+
60+
61+
class CurrentTimeAttributes(DivAttributes):
62+
def __init__(self, date: datetime) -> None:
63+
current = datetime.now()
64+
self.dayview_date = date.date()
65+
self.is_viewed = self._date_is_today()
66+
self.grid_position = self._get_position(current) - 1
67+
self.sub_grid_position = self._minutes_position(current.minute)
68+
self.sub_grid_position = self.sub_grid_position["min_deviation"]
69+
70+
def _date_is_today(self) -> bool:
71+
today = datetime.today().date()
72+
return today == self.dayview_date
73+
74+
75+
class EventsAttributes(DivAttributes):
2976
def __init__(
3077
self,
3178
event: Event,
@@ -46,23 +93,6 @@ def _check_color(self, color: str) -> str:
4693
return self.DEFAULT_COLOR
4794
return color
4895

49-
def _minutes_position(self, minutes: int) -> Union[int, None]:
50-
min_minutes = self.MIN_MINUTES
51-
max_minutes = self.MAX_MINUTES
52-
for i in range(self.GRID_BAR_QUARTER, self.FULL_GRID_BAR + 1):
53-
if min_minutes < minutes <= max_minutes:
54-
return i
55-
min_minutes = max_minutes
56-
max_minutes += 15
57-
return None
58-
59-
def _get_position(self, time: datetime) -> int:
60-
grid_hour_position = time.hour * self.FULL_GRID_BAR
61-
grid_minutes_modifier = self._minutes_position(time.minute)
62-
if grid_minutes_modifier is None:
63-
grid_minutes_modifier = 0
64-
return grid_hour_position + grid_minutes_modifier + self.BASE_GRID_BAR
65-
6696
def _set_grid_position(self) -> str:
6797
if self.start_multiday:
6898
start = self.FIRST_GRID_BAR
@@ -137,12 +167,12 @@ def get_events_and_attributes(
137167
day: datetime,
138168
session,
139169
user_id: int,
140-
) -> Iterator[Tuple[Event, DivAttributes]]:
170+
) -> Iterator[Tuple[Event, EventsAttributes]]:
141171
events = get_all_user_events(session, user_id)
142172
day_end = day + timedelta(hours=24)
143173
for event in events:
144174
if is_specific_time_event_in_day(event, day, day_end):
145-
yield event, DivAttributes(event, day)
175+
yield event, EventsAttributes(event, day)
146176

147177

148178
def get_all_day_events(
@@ -154,51 +184,46 @@ def get_all_day_events(
154184
day_end = day + timedelta(hours=24)
155185
for event in events:
156186
if is_all_day_event_in_day(event=event, day=day, day_end=day_end):
157-
yield (event)
187+
yield event
158188

159189

160190
@router.get("/day/{date}", include_in_schema=False)
161191
async def dayview(
162192
request: Request,
163193
date: str,
164-
session=Depends(get_db),
165194
view="day",
195+
session=Depends(get_db),
196+
user: User = Depends(current_user),
166197
):
167-
# TODO: add a login session
168-
user = session.query(User).filter_by(username="test_username").first()
169-
if not user:
170-
error_message = "User not found."
171-
raise HTTPException(
172-
status_code=status.HTTP_404_NOT_FOUND,
173-
detail=error_message,
174-
)
175198
try:
176199
day = datetime.strptime(date, "%Y-%m-%d")
177200
except ValueError as err:
178201
raise HTTPException(status_code=404, detail=f"{err}")
179202
zodiac_obj = zodiac.get_zodiac_of_day(session, day)
180-
events_n_attrs = get_events_and_attributes(
203+
events_with_attrs = get_events_and_attributes(
181204
day=day,
182205
session=session,
183-
user_id=user.id,
206+
user_id=user.user_id,
184207
)
185208
all_day_events = get_all_day_events(
186209
day=day,
187210
session=session,
188-
user_id=user.id,
211+
user_id=user.user_id,
189212
)
213+
current_time_with_attrs = CurrentTimeAttributes(date=day)
190214
inter_day = international_days.get_international_day_per_day(session, day)
191215
month = day.strftime("%B").upper()
192216
return templates.TemplateResponse(
193217
"calendar_day_view.html",
194218
{
195219
"request": request,
196-
"events": events_n_attrs,
220+
"events_and_attrs": events_with_attrs,
197221
"all_day_events": all_day_events,
198222
"month": month,
199223
"day": day.day,
200224
"international_day": inter_day,
201225
"zodiac": zodiac_obj,
202226
"view": view,
227+
"current_time": current_time_with_attrs,
203228
},
204229
)

app/routers/event.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,6 @@ async def create_new_event(
138138
availability = data.get("availability", "True") == "True"
139139
location = data["location"]
140140
all_day = data["event_type"] and data["event_type"] == "on"
141-
142141
vc_link = data.get("vc_link")
143142
category_id = data.get("category_id")
144143
privacy = data["privacy"]

app/routers/weekview.py

Lines changed: 33 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,12 @@
88

99
from app.database.models import Event, User
1010
from app.dependencies import TEMPLATES_PATH, get_db
11+
from app.internal.security.dependencies import current_user
1112
from app.routers.dayview import (
12-
DivAttributes,
13+
CurrentTimeAttributes,
14+
EventsAttributes,
1315
dayview,
16+
get_all_day_events,
1417
get_events_and_attributes,
1518
)
1619

@@ -23,12 +26,14 @@
2326
class DayEventsAndAttrs(NamedTuple):
2427
day: datetime
2528
template: Jinja2Templates.TemplateResponse
26-
events_and_attrs: Tuple[Event, DivAttributes]
29+
events_and_attrs: Tuple[Event, EventsAttributes]
30+
current_time_and_attrs: CurrentTimeAttributes
31+
all_day_events: Event
2732

2833

29-
def get_week_dates(firstday: datetime) -> Iterator[datetime]:
34+
def get_week_dates(first_day: datetime) -> Iterator[datetime]:
3035
rest_of_days = [timedelta(days=1) for _ in range(6)]
31-
rest_of_days.insert(0, firstday)
36+
rest_of_days.insert(0, first_day)
3237
return accumulate(rest_of_days)
3338

3439

@@ -43,20 +48,37 @@ async def get_day_events_and_attributes(
4348
date=day.strftime("%Y-%m-%d"),
4449
view="week",
4550
session=session,
51+
user=user,
4652
)
4753
events_and_attrs = get_events_and_attributes(
4854
day=day,
4955
session=session,
50-
user_id=user.id,
56+
user_id=user.user_id,
57+
)
58+
current_time_and_attrs = CurrentTimeAttributes(date=day)
59+
all_day_events = get_all_day_events(
60+
day=day,
61+
session=session,
62+
user_id=user.user_id,
63+
)
64+
return DayEventsAndAttrs(
65+
day,
66+
template,
67+
events_and_attrs,
68+
current_time_and_attrs,
69+
all_day_events,
5170
)
52-
return DayEventsAndAttrs(day, template, events_and_attrs)
5371

5472

55-
@router.get("/week/{firstday}")
56-
async def weekview(request: Request, firstday: str, session=Depends(get_db)):
57-
user = session.query(User).filter_by(username="test_username").first()
58-
firstday = datetime.strptime(firstday, "%Y-%m-%d")
59-
week_days = get_week_dates(firstday)
73+
@router.get("/week/{first_day}")
74+
async def weekview(
75+
request: Request,
76+
first_day: str,
77+
session=Depends(get_db),
78+
user: User = Depends(current_user),
79+
):
80+
first_day = datetime.strptime(first_day, "%Y-%m-%d")
81+
week_days = get_week_dates(first_day)
6082
week = [
6183
await get_day_events_and_attributes(request, day, session, user)
6284
for day in week_days

app/static/dayview.css

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,20 @@ body {
4646
grid-row: 1 / -1;
4747
grid-column: 1 / -1;
4848
display: grid;
49-
grid-template-rows: repeat(100, 1fr);
49+
grid-template-rows: repeat(100, auto);
50+
}
51+
52+
.timegrid {
53+
grid-row: 1 / -1;
54+
grid-column: 1 / -1;
55+
display: grid;
56+
grid-template-rows: repeat(100, auto);
57+
z-index: 43;
58+
}
59+
60+
.sub-timegrid {
61+
display: grid;
62+
grid-template-rows: repeat(15, auto);
5063
}
5164

5265
.hour-block {
@@ -164,3 +177,12 @@ body {
164177
width: 1.2rem;
165178
height: 1.2rem;
166179
}
180+
181+
#current_time_cursor {
182+
border-bottom: 2.5px dotted rgba(255, 0, 0, 0.808);
183+
}
184+
185+
#all-day-events {
186+
background-color: var(--primary);
187+
word-spacing: 0.25em;
188+
}

app/static/weekview.css

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,3 +39,7 @@
3939
margin-left: -2;
4040
overflow: hidden;
4141
}
42+
43+
#all_day_event_in_week {
44+
color: #EF5454;
45+
}

app/templates/calendar_day_view.html

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -26,16 +26,16 @@
2626
<span class="fw-bold text-white date-nums">{{day}} / {{month}}</span>
2727
{% endif %}
2828
</div>
29+
<div id="all-day-events" class="d-flex justify-content-center text-truncate my-0 {{size}} text-light">
30+
{% for event in all_day_events %}
31+
<a href={{ url_for('eventview', event_id=event.id) }} title="See full event">{{ event.title }}&nbsp;&nbsp;&nbsp;</a>
32+
{% endfor %}
33+
</div>
2934
{% if international_day %}
3035
<div class="international-days">
3136
The International days are: "{{ international_day.international_day }}"
3237
</div>
3338
{% endif %}
34-
<div class="all_day_events">
35-
{% for event in all_day_events %}
36-
<p class="text-truncate my-0 {{size}}">{{ event.title }}</p>
37-
{% endfor %}
38-
</div>
3939
<div class="schedule">
4040
<div class="container times bg-primeary position">
4141
{% for hour in range(25)%}
@@ -50,8 +50,16 @@
5050
</div>
5151
{% endfor %}
5252
</div>
53+
<div class="timegrid">
54+
{% if current_time.is_viewed %}
55+
<div class="sub-timegrid" style="grid-row: {{ current_time.grid_position }};">
56+
<div id="current_time_cursor" style="grid-row: {{ current_time.sub_grid_position }};">
57+
</div>
58+
</div>
59+
{% endif %}
60+
</div>
5361
<div class="event-grid">
54-
{% for event, attr in events %}
62+
{% for event, attr in events_and_attrs %}
5563
<div id="event{{event.id}}"
5664
class="d-flex flex-column text-truncate px-2 event"
5765
style="background-color: {{attr.color}}; grid-row: {{attr.grid_position}}; max-hight:1.5rem;">
@@ -92,4 +100,4 @@
92100
crossorigin="anonymous"></script>
93101
<script type="text/javascript"
94102
src="{{ url_for('static', path='/eventdisplay.js') }}"></script>
95-
{% endblock body %}
103+
{% endblock body %}

app/templates/weekview.html

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,15 @@
1212
<body>
1313
<div id="week-view">
1414
<div id="week-schedule" class="d-flex justify-content-between">
15-
{% for day, dayview, events_and_attr in week %}
15+
{% for day, dayview, events_and_attrs, current_time, all_day_events in week %}
1616
<div class="day-weekview m-0 flex-fill border-start" style="width: 100%;">
17-
<div class="day-name sticky-top">{{ day.strftime('%A').upper()[:3] }}</div>
17+
<div class="day-name sticky-top">{{ day.strftime('%a').upper()}}
18+
{% for event in all_day_events %}
19+
<a href={{ url_for('eventview', event_id=event.id) }} title="See full event" class="text-danger">{{ event.title }}</a>
20+
{% endfor %}
21+
</div>
1822
{% set month = day.month %}
1923
{% set day = day.day %}
20-
{% set events = events_and_attr%}
2124
{% include dayview.template %}
2225
</div>
2326
{% endfor %}
@@ -35,4 +38,4 @@
3538
</div>
3639
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0-beta1/dist/js/bootstrap.bundle.min.js" integrity="sha384-ygbV9kiqUc6oa4msXn9868pTtWMgiQaeYH7/t7LECLbyPA2x65Kgf80OJFdroafW" crossorigin="anonymous"></script>
3740
</body>
38-
</html>
41+
</html>

tests/fixtures/client_fixture.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,14 @@
1010
agenda,
1111
audio,
1212
categories,
13+
dayview,
1314
event,
1415
friendview,
1516
google_connect,
1617
meds,
1718
notification,
1819
profile,
20+
weekview,
1921
weight,
2022
)
2123
from app.routers.salary import routes as salary
@@ -138,3 +140,13 @@ def google_connect_test_client():
138140

139141
main.app.dependency_overrides = {}
140142
Base.metadata.drop_all(bind=test_engine)
143+
144+
145+
@pytest.fixture(scope="session")
146+
def dayview_test_client() -> Iterator[TestClient]:
147+
yield from create_test_client(dayview.get_db)
148+
149+
150+
@pytest.fixture(scope="session")
151+
def weekview_test_client() -> Iterator[TestClient]:
152+
yield from create_test_client(weekview.get_db)

0 commit comments

Comments
 (0)