Skip to content

Commit

Permalink
Completely overhaul signups view
Browse files Browse the repository at this point in the history
Fixes https://magfest.atlassian.net/browse/MAGDEV-1198, including:
- Swapping the existing calendar widget for one that doesn't have nested scrolling, improving experience on mobile devices
- Adding a "details" box when you click on an event in the calendar view, allowing you to use the calendar view on mobile without text spilling out everywhere
- Showing which department each shift is in and how many slots it has filled out of its total, and allowing you to highlight empty shifts
- A ton of other improvements, including making sure you never need to refresh the page (this is to prevent your view getting reset -- everything is purely clientside so saving its state is pretty difficult) and allowing you to filter by department
  • Loading branch information
kitsuta committed Nov 5, 2024
1 parent 027ef5f commit a3e6033
Show file tree
Hide file tree
Showing 16 changed files with 748 additions and 382 deletions.
11 changes: 11 additions & 0 deletions uber/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -900,6 +900,17 @@ def CURRENT_ADMIN(self):
except Exception:
return {}

@request_cached_property
@dynamic
def CURRENT_VOLUNTEER(self):
try:
from uber.models import Session
with Session() as session:
attendee = session.logged_in_volunteer()
return attendee.to_dict()
except Exception:
return {}

@request_cached_property
@dynamic
def DEPARTMENTS(self):
Expand Down
11 changes: 3 additions & 8 deletions uber/models/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -961,20 +961,15 @@ def checklist_status(self, slug, department_id):
}

def jobs_for_signups(self, all=False):
fields = [
'name', 'department_id', 'department_name', 'description',
'weight', 'start_time_local', 'end_time_local', 'duration',
'weighted_hours', 'restricted', 'extra15', 'taken',
'visibility', 'is_public', 'is_setup', 'is_teardown']
jobs = self.logged_in_volunteer().possible_and_current
jobs = self.logged_in_volunteer().possible
restricted_minutes = set()
for job in jobs:
if job.required_roles:
restricted_minutes.add(frozenset(job.minutes))
if all:
return [job.to_dict(fields) for job in jobs]
return jobs
return [
job.to_dict(fields)
job
for job in jobs if (job.required_roles or frozenset(job.minutes) not in restricted_minutes)]

def possible_match_list(self):
Expand Down
4 changes: 4 additions & 0 deletions uber/models/commerce.py
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,10 @@ def all_sorted_items_and_txns(self):
@property
def sorted_txns(self):
return sorted([txn for txn in self.receipt_txns], key=lambda x: x.added)

@property
def sorted_items(self):
return sorted([item for item in self.receipt_items], key=lambda x: x.added)

@property
def total_processing_fees(self):
Expand Down
148 changes: 121 additions & 27 deletions uber/site_sections/staffing.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,22 @@
from datetime import datetime, timedelta
from pockets.autolog import log
import ics
from sqlalchemy.orm.exc import NoResultFound

from uber.config import c
from uber.custom_tags import safe_string
from uber.decorators import ajax, ajax_gettable, all_renderable, check_shutdown, csrf_protected, render, public
from uber.errors import HTTPRedirect
from uber.models import Attendee
from uber.utils import check_csrf, create_valid_user_supplied_redirect_url, ensure_csrf_token_exists, localized_now
from uber.models import Attendee, Job
from uber.utils import check_csrf, create_valid_user_supplied_redirect_url, ensure_csrf_token_exists, localized_now, extract_urls


def _convert_urls(desc):
urls = extract_urls(desc)
log.error(urls)
for url in urls:
desc = desc.replace(url, f'<a href="{url}" target="_blank">{url}</a>')
return desc


@all_renderable()
Expand Down Expand Up @@ -208,8 +217,7 @@ def shifts(self, session, view='', start='', all=''):
assigned_dept_ids = set(volunteer.assigned_depts_ids)
has_public_jobs = False
for job in joblist:
job['is_public_to_volunteer'] = job['is_public'] and job['department_id'] not in assigned_dept_ids
if job['is_public_to_volunteer']:
if job.is_public and job.department_id not in assigned_dept_ids:
has_public_jobs = True

