diff --git a/.gitignore b/.gitignore index 2192df5edab..16769fb6162 100644 --- a/.gitignore +++ b/.gitignore @@ -57,3 +57,4 @@ package-lock.json # Virtual environments venv/ .venv/ + diff --git a/docs/source/developing/usernames.md b/docs/source/developing/usernames.md index bd26ea46469..19f096a7d6b 100644 --- a/docs/source/developing/usernames.md +++ b/docs/source/developing/usernames.md @@ -54,4 +54,6 @@ There are more usernames generated by default than the ones in this table, but t | 2028student1 | student | 2028 | FALSE | | 2028student | student | 2028 | FALSE | -The usernames in the table above depend on when they are generated - if the current school year has started (in August), then 1 year will be appended to the current year for graduation year calculation. \ No newline at end of file + +The usernames in the table above depend on when they are generated - if the current school year has started (in August), then 1 year will be appended to the current year for graduation year calculation. + diff --git a/intranet/apps/dashboard/views.py b/intranet/apps/dashboard/views.py index 70c4806a0eb..f9b4d72f3eb 100644 --- a/intranet/apps/dashboard/views.py +++ b/intranet/apps/dashboard/views.py @@ -94,6 +94,16 @@ def gen_schedule(user, num_blocks: int = 6, surrounding_blocks: Iterable[EighthB # don't duplicate this info; already caught current_signup = current_signup.replace(" (Cancelled)", "") + # check if attendance open, if so, will display attendance button + attendance_open = False + if current_sched_act: + from ..eighth.views.attendance import check_attendance_open # noqa: PLC0415 + + attendance_open = check_attendance_open(current_sched_act) is None + sch_act_id = None + if attendance_open: + sch_act_id = current_sched_act.id + info = { "id": b.id, "block": b, @@ -108,6 +118,8 @@ def gen_schedule(user, num_blocks: int = 6, surrounding_blocks: Iterable[EighthB "signup_time": b.signup_time, "signup_time_future": b.signup_time_future(), "rooms": rooms, + "attendance_open": attendance_open, + "sch_act_id": sch_act_id, } schedule.append(info) diff --git a/intranet/apps/eighth/migrations/0073_eighth_attendance_code.py b/intranet/apps/eighth/migrations/0073_eighth_attendance_code.py new file mode 100644 index 00000000000..1f0b766bbd6 --- /dev/null +++ b/intranet/apps/eighth/migrations/0073_eighth_attendance_code.py @@ -0,0 +1,57 @@ +# Generated by Django 3.2.25 on 2025-04-29 15:28 + +from django.db import migrations, models +import intranet.apps.eighth.models + +def generate_attendance_codes(apps, schema_editor): + EighthScheduledActivity = apps.get_model('eighth', 'EighthScheduledActivity') + HistoricalEighthScheduledActivity = apps.get_model('eighth', 'HistoricalEighthScheduledActivity') + + for activity in EighthScheduledActivity.objects.all(): + activity.attendance_code = intranet.apps.eighth.models.random_code() + activity.save(update_fields=['attendance_code']) + + for historical_activity in HistoricalEighthScheduledActivity.objects.all(): + historical_activity.attendance_code = intranet.apps.eighth.models.random_code() + historical_activity.save(update_fields=['attendance_code']) + + +class Migration(migrations.Migration): + + dependencies = [ + ('eighth', '0072_alter_eighthscheduledactivity_waitlist'), + ] + + operations = [ + migrations.AddField( + model_name='eighthscheduledactivity', + name='attendance_code', + field=models.CharField(default=intranet.apps.eighth.models.random_code, max_length=6), + ), + migrations.AddField( + model_name='historicaleighthscheduledactivity', + name='attendance_code', + field=models.CharField(default=intranet.apps.eighth.models.random_code, max_length=6), + ), + migrations.RunPython(generate_attendance_codes, reverse_code=migrations.RunPython.noop), + migrations.AddField( + model_name='eighthscheduledactivity', + name='code_mode', + field=models.IntegerField(choices=[(0, 'Auto'), (1, 'Open'), (2, 'Closed')], default=0), + ), + migrations.AddField( + model_name='historicaleighthscheduledactivity', + name='code_mode', + field=models.IntegerField(choices=[(0, 'Auto'), (1, 'Open'), (2, 'Closed')], default=0), + ), + migrations.AddField( + model_name='eighthsignup', + name='attendance_marked', + field=models.BooleanField(blank=True, default=False), + ), + migrations.AddField( + model_name='historicaleighthsignup', + name='attendance_marked', + field=models.BooleanField(blank=True, default=False), + ), + ] diff --git a/intranet/apps/eighth/models.py b/intranet/apps/eighth/models.py index dbec7b3a1ec..427ff494b4d 100644 --- a/intranet/apps/eighth/models.py +++ b/intranet/apps/eighth/models.py @@ -1,6 +1,7 @@ # pylint: disable=too-many-lines; Allow more than 1000 lines import datetime import logging +import secrets import string from collections.abc import Collection, Iterable, Sequence from typing import Optional @@ -794,6 +795,10 @@ def for_sponsor(self, sponsor: EighthSponsor, include_cancelled: bool = False) - return sched_acts +def random_code(): + return "".join(secrets.choice(string.ascii_uppercase + string.digits) for _ in range(6)) + + class EighthScheduledActivity(AbstractBaseEighthModel): r"""Represents the relationship between an activity and a block in which it has been scheduled. Attributes: @@ -822,6 +827,14 @@ class EighthScheduledActivity(AbstractBaseEighthModel): attendance_taken Whether the :class:`EighthSponsor` for the scheduled :class:`EighthActivity` has taken attendance yet + attendance_code + Random 6-character code of digits and uppercase letters to check attendance + Distinct for each 8th activity-block + code_mode + Whether the activity is automatically enabling/disabling the attendance code based on the daily schedule + Is an integer: + 0 = Automatic; 1 = Open; 2 = Closed + default = 0 special Whether this scheduled instance of the activity is special. If not set, falls back on the EighthActivity's special setting. @@ -842,6 +855,14 @@ class EighthScheduledActivity(AbstractBaseEighthModel): blank=True, ) + attendance_code = models.CharField(max_length=6, default=random_code) + mode_choices = [ + (0, "Auto"), + (1, "Open"), + (2, "Closed"), + ] + code_mode = models.IntegerField(choices=mode_choices, default=0) + admin_comments = models.CharField(max_length=1000, blank=True) title = models.CharField(max_length=1000, blank=True) comments = models.CharField(max_length=1000, blank=True) @@ -1195,6 +1216,10 @@ def set_sticky_students(self, users: "Sequence[AbstractBaseUser]") -> None: bcc=True, ) + def set_code_mode(self, mode): + self.code_mode = mode + self.save(update_fields=["code_mode"]) + @transaction.atomic # This MUST be run in a transaction. Do NOT remove this decorator. def add_user( self, @@ -1703,6 +1728,8 @@ class EighthSignup(AbstractBaseEighthModel): Whether the pass was accepted was_absent Whether the student was absent. + attendance_marked + Whether the student has been marked / filled out attendance. absence_acknowledged Whether the student has dismissed the absence notification. absence_emailed @@ -1725,6 +1752,7 @@ class EighthSignup(AbstractBaseEighthModel): pass_accepted = models.BooleanField(default=False, blank=True) was_absent = models.BooleanField(default=False, blank=True) + attendance_marked = models.BooleanField(default=False, blank=True) absence_acknowledged = models.BooleanField(default=False, blank=True) absence_emailed = models.BooleanField(default=False, blank=True) @@ -1814,7 +1842,8 @@ def accept_pass(self): """Accepts an eighth period pass for the EighthSignup object.""" self.was_absent = False self.pass_accepted = True - self.save(update_fields=["was_absent", "pass_accepted"]) + self.attendance_marked = True + self.save(update_fields=["was_absent", "pass_accepted", "attendance_marked"]) def reject_pass(self): """Rejects an eighth period pass for the EighthSignup object.""" diff --git a/intranet/apps/eighth/serializers.py b/intranet/apps/eighth/serializers.py index 6f187581b57..23795a5100a 100644 --- a/intranet/apps/eighth/serializers.py +++ b/intranet/apps/eighth/serializers.py @@ -10,6 +10,7 @@ from rest_framework.reverse import reverse from .models import EighthActivity, EighthBlock, EighthScheduledActivity, EighthSignup, EighthSponsor +from .views.attendance import check_attendance_open logger = logging.getLogger(__name__) @@ -139,6 +140,8 @@ def process_scheduled_activity( "name_with_flags_for_user": name_with_flags_for_user, "description": activity.description, "cancelled": scheduled_activity.cancelled, + "attendance_open": check_attendance_open(scheduled_activity) is None, + "attendance_url": reverse("student_attendance", args=[scheduled_activity.id]), "favorited": activity.id in favorited_activities, "subscribed_to": activity.id in subscribed_activities, "subscriptions_enabled": subscriptions_enabled, diff --git a/intranet/apps/eighth/urls.py b/intranet/apps/eighth/urls.py index d6b22138c81..a22df1a97ec 100644 --- a/intranet/apps/eighth/urls.py +++ b/intranet/apps/eighth/urls.py @@ -20,6 +20,7 @@ path("/absences", attendance.eighth_absences_view, name="eighth_absences"), path("/absences/", attendance.eighth_absences_view, name="eighth_absences"), path("/glance", signup.eighth_location, name="eighth_location"), + re_path(r"^/student_attendance/(?P\d+)(?:/(?P[A-Za-z0-9]+))?$", attendance.student_attendance_view, name="student_attendance"), # Teachers path("/attendance", attendance.teacher_choose_scheduled_activity_view, name="eighth_attendance_choose_scheduled_activity"), path("/attendance/", attendance.take_attendance_view, name="eighth_take_attendance"), diff --git a/intranet/apps/eighth/views/attendance.py b/intranet/apps/eighth/views/attendance.py index 7a9fe614394..f88841d7673 100644 --- a/intranet/apps/eighth/views/attendance.py +++ b/intranet/apps/eighth/views/attendance.py @@ -1,6 +1,7 @@ import csv import io import logging +from datetime import time as tm from html import escape from cacheops import invalidate_obj @@ -22,9 +23,11 @@ from reportlab.lib.units import inch from reportlab.platypus import PageBreak, Paragraph, SimpleDocTemplate, Spacer, Table, TableStyle +from ....settings.__init__ import ATTENDANCE_CODE_BUFFER from ....utils.date import get_date_range_this_year from ...auth.decorators import attendance_taker_required, deny_restricted, eighth_admin_required from ...dashboard.views import gen_sponsor_schedule +from ...schedule.models import Block, Day, shift_time from ...schedule.views import decode_date from ..forms.admin.activities import ActivitySelectionForm from ..forms.admin.blocks import BlockSelectionForm @@ -287,6 +290,11 @@ def take_attendance_view(request, scheduled_activity_id): scheduled_activity.save() invalidate_obj(scheduled_activity) + for signup in EighthSignup.objects.filter(scheduled_activity=scheduled_activity): + signup.attendance_marked = False + signup.save() + invalidate_obj(signup) + messages.success(request, f"Attendance bit cleared for {scheduled_activity}") redirect_url = reverse(url_name, args=[scheduled_activity.id]) @@ -304,12 +312,19 @@ def take_attendance_view(request, scheduled_activity_id): status=403, ) + att_code_mode = request.POST.get("att_code_mode") + if att_code_mode is not None and int(att_code_mode) != scheduled_activity.code_mode: + scheduled_activity.set_code_mode(int(att_code_mode)) + redirect_url = reverse(url_name, args=[scheduled_activity.id]) + return redirect(redirect_url) + if not scheduled_activity.block.locked and request.user.is_eighth_admin: messages.success(request, "Note: Taking attendance on an unlocked block.") - present_user_ids = list(request.POST.keys()) + present_user_ids = list(request.POST.keys()) # list of member.ids checked off if request.FILES.get("attendance"): + # csv attendance try: csv_file = request.FILES["attendance"].read().decode("utf-8") data = csv.DictReader(io.StringIO(csv_file)) @@ -334,6 +349,9 @@ def take_attendance_view(request, scheduled_activity_id): except (csv.Error, ValueError, KeyError, IndexError): messages.error(request, "Could not interpret file. Did you upload a Google Meet attendance report without modification?") + if "att_code_mode" in present_user_ids: + present_user_ids.remove("att_code_mode") + csrf = "csrfmiddlewaretoken" if csrf in present_user_ids: present_user_ids.remove(csrf) @@ -349,6 +367,7 @@ def take_attendance_view(request, scheduled_activity_id): present_signups = EighthSignup.objects.filter(scheduled_activity=scheduled_activity, user__in=present_user_ids) present_signups.update(was_absent=False) + present_signups.update(attendance_marked=True) for s in present_signups: invalidate_obj(s) @@ -406,6 +425,21 @@ def take_attendance_view(request, scheduled_activity_id): members.sort(key=lambda m: m["name"]) + auto_time_string = "" + try: + day_block = ( + Day.objects.select_related("day_type") + .get(date=scheduled_activity.block.date) + .day_type.blocks.get(name="8" + scheduled_activity.block.block_letter) + ) + start_time = shift_time(tm(hour=day_block.start.hour, minute=day_block.start.minute), -ATTENDANCE_CODE_BUFFER) + end_time = shift_time(tm(hour=day_block.end.hour, minute=day_block.end.minute), ATTENDANCE_CODE_BUFFER) + auto_time_string = f"({start_time.strftime('%-I:%M')} - {end_time.strftime('%-I:%M %p')})" + except Day.DoesNotExist: + auto_time_string = "" + except Block.DoesNotExist: + auto_time_string = "" + print(auto_time_string) context = { "scheduled_activity": scheduled_activity, "passes": passes, @@ -416,6 +450,8 @@ def take_attendance_view(request, scheduled_activity_id): "show_checkboxes": (scheduled_activity.block.locked or request.user.is_eighth_admin), "show_icons": (scheduled_activity.block.locked and scheduled_activity.block.attendance_locked() and not request.user.is_eighth_admin), "bbcu_script": settings.BBCU_SCRIPT, + "qr_url": request.build_absolute_uri(reverse("student_attendance", args=[scheduled_activity.id, scheduled_activity.attendance_code])), + "auto_time_string": auto_time_string, } if request.user.is_eighth_admin: @@ -527,7 +563,9 @@ def accept_all_passes_view(request, scheduled_activity_id): if not can_accept: return render(request, "error/403.html", {"reason": "You do not have permission to take accept these passes."}, status=403) - EighthSignup.objects.filter(after_deadline=True, scheduled_activity=scheduled_activity).update(pass_accepted=True, was_absent=False) + EighthSignup.objects.filter(after_deadline=True, scheduled_activity=scheduled_activity).update( + pass_accepted=True, was_absent=False, attendance_marked=True + ) invalidate_obj(scheduled_activity) if "admin" in request.path: @@ -757,3 +795,125 @@ def email_students_view(request, scheduled_activity_id): context = {"scheduled_activity": scheduled_activity} return render(request, "eighth/email_students.html", context) + + +@login_required +@deny_restricted +def student_attendance_view(request, sch_act_id, code=None): + """Handles the initial steps for code or QR code student-based attendance + Args: + request: the user's request + sch_act_id: the id of the EighthScheduledActivity for which the user attempts to take attendance. It is from url, required. + code: the code which the user is attempting to take attendance. From url, optional (if the user just wants to access the page) + Returns: + An HttpResponse rendering the student attendance page for the specified EighthScheduledActivity (if it exists). + Otherwise, redirects to index / dashboard. + """ + result = None # could end as None (no attendance attempt), no_signup, no_school, invalid_time, code_closed, code_fail, or marked + try: + sch_act = EighthScheduledActivity.objects.get(id=sch_act_id) + except EighthScheduledActivity.DoesNotExist: + messages.error(request, "Error marking attendance.") + return redirect("index") + + try: + user_signup = EighthSignup.objects.get(user=request.user, scheduled_activity=sch_act) + if user_signup.attendance_marked and not user_signup.was_absent: + return student_frontend(request, sch_act_id, result="marked") + except EighthSignup.DoesNotExist: + result = "no_signup" + return student_frontend(request, sch_act_id, result) + + is_open = check_attendance_open(sch_act) # returns None if attendance is open, error string if closed + if is_open is None: + if code is not None: + result = mark_attendance(request, sch_act, code) + else: + result = is_open + + return student_frontend(request, sch_act_id, result) + + +@login_required +@deny_restricted +def student_frontend(request, sch_act_id, result): + """Handles the process of rendering the frontend UI for student-based attendance. + It is only called by the student_attendance_view function + Args: + request: the user's request + sch_act_id: the EighthScheduledActivity for which the user attempts to take attendance + result: The result of the student's attempt to take attendance (if there was an attempt) + Returns: + An HttpResponse which renders the UI with a corresponding message. Returns this to student_attendance_view. + """ + + result_to_html = { + None: "", + "no_signup": '

