diff --git a/app/controllers/main/routes.py b/app/controllers/main/routes.py index 50c02278e..5f9fb0b40 100644 --- a/app/controllers/main/routes.py +++ b/app/controllers/main/routes.py @@ -37,7 +37,7 @@ from app.logic.landingPage import getManagerProgramDict, getActiveEventTab from app.logic.minor import toggleMinorInterest, declareMinorInterest, getCommunityEngagementByTerm, getEngagementTotal from app.logic.participants import unattendedRequiredEvents, trainedParticipants, getParticipationStatusForTrainings, checkUserRsvp, addPersonToEvent -from app.logic.users import addUserInterest, removeUserInterest, banUser, unbanUser, isEligibleForProgram, getUserBGCheckHistory, addProfileNote, deleteProfileNote, updateDietInfo +from app.logic.users import addUserInterest, removeUserInterest, banUser, unbanUser, isEligibleForProgram, getUserBGCheckHistory,addProfileNote, deleteProfileNote, updateDietInfo, updateProfileNote, getProfileNoteData @main_bp.route('/logout', methods=['GET']) def redirectToLogout(): @@ -378,21 +378,46 @@ 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 ValueError as error: + flash(str(error), "danger") + return str(error), 400 + except User.DoesNotExist: + flash("User not found", "danger") + return "User not found", 404 + 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 "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: + abort(403) + if ( profileNote.note.createdBy != g.current_user and not g.current_user.isCeltsAdmin ): + abort(403) + updateProfileNote(**noteData) + flash("Successfully updated profile note", "success") + except ValueError as error: + flash(str(error), "danger") + return str(error), 400 + except ProfileNote.DoesNotExist: + flash("Profile note not found", "danger") + return "Profile note not found", 404 + except Exception as error: + print("Error updating profile note:", error) + flash("Failed to update profile note", "danger") + return "Failed to update profile note", 500 + return "success" @main_bp.route('//deleteNote', methods=['POST']) def deleteNote(username): diff --git a/app/logic/users.py b/app/logic/users.py index 36b9ba2bf..f15e6ede0 100644 --- a/app/logic/users.py +++ b/app/logic/users.py @@ -123,20 +123,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/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/js/userProfile.js b/app/static/js/userProfile.js index 494986116..e5ef7e29e 100644 --- a/app/static/js/userProfile.js +++ b/app/static/js/userProfile.js @@ -101,9 +101,7 @@ $(document).ready(function(){ }).attr('readonly','readonly'); }); - /* - * Ban Functionality - */ + // Ban Functionality $(".banEdit").click(function() { var banButton = $("#banButton") var banEndDateDiv = $("#banEndDate") // Div containing the datepicker in the ban modal @@ -163,136 +161,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); +} - function bonnerNoteOn() { - $("#bonnerInput").prop("checked", true); - $("#noteDropdown").hide() - $("#bonnerStatement").show() - $("#visibilityLabel").hide() +// 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(); } +}); - $("#addNoteButton").click(function() { - bonnerNoteOff() - $("#addNoteTextArea").val('') - $("#notesSaveButton").data('mode', 'add') - $("#notesSaveButton").data('noteid', null) - $("#noteModal").modal("toggle") - }); +// Add or update a note. +$("#addNoteForm").submit(function (event) { + event.preventDefault(); - $("#addVisibility").click(function() { - var bonnerChecked = $("input[name='bonner']:checked").val() + const saveButton = $("#notesSaveButton"); - if (bonnerChecked == 'on') { - bonnerNoteOn() - } else { - bonnerNoteOff() - } - }); + const username = saveButton.data("username"); + const mode = saveButton.data("mode"); + const noteid = saveButton.data("noteid"); - $("#addBonnerNoteButton").click(function() { - bonnerNoteOn() - $("#addNoteTextArea").val('') - $("#notesSaveButton").data('mode', 'add') - $("#notesSaveButton").data('noteid', null) - $("#noteModal").modal("toggle"); - }); + const noteTextbox = $("#addNoteTextArea").val().trim(); + const visibility = $("#noteDropdown").val(); - $('#addNoteForm').submit(function(event) { + const isBonner = $("#bonnerInput").is(":checked"); + const isCCEMinor = $("#cceMinorInput").is(":checked"); - event.preventDefault() - let username = $("#notesSaveButton").data('username') - let isBonner = $("#bonnerInput").is(":checked") - let mode = $("#notesSaveButton").data('mode') - let noteid = $("#notesSaveButton").data('noteid') + if (!noteTextbox) { + $("#addNoteTextArea").focus(); + return; + } - // If we're editing, delete the old note first - if (mode === 'edit') { - $.ajax({ - method: "POST", - url: "/" + username + "/deleteNote", - data: { "id": 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 (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"; + + $("#addNoteTextArea").val(noteText); + $("#noteDropdown").val(visibility); + + if (isBonner) { bonnerNoteOn(); } + else { + bonnerNoteOff(); + $("#noteDropdown").val(visibility); } + - $("#notesSaveButton").data('noteid', $(this).data('noteid')) - $("#notesSaveButton").data('mode', 'edit') +// This is the part that restores the CCE Minor toggle. + + $("#cceMinorInput").prop( "checked", isCCEMinor); + $("#notesSaveButton").data( "noteid", noteid ); + $("#notesSaveButton").data( "mode", "edit" ); + $("#notesSaveButton").prop( "disabled", false ); + $("#noteModal").modal("toggle"); +}); - - $("#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"); }); - $.ajax({ - method: "POST", - url: "/" + username + "/editNote", - data: {"id": noteid}, - success: function(response) { - reloadWithAccordion("notes") - } + +// 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 */ @@ -418,7 +439,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/main/userProfile.html b/app/templates/main/userProfile.html index 70b068075..a10f19415 100644 --- a/app/templates/main/userProfile.html +++ b/app/templates/main/userProfile.html @@ -394,72 +394,115 @@
{{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 %} - {% 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.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%} - - + + {% 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 %} -
-
-
+
+
@@ -511,7 +554,14 @@
Notes
data-noteid="{{row.note.id}}" data-username="{{volunteer.username}}" style="cursor:pointer"> -
{{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 %} @@ -542,64 +592,85 @@
Requirement Progress
- -