has_setup = volunteer.can_work_setup or any(d.is_setup_approval_exempt for d in volunteer.assigned_depts)
Expand All @@ -226,28 +234,111 @@ def shifts(self, session, view='', start='', all=''):
else:
start = datetime.strptime(start, '%Y-%m-%dT%H:%M:%S.%f')

if has_setup and has_teardown:
cal_length = c.CON_TOTAL_DAYS
elif has_setup:
cal_length = con_days + c.SETUP_SHIFT_DAYS
elif has_teardown:
cal_length = con_days + 2 # There's no specific config for # of shift signup days
else:
cal_length = con_days
end = c.TEARDOWN_JOB_END if has_teardown else c.ESCHATON

total_duration = 0
event_dates = []
day = start
while day <= end:
total_duration += 1
if c.EPOCH <= day and day <= c.ESCHATON:
event_dates.append(day.strftime('%Y-%m-%d'))
day += timedelta(days=1)

default_filters = []
for department in volunteer.assigned_depts:
default_filters.append({
'id': department.id,
'title': department.name,
})
other_filters = [
{'id': 'public', 'title': "Public Shifts",},
]

return {
'jobs': joblist,
'has_public_jobs': has_public_jobs,
'has_public_jobs': session.query(Job).filter(Job.is_public == True).count(),
'depts_with_roles': [membership.department.name for membership in volunteer.dept_memberships_with_role],
'assigned_depts_list': [(dept.id, dept.name) for dept in volunteer.assigned_depts],
'name': volunteer.full_name,
'hours': volunteer.weighted_hours,
'assigned_depts_labels': volunteer.assigned_depts_labels,
'default_filters': default_filters,
'all_filters': default_filters + other_filters,
'view': view,
'start': start,
'end': start + timedelta(days=cal_length),
'start': start.date(),
'end': end.date(),
'total_duration': total_duration,
'highlighted_dates': event_dates,
'setup_duration': 0 if not has_setup else (c.EPOCH - c.SETUP_JOB_START).days,
'teardown_duration': 0 if not has_teardown else (c.TEARDOWN_JOB_END - c.ESCHATON).days,
'start_day': c.SHIFTS_START_DAY if has_setup else c.EPOCH,
'show_all': all,
}

@ajax_gettable
def get_available_jobs(self, session, all=False, highlight=False, **params):
joblist = session.jobs_for_signups(all=all)

volunteer = session.logged_in_volunteer()
assigned_dept_ids = set(volunteer.assigned_depts_ids)
event_list = []

for job in joblist:
resource_id = job.department_id
bg_color = "#0d6efd"
if job.is_public and job.department_id not in assigned_dept_ids:
resource_id = "public"
bg_color = "#0dcaf0"
if highlight and len(job.shifts) == 0:
bg_color = "#dc3545"
event_list.append({
'id': job.id,
'resourceIds': [resource_id],
'allDay': False,
'start': job.start_time_local.isoformat(),
'end': job.end_time_local.isoformat(),
'title': f"{job.name}",
'backgroundColor': bg_color,
'extendedProps': {
'department_name': job.department_name,
'desc': _convert_urls(job.description),
'desc_text': job.description,
'weight': job.weight,
'slots': f"{len(job.shifts)}/{job.slots}",
'is_public': job.is_public,
'assigned': False,
}
})
return event_list

@ajax_gettable
def get_assigned_jobs(self, session, **params):
volunteer = session.logged_in_volunteer()
event_list = []

for shift in volunteer.shifts:
job = shift.job
event_list.append({
'id': shift.id,
'resourceIds': [job.department_id],
'allDay': False,
'start': job.start_time_local.isoformat(),
'end': job.end_time_local.isoformat(),
'title': f"{job.name}",
'backgroundColor': '#198754',
'extendedProps': {
'department_name': job.department_name,
'desc': _convert_urls(job.description),
'desc_text': job.description,
'weight': job.weight,
'slots': f"{len(job.shifts)}/{job.slots}",
'is_public': job.is_public,
'assigned': True,
}
})
return event_list


