diff --git a/.github/workflows/actionTests.yml b/.github/workflows/actionTests.yml index c9d45df4a..58f86d1ef 100644 --- a/.github/workflows/actionTests.yml +++ b/.github/workflows/actionTests.yml @@ -18,7 +18,7 @@ jobs: pull-requests: write strategy: matrix: - python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"] + python-version: ["3.10", "3.11", "3.12","3.13"] # each step can define `env` vars, but it's easiest to define them on the build level env: diff --git a/app/__init__.py b/app/__init__.py index 35f0eb035..3c7058611 100755 --- a/app/__init__.py +++ b/app/__init__.py @@ -7,6 +7,7 @@ # Initialize our application app = Flask(__name__, template_folder="templates") +app.jinja_env.add_extension('jinja2.ext.loopcontrols') app.env = os.environ.get('APP_ENV', 'production') load_config_files(app) diff --git a/app/controllers/__init__.py b/app/controllers/__init__.py index f55b95fb2..ed2f8d89f 100644 --- a/app/controllers/__init__.py +++ b/app/controllers/__init__.py @@ -2,7 +2,6 @@ import os #from app.login_manager import require_login - #@app.context_processor #def injectGlobalData(): #currentUser = require_login() @@ -10,4 +9,4 @@ # for root_path, dirs, files in os.walk('app/static') # for f in files)) #return {'currentUser': currentUser, - # 'lastStaticUpdate': lastStaticUpdate} + # 'lastStaticUpdate': lastStaticUpdate} \ No newline at end of file diff --git a/app/controllers/admin/routes.py b/app/controllers/admin/routes.py index e48bd0fba..8d24ffdf6 100644 --- a/app/controllers/admin/routes.py +++ b/app/controllers/admin/routes.py @@ -71,20 +71,18 @@ def switchUser(): @admin_bp.route('/eventTemplates') def templateSelect(): - if g.current_user.isCeltsAdmin or g.current_user.isCeltsStudentStaff: - allprograms = getAllowedPrograms(g.current_user) - visibleTemplates = getAllowedTemplates(g.current_user) - return render_template("/events/templateSelector.html", - programs=allprograms, - celtsSponsoredProgram = Program.get(Program.isOtherCeltsSponsored), - templates=visibleTemplates) - else: + programs = getAllowedPrograms(g.current_user) + if not programs: abort(403) - + visibleTemplates = getAllowedTemplates(g.current_user) + return render_template("/events/templateSelector.html", + programs=programs, + celtsSponsoredProgram = Program.get(Program.isOtherCeltsSponsored), + templates=visibleTemplates) @admin_bp.route('/eventTemplates///create', methods=['GET','POST']) def createEvent(templateid, programid): - if not (g.current_user.isAdmin or g.current_user.isProgramManagerFor(programid)): + if not (g.current_user.isCeltsAdmin or g.current_user.isProgramManagerFor(programid)): abort(403) # Validate given URL @@ -173,8 +171,6 @@ def createEvent(templateid, programid): preprocessEventData(eventData) isProgramManager = g.current_user.isProgramManagerFor(programid) - futureTerms = selectSurroundingTerms(g.current_term, prevTerms=0) - requirements, bonnerCohorts = [], [] if eventData['program'] is not None and eventData['program'].isBonnerScholars: requirements = getCertRequirements(Certification.BONNER) @@ -189,7 +185,7 @@ def createEvent(templateid, programid): return render_template(f"/events/{template.templateFile}", template = template, eventData = eventData, - futureTerms = futureTerms, + termList = selectSurroundingTerms(g.current_term, prevTerms=0), requirements = requirements, bonnerCohorts = bonnerCohorts, isProgramManager = isProgramManager) @@ -314,7 +310,6 @@ def eventDisplay(eventId): # make sure our data is the same regardless of GET and POST preprocessEventData(eventData) eventData['program'] = event.program - futureTerms = selectSurroundingTerms(g.current_term) userHasRSVPed = checkUserRsvp(g.current_user, event) filepaths = FileHandler(eventId=event.id).retrievePath(associatedAttachments) isProgramManager = g.current_user.isProgramManagerFor(eventData['program']) @@ -342,7 +337,7 @@ def eventDisplay(eventId): if 'edit' in rule.rule: return render_template("events/createEvent.html", eventData = eventData, - futureTerms = futureTerms, + termList = Term.select().order_by(Term.termOrder), event = event, requirements = requirements, bonnerCohorts = bonnerCohorts, diff --git a/app/controllers/admin/userManagement.py b/app/controllers/admin/userManagement.py index 5f1193424..ca944b7af 100644 --- a/app/controllers/admin/userManagement.py +++ b/app/controllers/admin/userManagement.py @@ -137,4 +137,5 @@ def changeTerm(): @admin_bp.route('/admin/addNewTerm', methods = ['POST']) def addNewTerm(): addNextTerm() + flash("New term added", "success") return "" diff --git a/app/controllers/main/routes.py b/app/controllers/main/routes.py index 997840c38..f9d17d1c4 100644 --- a/app/controllers/main/routes.py +++ b/app/controllers/main/routes.py @@ -26,7 +26,7 @@ from app.models.courseInstructor import CourseInstructor from app.models.backgroundCheckType import BackgroundCheckType -from app.logic.events import getUpcomingEventsForUser, getParticipatedEventsForUser, getTrainingEvents, getEventRsvpCountsForTerm, getUpcomingStudentLedCount, getStudentLedEvents, getBonnerEvents, getOtherEvents, getEngagementEvents +from app.logic.events import getUpcomingEventsForUser, getParticipatedEventsForUser, getTrainingEvents, getEventRsvpCountsForTerm, getUpcomingVolunteerOpportunitiesCount, getVolunteerOpportunities, getBonnerEvents, getCeltsLabor, getEngagementEvents from app.logic.transcript import * from app.logic.loginManager import logout from app.logic.searchUsers import searchUsers @@ -69,49 +69,53 @@ def landingPage(): def goToEventsList(programID): return {"activeTab": getActiveEventTab(programID)} -@main_bp.route('/eventsList/', methods=['GET'], defaults={'activeTab': "studentLedEvents", 'programID': 0}) +@main_bp.route('/eventsList/', methods=['GET'], defaults={'activeTab': "volunteerOpportunities", 'programID': 0}) +@main_bp.route('/eventsList//', methods=['GET'], defaults={'activeTab': "volunteerOpportunities", 'programID': 0}) @main_bp.route('/eventsList//', methods=['GET'], defaults={'programID': 0}) @main_bp.route('/eventsList///', methods=['GET']) def events(selectedTerm, activeTab, programID): - currentTerm = g.current_term - if selectedTerm: - currentTerm = selectedTerm - + currentTime = datetime.datetime.now() listOfTerms = Term.select().order_by(Term.termOrder) participantRSVP = EventRsvp.select(EventRsvp, Event).join(Event).where(EventRsvp.user == g.current_user) rsvpedEventsID = [event.event.id for event in participantRSVP] - term: Term = Term.get_by_id(currentTerm) + term = g.current_term + if selectedTerm: + term = selectedTerm + + # Make sure we have a Term object + term = Term.get_or_none(Term.id == term) + if term is None: + term = Term.get(Term.isCurrentTerm == True) currentEventRsvpAmount = getEventRsvpCountsForTerm(term) - studentLedEvents = getStudentLedEvents(term) - countUpcomingStudentLedEvents = getUpcomingStudentLedCount(term, currentTime) + volunteerOpportunities = getVolunteerOpportunities(term) + countUpcomingVolunteerOpportunities = getUpcomingVolunteerOpportunitiesCount(term, currentTime) trainingEvents = getTrainingEvents(term, g.current_user) engagementEvents = getEngagementEvents(term) bonnerEvents = getBonnerEvents(term) - otherEvents = getOtherEvents(term) + celtsLabor = getCeltsLabor(term) managersProgramDict = getManagerProgramDict(g.current_user) # Fetch toggle state from session toggleState = request.args.get('toggleState', 'unchecked') - # compile all student led events into one list + # compile all volunteer opportunitiesevents into one list studentEvents = [] - for studentEvent in studentLedEvents.values(): + for studentEvent in volunteerOpportunities.values(): studentEvents += studentEvent # add all contents of studentEvent to the studentEvents list # Get the count of all term events for each category to display in the event list page. - studentLedEventsCount: int = len(studentEvents) + volunteerOpportunitiesCount: int = len(studentEvents) trainingEventsCount: int = len(trainingEvents) engagementEventsCount: int = len(engagementEvents) bonnerEventsCount: int = len(bonnerEvents) - otherEventsCount: int = len(otherEvents) + celtsLaborCount: int = len(celtsLabor) # gets only upcoming events to display in indicators if (toggleState == 'unchecked'): - studentLedEventsCount: int = sum(list(countUpcomingStudentLedEvents.values())) for event in trainingEvents: if event.isPastEnd: trainingEventsCount -= 1 @@ -121,28 +125,27 @@ def events(selectedTerm, activeTab, programID): for event in bonnerEvents: if event.isPastEnd: bonnerEventsCount -= 1 - for event in otherEvents: + for event in celtsLabor: if event.isPastEnd: - otherEventsCount -= 1 - + celtsLaborCount -= 1 + # Handle ajax request for Event category header number notifiers and toggle if request.headers.get('X-Requested-With') == 'XMLHttpRequest': return jsonify({ - "studentLedEventsCount": studentLedEventsCount, + "volunteerOpportunitiesCount": volunteerOpportunitiesCount, "trainingEventsCount": trainingEventsCount, "engagementEventsCount": engagementEventsCount, "bonnerEventsCount": bonnerEventsCount, - "otherEventsCount": otherEventsCount, + "celtsLaborCount": celtsLaborCount, "toggleStatus": toggleState }) - return render_template("/events/eventList.html", selectedTerm = term, - studentLedEvents = studentLedEvents, + volunteerOpportunities = volunteerOpportunities, trainingEvents = trainingEvents, engagementEvents = engagementEvents, bonnerEvents = bonnerEvents, - otherEvents = otherEvents, + celtsLabor = celtsLabor, listOfTerms = listOfTerms, rsvpedEventsID = rsvpedEventsID, currentEventRsvpAmount = currentEventRsvpAmount, @@ -151,7 +154,7 @@ def events(selectedTerm, activeTab, programID): activeTab = activeTab, programID = int(programID), managersProgramDict = managersProgramDict, - countUpcomingStudentLedEvents = countUpcomingStudentLedEvents, + countUpcomingVolunteerOpportunities = countUpcomingVolunteerOpportunities, toggleState = toggleState, ) @@ -243,7 +246,6 @@ def viewUsersProfile(username): managersList = managersList, participatedInLabor = getCeltsLaborHistory(volunteer), totalSustainedEngagements = totalSustainedEngagements, - expressInterest = volunteer.minorInterest ) abort(403) @@ -306,9 +308,7 @@ def insuranceInfo(username): if g.current_user.username != username: abort(403) - rowsUpdated = InsuranceInfo.update(**request.form).where(InsuranceInfo.user == username).execute() - if not rowsUpdated: - InsuranceInfo.create(user = username, **request.form) + InsuranceInfo.replace({**request.form, "user": username}).execute() createActivityLog(f"{g.current_user.fullName} updated {user.fullName}'s insurance information.") flash('Insurance information saved successfully!', 'success') diff --git a/app/logic/events.py b/app/logic/events.py index f7e63cfac..db921d149 100644 --- a/app/logic/events.py +++ b/app/logic/events.py @@ -224,17 +224,20 @@ def saveEventToDb(newEventData, renewedEvent = False): eventRecords.append(eventRecord) return eventRecords -def getStudentLedEvents(term): - studentLedEvents = list(Event.select(Event, Program) +def getVolunteerOpportunities(term): + volunteerOpportunities = list(Event.select(Event, Program) .join(Program) - .where(Program.isStudentLed, - Event.term == term, Event.deletionDate == None) + .where((Event.term == term) & + (Event.deletionDate.is_null(True)) & + (Event.isService == True) & + ((Event.isLaborOnly == False) | Event.isLaborOnly.is_null(True)) + ) .order_by(Event.startDate, Event.timeStart) .execute()) programs = {} - for event in studentLedEvents: + for event in volunteerOpportunities: programs.setdefault(event.program, []).append(event) return programs @@ -242,27 +245,34 @@ def getStudentLedEvents(term): def getEngagementEvents(term): engagementEvents = list(Event.select(Event, Program) .join(Program) - .where(Event.isEngagement, + .where(Event.isEngagement, Event.isLaborOnly == False, Event.term == term, Event.deletionDate == None) .order_by(Event.startDate, Event.timeStart) .execute()) return engagementEvents -def getUpcomingStudentLedCount(term, currentTime): +def getUpcomingVolunteerOpportunitiesCount(term, currentTime): """ - Return a count of all upcoming events for each student led program. + Return a count of all upcoming events for each volunteer opportunitiesprogram. """ - upcomingCount = (Program.select(Program.id, fn.COUNT(Event.id).alias("eventCount")) - .join(Event, on=(Program.id == Event.program_id)) - .where(Program.isStudentLed, - Event.term == term, Event.deletionDate == None, - (Event.startDate > currentTime) | ((Event.startDate == currentTime) & (Event.timeEnd >= currentTime)), - Event.isCanceled == False) - .group_by(Program.id)) - - programCountDict = {} + upcomingCount = ( + Program + .select(Program.id, fn.COUNT(Event.id).alias("eventCount")) + .join(Event, on=(Program.id == Event.program_id)) + .where( + (Event.term == term) & + (Event.deletionDate.is_null(True)) & + (Event.isService == True) & + ((Event.isLaborOnly == False) | Event.isLaborOnly.is_null(True)) & + ((Event.startDate > currentTime) | + ((Event.startDate == currentTime) & (Event.timeEnd >= currentTime))) & + (Event.isCanceled == False) + ) + .group_by(Program.id) + ) + programCountDict = {} for programCount in upcomingCount: programCountDict[programCount.id] = programCount.eventCount return programCountDict @@ -279,7 +289,7 @@ def getTrainingEvents(term, user): """ trainingQuery = (Event.select(Event).distinct() .join(Program, JOIN.LEFT_OUTER) - .where(Event.isTraining == True, + .where(Event.isTraining == True, Event.isLaborOnly == False, Event.term == term, Event.deletionDate == None) .order_by(Event.isAllVolunteerTraining.desc(), Event.startDate, Event.timeStart)) @@ -290,36 +300,28 @@ def getTrainingEvents(term, user): return list(trainingQuery.execute()) def getBonnerEvents(term): - bonnerScholarsEvents = list(Event.select(Event, Program.id.alias("program_id")) - .join(Program) - .where(Program.isBonnerScholars, - Event.term == term, Event.deletionDate == None) - .order_by(Event.startDate, Event.timeStart) - .execute()) + bonnerScholarsEvents = list( + Event.select(Event, Program.id.alias("program_id")) + .join(Program) + .where( + Program.isBonnerScholars, + Event.term == term, + Event.deletionDate == None + ) + .order_by(Event.startDate, Event.timeStart) + .execute() + ) return bonnerScholarsEvents -def getOtherEvents(term): +def getCeltsLabor(term): """ - - Get the list of the events not caught by other functions to be displayed in - the Other Events section of the Events List page. - :return: A list of Other Event objects + Labor tab: events explicitly marked as Labor Only. """ - # Gets all events that are not associated with a program and are not trainings - # Gets all events that have a program but don't fit anywhere - - otherEvents = list(Event.select(Event, Program) - .join(Program, JOIN.LEFT_OUTER) - .where(Event.term == term, Event.deletionDate == None, - Event.isTraining == False, - Event.isAllVolunteerTraining == False, - ((Program.isOtherCeltsSponsored) | - ((Program.isStudentLed == False) & - (Program.isBonnerScholars == False)))) + celtsLabor = list(Event.select() + .where(Event.term == term, Event.deletionDate == None, Event.isLaborOnly == True) .order_by(Event.startDate, Event.timeStart, Event.id) .execute()) - - return otherEvents + return celtsLabor def getUpcomingEventsForUser(user, asOf=datetime.now(), program=None): """ diff --git a/app/logic/landingPage.py b/app/logic/landingPage.py index 2def7672a..b3976ddd4 100644 --- a/app/logic/landingPage.py +++ b/app/logic/landingPage.py @@ -31,11 +31,13 @@ def getManagerProgramDict(user): managerProgramDict[row.program]["managers"] = f'{managerProgramDict[row.program]["managers"]}, {row.user.firstName} {row.user.lastName}' return managerProgramDict -def getActiveEventTab(programID): - program = Program.get_by_id(programID) +def getActiveEventTab(programID=None): + try: + program = Program.get_by_id(programID) + except Program.DoesNotExist: + return "celtsLabor" + if program.isBonnerScholars: return "bonnerScholarsEvents" - elif program.isStudentLed: - return "studentLedEvents" else: - return "otherEvents" + return "volunteerOpportunities" diff --git a/app/logic/loginManager.py b/app/logic/loginManager.py index 4061aaa17..f21a8815e 100644 --- a/app/logic/loginManager.py +++ b/app/logic/loginManager.py @@ -16,7 +16,7 @@ def logout(): url ="/" if app.config['use_shibboleth']: - url = "/Shibboleth.sso/Logout" + url = "/Shibboleth.sso/Logout?return=https://login.berea.edu/idp/profile/Logout" return url def getUsernameFromEnvironment(): diff --git a/app/logic/transcript.py b/app/logic/transcript.py index 3e918c92b..dcbfbd3b7 100644 --- a/app/logic/transcript.py +++ b/app/logic/transcript.py @@ -13,14 +13,15 @@ def getProgramTranscript(username): """ - Returns a dictionary with programs as keys and a list of service events and hours earned as values for the given user. + Returns a dictionary with programs as keys and a list of service or bonner events and hours earned as values for the given user. """ # Add up hours earned in a term for each program they've participated in EventData = (Event.select(Event, fn.SUM(EventParticipant.hoursEarned).alias("hoursEarned")) - .join(EventParticipant) + .join(EventParticipant).switch() + .join(Program) .where(EventParticipant.user == username, - Event.isService == True, + Event.isService | Program.isBonnerScholars, Event.deletionDate == None, Event.isCanceled == False) .group_by(Event.program, Event.term) diff --git a/app/models/program.py b/app/models/program.py index 16ece24c0..fd76eb4d1 100644 --- a/app/models/program.py +++ b/app/models/program.py @@ -9,7 +9,6 @@ class Program(baseModel): bereaUrl = TextField(null=True) programDescription = TextField() partner = CharField(null=True) - isStudentLed = BooleanField(default=False) isBonnerScholars = BooleanField(default=False) isOtherCeltsSponsored = BooleanField(default=False) contactName = CharField(null=True,default='') diff --git a/app/models/user.py b/app/models/user.py index 748568794..d901d26b0 100644 --- a/app/models/user.py +++ b/app/models/user.py @@ -26,6 +26,7 @@ def __init__(self,*args, **kwargs): self._pmCache = {} self._bsCache = None + self._laborCache = None self._isProgramManagerCache = None @property @@ -46,7 +47,19 @@ def isBonnerScholar(self): self._bsCache = BonnerCohort.select().where(BonnerCohort.user == self).exists() return self._bsCache - + + @property + def hasCurrentCeltsLabor(self): + if self._laborCache is None: + from app.models.celtsLabor import CeltsLabor + from app.models.term import Term + self._laborCache = (CeltsLabor.select() + .join(Term) + .where(CeltsLabor.user == self, + CeltsLabor.term.isCurrentTerm == True) + .exists()) + return self._laborCache + @property def fullName(self): return f"{self.firstName} {self.lastName}" diff --git a/app/scripts/import_users.py b/app/scripts/import_users.py index 54f3c6ba7..387c16c3c 100644 --- a/app/scripts/import_users.py +++ b/app/scripts/import_users.py @@ -156,7 +156,7 @@ def addToDb(userList): lastName=user['lastName'], email=user['email'], major=user['major'], - rawClassLevel=user['classLevel'], + rawClassLevel=user['rawClassLevel'], cpoNumber=user['cpoNumber'] ).where(User.bnumber == user['bnumber'])).execute() logger.debug(f" Updated user {user['bnumber']}") @@ -168,7 +168,7 @@ def addToDb(userList): except Exception as e: logger.error(f" Failed to insert or update user {user['bnumber']}: {e}") - return [usersAdded, usersUpdated] + return [usersAdded, usersUpdated] def getFacultyStaffData(): """ @@ -198,7 +198,7 @@ def getFacultyStaffData(): "isFaculty": True, "isStaff": False, "major": None, - "rawCassLevel": None, + "rawClassLevel": None, "cpoNumber": row[5].strip(), } for row in c.execute('select * from STUSTAFF') diff --git a/app/static/css/createEvent.css b/app/static/css/createEvent.css index 08a537c87..e9cfd7ae0 100644 --- a/app/static/css/createEvent.css +++ b/app/static/css/createEvent.css @@ -55,3 +55,8 @@ background-color: #fff } +.fixedButton { + position: fixed; + right: 1.5rem; + bottom: 1.5rem; +} diff --git a/app/static/css/userProfile.css b/app/static/css/userProfile.css index 7136d62cc..561e2fc7f 100644 --- a/app/static/css/userProfile.css +++ b/app/static/css/userProfile.css @@ -1,3 +1,11 @@ +div.profile-links { + margin-bottom:1em; +} +div.profile-links a:not(:first-child) { + margin-left: 5px; + float: right; +} + .banTable tr { vertical-align: middle; } @@ -10,27 +18,24 @@ background-color: #cdebaa; } -dt { +.bonnerNotes dt { font-weight: normal; padding: 5px; padding-bottom:0px; } -dd { +.bonnerNotes dd { font-size:.9em; padding: 5px; padding-top:0px; } -#printButton{ - font-size: 15px; -} - .bonnerNotes dd:nth-of-type(even) { background-color:#f2f2f2; } .bonnerNotes dt:nth-of-type(even) { background-color:#f2f2f2; } .bonnerNotes dd:nth-of-type(odd) { background-color:#FFF; } .bonnerNotes dt:nth-of-type(odd) { background-color:#FFF; } -#togglebuttonuser { +#toggleMinorInterest { position: relative; left: 12px; width: 95%; } + diff --git a/app/static/js/base.js b/app/static/js/base.js index 5ddff83b0..3ef308142 100644 --- a/app/static/js/base.js +++ b/app/static/js/base.js @@ -111,7 +111,7 @@ function setupPhoneNumber(editButtonId, phoneInput){ function processPhoneSetup (editButtonId, phoneInputId, username, action) { if (action == "edit" ) { - $(editButtonId).html("Save"); + $(editButtonId).html('Save'); } else if (action == "save" ) { validatePhoneNumber(editButtonId, phoneInputId, username) @@ -119,7 +119,7 @@ function processPhoneSetup (editButtonId, phoneInputId, username, action) { else if (action == "restore"){ var phoneInput = $(phoneInputId); $(phoneInputId).val(phoneInput.attr("data-value")) - $(editButtonId).html('Edit'); + $(editButtonId).text('Edit'); } } diff --git a/app/static/js/bonnerManagement.js b/app/static/js/bonnerManagement.js index 1af4f7059..19130df95 100644 --- a/app/static/js/bonnerManagement.js +++ b/app/static/js/bonnerManagement.js @@ -218,13 +218,27 @@ function addRequirementsRowHandlers() { $(".frequency-select").change(); // add this one after we trigger the first change event $(".frequency-select").change(function(e) { - enableSave(); }); // detect changes so we can enable saving $(".required-select").change(function(e) { - enableSave(); }); + $("#requirements input").on("input blur", function(e) { + if($(this).val() == "") { + this.setCustomValidity('Please enter a name.'); + this.reportValidity(); + $(".saveBtn").attr("disabled", "disabled"); + $("#reqAdd").attr("disabled", "disabled"); + } else { + $(".saveBtn").removeAttr("disabled"); + $("#reqAdd").removeAttr("disabled"); + this.setCustomValidity(''); + this.reportValidity(); + enableSave(); + } + }); + + // handle invalid and valid entries $("#requirements input").keyup(function(e) { diff --git a/app/static/js/createEvents.js b/app/static/js/createEvents.js index e2e697221..1a734a73c 100644 --- a/app/static/js/createEvents.js +++ b/app/static/js/createEvents.js @@ -167,6 +167,23 @@ function verifyRepeatingFields(){ return isEmpty } +/* + * Update the position of the Save button so that it is always on the screen as + * the window scrolls and changes size. The position on the screen is determined + * in the fixedButton class */ +const saveBtn = $('#saveButton'); +function updateSavePosition() { + const originalTop = saveBtn.parent()[0].getBoundingClientRect().top; // relative to scroll + const buttonHeight = 40; + + if ($(window).height() < originalTop + buttonHeight) { + saveBtn.addClass('fixedButton'); + } else { + saveBtn.removeClass('fixedButton'); + } +} +$(window).on('scroll resize load', updateSavePosition); + $('#saveSeries').on('click', function(e) { e.preventDefault(); // Prevent default form submission at the start enableLiveCustomValidityClearing([".multipleOfferingNameField"]) @@ -393,6 +410,9 @@ function updateOfferingsTable() { "" ); }); + + //recalculate the save button + updateSavePosition() } //visual date formatting for multi-event table @@ -802,4 +822,3 @@ function handleTimeFormatting(timeArray){ - diff --git a/app/static/js/eventList.js b/app/static/js/eventList.js index a1dbd3f8b..e776503f5 100644 --- a/app/static/js/eventList.js +++ b/app/static/js/eventList.js @@ -35,8 +35,10 @@ $(document).ready(function(){ var tableRows = $(".showlist"); if (isChecked) { tableRows.show(); + $(".no-upcoming").hide() } else { tableRows.hide(); + $(".no-upcoming").show() } } }); @@ -84,21 +86,21 @@ function updateIndicatorCounts(isChecked){ toggleState: isChecked ? "checked" : "unchecked", }, success: function(eventsCount) { - const studentLedEventsCount = Number(eventsCount.studentLedEventsCount); + const volunteerOpportunitiesCount = Number(eventsCount.volunteerOpportunitiesCount); const trainingEventsCount = Number(eventsCount.trainingEventsCount); const engagementEventsCount = Number(eventsCount.engagementEventsCount); const bonnerEventsCount = Number(eventsCount.bonnerEventsCount); - const otherEventsCount = Number(eventsCount.otherEventsCount); + const celtsLaborCount = Number(eventsCount.celtsLaborCount); const toggleStatus = eventsCount.toggleStatus; $("#viewPastEventsToggle").prop(toggleStatus, true); // use ternary operators to populate the tab with a number if there are events, and clear the count if there are none - studentLedEventsCount > 0 ? $("#studentLedEvents").html(`Student-Led Services (${studentLedEventsCount})`) : $("#studentLedEvents").html(`Student-Led Services`) + volunteerOpportunitiesCount > 0 ? $("#volunteerOpportunities").html(`Volunteer Opportunities (${volunteerOpportunitiesCount})`) : $("#volunteerOpportunities").html(`Volunteer Opportunities`) trainingEventsCount > 0 ? $("#trainingEvents").html(`Trainings (${trainingEventsCount})`) : $("#trainingEvents").html(`Trainings`) engagementEventsCount > 0 ? $("#engagementEvents").html(`Education and Engagement (${engagementEventsCount})`) : $("#engagementEvents").html('Education and Engagement') bonnerEventsCount > 0 ? $("#bonnerScholarsEvents").html(`Bonner Scholars (${bonnerEventsCount})`) : $("#bonnerScholarsEvents").html(`Bonner Scholars`) - otherEventsCount > 0 ? $("#otherEvents").html(`Other Events (${otherEventsCount})`) : $("#otherEvents").html(`Other Events`) + celtsLaborCount > 0 ? $("#celtsLabor").html(`Celts Labor (${celtsLaborCount})`) : $("#celtsLabor").html(`Celts Labor`) }, error: function(request, status, error) { console.log(status,error); diff --git a/app/static/js/landingPage.js b/app/static/js/landingPage.js index 539c82def..000f18766 100644 --- a/app/static/js/landingPage.js +++ b/app/static/js/landingPage.js @@ -21,7 +21,7 @@ $(document).ready(function(){ url: "/goToEventsList/"+programID, type: "GET", success: function(response) { - if (response.activeTab === "studentLedEvents"){ + if (response.activeTab === "volunteerOpportunities"){ window.location.href += "eventsList/"+term+"/"+response.activeTab+"/"+programID } else { window.location.href += "eventsList/"+term+"/"+response.activeTab diff --git a/app/static/js/userProfile.js b/app/static/js/userProfile.js index ae52653da..7fe6a2c8b 100644 --- a/app/static/js/userProfile.js +++ b/app/static/js/userProfile.js @@ -1,6 +1,15 @@ $(document).ready(function(){ - //Load flash message from sessionStorage, if any - msgFlash(); + + $("#checkDietRestriction").on("change", function() { + let norestrict = $(this).is(':checked'); + if (norestrict) { + $("#dietContainer").hide(); + $("#diet").val("No dietary restrictions"); + + } else { + $("#dietContainer").show(); + } + }); $("#checkIsInterest").on("change", function() { let username = $(this).data('username') @@ -26,7 +35,6 @@ $(document).ready(function(){ }); }) - $("#actions").on("change", changeAction) $("#phoneInput").inputmask('(999)-999-9999'); $(".notifyInput").click(function updateInterest(){ var programID = $(this).data("programid"); @@ -82,23 +90,6 @@ $(document).ready(function(){ }, 500); } - function changeAction(e){ - let profileAction = $(this).val() - let username = $(this).data('username') - if (profileAction == "Emergency Contact"){ - window.location.href = `/profile/${username}/emergencyContact` - } else if (profileAction == "Insurance Information"){ - window.location.href = `/profile/${username}/insuranceInfo` - } else if(profileAction == "Print Travel Form"){ - window.open(`/profile/${username}/travelForm`, '_blank') - } else if (profileAction == "View Service Transcript"){ - window.location.href = `/profile/${username}/serviceTranscript` - } else if (profileAction == "Manage CCE Minor") { - window.location.href = `/profile/${username}/cceMinor` - } - $(this).val('') - } - // This function is to disable all the dates before current date in the ban modal End Date picker $(function(){ var banEndDatepicker = $("#banEndDatepicker"); @@ -328,35 +319,47 @@ $(document).ready(function(){ }); setupPhoneNumber("#updatePhone", "#phoneInput") - $("#checkDietRestriction").on("change", function() { - let norestrict = $(this).is(':checked'); - if (norestrict) { - $("#dietContainer").hide(); - $("#diet").val("No dietary restrictions"); - } else { - $("#dietContainer").show(); - $("#diet").val(""); - } - }); - $(".saveDiet").on('click', function() { + // Dietary Restrictions + function saveDiet() { let data = { dietInfo: $("#diet").val(), - user: $(this).data("user") - } + user: $("#diet").data("user") + }; + $.ajax({ type: "POST", url: "/updateDietInformation", data: data, - success: function(s){ - reloadWithAccordion("dietaryInformation"); - }, - }) + success: function(s) { + $('#saveNotification').fadeIn('fast').delay(1000).fadeOut('slow'); + } + }); + } + + $("#checkDietRestriction").on("change", function() { + let norestrict = $(this).is(':checked'); + if (norestrict) { + $("#dietContainer").hide(); + $("#diet").val("No dietary restrictions"); + saveDiet() + } + + var typingTimer; + var saveInterval = 1000; //milliseconds + + $("#diet").on('input', function() { + clearTimeout(typingTimer); + $('#check-icon').remove(); + + typingTimer = setTimeout(saveDiet, saveInterval); + }); }); -}); +}); // end document.ready() -function updateManagers(el, volunteerUsername ){// retrieve the data of the student staff and program id if the boxes are checked or not +// Update program manager status +function updateManagers(el, volunteerUsername ) { let programId=$(el).attr('data-programid'); let programName = $(el).attr('data-programName') let name = $(el).attr('data-name') @@ -383,6 +386,4 @@ function updateManagers(el, volunteerUsername ){// retrieve the data of the stud console.log(error, status) } }) - - -} \ No newline at end of file +} diff --git a/app/templates/admin/bonnerManagement.html b/app/templates/admin/bonnerManagement.html index d497980a0..ebe02bf95 100644 --- a/app/templates/admin/bonnerManagement.html +++ b/app/templates/admin/bonnerManagement.html @@ -147,7 +147,6 @@

