diff --git a/app/__init__.py b/app/__init__.py index 3c7058611..d17f73570 100755 --- a/app/__init__.py +++ b/app/__init__.py @@ -77,13 +77,8 @@ def load_user(): from app.logic.loginManager import getCurrentTerm @app.before_request def load_currentTerm(): - # An exception handles both current_term not being set and a mismatch between models - try: - g.current_term = dict_to_model(Term, session['current_term']) - except Exception as e: - term = getCurrentTerm() - session['current_term'] = model_to_dict(term) - g.current_term = term + # Query the current term from the database on each request to avoid stale session data + g.current_term = getCurrentTerm() import datetime @app.before_request diff --git a/app/config/default.yml b/app/config/default.yml index b480cc7d2..172d0afe0 100644 --- a/app/config/default.yml +++ b/app/config/default.yml @@ -7,6 +7,8 @@ support_email_contact: "support@bereacollege.onmicrosoft.com" show_queries: True test_entry: "Default" +lsf_url: "USE local-override.yml" + db: name: "celts" host: "db" diff --git a/app/controllers/admin/routes.py b/app/controllers/admin/routes.py index 5fc6ee7be..caefd8a2e 100644 --- a/app/controllers/admin/routes.py +++ b/app/controllers/admin/routes.py @@ -373,7 +373,7 @@ def eventDisplay(eventId): currentEventRsvpAmount = getEventRsvpCount(event.id) - userParticipatedTrainingEvents = getParticipationStatusForTrainings(eventData['program'], [g.current_user], g.current_term) + userParticipatedTrainingEvents = getParticipationStatusForTrainings(eventData['program'], [g.current_user], g.current_term, includeFutureEvents = False) return render_template("events/eventView.html", eventData=eventData, diff --git a/app/controllers/main/routes.py b/app/controllers/main/routes.py index 8424bd6ce..9c5d93b5c 100644 --- a/app/controllers/main/routes.py +++ b/app/controllers/main/routes.py @@ -24,7 +24,6 @@ from app.models.programManager import ProgramManager from app.models.backgroundCheck import BackgroundCheck from app.models.emergencyContact import EmergencyContact -from app.models.eventParticipant import EventParticipant from app.models.courseInstructor import CourseInstructor from app.models.backgroundCheckType import BackgroundCheckType @@ -39,7 +38,7 @@ from app.logic.landingPage import getManagerProgramDict, getActiveEventTab from app.logic.minor import toggleMinorInterest, declareMinorInterest, getCommunityEngagementByTerm, getEngagementTotal from app.logic.participants import hasGoneToTraining, unattendedRequiredEvents, getParticipationStatusForTrainings, checkUserRsvp, addPersonToEvent -from app.logic.users import addUserInterest, isBannedFromEvent, removeUserInterest, banUser, unbanUser, isEligibleForProgram, getUserBGCheckHistory, addProfileNote, deleteProfileNote, updateDietInfo, trainedParticipants +from app.logic.users import * @main_bp.route('/logout', methods=['GET']) def redirectToLogout(): @@ -234,8 +233,8 @@ def viewUsersProfile(username): managersProgramDict = getManagerProgramDict(g.current_user) managersList = [id[1] for id in managersProgramDict.items()] totalSustainedEngagements = getEngagementTotal(getCommunityEngagementByTerm(volunteer)) - handbookOverdue = getHandbookStatus(volunteer) + handbookOverdue = getHandbookStatus(volunteer) training = hasGoneToTraining(g.current_user, g.current_term) return render_template ("/main/userProfile.html", @@ -390,22 +389,34 @@ def eventTravelForm(eventID): userList = userList, ) -@main_bp.route('/profile/addNote', methods=['POST']) +@main_bp.route("/profile/addNote", methods=["POST"]) def addNote(): - """ - This function adds a note to the user's profile. - """ - postData = request.form try: - note = addProfileNote(postData["visibility"], postData["bonner"] == "yes", postData["noteTextbox"], postData["username"]) + noteData = getProfileNoteData( + request.form, + includeUsername=True, + ) + addProfileNote(**noteData) flash("Successfully added profile note", "success") - return redirect(url_for("main.viewUsersProfile", username=postData["username"])) - except Exception as e: - print("Error adding note", e) + except Exception as error: + print("Error adding profile note:", error) flash("Failed to add profile note", "danger") - return "Failed to add profile note", 500 - - + return str(error), 500 + return "success" +@main_bp.route("//editNote", methods=["POST"]) +def editProfileNote(username): + try: + noteData = getProfileNoteData( request.form, includeId=True, ) + profileNote = ProfileNote.get_by_id(noteData["profileNoteID"] ) + if (profileNote.user.username != username or (profileNote.note.createdBy != g.current_user and not g.current_user.isCeltsAdmin) ): + abort(403) + updateProfileNote(**noteData) + flash("Successfully updated profile note", "success") + except Exception as error: + print("Error updating profile note:", error) + flash("Failed to update profile note", "danger") + return str(error), 500 + return "success" @main_bp.route('//deleteNote', methods=['POST']) def deleteNote(username): """ diff --git a/app/controllers/minor/routes.py b/app/controllers/minor/routes.py index 64575ba4f..099ee6b64 100644 --- a/app/controllers/minor/routes.py +++ b/app/controllers/minor/routes.py @@ -29,6 +29,9 @@ def viewCceMinor(username): """ Load minor management page with community engagements and summer experience """ + if not (g.current_user.isAdmin or g.current_user.username == username or g.current_user.isCeltsStudentStaff): + return abort(403) + sustainedEngagementByTerm = getCommunityEngagementByTerm(username) activeTab = request.args.get("tab", "sustainedCommunityEngagements") diff --git a/app/logic/celtsLabor.py b/app/logic/celtsLabor.py index c32bc2eae..6b8766f59 100644 --- a/app/logic/celtsLabor.py +++ b/app/logic/celtsLabor.py @@ -97,13 +97,16 @@ def refreshCeltsLaborRecords(laborDict): for positionTitle, termNames in value.items(): for term in termNames: termTableMatch = Term.select() - if term[0].isalpha(): + isAcademicYear = False + + if term[0].isalpha(): # Fall, Spring, Summer termTableMatch = termTableMatch.where(Term.description == term) - else: + else: # e.g., 2025-2026 termTableMatch = termTableMatch.where(Term.academicYear == term, Term.description % "Fall%") + isAcademicYear = True + try: laborTerm = termTableMatch.get() - isAcademicYear = not laborTerm.isSummer celtsLabor.append({"user": key, "positionTitle": positionTitle, "term": laborTerm, @@ -117,14 +120,13 @@ def refreshCeltsLaborRecords(laborDict): def getCeltsLaborHistory(volunteer): laborHistoryList = list(CeltsLabor.select(CeltsLabor.positionTitle, + CeltsLabor.id, + CeltsLabor.isAcademicYear, Term.description, Term.academicYear, Term.isSummer) .join(Term, on=(CeltsLabor.term == Term.id)) - .where(CeltsLabor.user == volunteer)) - - laborHistoryDict= {} - for position in laborHistoryList: - laborHistoryDict[position.positionTitle] = position.term.description if position.term.isSummer else position.term.academicYear + .where(CeltsLabor.user == volunteer) + .order_by(Term.termOrder.asc())) - return laborHistoryDict + return [(p.positionTitle, f"AY {p.term.academicYear}" if p.isAcademicYear else p.term.description) for p in laborHistoryList] diff --git a/app/logic/events.py b/app/logic/events.py index 2577aa85a..ae02cf477 100644 --- a/app/logic/events.py +++ b/app/logic/events.py @@ -411,22 +411,32 @@ def getParticipatedEventsForUser(user): :return: A list of Event objects """ - # Does this handle labor only and/or includes labor events? - participatedEvents = (Event.select(Event, Program.programName, Case(None, ( - ((Event.allowsLabor | Event.name.contains("Labor")) & Event.isService, "Labor & Volunteer"), - ((Event.allowsLabor | Event.isLaborOnly | Event.name.contains("Labor")), "Labor"), - (Event.isService, "Volunteer")), "Attendee").alias("participatedType")) + eventName = fn.LOWER(Event.name) + checkIfLaborMeeting = eventName.contains("labor meeting") + + participatedEvents = (Event.select(Event, + Program.programName, + Case(None, + ( + ((Event.allowsLabor | Event.name.contains("Labor")) & Event.isService, "Labor & Volunteer"), + ((Event.allowsLabor | Event.isLaborOnly | Event.name.contains("Labor")), "Labor"), + (Event.isService, "Volunteer") + ), + "Attendee").alias("participatedType"), + EventParticipant.hoursEarned + ) .join(Program, JOIN.LEFT_OUTER).switch() .join(EventParticipant) .where(EventParticipant.user == user, Event.isAllVolunteerTraining == False, Event.deletionDate == None, Event.isCeltsTraining == False) .order_by(Event.startDate, Event.name)) - allVolunteer = (Event.select(Event, "", Value("Volunteer").alias("participatedType")) + + allVolunteer = (Event.select(Event, "", Value("Volunteer").alias("participatedType"), Value(0).alias("hoursEarned")) .join(EventParticipant) .where(Event.isAllVolunteerTraining == True, EventParticipant.user == user)) union = participatedEvents.union_all(allVolunteer) - unionParticipationWithVolunteer = list(union.select_from(union.c.id, union.c.programName, union.c.startDate, union.c.name, union.c.participatedType).order_by(union.c.startDate, union.c.name).execute()) + unionParticipationWithVolunteer = list(union.select_from(union.c.id, union.c.isService, union.c.programName, union.c.startDate, union.c.name, union.c.participatedType, union.c.hoursEarned).order_by(union.c.startDate, union.c.name).execute()) return unionParticipationWithVolunteer def validateNewEventData(data): diff --git a/app/logic/participants.py b/app/logic/participants.py index cfa28cc88..82a6c5e10 100644 --- a/app/logic/participants.py +++ b/app/logic/participants.py @@ -116,7 +116,7 @@ def getEventParticipants(event): return [p for p in eventParticipants] -def getParticipationStatusForTrainings(program, userList, term, returnStr = True): +def getParticipationStatusForTrainings(program, userList, term, includeFutureEvents = True): """ This function returns a dictionary of all trainings for a program and whether the current user participated in them. @@ -130,7 +130,10 @@ def getParticipationStatusForTrainings(program, userList, term, returnStr = True .join(EventRsvp, JOIN.LEFT_OUTER).switch() .join(Term) .where(isRelevantTraining, (Event.isCanceled != True)).order_by(Event.startDate)) - + if not includeFutureEvents: + programTrainings = programTrainings.where( + (Event.startDate < datetime.now().date()) | + ((Event.startDate == datetime.now().date()) & (Event.timeStart <= datetime.now().time()))) # Create a dictionary where the keys are trainings and values are a set of those who attended trainingData = defaultdict(set) for training in programTrainings: @@ -151,7 +154,7 @@ def getParticipationStatusForTrainings(program, userList, term, returnStr = True for user in userList: if training.name not in userParticipationStatus[user.username] or user.username in attendeeList: userParticipationStatus[user.username][training.name] = [training, user.username in attendeeList] - if returnStr: + if includeFutureEvents: return {user.username: list(userParticipationStatus[user.username].values()) for user in userList} else: return {user: list(userParticipationStatus[user.username].values()) for user in userList} @@ -178,7 +181,7 @@ def getTrainingsForInterestedParticipants(programID, interestedUsers): Gracefully handles multiple trainings of the same type (e.g., two All Volunteers Trainings) """ - trainedUsers = getParticipationStatusForTrainings(programID, interestedUsers, g.current_term, returnStr = False) + trainedUsers = getParticipationStatusForTrainings(programID, interestedUsers, g.current_term, includeFutureEvents = False) now = datetime.now() bannedUsers = list(User .select(User.username) @@ -207,7 +210,7 @@ def getTrainingsForInterestedParticipants(programID, interestedUsers): for event in trainedUsers[interestedUser]: if not event[1]: # they didn't attend this training continue - elif event[0].isAllVolunteerTraining: # They attended AVT + elif event[0].isAllVolunteerTraining or event[0].isCeltsTraining: # They attended AVT or ACT trainedAndInterested[interestedUser.username]["allVolunteer"] = True elif event[0].isTraining: # They attended the Program-specific training trainedAndInterested[interestedUser.username]["programSpecific"] = True @@ -241,7 +244,10 @@ def getParticipantsForProgramForAY(program, academicYear): .where(Program.id == program, Term.academicYear == academicYear, User.hasGraduated == False, - EventParticipant.hoursEarned > 0) + EventParticipant.hoursEarned > 0, + # Only include currently enrolled undergraduate students. + # This prevents alumni and past participants from appearing in the older reports. + User.rawClassLevel.in_(["Freshman", "Sophomore", "Junior", "Senior"])) .distinct() ) return participants diff --git a/app/logic/searchUsers.py b/app/logic/searchUsers.py index a4bc829a3..d7511ee37 100644 --- a/app/logic/searchUsers.py +++ b/app/logic/searchUsers.py @@ -1,3 +1,4 @@ +from peewee import fn from playhouse.shortcuts import model_to_dict from app.models.user import User def searchUsers(query, category=None): @@ -6,15 +7,19 @@ def searchUsers(query, category=None): MySQL LIKE is case insensitive ''' - # add wildcards to each piece of the query splitSearch = query.strip().split() - firstName = splitSearch[0] + "%" - lastName = " ".join(splitSearch[1:]) +"%" - - if len(splitSearch) == 1: # search for query in first OR last name - searchWhere = (User.firstName ** firstName | User.lastName ** firstName | User.username ** splitSearch) - else: # search for first AND last name - searchWhere = (User.firstName ** firstName & User.lastName ** lastName) + if not splitSearch: + return User.select().where(False) + searchWhere = None + for namePart in splitSearch: + nameSearch = namePart + "%" + # This individual search term can match the user's first name, last name, or username. + namePartWhere = (User.firstName.contains(namePart) | User.lastName.contains(namePart) | User.username.contains(namePart)) + # For the first search term, initialize the WHERE condition. + if searchWhere is None: + searchWhere = namePartWhere + else: + searchWhere &= namePartWhere # Require every search term to match at least one of the first name, last name, or username fields. if category == "instructor": userWhere = (User.isFaculty | User.isStaff) @@ -26,12 +31,21 @@ def searchUsers(query, category=None): userWhere = (User.isCeltsOperationsTeam) elif category == "celtsLinkAdmin": userWhere = (User.isFaculty | User.isStaff | User.isCeltsStudentStaff | User.isCeltsOperationsTeam) + elif category == "currentStudents": + userWhere = (User.rawClassLevel.in_(["Freshman", "Sophomore", "Junior", "Senior"])) elif category == "all": userWhere = (True) else: userWhere = (User.isStudent) + fullSearchText = " ".join(splitSearch) # Combine into query - searchResults = User.select().where(searchWhere, userWhere) + searchResults = User.select().where(searchWhere, userWhere).order_by( + fn.CONCAT(User.firstName, " ", User.lastName).contains(fullSearchText).desc(), + User.firstName.startswith(fullSearchText).desc(), + User.lastName.startswith(fullSearchText).desc(), + User.lastName, + User.firstName + ) return { user.username : model_to_dict(user) for user in searchResults } diff --git a/app/logic/users.py b/app/logic/users.py index b33f175b5..9f7aef0eb 100644 --- a/app/logic/users.py +++ b/app/logic/users.py @@ -91,7 +91,7 @@ def getProgramInterest(program): Parameters: program: Program object """ - return User.select().join(Interest).where(Interest.program == program) + return User.select().join(Interest).where(Interest.program == program, User.rawClassLevel.in_(["Freshman", "Sophomore", "Junior", "Senior"])) def getBannedUsers(program): """ @@ -196,20 +196,48 @@ def getUserBGCheckHistory(username): for row in allBackgroundChecks: bgHistory[row.type_id].append(row) return bgHistory - -def addProfileNote(visibility, bonner, noteTextbox, username): +def getProfileNoteData(formData, includeUsername=False, includeId=False): + noteData = { + "visibility": int(formData.get("visibility", 1)), + "bonner": formData.get("bonner") == "yes", + "cceMinor": formData.get("cceMinor") == "yes", + "noteTextbox": formData.get("noteTextbox", "").strip(), + } + if not noteData["noteTextbox"]: + raise ValueError("Note cannot be empty") + if includeUsername: + noteData["username"] = formData.get("username") + if not noteData["username"]: + raise ValueError("Missing username") + if includeId: + noteData["profileNoteID"] = formData.get("id") + if not noteData["profileNoteID"]: + raise ValueError("Missing profile note ID") + return noteData + +def addProfileNote(visibility, bonner, cceMinor, noteTextbox, username): + user = User.get(User.username == username) + visibility = int(visibility) if bonner: - visibility = 1 # bonner notes are always admins and the student + visibility = 1 - noteForDb = Note.create(createdBy = g.current_user, - createdOn = datetime.datetime.now(), - noteContent = noteTextbox, - noteType = "profile") - createProfileNote = ProfileNote.create(user = User.get(User.username == username), - note = noteForDb, - isBonnerNote = bonner, - viewTier = visibility) - return createProfileNote + noteForDb = Note.create( createdBy=g.current_user, createdOn=datetime.datetime.now(), noteContent=noteTextbox, noteType="profile" ) + profileNote = ProfileNote.create( user=user, note=noteForDb, isBonnerNote=bonner, isCCEMinorNote=cceMinor, viewTier=visibility, ) + return profileNote + +def updateProfileNote( profileNoteID, visibility, bonner, cceMinor, noteTextbox): + profileNote = ProfileNote.get_by_id(profileNoteID) + visibility = int(visibility) + if bonner: + visibility = 1 + note = profileNote.note + note.noteContent = noteTextbox + note.save() + profileNote.viewTier = visibility + profileNote.isBonnerNote = bonner + profileNote.isCCEMinorNote = cceMinor + profileNote.save() + return profileNote def deleteProfileNote(noteId): return ProfileNote.delete().where(ProfileNote.id == noteId).execute() diff --git a/app/logic/volunteerSpreadsheet.py b/app/logic/volunteerSpreadsheet.py index 411bab07a..86dd59cac 100644 --- a/app/logic/volunteerSpreadsheet.py +++ b/app/logic/volunteerSpreadsheet.py @@ -319,7 +319,7 @@ def makeDataXls(sheetName, sheetData, workbook, sheetDesc=None): if type(dataRows) == list: for row, rowData in enumerate(dataRows): col_idx = 0 - for column, value in rowData.items(): + for value in rowData: # dates and times should use their text representation if isinstance(value, (datetime, date, time)): value = str(value) diff --git a/app/models/profileNote.py b/app/models/profileNote.py index 7c8924ab2..9bacbecc2 100644 --- a/app/models/profileNote.py +++ b/app/models/profileNote.py @@ -2,8 +2,10 @@ from app.models.user import User from app.models.note import Note + class ProfileNote(baseModel): user = ForeignKeyField(User) note = ForeignKeyField(Note, null=False) isBonnerNote = BooleanField(default=False) - viewTier = IntegerField(default=3) + isCCEMinorNote = BooleanField(default=False) + viewTier = IntegerField(default=3) \ No newline at end of file diff --git a/app/static/css/userProfile.css b/app/static/css/userProfile.css index 461deec8f..984ba3e76 100644 --- a/app/static/css/userProfile.css +++ b/app/static/css/userProfile.css @@ -29,6 +29,15 @@ div.profile-links a:not(:first-child) { padding: 5px; padding-top:0px; } +.note-badges { + margin-top: 4px; +} +.note-badge { + font-size: 0.65rem; + font-weight: 500; + line-height: 1; + padding: 0.2rem 0.45rem; + .bonnerNotes dd:nth-of-type(even) { background-color:#f2f2f2; } .bonnerNotes dt:nth-of-type(even) { background-color:#f2f2f2; } diff --git a/app/static/js/rosterManagement.js b/app/static/js/rosterManagement.js index 0010fd48f..92a1e69f7 100644 --- a/app/static/js/rosterManagement.js +++ b/app/static/js/rosterManagement.js @@ -3,7 +3,7 @@ import searchUser from './searchUser.js' $(document).ready(function(){ var dt = $('#rosterTable').DataTable(); - searchUser("searchStudentsInput", callback, "searchStudentsInput"); // initialize ONCE + searchUser("searchStudentsInput", callback, true, null, "currentStudents"); // initialize ONCE $("#searchIcon").click(function (e) { e.preventDefault(); diff --git a/app/static/js/searchStudent.js b/app/static/js/searchStudent.js index bdfcc9e1f..3d6f8a087 100644 --- a/app/static/js/searchStudent.js +++ b/app/static/js/searchStudent.js @@ -1,7 +1,7 @@ import searchUser from './searchUser.js' function callback(selected) { - $("#searchStudentsInput").submit(); + $("#searchStudentsInput").closest("form").submit(); } $(document).ready(function() { diff --git a/app/static/js/userProfile.js b/app/static/js/userProfile.js index ddfda7d37..f13fb3db4 100644 --- a/app/static/js/userProfile.js +++ b/app/static/js/userProfile.js @@ -105,9 +105,7 @@ $(document).ready(function(){ $("#banEndDatepicker").attr("min",`${year}-${month}-${day}`); }); - /* - * Ban Functionality - */ + // Ban Functionality $(".banEdit").click(function() { var banButton = $("#banButton") var banEndDateDiv = $("#banEndDate") // Div containing the datepicker in the ban modal @@ -174,138 +172,159 @@ $(document).ready(function(){ }); }); - /* - * Note Functionality - */ - function bonnerNoteOff() { - $("#bonnerInput").prop("checked", false); - $("#noteDropdown").show() - $("#bonnerStatement").hide() - $("#visibilityLabel").show() - } +// Note Functionality + +function bonnerNoteOff() { + $("#bonnerInput").prop("checked", false); + $("#noteDropdown").show(); + $("#bonnerStatement").hide(); + $("#visibilityLabel").show(); +} + +function bonnerNoteOn() { + $("#bonnerInput").prop("checked", true); + $("#noteDropdown").hide(); + $("#bonnerStatement").show(); + $("#visibilityLabel").hide(); +} + +function resetNoteModal() { + bonnerNoteOff(); + + $("#cceMinorInput").prop("checked", false); + $("#addNoteTextArea").val(""); + $("#noteDropdown").val("1"); + + $("#notesSaveButton").data("mode", "add"); + $("#notesSaveButton").data("noteid", null); + $("#notesSaveButton").prop("disabled", false); +} + +// Open the modal for a normal new note. +$("#addNoteButton").click(function () { + resetNoteModal(); + $("#noteModal").modal("toggle"); +}); + +// Open the modal from the Bonner Notes area. Bonner is selected by default, but CCE Minor stays independent. +$("#addBonnerNoteButton").click(function () { + resetNoteModal(); + bonnerNoteOn(); + $("#noteModal").modal("toggle"); +}); + +// Show or hide visibility whenever Bonner changes. +$("#bonnerInput").on("change", function () { + if ($(this).is(":checked")) { + bonnerNoteOn(); + } else { + bonnerNoteOff(); + } +}); - function bonnerNoteOn() { - $("#bonnerInput").prop("checked", true); - $("#noteDropdown").hide() - $("#bonnerStatement").show() - $("#visibilityLabel").hide() - } +// Add or update a note. +$("#addNoteForm").submit(function (event) { + event.preventDefault(); - $("#addNoteButton").click(function() { - bonnerNoteOff() - $("#addNoteTextArea").val('') - $("#notesSaveButton").data('mode', 'add') - $("#notesSaveButton").data('noteid', null) - $("#noteModal").modal("toggle") - }); + const saveButton = $("#notesSaveButton"); - $("#addVisibility").click(function() { - var bonnerChecked = $("input[name='bonner']:checked").val() + const username = saveButton.data("username"); + const mode = saveButton.data("mode"); + const noteid = saveButton.data("noteid"); - if (bonnerChecked == 'on') { - bonnerNoteOn() - } else { - bonnerNoteOff() - } - }); + const noteTextbox = $("#addNoteTextArea").val().trim(); + const visibility = $("#noteDropdown").val(); - $("#addBonnerNoteButton").click(function() { - bonnerNoteOn() - $("#addNoteTextArea").val('') - $("#notesSaveButton").data('mode', 'add') - $("#notesSaveButton").data('noteid', null) - $("#noteModal").modal("toggle"); - }); + const isBonner = $("#bonnerInput").is(":checked"); + const isCCEMinor = $("#cceMinorInput").is(":checked"); - $('#addNoteForm').submit(function(event) { + if (!noteTextbox) { + $("#addNoteTextArea").focus(); + return; + } - event.preventDefault() - let username = $("#notesSaveButton").data('username') - let isBonner = $("#bonnerInput").is(":checked") - let mode = $("#notesSaveButton").data('mode') - let noteid = $("#notesSaveButton").data('noteid') + const requestData = { username: username, visibility: visibility, noteTextbox: noteTextbox, bonner: isBonner ? "yes" : "no", cceMinor: isCCEMinor ? "yes" : "no" }; + let requestURL = "/profile/addNote"; + let successMessage = "Successfully added note"; - // If we're editing, delete the old note first - if (mode === 'edit') { - $.ajax({ - method: "POST", - url: "/" + username + "/deleteNote", - data: { "id": noteid } - }) + if (mode === "edit") { requestURL = "/" + username + "/editNote"; + requestData.id = noteid; + successMessage = "Successfully updated note"; } - $.ajax({ - method: "POST", - url: "/profile/addNote", - data: {"username": username, - "visibility": $("#noteDropdown").val(), - "noteTextbox": $("#addNoteTextArea").val(), - "bonner": isBonner ? "yes" : "no"}, - success: function(response) { - target = isBonner ? "bonner" : "notes" - msgFlash("Successfully added a note", "success", 1300, true); - location.reload() - }, - error: function(error) { - console.log("error") - } - }); - }); - $(".deleteNoteButton").click(function() { - $("#confirmDeleteNote").data('username', $(this).data('username')) - $("#confirmDeleteNote").data('noteid', $(this).data('noteid')) - $("#deleteNoteWarning").modal("show") + saveButton.prop("disabled", true); - }); - - $("#confirmDeleteNote").click(function() { - let username = $(this).data('username') - let noteid = $(this).data('noteid') $.ajax({ - method: "POST", - url: "/" + username + "/deleteNote", - data: {"id": noteid}, - success: function(response) { - msgFlash("Successfully deleted note", "success", 1300, true) - reloadWithAccordion("notes") - } + method: "POST", + url: requestURL, + data: requestData, + success: function () {location.reload();}, + error: function (xhr) { const errorMessage = xhr.responseText || "Unable to save profile note"; msgFlash( errorMessage, "danger", 3000, true ); saveButton.prop("disabled", false);} }); - }); - - $(".editNoteButton").click(function() { - let noteText = $(this).data('notetext') - let visibility = $(this).data('visibility') - let isBonner = $(this).data('bonner') - let noteid = $(this).data('noteid') - - - $("#addNoteTextArea").val(noteText) - $("#noteDropdown").val(visibility) - - - if (isBonner === 'yes') { - bonnerNoteOn() - } else { - bonnerNoteOff() +}); + +// Open an existing note for editing. +$(document).on("click", ".editNoteButton", function () { + const noteText = $(this).data("notetext"); + const visibility = String($(this).data("visibility")); + const noteid = $(this).data("noteid"); + + const isBonner = + String($(this).data("bonner")) === "yes"; + + const isCCEMinor = + String($(this).data("cceminor")) === "yes"; + $("#cceMinorInput").prop("checked", isCCEMinor); + $("#addNoteTextArea").val(noteText); + $("#noteDropdown").val(visibility); + + if (isBonner) { bonnerNoteOn(); } + else { + bonnerNoteOff(); + $("#noteDropdown").val(visibility); } - - $("#notesSaveButton").data('noteid', $(this).data('noteid')) - $("#notesSaveButton").data('mode', 'edit') - - $("#noteModal").modal("toggle") - + +// This is the part that restores the CCE Minor toggle. - $.ajax({ - method: "POST", - url: "/" + username + "/editNote", - data: {"id": noteid}, - success: function(response) { - reloadWithAccordion("notes") - } + $("#cceMinorInput").prop( "checked", isCCEMinor); + $("#notesSaveButton").data( "noteid", noteid ); + $("#notesSaveButton").data( "mode", "edit" ); + $("#notesSaveButton").prop( "disabled", false ); + $("#noteModal").modal("toggle"); +}); + + +// Open the delete confirmation. +$(document).on("click", ".deleteNoteButton", function () { + $("#confirmDeleteNote").data( + "username", + $(this).data("username") + ); + + $("#confirmDeleteNote").data( + "noteid", + $(this).data("noteid") + ); + + $("#deleteNoteWarning").modal("show"); +}); + +// Confirm note deletion. +$("#confirmDeleteNote").click(function () { + const username = $(this).data("username"); + const noteid = $(this).data("noteid"); + + $.ajax({method: "POST", url: "/" + username + "/deleteNote", data: { id: noteid }, + success: function () { msgFlash("Successfully deleted note", "success", 1300, true ); + reloadWithAccordion("notes"); + }, + + error: function (xhr) { console.error("Unable to delete note:", xhr.responseText ); + + } }); - }); -}); +}); /* * Background Check Functionality */ @@ -433,7 +452,7 @@ $(document).ready(function(){ typingTimer = setTimeout(saveDiet, saveInterval); }); }); - // end document.ready() +}); // end document.ready() // Update program manager status diff --git a/app/templates/admin/graduationManagement.html b/app/templates/admin/graduationManagement.html index 58d3fa8a6..22e753f6e 100644 --- a/app/templates/admin/graduationManagement.html +++ b/app/templates/admin/graduationManagement.html @@ -19,7 +19,7 @@

