From baf7eb0fee4141af5d1863180d7b38b707b22e45 Mon Sep 17 00:00:00 2001 From: Odzen Date: Sun, 30 Nov 2025 15:37:21 +0100 Subject: [PATCH 1/5] Add Juan Sebastian Velasquez and reorganize team section --- README.md | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index b71fcedf..d8ba858b 100644 --- a/README.md +++ b/README.md @@ -63,14 +63,14 @@ This application is built with React, Flask, MongoDB, and Ant Design + + - - - + @@ -83,6 +83,9 @@ This application is built with React, Flask, MongoDB, and Ant Design + + +
Juan Sebastian Velasquez
Juan Sebastian Velasquez

IT Director
Leonardo Galindo
Leonardo Galindo

Technical Lead
Angela Luo
Angela Luo

Product Manager
Lam Tran
Lam Tran

Product Manager
Kelley Chau
Kelley Chau

Technical Lead
Kendall Hester
Kendall Hester

Technical Lead
Leonardo Galindo
Leonardo Galindo

Technical Lead
Faith Losbanes
Faith Losbanes

Product Designer
Kendall Hester
Kendall Hester

Technical Lead
Nikhil Gargeya
Nikhil Gargeya

Product Designer
Nayonika Roy
Nayonika Roy

Software Developer
Michael Chen
Michael Chen

Software Developer
Zayyan Faizal
Zayyan Faizal

Software Developer
Luciana Toledo-López
Luciana Toledo-Lopez

Software Developer
Faith Losbanes
Faith Losbanes