def shifts_ical(self, session, **params):
attendee = session.logged_in_volunteer()
Expand Down Expand Up @@ -277,28 +368,31 @@ def jobs(self, session, all=False):

@check_shutdown
@ajax
def sign_up(self, session, job_id, all=False):
return {
'error': session.assign(session.logged_in_volunteer().id, job_id),
'jobs': session.jobs_for_signups(all=all)
}
def sign_up(self, session, job_id, **params):
message = session.assign(session.logged_in_volunteer().id, job_id)
if message:
return {'success': False, 'message': message}
return {'success': True, 'message': "Signup complete!"}

@check_shutdown
@ajax
def drop(self, session, job_id, all=False):
def drop(self, session, shift_id, all=False):
if c.AFTER_DROP_SHIFTS_DEADLINE:
return {
'error': "You can no longer drop shifts.",
'jobs': session.jobs_for_signups(all=all)
'success': False,
'message': "You can no longer drop shifts."
}
try:
shift = session.shift(job_id=job_id, attendee_id=session.logged_in_volunteer().id)
shift = session.shift(shift_id)
session.delete(shift)
session.commit()
except Exception:
pass
except NoResultFound:
return {
'success': True,
'message': "You've already dropped or have been unassigned from this shift."
}
finally:
return {'jobs': session.jobs_for_signups(all=all)}
return {'success': True, 'message': "Shift dropped."}

@public
def login(self, session, message='', first_name='', last_name='', email='', zip_code='', original_location=None):
Expand Down
2 changes: 1 addition & 1 deletion uber/templates/base.html
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{% import 'macros.html' as macros with context %}
{% set page_ro = false %}
{% set bootstrap5 = 'preregistration' in c.PAGE_PATH or 'landing' in c.PAGE_PATH or 'art_show_applications' in c.PAGE_PATH or 'attractions' in c.PAGE_PATH or 'hotel_lottery' in c.PAGE_PATH or 'marketplace' in c.PAGE_PATH or admin_area %}
{% set bootstrap5 = 'preregistration' in c.PAGE_PATH or 'landing' in c.PAGE_PATH or 'art_show_applications' in c.PAGE_PATH or 'attractions' in c.PAGE_PATH or 'hotel_lottery' in c.PAGE_PATH or 'marketplace' in c.PAGE_PATH or 'staffing' in c.PAGE_PATH or admin_area %}
<!DOCTYPE HTML>
<html lang="en">
<head>
Expand Down
75 changes: 49 additions & 26 deletions uber/templates/signup_base.html
Original file line number Diff line number Diff line change
@@ -1,26 +1,49 @@
{%- import 'macros.html' as macros -%}
<!doctype html>
<html>
<head>
<title>{{ name }}'s shifts</title>
<link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3" crossorigin="anonymous">
{{ "styles/styles.css"|serve_static_content }}
{{ "fullcalendar-5.3.2/lib/main.min.css"|serve_static_content }}
<meta name="viewport"
content="
height = device-height,
width = device-width,
initial-scale = 1.0,
minimum-scale = 1.0,
maximum-scale = 1.0,
user-scalable = no
" />
{% block page_style %}{% endblock %}
</head>
<body>
<div class="container-fluid">
{% block main_content %} {% endblock %}
</div>
</body>
{% block page_script %} {% endblock %}
</html>
{% extends "preregistration/preregbase.html" %}

{% block head_styles %}
{{ "deps/combined.min.css"|serve_static_content }}
<link rel="stylesheet" href="../static_views/styles/main.css" />
{{ "deps/bootstrap5/bootstrap.min.css"|serve_static_content }}
{{ "deps/bootstrap5/font-awesome.min.css"|serve_static_content }}
{{ "deps/bootstrap5/datatables.min.css"|serve_static_content }}

<!-- additional styles -->
{% block additional_styles %}
{% block page_styles %}{% endblock %}
{% endblock %}
{% endblock %}

{% block masthead %}{% endblock %}