Graduation Management

-
+
diff --git a/app/templates/events/createEvent.html b/app/templates/events/createEvent.html index 549e45013..90ddbbdee 100644 --- a/app/templates/events/createEvent.html +++ b/app/templates/events/createEvent.html @@ -71,6 +71,7 @@ {% else %}

{{page_title}}

+
{% endif %} @@ -325,10 +326,13 @@

{{page_title}}

{% endif %}
-
+
-
+
+ {% if not event %} + Back + {% endif %} {% if event %} @@ -341,7 +345,7 @@

{{page_title}}

{% endif %} - +
diff --git a/app/templates/events/eventView.html b/app/templates/events/eventView.html index f18f5472e..675dd3a71 100644 --- a/app/templates/events/eventView.html +++ b/app/templates/events/eventView.html @@ -167,7 +167,6 @@

Program Trainings:

{% endif %} {% endif %} - {% if filepaths.keys()|count %}
Event Attachments @@ -215,4 +214,7 @@

Program Trainings:

+
+ Back +
{% endblock %} diff --git a/app/templates/main/userProfile.html b/app/templates/main/userProfile.html index d722da904..0b4653610 100644 --- a/app/templates/main/userProfile.html +++ b/app/templates/main/userProfile.html @@ -174,6 +174,7 @@