Product Designer
## License From 6a0a06e69055162d39247d4e65acfb4e951c1ddb Mon Sep 17 00:00:00 2001 From: Juan Sebastian Velasquez Acevedo Date: Tue, 6 Jan 2026 16:28:04 -0500 Subject: [PATCH 2/5] Add download functionality for partner mentors and mentees data in various formats; enhance AdminPartnerData component with export options and message display improvements. --- .gitignore | 4 +- backend/api/views/download.py | 290 ++++++++++- frontend/src/components/AdminPartnerData.js | 545 +++++++++++++++++--- frontend/src/utils/api.js | 32 ++ 4 files changed, 798 insertions(+), 73 deletions(-) diff --git a/.gitignore b/.gitignore index 8d0d0d75..dd4995aa 100644 --- a/.gitignore +++ b/.gitignore @@ -14,4 +14,6 @@ pyproject.toml000 app.log backend/app.log tests/.env -tests/cypress/screenshots/ \ No newline at end of file +tests/cypress/screenshots/ + +.cursor \ No newline at end of file diff --git a/backend/api/views/download.py b/backend/api/views/download.py index b34f4147..866f48ea 100644 --- a/backend/api/views/download.py +++ b/backend/api/views/download.py @@ -528,7 +528,7 @@ def generate_sheet(sheet_name, row_data, columns): worksheet = writer.sheets[sheet_name] format = workbook.add_format() format.set_bg_color("#eeeeee") - worksheet.set_column(0, len(row_data[0]), 28) + worksheet.set_column(0, len(row_data[0]) if row_data else 0, 28) writer.close() output.seek(0) @@ -544,3 +544,291 @@ def generate_sheet(sheet_name, row_data, columns): msg = "Downloads failed" logger.info(msg) return create_response(status=422, message=msg) + + +def generate_file(file_name, row_data, columns, file_format="xlsx"): + """Generate either CSV or Excel file based on format parameter.""" + df = pd.DataFrame(row_data, columns=columns) + output = BytesIO() + + if file_format == "csv": + # Generate CSV + csv_data = df.to_csv(index=False) + output.write(csv_data.encode("utf-8")) + output.seek(0) + + try: + return send_file( + output, + mimetype="text/csv", + download_name="{0}.csv".format(file_name), + as_attachment=True, + ) + except FileNotFoundError: + msg = "Downloads failed" + logger.info(msg) + return create_response(status=422, message=msg) + else: + # Generate Excel (default) + writer = pd.ExcelWriter(output, engine="xlsxwriter") + + df.to_excel( + writer, startrow=0, merge_cells=False, sheet_name=file_name, index=False + ) + workbook = writer.book + worksheet = writer.sheets[file_name] + format = workbook.add_format() + format.set_bg_color("#eeeeee") + worksheet.set_column(0, len(row_data[0]) if row_data else 0, 28) + + writer.close() + output.seek(0) + + try: + return send_file( + output, + mimetype="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", + download_name="{0}.xlsx".format(file_name), + as_attachment=True, + ) + except FileNotFoundError: + msg = "Downloads failed" + logger.info(msg) + return create_response(status=422, message=msg) + + +@download.route("/partner//accounts", methods=["GET"]) +@admin_only +def download_partner_accounts_data(partner_id): + """Download mentors or mentees assigned to a specific partner with activity status.""" + data = request.args + account_type = int(data.get("account_type", 0)) + file_format = data.get("format", "xlsx") # 'xlsx' or 'csv' + + try: + partner = PartnerProfile.objects.get(id=partner_id) + except: + msg = "Partner not found" + logger.info(msg) + return create_response(status=422, message=msg) + + messages = DirectMessage.objects() + + if account_type == Account.MENTOR: + return download_partner_mentor_accounts(partner, messages, file_format) + elif account_type == Account.MENTEE: + return download_partner_mentee_accounts(partner, messages, file_format) + + msg = "Invalid account type" + logger.info(msg) + return create_response(status=422, message=msg) + + +def download_partner_mentor_accounts(partner, messages, file_format="xlsx"): + """Download mentors assigned to a partner with activity status.""" + if not partner.assign_mentors: + return generate_file("partner_mentors", [], get_mentor_columns_with_activity(), file_format) + + accts = [] + + for mentor_ref in partner.assign_mentors: + mentor_id = mentor_ref.get("id") + if not mentor_id: + continue + + try: + mentor = MentorProfile.objects.get(id=mentor_id) + except: + continue + + # Count messages + sent_messages = [msg for msg in messages if str(msg.sender_id) == str(mentor.id)] + received_messages = [msg for msg in messages if str(msg.recipient_id) == str(mentor.id)] + + # Determine activity status + total_sent = len(sent_messages) + total_received = len(received_messages) + has_messages = total_sent > 0 or total_received > 0 + + if has_messages: + is_active = "Yes" + activity_reason = f"Sent: {total_sent}, Received: {total_received} messages" + else: + is_active = "No" + activity_reason = "No messages sent or received" + + # Format educations + educations = [] + for edu in mentor.education: + educations.append( + "{0} from {1} ({2})".format( + edu.education_level if edu.education_level else "", + edu.school if edu.school else "", + edu.graduation_year if edu.graduation_year else "", + ) + ) + + accts.append( + [ + mentor.name, + mentor.email, + mentor.professional_title, + mentor.linkedin, + mentor.website, + "Yes" if mentor.image and mentor.image.url else "No", + mentor.image.url if mentor.image else "", + mentor.videos[0].url if mentor.videos and len(mentor.videos) > 0 else "", + "Yes" if mentor.videos and len(mentor.videos) > 0 else "No", + "|".join(educations), + ",".join(mentor.languages) if mentor.languages else "", + ",".join(mentor.specializations) if mentor.specializations else "", + mentor.biography, + "Yes" if mentor.taking_appointments else "No", + "Yes" if mentor.offers_in_person else "No", + "Yes" if mentor.offers_group_appointments else "No", + "Yes" if mentor.text_notifications else "No", + "Yes" if mentor.email_notifications else "No", + total_received, + total_sent, + partner.organization if partner.organization else "", + is_active, + activity_reason, + ] + ) + + return generate_file("partner_mentors", accts, get_mentor_columns_with_activity(), file_format) + + +def download_partner_mentee_accounts(partner, messages, file_format="xlsx"): + """Download mentees assigned to a partner with activity status.""" + if not partner.assign_mentees: + return generate_file("partner_mentees", [], get_mentee_columns_with_activity(), file_format) + + accts = [] + + for mentee_ref in partner.assign_mentees: + mentee_id = mentee_ref.get("id") + if not mentee_id: + continue + + try: + mentee = MenteeProfile.objects.get(id=mentee_id) + except: + continue + + # Count messages + sent_messages = [msg for msg in messages if str(msg.sender_id) == str(mentee.id)] + received_messages = [msg for msg in messages if str(msg.recipient_id) == str(mentee.id)] + + # Determine activity status + total_sent = len(sent_messages) + total_received = len(received_messages) + has_messages = total_sent > 0 or total_received > 0 + + if has_messages: + is_active = "Yes" + activity_reason = f"Sent: {total_sent}, Received: {total_received} messages" + else: + is_active = "No" + activity_reason = "No messages sent or received" + + # Format educations + educations = [] + for edu in mentee.education: + educations.append( + "{0} from {1} ({2})".format( + edu.education_level if edu.education_level else "", + edu.school if edu.school else "", + edu.graduation_year if edu.graduation_year else "", + ) + ) + if mentee.education_level and not educations: + educations.append(EDUCATION_LEVEL.get(mentee.education_level, mentee.education_level)) + + accts.append( + [ + mentee.name, + mentee.gender, + mentee.location, + mentee.age, + mentee.email, + mentee.phone_number, + mentee.image.url if mentee.image else "", + "|".join(educations), + ",".join(mentee.languages) if mentee.languages else "", + "|".join(mentee.specializations) if mentee.specializations else "", + mentee.biography, + partner.organization if partner.organization else "", + "Yes" if mentee.image and mentee.image.url else "No", + "Yes" if mentee.video and mentee.video.url else "No", + 1 if mentee.text_notifications else 0, + 1 if mentee.email_notifications else 0, + 1 if mentee.is_private else 0, + mentee.video.url if mentee.video else "", + ",".join(mentee.favorite_mentors_ids) if mentee.favorite_mentors_ids else "", + total_sent, + total_received, + partner.organization if partner.organization else "", + is_active, + activity_reason, + ] + ) + + return generate_file("partner_mentees", accts, get_mentee_columns_with_activity(), file_format) + + +def get_mentor_columns_with_activity(): + return [ + "mentor Full Name", + "email", + "professional_title", + "linkedin", + "website", + "profile pic up", + "image url", + "video url", + "video(s) up", + "educations", + "languages", + "specializations", + "biography", + "taking_appointments", + "offers_in_person", + "offers_group_appointments", + "text_notifications", + "email_notifications", + "total_received_messages", + "total_sent_messages", + "Affiliated", + "is_active", + "activity_reason", + ] + + +def get_mentee_columns_with_activity(): + return [ + "mentee name", + "gender", + "location", + "age", + "email", + "phone number", + "image url", + "educations", + "languages", + "Areas of interest", + "biography", + "Organization Affiliation", + "profile pic up", + "video(s) up", + "text_notifications", + "email_notifications", + "private account", + "video url", + "favorite_mentor_ids", + "total_sent_messages", + "total_received_messages", + "Affiliated", + "is_active", + "activity_reason", + ] diff --git a/frontend/src/components/AdminPartnerData.js b/frontend/src/components/AdminPartnerData.js index 68d84a27..f14a7420 100644 --- a/frontend/src/components/AdminPartnerData.js +++ b/frontend/src/components/AdminPartnerData.js @@ -1,11 +1,15 @@ import React, { useEffect, useState } from "react"; -import { fetchAccounts, fetchAccountById } from "utils/api"; +import { + fetchAccounts, + fetchAccountById, + downloadPartnerMentorsData, + downloadPartnerMenteesData, +} from "utils/api"; import Meta from "antd/lib/card/Meta"; import { Table, Input, Dropdown, - Menu, message, Avatar, Layout, @@ -21,12 +25,22 @@ import { Typography, Tag, Button, + Tooltip, + Space, + Segmented, + Badge, } from "antd"; import { UserOutlined, SearchOutlined, DownOutlined, ExclamationCircleOutlined, + DownloadOutlined, + TeamOutlined, + CheckCircleOutlined, + MessageOutlined, + FileExcelOutlined, + FileTextOutlined, } from "@ant-design/icons"; import { HubsDropdown } from "../components/AdminDropdowns"; @@ -138,27 +152,131 @@ export const AdminPartnerData = () => { setModalData(sortedMessages || []); }; - const overlay = ( - - - setOption(options.MENTORS)}>Mentors - - - setOption(options.MENTEES)}>Mentees - - - ); + + // Collapsible message list component for the table + const MessageListCell = ({ message_receive_data, record }) => { + const [expanded, setExpanded] = useState(false); + const activeMessages = message_receive_data?.filter( + (item) => item.numberOfMessages > 0 + ); + + if (!activeMessages || activeMessages.length === 0) { + return ( + + No messages + + ); + } + + const displayCount = expanded ? activeMessages.length : 3; + const visibleItems = activeMessages.slice(0, displayCount); + const remainingCount = activeMessages.length - 3; + + return ( +
+ {visibleItems.map((item, index) => ( +
showDetailModal(item, record)} + > + } + src={item.image ? item.image.url : ""} + className={css` + flex-shrink: 0; + `} + /> +
+ + {item.receiver_name} + +
+ +
+ ))} + + {activeMessages.length > 3 && ( + + )} +
+ ); + }; const columns = [ { title: "Logo", dataIndex: "image", key: "image", + width: 60, render: (image) => { return (
} className="modal-profile-icon2" src={image ? image.url : ""} @@ -171,55 +289,37 @@ export const AdminPartnerData = () => { title: "Name", dataIndex: "name", key: "organization", - render: (organization) => {organization}, + width: 180, + render: (organization) => ( + {organization} + ), }, { title: "Email", dataIndex: "email", key: "email", - render: (email) => {email}, + width: 220, + render: (email) => ( + {email} + ), }, { - title: - option.key === ACCOUNT_TYPE.MENTEE - ? "Receiver(Count of Messages)" - : "Sender(Count of Messages)", + title: ( + + + {option.key === ACCOUNT_TYPE.MENTEE + ? "Conversations with Mentors" + : "Conversations with Mentees"} + + ), dataIndex: "message_receive_data", key: "message_receive_data", - render: (message_receive_data, record) => { - return ( - <> - {message_receive_data && - message_receive_data.length > 0 && - message_receive_data.map((item) => { - if (item.numberOfMessages > 0) { - return ( -
- } - className="modal-profile-icon2" - src={item.image ? item.image.url : ""} - /> -
showDetailModal(item, record)} - style={{ - cursor: "pointer", - textDecoration: "underline", - }} - > - {item.receiver_name}  ({item.numberOfMessages} - ) -
-
- ); - } else { - return <>; - } - })} - - ); - }, + render: (message_receive_data, record) => ( + + ), }, ]; useEffect(() => { @@ -511,24 +611,289 @@ export const AdminPartnerData = () => { paddingRight: "2rem", }} > -
- - - {option.text} - - - - setSelectActived(e)} - style={{ marginLeft: "2rem", marginRight: "0.5rem" }} - checked={selectActived} - /> - {"Active"} + {/* Header Controls */} +
+ {/* Left side: View toggle and Active filter */} + + {/* Mentors/Mentees Segmented Control */} +
+ + { + setOption( + value === ACCOUNT_TYPE.MENTOR + ? options.MENTORS + : options.MENTEES + ); + }} + options={[ + { + label: ( + + + Mentees + + ), + value: ACCOUNT_TYPE.MENTEE, + }, + { + label: ( + + + Mentors + + ), + value: ACCOUNT_TYPE.MENTOR, + }, + ]} + /> +
+ + {/* Active Toggle with Tooltip */} + + What is an Active User? +