Error: You are not signed up for this activity.

', + "no_school": '

Error: Attendance is only available on school days.

', + "invalid_time": '

Error: Please fill out attendance during the timeframe of this activity.

', + "code_closed": '

Error: Your activity sponsor has closed the attendance code.

', + "code_fail": '

Error: Invalid Code.

', + "marked": '

Attendance marked.

', + } + html_text = result_to_html[result] + sch_act = EighthScheduledActivity.objects.get(id=sch_act_id) + date_string = sch_act.block.date.strftime("%b %d") + show_form = result in (None, "code_fail") + return render( + request, + "eighth/student_submit_attendance.html", + context={"result": result, "result_html": html_text, "show_form": show_form, "sch_act": sch_act, "date_string": date_string}, + ) + + +def mark_attendance(request, act, code): + """Handles the process of checking the code and marking attendance for the user. + Args: + request: the user's request + act: the EighthScheduledActivity for which the user attempts to take attendance + code: the code which the user is attempting to take attendance + Returns: + A string reporting the result of the attempt. Either "code_correct" or "code_fail" + """ + if code.upper() == act.attendance_code: + try: + present = EighthSignup.objects.get(scheduled_activity=act, user__in=[request.user.id]) + present.was_absent = False + present.attendance_marked = True + present.save() + invalidate_obj(present) + act.attendance_taken = True + act.save() + invalidate_obj(act) + return "marked" + except EighthSignup.DoesNotExist: + return "code_fail" + return "code_fail" + + +def check_attendance_open(act: EighthScheduledActivity): + """Checks whether attendance is open for an EighthScheduledActivity. + Args: + act: the EighthScheduledActivity in question + day_blocks: the set of Block objects in the current day's day_type (defined in student_attendance_view) + Returns: + mark_result: a string, either "no_school", "invalid_time" or "code_closed", or None if attendance is open. + """ + block = act.block + mark_result = None + if act.get_code_mode_display() == "Auto": + now = timezone.localtime() + try: + day_blocks = Day.objects.select_related("day_type").get(date=now).day_type.blocks + except Day.DoesNotExist: + return "no_school" + try: + day_block = day_blocks.get(name="8" + block.block_letter) + except Block.DoesNotExist: + mark_result = "invalid_time" + if not day_block.eighth_auto_open <= timezone.localtime().time() <= day_block.eighth_auto_close: + mark_result = "invalid_time" + elif act.get_code_mode_display() == "Closed": + mark_result = "code_closed" + return mark_result diff --git a/intranet/apps/eighth/views/signup.py b/intranet/apps/eighth/views/signup.py index 94b525aaaf1..24feb991c8b 100644 --- a/intranet/apps/eighth/views/signup.py +++ b/intranet/apps/eighth/views/signup.py @@ -451,8 +451,7 @@ def eighth_location(request): sch_acts.append([b, act, ", ".join([r.name for r in act.get_true_rooms()]), ", ".join([s.name for s in act.get_true_sponsors()])]) except EighthScheduledActivity.DoesNotExist: sch_acts.append([b, None]) - - response = render(request, "eighth/location.html", context={"sch_acts": sch_acts}) + response = render(request, "eighth/location.html", context={"sch_acts": sch_acts, "real_user": request.user}) else: messages.error(request, "There are no eighth period blocks scheduled today.") response = redirect("index") diff --git a/intranet/apps/schedule/migrations/0012_attendance_auto_times.py b/intranet/apps/schedule/migrations/0012_attendance_auto_times.py new file mode 100644 index 00000000000..d4a8a36b1e5 --- /dev/null +++ b/intranet/apps/schedule/migrations/0012_attendance_auto_times.py @@ -0,0 +1,37 @@ +from django.db import migrations, models +import datetime +from ..models import shift_time +from ....settings.__init__ import ATTENDANCE_CODE_BUFFER + +def populate_eighth_auto_fields(apps, schema_editor): + Block = apps.get_model('schedule', 'Block') + for obj in Block.objects.all(): + if "8" in obj.name: + start = datetime.time(hour=obj.start.hour, minute=obj.start.minute) + end = datetime.time(hour=obj.end.hour, minute=obj.end.minute) + obj.eighth_auto_open = shift_time(start, -ATTENDANCE_CODE_BUFFER) + obj.eighth_auto_close = shift_time(end, ATTENDANCE_CODE_BUFFER) + else: + obj.eighth_auto_open = None + obj.eighth_auto_close = None + obj.save() + +class Migration(migrations.Migration): + + dependencies = [ + ('schedule', '0011_day_comment'), + ] + + operations = [ + migrations.AddField( + model_name='block', + name='eighth_auto_close', + field=models.TimeField(blank=True, null=True), + ), + migrations.AddField( + model_name='block', + name='eighth_auto_open', + field=models.TimeField(blank=True, null=True), + ), + migrations.RunPython(populate_eighth_auto_fields, reverse_code=migrations.RunPython.noop), + ] diff --git a/intranet/apps/schedule/models.py b/intranet/apps/schedule/models.py index 95cfd3a2794..f43cc44865c 100644 --- a/intranet/apps/schedule/models.py +++ b/intranet/apps/schedule/models.py @@ -3,6 +3,30 @@ from django.db import models from django.utils import timezone +from ...settings.__init__ import ATTENDANCE_CODE_BUFFER + + +def shift_time(time: datetime.time, minutes: int) -> datetime.time: + """Shifts the given time field by a certain number of minutes, with safeties: if shifting under 0,0,0 or past 23,59,59, + it returns 0,0,0 or 23,59,59, respectively. + Args: + time: A datetime.time object + minutes: Number of minutes (int) + Returns: + A datetime.time object which is the time argument shifted by minutes (if minutes is negative, shifts backwards) + """ + today = datetime.datetime.today() + if minutes < 0: + t = datetime.time(0, -minutes, 0) + if time < t: + return datetime.time(0, 0, 0) + else: + delta = datetime.datetime.combine(today, datetime.time(23, 59, 59)) - datetime.datetime.combine(today, time) + if minutes > delta.total_seconds() / 60: + return datetime.time(23, 59, 59) + dt = datetime.datetime.combine(today, time) + return (dt + datetime.timedelta(minutes=minutes)).time() + class Time(models.Model): hour = models.IntegerField() @@ -30,10 +54,30 @@ class Block(models.Model): start = models.ForeignKey("Time", related_name="blockstart", on_delete=models.CASCADE) end = models.ForeignKey("Time", related_name="blockend", on_delete=models.CASCADE) order = models.IntegerField(default=0) + eighth_auto_open = models.TimeField( + null=True, blank=True + ) # time when attendance is opened for sch_acts with "auto" mode set, 11:59:59 if not eighth period + eighth_auto_close = models.TimeField(null=True, blank=True) # 00:00:00 if not eighth period def __str__(self): return f"{self.name}: {self.start} - {self.end}" + def calculate_eighth_auto_times(self): + """Generates the times when eighth-block code attendance opens and closes automatically.""" + if "8" in self.name: + start = datetime.time(hour=self.start.hour, minute=self.start.minute) + end = datetime.time(hour=self.end.hour, minute=self.end.minute) + self.eighth_auto_open = shift_time(start, -ATTENDANCE_CODE_BUFFER) + self.eighth_auto_close = shift_time(end, ATTENDANCE_CODE_BUFFER) + else: + self.eighth_auto_open = None + self.eighth_auto_close = None + + def save(self, *args, **kwargs): + super().save(*args, **kwargs) + self.calculate_eighth_auto_times() + super().save(update_fields=["eighth_auto_open", "eighth_auto_close"]) + class Meta: unique_together = ("order", "name", "start", "end") ordering = ("order", "name", "start", "end") diff --git a/intranet/settings/__init__.py b/intranet/settings/__init__.py index e67095be5b5..05ab7298538 100644 --- a/intranet/settings/__init__.py +++ b/intranet/settings/__init__.py @@ -886,6 +886,10 @@ def get_log(name): # pylint: disable=redefined-outer-name; 'name' is used as th # taking attendance (10PM) ATTENDANCE_LOCK_HOUR = 22 +# The time buffer (in minutes) before the start or end of an eighth block (times from the Block model in schedule) during which +# code/qr attendance is allowed, if the code mode is "auto" +ATTENDANCE_CODE_BUFFER = 20 + # The number of days to show an absence message (2 weeks) CLEAR_ABSENCE_DAYS = 14 diff --git a/intranet/static/css/bus.scss b/intranet/static/css/bus.scss index a0c1cc56817..8572eca3192 100644 --- a/intranet/static/css/bus.scss +++ b/intranet/static/css/bus.scss @@ -323,4 +323,4 @@ svg text { transform: rotate(180deg) translate(87%, 18%); transform-origin: center; } -*/ \ No newline at end of file +*/ diff --git a/intranet/static/css/dashboard.widgets.scss b/intranet/static/css/dashboard.widgets.scss index e2d1c018e15..b36c9a2d260 100644 --- a/intranet/static/css/dashboard.widgets.scss +++ b/intranet/static/css/dashboard.widgets.scss @@ -414,7 +414,6 @@ a#eighth-sponsor { .btn-link { text-decoration: none; color: $grey; - display: inline-block; line-height: 18px; padding: 0 3px; margin: 5px 0; diff --git a/intranet/static/vendor/qr-code-styling.js b/intranet/static/vendor/qr-code-styling.js new file mode 100644 index 00000000000..a7a79d2e53e --- /dev/null +++ b/intranet/static/vendor/qr-code-styling.js @@ -0,0 +1,2 @@ +!function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e():"function"==typeof define&&define.amd?define([],e):"object"==typeof exports?exports.QRCodeStyling=e():t.QRCodeStyling=e()}(self,(function(){return(()=>{var t={192:(t,e)=>{var r,n,o=function(){var t=function(t,e){var r=t,n=a[e],o=null,i=0,u=null,v=[],y={},_=function(t,e){o=function(t){for(var e=new Array(t),r=0;r=7&&M(t),null==u&&(u=P(r,n,v)),C(u,e)},m=function(t,e){for(var r=-1;r<=7;r+=1)if(!(t+r<=-1||i<=t+r))for(var n=-1;n<=7;n+=1)e+n<=-1||i<=e+n||(o[t+r][e+n]=0<=r&&r<=6&&(0==n||6==n)||0<=n&&n<=6&&(0==r||6==r)||2<=r&&r<=4&&2<=n&&n<=4)},b=function(){for(var t=8;t>n&1);o[Math.floor(n/3)][n%3+i-8-3]=a}for(n=0;n<18;n+=1)a=!t&&1==(e>>n&1),o[n%3+i-8-3][Math.floor(n/3)]=a},S=function(t,e){for(var r=n<<3|e,a=s.getBCHTypeInfo(r),u=0;u<15;u+=1){var c=!t&&1==(a>>u&1);u<6?o[u][8]=c:u<8?o[u+1][8]=c:o[i-15+u][8]=c}for(u=0;u<15;u+=1)c=!t&&1==(a>>u&1),u<8?o[8][i-u-1]=c:u<9?o[8][15-u-1+1]=c:o[8][15-u-1]=c;o[i-8][8]=!t},C=function(t,e){for(var r=-1,n=i-1,a=7,u=0,c=s.getMaskFunction(e),h=i-1;h>0;h-=2)for(6==h&&(h-=1);;){for(var d=0;d<2;d+=1)if(null==o[n][h-d]){var l=!1;u>>a&1)),c(n,h-d)&&(l=!l),o[n][h-d]=l,-1==(a-=1)&&(u+=1,a=7)}if((n+=r)<0||i<=n){n-=r,r=-r;break}}},P=function(t,e,r){for(var n=h.getRSBlocks(t,e),o=d(),i=0;i8*u)throw"code length overflow. ("+o.getLengthInBits()+">"+8*u+")";for(o.getLengthInBits()+4<=8*u&&o.put(0,4);o.getLengthInBits()%8!=0;)o.putBit(!1);for(;!(o.getLengthInBits()>=8*u||(o.put(236,8),o.getLengthInBits()>=8*u));)o.put(17,8);return function(t,e){for(var r=0,n=0,o=0,i=new Array(e.length),a=new Array(e.length),u=0;u=0?p.getAt(g):0}}var v=0;for(l=0;ln)&&(t=n,e=r)}return e}())},y.createTableTag=function(t,e){t=t||2;var r="";r+='',r+="";for(var n=0;n";for(var o=0;o';r+=""}return(r+="")+"
"},y.createSvgTag=function(t,e,r,n){var o={};"object"==typeof arguments[0]&&(t=(o=arguments[0]).cellSize,e=o.margin,r=o.alt,n=o.title),t=t||2,e=void 0===e?4*t:e,(r="string"==typeof r?{text:r}:r||{}).text=r.text||null,r.id=r.text?r.id||"qrcode-description":null,(n="string"==typeof n?{text:n}:n||{}).text=n.text||null,n.id=n.text?n.id||"qrcode-title":null;var i,a,s,u,c=y.getModuleCount()*t+2*e,h="";for(u="l"+t+",0 0,"+t+" -"+t+",0 0,-"+t+"z ",h+=''+O(n.text)+"":"",h+=r.text?''+O(r.text)+"":"",h+='',h+='"},y.createDataURL=function(t,e){t=t||2,e=void 0===e?4*t:e;var r=y.getModuleCount()*t+2*e,n=e,o=r-e;return w(r,r,(function(e,r){if(n<=e&&e"};var O=function(t){for(var e="",r=0;r":e+=">";break;case"&":e+="&";break;case'"':e+=""";break;default:e+=n}}return e};return y.createASCII=function(t,e){if((t=t||1)<2)return function(t){t=void 0===t?2:t;var e,r,n,o,i,a=1*y.getModuleCount()+2*t,s=t,u=a-t,c={"██":"█","█ ":"▀"," █":"▄"," ":" "},h={"██":"▀","█ ":"▀"," █":" "," ":" "},d="";for(e=0;e=u?h[i]:c[i];d+="\n"}return a%2&&t>0?d.substring(0,d.length-a-1)+Array(a+1).join("▀"):d.substring(0,d.length-1)}(e);t-=1,e=void 0===e?2*t:e;var r,n,o,i,a=y.getModuleCount()*t+2*e,s=e,u=a-e,c=Array(t+1).join("██"),h=Array(t+1).join(" "),d="",l="";for(r=0;r>>8),e.push(255&a)):e.push(n)}}return e}};var e,r,n,o,i,a={L:1,M:0,Q:3,H:2},s=(e=[[],[6,18],[6,22],[6,26],[6,30],[6,34],[6,22,38],[6,24,42],[6,26,46],[6,28,50],[6,30,54],[6,32,58],[6,34,62],[6,26,46,66],[6,26,48,70],[6,26,50,74],[6,30,54,78],[6,30,56,82],[6,30,58,86],[6,34,62,90],[6,28,50,72,94],[6,26,50,74,98],[6,30,54,78,102],[6,28,54,80,106],[6,32,58,84,110],[6,30,58,86,114],[6,34,62,90,118],[6,26,50,74,98,122],[6,30,54,78,102,126],[6,26,52,78,104,130],[6,30,56,82,108,134],[6,34,60,86,112,138],[6,30,58,86,114,142],[6,34,62,90,118,146],[6,30,54,78,102,126,150],[6,24,50,76,102,128,154],[6,28,54,80,106,132,158],[6,32,58,84,110,136,162],[6,26,54,82,110,138,166],[6,30,58,86,114,142,170]],r=1335,n=7973,i=function(t){for(var e=0;0!=t;)e+=1,t>>>=1;return e},(o={}).getBCHTypeInfo=function(t){for(var e=t<<10;i(e)-i(r)>=0;)e^=r<=0;)e^=n<5&&(r+=3+i-5)}for(n=0;n=256;)e-=255;return t[e]}}}();function c(t,e){if(void 0===t.length)throw t.length+"/"+e;var r=function(){for(var r=0;r>>7-e%8&1)},put:function(t,e){for(var n=0;n>>e-n-1&1))},getLengthInBits:function(){return e},putBit:function(r){var n=Math.floor(e/8);t.length<=n&&t.push(0),r&&(t[n]|=128>>>e%8),e+=1}};return r},l=function(t){var e=t,r={getMode:function(){return 1},getLength:function(t){return e.length},write:function(t){for(var r=e,o=0;o+2>>8&255)+(255&o),t.put(o,13),r+=2}if(r>>8)},writeBytes:function(t,r,n){r=r||0,n=n||t.length;for(var o=0;o0&&(e+=","),e+=t[r];return e+"]"}};return e},y=function(t){var e=t,r=0,n=0,o=0,i={read:function(){for(;o<8;){if(r>=e.length){if(0==o)return-1;throw"unexpected end of file./"+o}var t=e.charAt(r);if(r+=1,"="==t)return o=0,-1;t.match(/^\s$/)||(n=n<<6|a(t.charCodeAt(0)),o+=6)}var i=n>>>o-8&255;return o-=8,i}},a=function(t){if(65<=t&&t<=90)return t-65;if(97<=t&&t<=122)return t-97+26;if(48<=t&&t<=57)return t-48+52;if(43==t)return 62;if(47==t)return 63;throw"c:"+t};return i},w=function(t,e,r){for(var n=function(t,e){var r=t,n=e,o=new Array(t*e),i={setPixel:function(t,e,n){o[e*r+t]=n},write:function(t){t.writeString("GIF87a"),t.writeShort(r),t.writeShort(n),t.writeByte(128),t.writeByte(0),t.writeByte(0),t.writeByte(0),t.writeByte(0),t.writeByte(0),t.writeByte(255),t.writeByte(255),t.writeByte(255),t.writeString(","),t.writeShort(0),t.writeShort(0),t.writeShort(r),t.writeShort(n),t.writeByte(0);var e=a(2);t.writeByte(2);for(var o=0;e.length-o>255;)t.writeByte(255),t.writeBytes(e,o,255),o+=255;t.writeByte(e.length-o),t.writeBytes(e,o,e.length-o),t.writeByte(0),t.writeString(";")}},a=function(t){for(var e=1<>>e!=0)throw"length over";for(;c+e>=8;)u.writeByte(255&(t<>>=8-c,h=0,c=0;h|=t<0&&u.writeByte(h)}});l.write(e,n);var f=0,p=String.fromCharCode(o[f]);for(f+=1;f=6;)i(t>>>e-6),e-=6},o.flush=function(){if(e>0&&(i(t<<6-e),t=0,e=0),r%3!=0)for(var o=3-r%3,a=0;a>6,128|63&n):n<55296||n>=57344?e.push(224|n>>12,128|n>>6&63,128|63&n):(r++,n=65536+((1023&n)<<10|1023&t.charCodeAt(r)),e.push(240|n>>18,128|n>>12&63,128|n>>6&63,128|63&n))}return e}(t)},void 0===(n="function"==typeof(r=function(){return o})?r.apply(e,[]):r)||(t.exports=n)},796:(t,e,r)=>{"use strict";r.d(e,{default:()=>W});var n=function(){return(n=Object.assign||function(t){for(var e,r=1,n=arguments.length;rn||o&&o2||a&&s||u&&c)this._basicSquare({x:e,y:r,size:n,context:o,rotation:0});else{if(2===h){var d=0;return a&&u?d=Math.PI/2:u&&s?d=Math.PI:s&&c&&(d=-Math.PI/2),void this._basicCornerRounded({x:e,y:r,size:n,context:o,rotation:d})}if(1===h)return d=0,u?d=Math.PI/2:s?d=Math.PI:c&&(d=-Math.PI/2),void this._basicSideRounded({x:e,y:r,size:n,context:o,rotation:d})}else this._basicDot({x:e,y:r,size:n,context:o,rotation:0})},t.prototype._drawExtraRounded=function(t){var e=t.x,r=t.y,n=t.size,o=t.context,i=t.getNeighbor,a=i?+i(-1,0):0,s=i?+i(1,0):0,u=i?+i(0,-1):0,c=i?+i(0,1):0,h=a+s+u+c;if(0!==h)if(h>2||a&&s||u&&c)this._basicSquare({x:e,y:r,size:n,context:o,rotation:0});else{if(2===h){var d=0;return a&&u?d=Math.PI/2:u&&s?d=Math.PI:s&&c&&(d=-Math.PI/2),void this._basicCornerExtraRounded({x:e,y:r,size:n,context:o,rotation:d})}if(1===h)return d=0,u?d=Math.PI/2:s?d=Math.PI:c&&(d=-Math.PI/2),void this._basicSideRounded({x:e,y:r,size:n,context:o,rotation:d})}else this._basicDot({x:e,y:r,size:n,context:o,rotation:0})},t.prototype._drawClassy=function(t){var e=t.x,r=t.y,n=t.size,o=t.context,i=t.getNeighbor,a=i?+i(-1,0):0,s=i?+i(1,0):0,u=i?+i(0,-1):0,c=i?+i(0,1):0;0!==a+s+u+c?a||u?s||c?this._basicSquare({x:e,y:r,size:n,context:o,rotation:0}):this._basicCornerRounded({x:e,y:r,size:n,context:o,rotation:Math.PI/2}):this._basicCornerRounded({x:e,y:r,size:n,context:o,rotation:-Math.PI/2}):this._basicCornersRounded({x:e,y:r,size:n,context:o,rotation:Math.PI/2})},t.prototype._drawClassyRounded=function(t){var e=t.x,r=t.y,n=t.size,o=t.context,i=t.getNeighbor,a=i?+i(-1,0):0,s=i?+i(1,0):0,u=i?+i(0,-1):0,c=i?+i(0,1):0;0!==a+s+u+c?a||u?s||c?this._basicSquare({x:e,y:r,size:n,context:o,rotation:0}):this._basicCornerExtraRounded({x:e,y:r,size:n,context:o,rotation:Math.PI/2}):this._basicCornerExtraRounded({x:e,y:r,size:n,context:o,rotation:-Math.PI/2}):this._basicCornersRounded({x:e,y:r,size:n,context:o,rotation:Math.PI/2})},t}(),w="square",_="extra-rounded";var m=function(){return(m=Object.assign||function(t){for(var e,r=1,n=arguments.length;r0&&o[o.length-1])||6!==i[0]&&2!==i[0])){a=0;continue}if(3===i[0]&&(!o||i[1]>o[0]&&i[1]=(e-o.hideXDots)/2&&t<(e+o.hideXDots)/2&&r>=(e-o.hideYDots)/2&&r<(e+o.hideYDots)/2||(null===(n=P[t])||void 0===n?void 0:n[r])||(null===(i=P[t-e+7])||void 0===i?void 0:i[r])||(null===(a=P[t])||void 0===a?void 0:a[r-e+7])||(null===(s=O[t])||void 0===s?void 0:s[r])||(null===(u=O[t-e+7])||void 0===u?void 0:u[r])||(null===(c=O[t])||void 0===c?void 0:c[r-e+7]))})),this.drawCorners(),this._options.image&&this.drawImage({width:o.width,height:o.height,count:e,dotSize:n}),[2]}}))},new((n=void 0)||(n=Promise))((function(t,i){function a(t){try{u(o.next(t))}catch(t){i(t)}}function s(t){try{u(o.throw(t))}catch(t){i(t)}}function u(e){var r;e.done?t(e.value):(r=e.value,r instanceof n?r:new n((function(t){t(r)}))).then(a,s)}u((o=o.apply(e,r||[])).next())}));var e,r,n,o},t.prototype.drawBackground=function(){var t=this.context,e=this._options;if(t){if(e.backgroundOptions.gradient){var r=e.backgroundOptions.gradient,n=this._createGradient({context:t,options:r,additionalRotation:0,x:0,y:0,size:this._canvas.width>this._canvas.height?this._canvas.width:this._canvas.height});r.colorStops.forEach((function(t){var e=t.offset,r=t.color;n.addColorStop(e,r)})),t.fillStyle=n}else e.backgroundOptions.color&&(t.fillStyle=e.backgroundOptions.color);t.fillRect(0,0,this._canvas.width,this._canvas.height)}},t.prototype.drawDots=function(t){var e=this;if(!this._qr)throw"QR code is not defined";var r=this.context;if(!r)throw"QR code is not defined";var n=this._options,o=this._qr.getModuleCount();if(o>n.width||o>n.height)throw"The canvas is too small.";var i=Math.min(n.width,n.height)-2*n.margin,a=Math.floor(i/o),s=Math.floor((n.width-o*a)/2),u=Math.floor((n.height-o*a)/2),c=new y({context:r,type:n.dotsOptions.type});r.beginPath();for(var h=function(r){for(var n=function(n){return t&&!t(r,n)?"continue":d._qr.isDark(r,n)?void c.draw(s+r*a,u+n*a,a,(function(i,a){return!(r+i<0||n+a<0||r+i>=o||n+a>=o)&&!(t&&!t(r+i,n+a))&&!!e._qr&&e._qr.isDark(r+i,n+a)})):"continue"},i=0;i=0&&c<=.25*Math.PI||c>1.75*Math.PI&&c<=2*Math.PI?(h-=s/2,d-=s/2*Math.tan(u),l+=s/2,f+=s/2*Math.tan(u)):c>.25*Math.PI&&c<=.75*Math.PI?(d-=s/2,h-=s/2/Math.tan(u),f+=s/2,l+=s/2/Math.tan(u)):c>.75*Math.PI&&c<=1.25*Math.PI?(h+=s/2,d+=s/2*Math.tan(u),l-=s/2,f-=s/2*Math.tan(u)):c>1.25*Math.PI&&c<=1.75*Math.PI&&(d+=s/2,h+=s/2/Math.tan(u),f-=s/2,l-=s/2/Math.tan(u)),e=r.createLinearGradient(Math.round(h),Math.round(d),Math.round(l),Math.round(f))}return e},t}();var D=function(){return(D=Object.assign||function(t){for(var e,r=1,n=arguments.length;r2||i&&a||s&&u)this._basicSquare({x:e,y:r,size:n,rotation:0});else{if(2===c){var h=0;return i&&s?h=Math.PI/2:s&&a?h=Math.PI:a&&u&&(h=-Math.PI/2),void this._basicCornerRounded({x:e,y:r,size:n,rotation:h})}if(1===c)return h=0,s?h=Math.PI/2:a?h=Math.PI:u&&(h=-Math.PI/2),void this._basicSideRounded({x:e,y:r,size:n,rotation:h})}else this._basicDot({x:e,y:r,size:n,rotation:0})},t.prototype._drawExtraRounded=function(t){var e=t.x,r=t.y,n=t.size,o=t.getNeighbor,i=o?+o(-1,0):0,a=o?+o(1,0):0,s=o?+o(0,-1):0,u=o?+o(0,1):0,c=i+a+s+u;if(0!==c)if(c>2||i&&a||s&&u)this._basicSquare({x:e,y:r,size:n,rotation:0});else{if(2===c){var h=0;return i&&s?h=Math.PI/2:s&&a?h=Math.PI:a&&u&&(h=-Math.PI/2),void this._basicCornerExtraRounded({x:e,y:r,size:n,rotation:h})}if(1===c)return h=0,s?h=Math.PI/2:a?h=Math.PI:u&&(h=-Math.PI/2),void this._basicSideRounded({x:e,y:r,size:n,rotation:h})}else this._basicDot({x:e,y:r,size:n,rotation:0})},t.prototype._drawClassy=function(t){var e=t.x,r=t.y,n=t.size,o=t.getNeighbor,i=o?+o(-1,0):0,a=o?+o(1,0):0,s=o?+o(0,-1):0,u=o?+o(0,1):0;0!==i+a+s+u?i||s?a||u?this._basicSquare({x:e,y:r,size:n,rotation:0}):this._basicCornerRounded({x:e,y:r,size:n,rotation:Math.PI/2}):this._basicCornerRounded({x:e,y:r,size:n,rotation:-Math.PI/2}):this._basicCornersRounded({x:e,y:r,size:n,rotation:Math.PI/2})},t.prototype._drawClassyRounded=function(t){var e=t.x,r=t.y,n=t.size,o=t.getNeighbor,i=o?+o(-1,0):0,a=o?+o(1,0):0,s=o?+o(0,-1):0,u=o?+o(0,1):0;0!==i+a+s+u?i||s?a||u?this._basicSquare({x:e,y:r,size:n,rotation:0}):this._basicCornerExtraRounded({x:e,y:r,size:n,rotation:Math.PI/2}):this._basicCornerExtraRounded({x:e,y:r,size:n,rotation:-Math.PI/2}):this._basicCornersRounded({x:e,y:r,size:n,rotation:Math.PI/2})},t}();var A=function(){return(A=Object.assign||function(t){for(var e,r=1,n=arguments.length;r0&&o[o.length-1])||6!==i[0]&&2!==i[0])){a=0;continue}if(3===i[0]&&(!o||i[1]>o[0]&&i[1]=(e-o.hideXDots)/2&&t<(e+o.hideXDots)/2&&r>=(e-o.hideYDots)/2&&r<(e+o.hideYDots)/2||(null===(n=B[t])||void 0===n?void 0:n[r])||(null===(i=B[t-e+7])||void 0===i?void 0:i[r])||(null===(a=B[t])||void 0===a?void 0:a[r-e+7])||(null===(s=E[t])||void 0===s?void 0:s[r])||(null===(u=E[t-e+7])||void 0===u?void 0:u[r])||(null===(c=E[t])||void 0===c?void 0:c[r-e+7]))})),this.drawCorners(),this._options.image&&this.drawImage({width:o.width,height:o.height,count:e,dotSize:n}),[2]}}))},new((n=void 0)||(n=Promise))((function(t,i){function a(t){try{u(o.next(t))}catch(t){i(t)}}function s(t){try{u(o.throw(t))}catch(t){i(t)}}function u(e){var r;e.done?t(e.value):(r=e.value,r instanceof n?r:new n((function(t){t(r)}))).then(a,s)}u((o=o.apply(e,r||[])).next())}));var e,r,n,o},t.prototype.drawBackground=function(){var t,e,r=this._element,n=this._options;if(r){var o=null===(t=n.backgroundOptions)||void 0===t?void 0:t.gradient,i=null===(e=n.backgroundOptions)||void 0===e?void 0:e.color;(o||i)&&this._createColor({options:o,color:i,additionalRotation:0,x:0,y:0,height:n.height,width:n.width,name:"background-color"})}},t.prototype.drawDots=function(t){var e,r,n=this;if(!this._qr)throw"QR code is not defined";var o=this._options,i=this._qr.getModuleCount();if(i>o.width||i>o.height)throw"The canvas is too small.";var a=Math.min(o.width,o.height)-2*o.margin,s=Math.floor(a/i),u=Math.floor((o.width-i*s)/2),c=Math.floor((o.height-i*s)/2),h=new I({svg:this._element,type:o.dotsOptions.type});this._dotsClipPath=document.createElementNS("http://www.w3.org/2000/svg","clipPath"),this._dotsClipPath.setAttribute("id","clip-path-dot-color"),this._defs.appendChild(this._dotsClipPath),this._createColor({options:null===(e=o.dotsOptions)||void 0===e?void 0:e.gradient,color:o.dotsOptions.color,additionalRotation:0,x:u,y:c,height:i*s,width:i*s,name:"dot-color"});for(var d=function(e){for(var o=function(o){return t&&!t(e,o)?"continue":(null===(r=l._qr)||void 0===r?void 0:r.isDark(e,o))?(h.draw(u+e*s,c+o*s,s,(function(r,a){return!(e+r<0||o+a<0||e+r>=i||o+a>=i)&&!(t&&!t(e+r,o+a))&&!!n._qr&&n._qr.isDark(e+r,o+a)})),void(h._element&&l._dotsClipPath&&l._dotsClipPath.appendChild(h._element))):"continue"},a=0;aa?s:a,h=document.createElementNS("http://www.w3.org/2000/svg","rect");if(h.setAttribute("x",String(o)),h.setAttribute("y",String(i)),h.setAttribute("height",String(a)),h.setAttribute("width",String(s)),h.setAttribute("clip-path","url('#clip-path-"+u+"')"),e){var d;if(e.type===C)(d=document.createElementNS("http://www.w3.org/2000/svg","radialGradient")).setAttribute("id",u),d.setAttribute("gradientUnits","userSpaceOnUse"),d.setAttribute("fx",String(o+s/2)),d.setAttribute("fy",String(i+a/2)),d.setAttribute("cx",String(o+s/2)),d.setAttribute("cy",String(i+a/2)),d.setAttribute("r",String(c/2));else{var l=((e.rotation||0)+n)%(2*Math.PI),f=(l+2*Math.PI)%(2*Math.PI),p=o+s/2,g=i+a/2,v=o+s/2,y=i+a/2;f>=0&&f<=.25*Math.PI||f>1.75*Math.PI&&f<=2*Math.PI?(p-=s/2,g-=a/2*Math.tan(l),v+=s/2,y+=a/2*Math.tan(l)):f>.25*Math.PI&&f<=.75*Math.PI?(g-=a/2,p-=s/2/Math.tan(l),y+=a/2,v+=s/2/Math.tan(l)):f>.75*Math.PI&&f<=1.25*Math.PI?(p+=s/2,g+=a/2*Math.tan(l),v-=s/2,y-=a/2*Math.tan(l)):f>1.25*Math.PI&&f<=1.75*Math.PI&&(g+=a/2,p+=s/2/Math.tan(l),y-=a/2,v-=s/2/Math.tan(l)),(d=document.createElementNS("http://www.w3.org/2000/svg","linearGradient")).setAttribute("id",u),d.setAttribute("gradientUnits","userSpaceOnUse"),d.setAttribute("x1",String(Math.round(p))),d.setAttribute("y1",String(Math.round(g))),d.setAttribute("x2",String(Math.round(v))),d.setAttribute("y2",String(Math.round(y)))}e.colorStops.forEach((function(t){var e=t.offset,r=t.color,n=document.createElementNS("http://www.w3.org/2000/svg","stop");n.setAttribute("offset",100*e+"%"),n.setAttribute("stop-color",r),d.appendChild(n)})),h.setAttribute("fill","url('#"+u+"')"),this._defs.appendChild(d)}else r&&h.setAttribute("fill",r);this._element.appendChild(h)},t}(),N="canvas";for(var T={},j=0;j<=40;j++)T[j]=j;const F={type:N,width:300,height:300,data:"",margin:0,qrOptions:{typeNumber:T[0],mode:void 0,errorCorrectionLevel:"Q"},imageOptions:{hideBackgroundDots:!0,imageSize:.4,crossOrigin:void 0,margin:0},dotsOptions:{type:"square",color:"#000"},backgroundOptions:{color:"#fff"}};var Q=function(){return(Q=Object.assign||function(t){for(var e,r=1,n=arguments.length;rMath.min(e.width,e.height)&&(e.margin=Math.min(e.width,e.height)),e.dotsOptions=Q({},e.dotsOptions),e.dotsOptions.gradient&&(e.dotsOptions.gradient=H(e.dotsOptions.gradient)),e.cornersSquareOptions&&(e.cornersSquareOptions=Q({},e.cornersSquareOptions),e.cornersSquareOptions.gradient&&(e.cornersSquareOptions.gradient=H(e.cornersSquareOptions.gradient))),e.cornersDotOptions&&(e.cornersDotOptions=Q({},e.cornersDotOptions),e.cornersDotOptions.gradient&&(e.cornersDotOptions.gradient=H(e.cornersDotOptions.gradient))),e.backgroundOptions&&(e.backgroundOptions=Q({},e.backgroundOptions),e.backgroundOptions.gradient&&(e.backgroundOptions.gradient=H(e.backgroundOptions.gradient))),e}var X=r(192),U=r.n(X),Y=function(t,e,r,n){return new(r||(r=Promise))((function(o,i){function a(t){try{u(n.next(t))}catch(t){i(t)}}function s(t){try{u(n.throw(t))}catch(t){i(t)}}function u(t){var e;t.done?o(t.value):(e=t.value,e instanceof r?e:new r((function(t){t(e)}))).then(a,s)}u((n=n.apply(t,e||[])).next())}))},$=function(t,e){var r,n,o,i,a={label:0,sent:function(){if(1&o[0])throw o[1];return o[1]},trys:[],ops:[]};return i={next:s(0),throw:s(1),return:s(2)},"function"==typeof Symbol&&(i[Symbol.iterator]=function(){return this}),i;function s(i){return function(s){return function(i){if(r)throw new TypeError("Generator is already executing.");for(;a;)try{if(r=1,n&&(o=2&i[0]?n.return:i[0]?n.throw||((o=n.return)&&o.call(n),0):n.next)&&!(o=o.call(n,i[1])).done)return o;switch(n=0,o&&(i=[2&i[0],o.value]),i[0]){case 0:case 1:o=i;break;case 4:return a.label++,{value:i[1],done:!1};case 5:a.label++,n=i[1],i=[0];continue;case 7:i=a.ops.pop(),a.trys.pop();continue;default:if(!((o=(o=a.trys).length>0&&o[o.length-1])||6!==i[0]&&2!==i[0])){a=0;continue}if(3===i[0]&&(!o||i[1]>o[0]&&i[1]\r\n'+n],{type:"image/svg+xml"})]):[2,new Promise((function(r){return e.getCanvas().toBlob(r,"image/"+t,1)}))]}}))}))},t.prototype.download=function(t){return Y(this,void 0,void 0,(function(){var e,r,n,o,i;return $(this,(function(a){switch(a.label){case 0:if(!this._qr)throw"QR code is empty";return e="png",r="qr","string"==typeof t?(e=t,console.warn("Extension is deprecated as argument for 'download' method, please pass object { name: '...', extension: '...' } as argument")):"object"==typeof t&&null!==t&&(t.name&&(r=t.name),t.extension&&(e=t.extension)),[4,this._getQRStylingElement(e)];case 1:return n=a.sent(),"svg"===e.toLowerCase()?(o=new XMLSerializer,i='\r\n'+(i=o.serializeToString(n.getElement())),s("data:image/svg+xml;charset=utf-8,"+encodeURIComponent(i),r+".svg")):s(n.getCanvas().toDataURL("image/"+e),r+"."+e),[2]}}))}))},t}()}},e={};function r(n){if(e[n])return e[n].exports;var o=e[n]={exports:{}};return t[n](o,o.exports,r),o.exports}return r.n=t=>{var e=t&&t.__esModule?()=>t.default:()=>t;return r.d(e,{a:e}),e},r.d=(t,e)=>{for(var n in e)r.o(e,n)&&!r.o(t,n)&&Object.defineProperty(t,n,{enumerable:!0,get:e[n]})},r.o=(t,e)=>Object.prototype.hasOwnProperty.call(t,e),r(796)})().default})); +//# sourceMappingURL=qr-code-styling.js.map \ No newline at end of file diff --git a/intranet/templates/eighth/location.html b/intranet/templates/eighth/location.html index dc2e85a77bc..6b39875b9c9 100644 --- a/intranet/templates/eighth/location.html +++ b/intranet/templates/eighth/location.html @@ -43,6 +43,7 @@
Sponsor(s): {{ s.3 }}


{% endfor %} +
Return to Dashboard {% endblock %} diff --git a/intranet/templates/eighth/signup.html b/intranet/templates/eighth/signup.html index d07dbdd6eed..45106b1c913 100644 --- a/intranet/templates/eighth/signup.html +++ b/intranet/templates/eighth/signup.html @@ -346,14 +346,22 @@

<% } else { %> You are currently signed up for this activity. +
+ <% if (attendance_open) { %> + + <% } %> <% } %> <% } %> <%}%> <%}%> - +
+ {% endverbatim %}
+ {% verbatim %} <% if (!showingRosterButton) { %> <% if(isEighthAdmin) { %> diff --git a/intranet/templates/eighth/signup_widget.html b/intranet/templates/eighth/signup_widget.html index bde683b2529..2320fc31c96 100644 --- a/intranet/templates/eighth/signup_widget.html +++ b/intranet/templates/eighth/signup_widget.html @@ -36,7 +36,23 @@

{{ block.date|date:"D, N j" }} - {% if block.locked %} + {% if block.attendance_open %} + + {% if block.rooms and not block.is_today and not block.current_signup_cancelled %} + + {% for r in block.rooms %} + {{ r.formatted_name }}{% if not forloop.last %}, {% endif %} + {% endfor %} + + {% endif %} + + {% if block.locked %}{% endif %} + Enter Code + + {% elif block.locked %} {% if block.current_signup %}