{% block backlink %}
<nav class="navbar navbar-expand-lg navbar-light bg-light">
<div class="container-fluid">
<a class="navbar-brand" href="#">
<img src="../static/images/favicon.png" alt="{{ c.EVENT_NAME }}" width="24" class="d-inline-block align-text-top">
{{ c.EVENT_NAME }} Staffing
</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<ul class="navbar-nav me-auto mb-2 mb-lg-0" id="main-menu">
<li class="nav-item"><a class="nav-link{% if c.PAGE_PATH == '/staffing/index' %} active" aria-current="page"{% else %}"{% endif %} href="../staffing/index">Volunteer Checklist</a></li>
{% if c.AFTER_SHIFTS_CREATED and c.CURRENT_VOLUNTEER.shift_prereqs_complete %}
<li class="nav-item"><a class="nav-link{% if c.PAGE_PATH == '/staffing/shifts' %} active" aria-current="page"{% else %}"{% endif %} href="../staffing/shifts">Shifts</a></li>
<li class="nav-item"><a class="nav-link" href="../staffing/printable" target="_blank">Printable Shift Schedule</a></li>
{% endif %}
</ul>
<ul class="navbar-nav d-flex">
<li class="nav-item"><a href="{{ c.VOLUNTEER_PERKS_URL }}" target="_blank" class="nav-link"><i class="fa fa-question-circle"></i> Volunteering Perks</a></li>
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" href="#" id="account-dropdown" role="button" data-bs-toggle="dropdown" aria-expanded="false">
<span>Logged in as: {{ c.CURRENT_VOLUNTEER.first_name }} {{ c.CURRENT_VOLUNTEER.last_name }}</span> <i title="Account Settings" class="fa fa-cog"></i>
</a>
<ul class="dropdown-menu" aria-labelledby="account-dropdown">
<li><a class="dropdown-item" href="../preregistration/confirm?id={{ c.CURRENT_VOLUNTEER.id }}">View My Badge</a></li>
<li><a class="dropdown-item" href="../staffing/login">Logout</a></li>
</ul>
</li>
</ul>
</div>
</nav>
{% endblock %}
4 changes: 2 additions & 2 deletions uber/templates/staffing/credits.html
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{% extends "base.html" %}{% set admin_area=True %}
{% extends 'signup_base.html' %}
{% block title %}Volunteer Agreement{% endblock %}
{% block backlink %}{% endblock %}

{% block content %}

<h1>Name in Credits</h1>
Expand Down
4 changes: 2 additions & 2 deletions uber/templates/staffing/emergency_procedures.html
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{% extends "base.html" %}{% set admin_area=True %}
{% extends 'signup_base.html' %}
{% block title %}Volunteer Agreement{% endblock %}
{% block backlink %}{% endblock %}

{% block content %}

<h1>Emergency Procedures</h1>
Expand Down
4 changes: 2 additions & 2 deletions uber/templates/staffing/food_restrictions.html
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{% extends "base.html" %}{% set admin_area=True %}
{% extends 'signup_base.html' %}
{% block title %}Dietary Restrictions{% endblock %}
{% block backlink %}{% endblock %}

{% block content %}

<h2> Dietary Restrictions </h2>
Expand Down
4 changes: 2 additions & 2 deletions uber/templates/staffing/hotel.html
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{% extends "base.html" %}{% set admin_area=True %}
{% extends 'signup_base.html' %}
{% block title %}Hotel Requests{% endblock %}
{% block backlink %}{% endblock %}

{% block content %}

<h2> Hotel Room Space </h2>
Expand Down
4 changes: 2 additions & 2 deletions uber/templates/staffing/index.html
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{% extends "base.html" %}{% set admin_area=True %}
{% extends 'signup_base.html' %}
{% block title %}Volunteer Checklist{% endblock %}
{% block backlink %}{% endblock %}

{% block content %}

<h2> Volunteer Checklist for {{ attendee.full_name }} </h2>
Expand Down
4 changes: 2 additions & 2 deletions uber/templates/staffing/onsite_jobs.html
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{% extends "base.html" %}{% set admin_area=True %}
{% extends 'signup_base.html' %}
{% block title %}On-Site Shifts{% endblock %}
{% block backlink %}{% endblock %}

{% block content %}

<h2>All Available Shifts</h2>
Expand Down
Loading

0 comments on commit a3e6033

Please sign in to comment.