+ An active user is someone who has exchanged at least one + message with another user (mentor or mentee). This filter + shows only users who have active conversations. +

+
+ } + placement="bottom" + overlayStyle={{ maxWidth: 300 }} + > +
setSelectActived(!selectActived)} + > + setSelectActived(e)} + /> + + + Active Only + + + (?) + +
+ + + + {/* Right side: Export Button */} + {selectedPartner && ( + + + Export Mentees Data + + ), + children: [ + { + key: "mentees-xlsx", + label: ( + + + Excel (.xlsx) + + ), + onClick: () => { + messageApi.loading("Preparing mentees Excel export..."); + downloadPartnerMenteesData(selectedPartner._id.$oid, "xlsx") + .then(() => { + messageApi.success("Mentees Excel downloaded!"); + }) + .catch(() => { + messageApi.error("Failed to export mentees data"); + }); + }, + }, + { + key: "mentees-csv", + label: ( + + + CSV (.csv) + + ), + onClick: () => { + messageApi.loading("Preparing mentees CSV export..."); + downloadPartnerMenteesData(selectedPartner._id.$oid, "csv") + .then(() => { + messageApi.success("Mentees CSV downloaded!"); + }) + .catch(() => { + messageApi.error("Failed to export mentees data"); + }); + }, + }, + ], + }, + { + key: "mentors", + label: ( + + + Export Mentors Data + + ), + children: [ + { + key: "mentors-xlsx", + label: ( + + + Excel (.xlsx) + + ), + onClick: () => { + messageApi.loading("Preparing mentors Excel export..."); + downloadPartnerMentorsData(selectedPartner._id.$oid, "xlsx") + .then(() => { + messageApi.success("Mentors Excel downloaded!"); + }) + .catch(() => { + messageApi.error("Failed to export mentors data"); + }); + }, + }, + { + key: "mentors-csv", + label: ( + + + CSV (.csv) + + ), + onClick: () => { + messageApi.loading("Preparing mentors CSV export..."); + downloadPartnerMentorsData(selectedPartner._id.$oid, "csv") + .then(() => { + messageApi.success("Mentors CSV downloaded!"); + }) + .catch(() => { + messageApi.error("Failed to export mentors data"); + }); + }, + }, + ], + }, + { + type: "divider", + }, + { + key: "info", + label: ( + + Exports include activity status + + ), + disabled: true, + }, + ], + }} + trigger={["click"]} + > + + + )}
+ + {/* Partner Info Banner */} + {selectedPartner && ( +
+ } + src={selectedPartner.image ? selectedPartner.image.url : null} + /> +
+ + {selectedPartner.name || selectedPartner.organization} + +
+ + Showing {tableData?.length || 0} {option.text.toLowerCase()} + {selectActived ? " with active conversations" : ""} + +
+
+
+ )} + + {/* Table */}
{ `} spinning={subLoading} > - +
record.id?.$oid || record.email} + pagination={{ + pageSize: 10, + showSizeChanger: true, + showTotal: (total, range) => + `${range[0]}-${range[1]} of ${total} ${option.text.toLowerCase()}`, + }} + locale={{ + emptyText: selectedPartner ? ( +
+ +
+ + {selectActived + ? `No active ${option.text.toLowerCase()} found for this partner` + : `No ${option.text.toLowerCase()} assigned to this partner`} + +
+
+ ) : ( +
+ +
+ + Select a partner from the sidebar to view their{" "} + {option.text.toLowerCase()} + +
+
+ ), + }} + /> diff --git a/frontend/src/utils/api.js b/frontend/src/utils/api.js index 257adcdc..8cfe82c2 100644 --- a/frontend/src/utils/api.js +++ b/frontend/src/utils/api.js @@ -1421,3 +1421,35 @@ export const getGroupParticipants = (hubId) => { } ); }; + +export const downloadPartnerMentorsData = async (partnerId, format = "xlsx") => { + const requestExtension = `/download/partner/${partnerId}/accounts`; + let response = await authGet(requestExtension, { + responseType: "blob", + params: { + account_type: ACCOUNT_TYPE.MENTOR, + format: format, + }, + }).catch(console.error); + + if (response) { + const extension = format === "csv" ? "csv" : "xlsx"; + downloadBlob(response, `partner_mentors.${extension}`); + } +}; + +export const downloadPartnerMenteesData = async (partnerId, format = "xlsx") => { + const requestExtension = `/download/partner/${partnerId}/accounts`; + let response = await authGet(requestExtension, { + responseType: "blob", + params: { + account_type: ACCOUNT_TYPE.MENTEE, + format: format, + }, + }).catch(console.error); + + if (response) { + const extension = format === "csv" ? "csv" : "xlsx"; + downloadBlob(response, `partner_mentees.${extension}`); + } +}; From 14254906a6be2dc61f9de83bf51caa4db5ab5f03 Mon Sep 17 00:00:00 2001 From: Juan Sebastian Velasquez Acevedo Date: Tue, 6 Jan 2026 16:35:02 -0500 Subject: [PATCH 3/5] format backend --- backend/api/utils/profile_parse.py | 24 ++++++++------ backend/api/views/download.py | 52 ++++++++++++++++++++++-------- backend/api/views/main.py | 6 ++-- backend/uv.lock | 3 ++ 4 files changed, 59 insertions(+), 26 deletions(-) create mode 100644 backend/uv.lock diff --git a/backend/api/utils/profile_parse.py b/backend/api/utils/profile_parse.py index 94265981..6c218244 100644 --- a/backend/api/utils/profile_parse.py +++ b/backend/api/utils/profile_parse.py @@ -332,9 +332,11 @@ def edit_profile(data: dict = {}, profile: object = None): title=video_data.get("title"), url=video_data.get("url"), tag=video_data.get("tag"), - date_uploaded=video_data.get("date_uploaded")["$date"] - if "$date" in video_data.get("date_uploaded") - else video_data.get("date_uploaded"), + date_uploaded=( + video_data.get("date_uploaded")["$date"] + if "$date" in video_data.get("date_uploaded") + else video_data.get("date_uploaded") + ), ) if profile.videos: profile.videos[0] = profile.video @@ -353,9 +355,11 @@ def edit_profile(data: dict = {}, profile: object = None): title=video.get("title"), url=video.get("url"), tag=video.get("tag"), - date_uploaded=video.get("date_uploaded")["$date"] - if "$date" in video.get("date_uploaded") - else video.get("date_uploaded"), + date_uploaded=( + video.get("date_uploaded")["$date"] + if "$date" in video.get("date_uploaded") + else video.get("date_uploaded") + ), ) for video in video_data ] @@ -413,9 +417,11 @@ def edit_profile(data: dict = {}, profile: object = None): title=video_data.get("title"), url=video_data.get("url"), tag=video_data.get("tag"), - date_uploaded=video_data.get("date_uploaded")["$date"] - if "$date" in video_data.get("date_uploaded") - else video_data.get("date_uploaded"), + date_uploaded=( + video_data.get("date_uploaded")["$date"] + if "$date" in video_data.get("date_uploaded") + else video_data.get("date_uploaded") + ), ) else: profile.video = None diff --git a/backend/api/views/download.py b/backend/api/views/download.py index 866f48ea..a5f37b2a 100644 --- a/backend/api/views/download.py +++ b/backend/api/views/download.py @@ -202,9 +202,7 @@ def download_mentor_apps(apps, partner_object): ( partner_object[acct.partner] if acct.partner and acct.partner in partner_object - else acct.organization - if acct.organization - else "" + else acct.organization if acct.organization else "" ), ] ) @@ -627,7 +625,9 @@ def download_partner_accounts_data(partner_id): def download_partner_mentor_accounts(partner, messages, file_format="xlsx"): """Download mentors assigned to a partner with activity status.""" if not partner.assign_mentors: - return generate_file("partner_mentors", [], get_mentor_columns_with_activity(), file_format) + return generate_file( + "partner_mentors", [], get_mentor_columns_with_activity(), file_format + ) accts = [] @@ -642,8 +642,12 @@ def download_partner_mentor_accounts(partner, messages, file_format="xlsx"): continue # Count messages - sent_messages = [msg for msg in messages if str(msg.sender_id) == str(mentor.id)] - received_messages = [msg for msg in messages if str(msg.recipient_id) == str(mentor.id)] + sent_messages = [ + msg for msg in messages if str(msg.sender_id) == str(mentor.id) + ] + received_messages = [ + msg for msg in messages if str(msg.recipient_id) == str(mentor.id) + ] # Determine activity status total_sent = len(sent_messages) @@ -677,7 +681,11 @@ def download_partner_mentor_accounts(partner, messages, file_format="xlsx"): mentor.website, "Yes" if mentor.image and mentor.image.url else "No", mentor.image.url if mentor.image else "", - mentor.videos[0].url if mentor.videos and len(mentor.videos) > 0 else "", + ( + mentor.videos[0].url + if mentor.videos and len(mentor.videos) > 0 + else "" + ), "Yes" if mentor.videos and len(mentor.videos) > 0 else "No", "|".join(educations), ",".join(mentor.languages) if mentor.languages else "", @@ -696,13 +704,17 @@ def download_partner_mentor_accounts(partner, messages, file_format="xlsx"): ] ) - return generate_file("partner_mentors", accts, get_mentor_columns_with_activity(), file_format) + return generate_file( + "partner_mentors", accts, get_mentor_columns_with_activity(), file_format + ) def download_partner_mentee_accounts(partner, messages, file_format="xlsx"): """Download mentees assigned to a partner with activity status.""" if not partner.assign_mentees: - return generate_file("partner_mentees", [], get_mentee_columns_with_activity(), file_format) + return generate_file( + "partner_mentees", [], get_mentee_columns_with_activity(), file_format + ) accts = [] @@ -717,8 +729,12 @@ def download_partner_mentee_accounts(partner, messages, file_format="xlsx"): continue # Count messages - sent_messages = [msg for msg in messages if str(msg.sender_id) == str(mentee.id)] - received_messages = [msg for msg in messages if str(msg.recipient_id) == str(mentee.id)] + sent_messages = [ + msg for msg in messages if str(msg.sender_id) == str(mentee.id) + ] + received_messages = [ + msg for msg in messages if str(msg.recipient_id) == str(mentee.id) + ] # Determine activity status total_sent = len(sent_messages) @@ -743,7 +759,9 @@ def download_partner_mentee_accounts(partner, messages, file_format="xlsx"): ) ) if mentee.education_level and not educations: - educations.append(EDUCATION_LEVEL.get(mentee.education_level, mentee.education_level)) + educations.append( + EDUCATION_LEVEL.get(mentee.education_level, mentee.education_level) + ) accts.append( [ @@ -765,7 +783,11 @@ def download_partner_mentee_accounts(partner, messages, file_format="xlsx"): 1 if mentee.email_notifications else 0, 1 if mentee.is_private else 0, mentee.video.url if mentee.video else "", - ",".join(mentee.favorite_mentors_ids) if mentee.favorite_mentors_ids else "", + ( + ",".join(mentee.favorite_mentors_ids) + if mentee.favorite_mentors_ids + else "" + ), total_sent, total_received, partner.organization if partner.organization else "", @@ -774,7 +796,9 @@ def download_partner_mentee_accounts(partner, messages, file_format="xlsx"): ] ) - return generate_file("partner_mentees", accts, get_mentee_columns_with_activity(), file_format) + return generate_file( + "partner_mentees", accts, get_mentee_columns_with_activity(), file_format + ) def get_mentor_columns_with_activity(): diff --git a/backend/api/views/main.py b/backend/api/views/main.py index 97e4c45b..88d151f8 100644 --- a/backend/api/views/main.py +++ b/backend/api/views/main.py @@ -125,9 +125,9 @@ def get_accounts(account_type): if partner_account.assign_mentees: for mentee_item in partner_account.assign_mentees: if "id" in mentee_item: - partners_by_assign_mentee[ - str(mentee_item["id"]) - ] = partner_account + partners_by_assign_mentee[str(mentee_item["id"])] = ( + partner_account + ) for account in mentees_data: if str(account.id) in partners_by_assign_mentee: pair_partner = partners_by_assign_mentee[str(account.id)] diff --git a/backend/uv.lock b/backend/uv.lock new file mode 100644 index 00000000..c0ba0910 --- /dev/null +++ b/backend/uv.lock @@ -0,0 +1,3 @@ +version = 1 +revision = 3 +requires-python = ">=3.9" From 1f9763ee91134f4e9313e9b1627ec997756f5d7a Mon Sep 17 00:00:00 2001 From: Juan Sebastian Velasquez Acevedo Date: Tue, 6 Jan 2026 16:38:27 -0500 Subject: [PATCH 4/5] reran black --- backend/api/views/download.py | 4 +++- backend/api/views/main.py | 6 +++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/backend/api/views/download.py b/backend/api/views/download.py index a5f37b2a..242cf63a 100644 --- a/backend/api/views/download.py +++ b/backend/api/views/download.py @@ -202,7 +202,9 @@ def download_mentor_apps(apps, partner_object): ( partner_object[acct.partner] if acct.partner and acct.partner in partner_object - else acct.organization if acct.organization else "" + else acct.organization + if acct.organization + else "" ), ] ) diff --git a/backend/api/views/main.py b/backend/api/views/main.py index 88d151f8..97e4c45b 100644 --- a/backend/api/views/main.py +++ b/backend/api/views/main.py @@ -125,9 +125,9 @@ def get_accounts(account_type): if partner_account.assign_mentees: for mentee_item in partner_account.assign_mentees: if "id" in mentee_item: - partners_by_assign_mentee[str(mentee_item["id"])] = ( - partner_account - ) + partners_by_assign_mentee[ + str(mentee_item["id"]) + ] = partner_account for account in mentees_data: if str(account.id) in partners_by_assign_mentee: pair_partner = partners_by_assign_mentee[str(account.id)] From fd45c955a24afbde938d187463554ef6f76ed166 Mon Sep 17 00:00:00 2001 From: Juan Sebastian Velasquez Acevedo Date: Tue, 6 Jan 2026 18:37:27 -0500 Subject: [PATCH 5/5] format frontend --- frontend/src/components/AdminPartnerData.js | 69 ++++++++++++++++----- frontend/src/utils/api.js | 10 ++- 2 files changed, 61 insertions(+), 18 deletions(-) diff --git a/frontend/src/components/AdminPartnerData.js b/frontend/src/components/AdminPartnerData.js index f14a7420..ae2c2b89 100644 --- a/frontend/src/components/AdminPartnerData.js +++ b/frontend/src/components/AdminPartnerData.js @@ -152,7 +152,6 @@ export const AdminPartnerData = () => { setModalData(sortedMessages || []); }; - // Collapsible message list component for the table const MessageListCell = ({ message_receive_data, record }) => { const [expanded, setExpanded] = useState(false); @@ -750,13 +749,20 @@ export const AdminPartnerData = () => { ), onClick: () => { - messageApi.loading("Preparing mentees Excel export..."); - downloadPartnerMenteesData(selectedPartner._id.$oid, "xlsx") + messageApi.loading( + "Preparing mentees Excel export..." + ); + downloadPartnerMenteesData( + selectedPartner._id.$oid, + "xlsx" + ) .then(() => { messageApi.success("Mentees Excel downloaded!"); }) .catch(() => { - messageApi.error("Failed to export mentees data"); + messageApi.error( + "Failed to export mentees data" + ); }); }, }, @@ -769,13 +775,20 @@ export const AdminPartnerData = () => { ), onClick: () => { - messageApi.loading("Preparing mentees CSV export..."); - downloadPartnerMenteesData(selectedPartner._id.$oid, "csv") + messageApi.loading( + "Preparing mentees CSV export..." + ); + downloadPartnerMenteesData( + selectedPartner._id.$oid, + "csv" + ) .then(() => { messageApi.success("Mentees CSV downloaded!"); }) .catch(() => { - messageApi.error("Failed to export mentees data"); + messageApi.error( + "Failed to export mentees data" + ); }); }, }, @@ -799,13 +812,20 @@ export const AdminPartnerData = () => { ), onClick: () => { - messageApi.loading("Preparing mentors Excel export..."); - downloadPartnerMentorsData(selectedPartner._id.$oid, "xlsx") + messageApi.loading( + "Preparing mentors Excel export..." + ); + downloadPartnerMentorsData( + selectedPartner._id.$oid, + "xlsx" + ) .then(() => { messageApi.success("Mentors Excel downloaded!"); }) .catch(() => { - messageApi.error("Failed to export mentors data"); + messageApi.error( + "Failed to export mentors data" + ); }); }, }, @@ -818,13 +838,20 @@ export const AdminPartnerData = () => { ), onClick: () => { - messageApi.loading("Preparing mentors CSV export..."); - downloadPartnerMentorsData(selectedPartner._id.$oid, "csv") + messageApi.loading( + "Preparing mentors CSV export..." + ); + downloadPartnerMentorsData( + selectedPartner._id.$oid, + "csv" + ) .then(() => { messageApi.success("Mentors CSV downloaded!"); }) .catch(() => { - messageApi.error("Failed to export mentors data"); + messageApi.error( + "Failed to export mentors data" + ); }); }, }, @@ -909,13 +936,19 @@ export const AdminPartnerData = () => { pageSize: 10, showSizeChanger: true, showTotal: (total, range) => - `${range[0]}-${range[1]} of ${total} ${option.text.toLowerCase()}`, + `${range[0]}-${ + range[1] + } of ${total} ${option.text.toLowerCase()}`, }} locale={{ emptyText: selectedPartner ? (
@@ -928,7 +961,11 @@ export const AdminPartnerData = () => { ) : (
diff --git a/frontend/src/utils/api.js b/frontend/src/utils/api.js index 8cfe82c2..a7304fe3 100644 --- a/frontend/src/utils/api.js +++ b/frontend/src/utils/api.js @@ -1422,7 +1422,10 @@ export const getGroupParticipants = (hubId) => { ); }; -export const downloadPartnerMentorsData = async (partnerId, format = "xlsx") => { +export const downloadPartnerMentorsData = async ( + partnerId, + format = "xlsx" +) => { const requestExtension = `/download/partner/${partnerId}/accounts`; let response = await authGet(requestExtension, { responseType: "blob", @@ -1438,7 +1441,10 @@ export const downloadPartnerMentorsData = async (partnerId, format = "xlsx") => } }; -export const downloadPartnerMenteesData = async (partnerId, format = "xlsx") => { +export const downloadPartnerMenteesData = async ( + partnerId, + format = "xlsx" +) => { const requestExtension = `/download/partner/${partnerId}/accounts`; let response = await authGet(requestExtension, { responseType: "blob",