Program Event Name Participation Type + Service Hours Event Date {% for event in participatedEvents %} @@ -181,6 +182,11 @@

{{event.programName}} {{event.name}} {{event.participatedType}} + {% if event.isService %} + {{event.hoursEarned}} + {% else %} + N/A + {% endif %} {{event.startDate.strftime('%m/%d/%Y')}} {% endfor %} @@ -288,8 +294,8 @@

{% if participatedInLabor %}
CELTS Labor History:
- {% for program, term in participatedInLabor.items() %} -

{{term}}: {{program}}

+ {% for positionTitle, term in participatedInLabor %} +

{{term}}: {{positionTitle}}

{% endfor %}
{% endif %} @@ -411,72 +417,123 @@

{{bgType.description}}
-
-

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

- {% set show = "show" if visibleAccordion == "notes" else "" %} -
-
- +
+ {% set notesOpen = visibleAccordion == "notes" %} + +

+ +

+ +
+
+ + {% if g.current_user.isCeltsAdmin %} + {% set userTier = 3 %} + {% elif g.current_user.isCeltsStudentStaff %} + {% set userTier = 2 %} + {% else %} + {% set userTier = 1 %} + {% endif %} + + {% set note = namespace(count=0) %} + +
+
- + + - {% if g.current_user.isCeltsAdmin or g.current_user.isCeltsOperationsTeam %} - {% set userTier = 3 %} - {% elif g.current_user.isCeltsStudentStaff %} - {% set userTier = 2 %} - {% else %} - {% set userTier = 1 %} - {% endif %} - {% set note = namespace(count=0) %} {% for row in profileNotes %} - {% if userTier >= row.viewTier and (not row.isBonnerNote or g.current_user.isBonnerScholar) %} + + {% set canViewBonnerNote = + not row.isBonnerNote + or g.current_user.isBonnerScholar + or g.current_user.isCeltsAdmin + or g.current_user.isCeltsStudentStaff + %} + + {% if userTier >= row.viewTier and canViewBonnerNote %} - - - + + + + + + + + {% set note.count = note.count + 1 %} {% endif %} {% endfor %} + + {% if note.count == 0 %} + + + + {% endif %}
Date Creator Note Visible ToActions
{{row.note.createdOn.strftime('%m/%d/%Y')}}{{row.note.createdBy.firstName+ " "+ row.note.createdBy.lastName}}{{ " (you)" if row.note.createdBy == g.current_user else ""}}{{row.note.noteContent}} - {% set bonner = "Bonner " if row.isBonnerNote else "" %} + {% if row.note.createdOn is string %} + {% set dateParts = row.note.createdOn[:10].split('-') %} + {{ dateParts[1] }}/{{ dateParts[2] }}/{{ dateParts[0] }} + {% else %} + {{ row.note.createdOn.strftime('%m/%d/%Y') }} + {% endif %} + + {{ row.note.createdBy.firstName }} + {{ row.note.createdBy.lastName }} + + {% if row.note.createdBy == g.current_user %} + (you) + {% endif %} + + {{ row.note.noteContent }} +
+ {% if row.isCCEMinorNote %} + CCE + {% endif %} + {% if row.isBonnerNote %} + Bonner + {% endif %} +
+
{% if row.viewTier == 3 %} - {{bonner}}Admins + Admins {% elif row.viewTier == 2 %} - {{bonner}}Admins/Student Staff + Admins/Student Staff + {% elif row.isBonnerNote %} + Bonner Scholars {% else %} - {{ "Bonner Scholar " if row.isBonnerNote else "Everyone"}} + Everyone {% endif %} - - {% if (g.current_user == row.note.createdBy) or g.current_user.isCeltsAdmin or g.current_user.isCeltsOperationsTeam %} - - + + {% if + g.current_user == row.note.createdBy + or g.current_user.isCeltsAdmin + %} + + {% else %} - - + + {% endif %}
There are no notes yet.
- {% if note.count == 0 %} - There are no notes yet - {% endif %} -
-
-
+
+
@@ -517,18 +574,26 @@
Notes
{% for row in profileNotes|selectattr("isBonnerNote") %}
{{row.note.noteContent}} - + -
{{row.note.createdBy.fullName}} {{row.note.createdOn.strftime('%m/%d/%Y')}} +
{{row.note.createdBy.fullName}} + + {% if row.note.createdOn is string %} + {% set dateParts = row.note.createdOn[:10].split('-') %} + {{ dateParts[1] }}/{{ dateParts[2] }}/{{ dateParts[0] }} + {% else %} + {{ row.note.createdOn.strftime('%m/%d/%Y') }} + {% endif %} {% endfor %} {% else %} @@ -556,67 +621,97 @@
Requirement Progress
{% endif %} - + + +{% if volunteer != g.current_user %} +
+ Back
- -