- {% for term in futureTerms %} + {% for term in termList %} {% endfor %} @@ -325,7 +325,7 @@

{{page_title}}

{% endif %} - + diff --git a/app/templates/events/eventList.html b/app/templates/events/eventList.html index cdb5586e2..ff0dbf522 100644 --- a/app/templates/events/eventList.html +++ b/app/templates/events/eventList.html @@ -27,9 +27,9 @@

Events List for {{selectedTerm.description}}

+ + {% endfor %} +
{% if user.isAdmin %} @@ -50,27 +50,37 @@

Events List for {{selectedTerm.description}}

@@ -80,7 +90,7 @@

Events List for {{selectedTerm.description}}

- {% if type != "bonner" and type != "studentLed" %} + {% if type != "bonner" and type != "volunteerOpportunities" %} {% endif %} @@ -98,7 +108,7 @@

Events List for {{selectedTerm.description}}

{% else %} {% endif %} - {% if type != "bonner" and type != "studentLed" %} + {% if type != "bonner" and type != "volunteerOpportunities" %} {% endif %} {% if user.isCeltsAdmin or user.isProgramManagerFor(event.program)%} @@ -125,7 +135,7 @@

Events List for {{selectedTerm.description}}

{% else %} {% set isPastStart = "false" %} {% endif %} - {% set defaultTemplate = "Test" %} {# The default template is set via the purpose column of the corresponding template #} + {% set defaultTemplate = "Test" %} {% set btn_class = 'btn-secondary' if event.isPastStart or event.isCanceled else 'btn-primary' %} {% set btn_text = 'Email' if event.isPastStart or event.isCanceled else 'Invite' %} - {% set colspan_value = 5 if type == 'bonner' or type == 'studentLed' else 6 %} - + {% set colspan_value = 5 if type == 'bonner' or type == 'volunteerOpportunities' else 6 %} + {% endif %}
ProgramEvent Name
{{event.program.programName}}
There are no upcoming events for this programThere are no upcoming events for this program
-{% elif type != "studentLed" %} -

There are no events for this program

+{% elif type != "volunteerOpportunities" %} + {% set typemap = { + 'training': "training", + 'education and engagement': "education and engagement", + 'bonner': "Bonner Scholars", + 'celtsLabor': "labor-only", + } + %} +

There are no {{typemap[type]}} events for this term.

{% endif %} {% endmacro %} - -
-
- {% if studentLedEvents %} +
+ {% if volunteerOpportunities %}
- {% for program,events in studentLedEvents.items() %} + {% for program,events in volunteerOpportunities.items() %}
@@ -187,13 +202,13 @@

Events List for {{selectedTerm.description}}

class="accordion-collapse collapse {{'show' if programID == program.id else ''}}" aria-labelledby="accordion__header_{{program}}" data-bs-parent="#categoryAccordion"> - {{createTable(events, "studentLed")}} + {{createTable(events, "volunteerOpportunities")}}
{% endfor %}
{% else %} - There are no events for this program +

There are no events that earn service for this term.

{% endif %}
@@ -205,8 +220,8 @@

Events List for {{selectedTerm.description}}

{{createTable(bonnerEvents, "bonner")}}
-
- {{createTable(otherEvents, "other")}} +
+ {{createTable(celtsLabor, "celtsLabor")}}
{% include 'events/emailModal.html' %} diff --git a/app/templates/events/templateSelector.html b/app/templates/events/templateSelector.html index 7f74185b2..ec2a2825e 100644 --- a/app/templates/events/templateSelector.html +++ b/app/templates/events/templateSelector.html @@ -2,6 +2,7 @@ {% extends "base.html"%} {% block app_content %}

Create an Event

+{% if templates %}
@@ -14,16 +15,19 @@

Shortcuts

+{% endif %}
+ {% if templates %}

Programs

-
+ {% endif %} +
{% for program in programs %} - {{ program.programName }} + {{ program.programName }} {% endfor %}
-
-{% endblock %} \ No newline at end of file +
+{% endblock %} \ No newline at end of file diff --git a/app/templates/main/userProfile.html b/app/templates/main/userProfile.html index 5fa81a99d..6fee84932 100644 --- a/app/templates/main/userProfile.html +++ b/app/templates/main/userProfile.html @@ -10,7 +10,6 @@ {% block styles %} {{super()}} - {% endblock %} {% block app_content %} @@ -18,14 +17,10 @@

{{volunteer.firstName}} {{volunteer.lastName}}

-
+
{{volunteer.bnumber}}
{{volunteer.email}}
-
- - Edit -
 
{% if volunteer.major -%} @@ -41,28 +36,21 @@

{{volunteer.firstName}} {{volunteer.lastName}}

{%endif%}
- -
- -
-
- -
-
+ +
@@ -81,13 +69,13 @@

here for more information.

-
+

{% endif %} + type="checkbox" id="checkIsInterest" name="isInterested" {%if volunteer.minorInterest %} checked {% endif %} />

@@ -115,9 +103,11 @@

{% set show = "show" if visibleAccordion == "upcoming" or (not visibleAccordion and volunteer.username == g.current_user.username) else ""%}
-
+
- {% if upcomingEvents|length > 0 %} + {% if upcomingEvents|length == 0 %} +
You have no upcoming events.
+ {% else %} @@ -136,7 +126,6 @@

{% if event.isPastEnd %} {% set eventStatus = 'past-event' %} {% endif %} -

@@ -153,8 +142,6 @@

{% endif %}

{% endfor %} - {% else %} -
You have no upcoming events.
{% endif %}
Program Event Name
{{event.program.programName if event.program else '--'}} {{event.name}}
@@ -189,12 +176,11 @@

{{event.startDate.strftime('%m/%d/%Y')}} {% endfor %} - {% else %} + {% else %}

You have not participated in any events.
{% endif %}
- View Service Transcript

@@ -322,62 +308,76 @@
{{volunteer.firstName}} {{volunteer.lastName}} is the manager of:
- {% if g.current_user.isCeltsAdmin %} -
-

- {% set focus = "open" if visibleAccordion == "background" else "collapsed" %} - -

- {% set show = "show" if visibleAccordion == "background" else "" %} -
-
-
- - - - - {% if g.current_user.isCeltsAdmin%} - - - - {% endif %} - - - - {% for bgType in backgroundTypes %} - - - {% if g.current_user.isCeltsAdmin %} - - - +{% if g.current_user.isCeltsAdmin or g.current_user.isCeltsStudentStaff %} +
+

+ {% set focus = "open" if visibleAccordion == "background" else "collapsed" %} + +

+ {% set show = "show" if visibleAccordion == "background" else "" %} +
+
+
+
DescriptionStatusDate
-
{{bgType.description}}
-
    - {% for bgStatus in allBackgroundHistory[bgType.id] %} -
  • - {{bgStatus.backgroundCheckStatus}}: {{bgStatus.dateCompleted.strftime("%m/%d/%Y")}} - -
  • - {% endfor %} -
-
- -
+ + + + {% if g.current_user.isCeltsAdmin %} + + + + {% endif %} + + + + {% set ns = namespace(backgroundChecksExist=false) %} + {% for bgType in backgroundTypes %} + {% if not g.current_user.isCeltsAdmin and not allBackgroundHistory[bgType.id] %} + {% continue %} + {% endif %} + {% set ns.backgroundChecksExist = true %} + + + {% if g.current_user.isCeltsAdmin %} + + + {% endif %} - {%endfor%} - -
DescriptionStatusDate
+
{{bgType.description}}
+
    + {% for bgStatus in allBackgroundHistory[bgType.id] %} +
  • + {{bgStatus.backgroundCheckStatus}}: {{bgStatus.dateCompleted.strftime("%m/%d/%Y")}} + {% if g.current_user.isCeltsAdmin %} + + {% endif %} +
  • + {% endfor %} +
+
+ + + +
-
+ {% endfor %} + {% if not ns.backgroundChecksExist %} + + No background checks have been submitted. + + {% endif %} + +
- {% endif %} +
+{% endif %} @@ -448,38 +448,6 @@

- -
-

- {% set focus = "open" if visibleAccordion == "dietaryInformation" else "collapsed" %} - -

- {% set show = "show" if visibleAccordion == "dietaryInformation" else "" %} -
-
-
-
-
-
- - -
-
- - -
-
-
- -
-
-
-
-
- - {% set canViewBonner = (g.current_user.isCeltsAdmin or g.current_user == volunteer) %} {% if volunteer.isBonnerScholar and canViewBonner %} @@ -510,7 +478,6 @@
{{address}} Upcoming Bonner Events

No RSVPs for any upcoming Bonner events.

{% endif %}
- {# Bonner Notes #}
Notes
{% set bonnerNotes = profileNotes|selectattr("isBonnerNote")|list %} @@ -526,7 +493,6 @@
Notes
{% endif %} - {# Bonner Requirements #}
Requirement Progress
@@ -548,8 +514,8 @@
Requirement Progress
{% endif %} - + -