From d2975388b3a15e2dafe1ef87f58325bb9ba68c1a Mon Sep 17 00:00:00 2001 From: Deekshtih Anantha Date: Sat, 25 Oct 2025 20:44:17 -0400 Subject: [PATCH 01/34] Heading in the assignment page is changed --- src/pages/Assignments/Assignment.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/Assignments/Assignment.tsx b/src/pages/Assignments/Assignment.tsx index b178162a..fc2e15e3 100644 --- a/src/pages/Assignments/Assignment.tsx +++ b/src/pages/Assignments/Assignment.tsx @@ -98,7 +98,7 @@ const Assignments = () => { -

Manage Assignments

+

Editing Assignments: OSS project & documentation


From ec3b4dbb04720cd2bc5b2e0890499358cb162afc Mon Sep 17 00:00:00 2001 From: Deekshtih Anantha Date: Sat, 25 Oct 2025 23:18:21 -0400 Subject: [PATCH 02/34] nav bar added in the Assignment page it has to be edited later --- src/pages/Assignments/Assignment.tsx | 36 ++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/src/pages/Assignments/Assignment.tsx b/src/pages/Assignments/Assignment.tsx index fc2e15e3..c4cbffaa 100644 --- a/src/pages/Assignments/Assignment.tsx +++ b/src/pages/Assignments/Assignment.tsx @@ -102,6 +102,42 @@ const Assignments = () => {
+ - - - - - - - {showDeleteConfirmation.visible && ( - - )} - - - - + + + + {/* Tab Content */} + {renderTabContent()} From c18165bad0009085b0b6500a1c8ddcad1a78a918 Mon Sep 17 00:00:00 2001 From: Deekshtih Anantha Date: Sat, 25 Oct 2025 23:47:16 -0400 Subject: [PATCH 04/34] App view from Assignment.tsx file moved to generalTab.tsx --- src/pages/Assignments/tabs/GeneralTab.tsx | 59 +++++++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 src/pages/Assignments/tabs/GeneralTab.tsx diff --git a/src/pages/Assignments/tabs/GeneralTab.tsx b/src/pages/Assignments/tabs/GeneralTab.tsx new file mode 100644 index 00000000..35334628 --- /dev/null +++ b/src/pages/Assignments/tabs/GeneralTab.tsx @@ -0,0 +1,59 @@ +import { Button, Col, Row } from "react-bootstrap"; +import { useNavigate } from "react-router-dom"; +import { BsFileText } from "react-icons/bs"; +import DeleteAssignment from "../AssignmentDelete"; +import { IAssignmentResponse } from "../../../utils/interfaces"; +import { Row as TRow } from "@tanstack/react-table"; +import Table from "components/Table/Table"; +import { assignmentColumns as ASSIGNMENT_COLUMNS } from "../AssignmentColumns"; + +interface GeneralTabProps { + tableData: any[]; + isLoading: boolean; + showDeleteConfirmation: { + visible: boolean; + data?: IAssignmentResponse; + }; + onDeleteAssignmentHandler: () => void; + onEditHandle: (row: TRow) => void; + onDeleteHandle: (row: TRow) => void; +} + +const GeneralTab = ({ + tableData, + isLoading, + showDeleteConfirmation, + onDeleteAssignmentHandler, + onEditHandle, + onDeleteHandle, +}: GeneralTabProps) => { + const navigate = useNavigate(); + + const tableColumns = ASSIGNMENT_COLUMNS(onEditHandle, onDeleteHandle); + + return ( + <> + + + + + {showDeleteConfirmation.visible && ( + + )} + + +
+ + + ); +}; + +export default GeneralTab; From 3d8fd72935742b97d06e33a403170b96aec7b257 Mon Sep 17 00:00:00 2001 From: Deekshtih Anantha Date: Sat, 25 Oct 2025 23:48:28 -0400 Subject: [PATCH 05/34] =?UTF-8?q?Tabs=20added=20and=20left=20for=20edit=20?= =?UTF-8?q?in=20the=20future=20src/pages/Assignments/=20=E2=94=9C=E2=94=80?= =?UTF-8?q?=E2=94=80=20Assignment.tsx=20(main=20file=20-=20now=20much=20cl?= =?UTF-8?q?eaner!)=20=E2=94=94=E2=94=80=E2=94=80=20tabs/=20=20=20=20=20?= =?UTF-8?q?=E2=94=9C=E2=94=80=E2=94=80=20GeneralTab.tsx=20=20=20=20=20?= =?UTF-8?q?=E2=94=9C=E2=94=80=E2=94=80=20TopicsTab.tsx=20=20=20=20=20?= =?UTF-8?q?=E2=94=9C=E2=94=80=E2=94=80=20RubricsTab.tsx=20=20=20=20=20?= =?UTF-8?q?=E2=94=9C=E2=94=80=E2=94=80=20ReviewStrategyTab.tsx=20=20=20=20?= =?UTF-8?q?=20=E2=94=9C=E2=94=80=E2=94=80=20DueDatesTab.tsx=20=20=20=20=20?= =?UTF-8?q?=E2=94=94=E2=94=80=E2=94=80=20EtcTab.tsx?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/Assignments/tabs/DueDatesTab.tsx | 16 ++ src/pages/Assignments/tabs/EtcTab.tsx | 16 ++ .../Assignments/tabs/ReviewStrategyTab.tsx | 16 ++ src/pages/Assignments/tabs/RubricsTab.tsx | 16 ++ src/pages/Assignments/tabs/TopicsTab.tsx | 166 ++++++++++++++++++ 5 files changed, 230 insertions(+) create mode 100644 src/pages/Assignments/tabs/DueDatesTab.tsx create mode 100644 src/pages/Assignments/tabs/EtcTab.tsx create mode 100644 src/pages/Assignments/tabs/ReviewStrategyTab.tsx create mode 100644 src/pages/Assignments/tabs/RubricsTab.tsx create mode 100644 src/pages/Assignments/tabs/TopicsTab.tsx diff --git a/src/pages/Assignments/tabs/DueDatesTab.tsx b/src/pages/Assignments/tabs/DueDatesTab.tsx new file mode 100644 index 00000000..646d4469 --- /dev/null +++ b/src/pages/Assignments/tabs/DueDatesTab.tsx @@ -0,0 +1,16 @@ +import { Col, Row } from "react-bootstrap"; + +const DueDatesTab = () => { + return ( + + +
+

Due Dates Section

+

This section will be implemented later.

+
+ + + ); +}; + +export default DueDatesTab; diff --git a/src/pages/Assignments/tabs/EtcTab.tsx b/src/pages/Assignments/tabs/EtcTab.tsx new file mode 100644 index 00000000..a7b2c95c --- /dev/null +++ b/src/pages/Assignments/tabs/EtcTab.tsx @@ -0,0 +1,16 @@ +import { Col, Row } from "react-bootstrap"; + +const EtcTab = () => { + return ( + + +
+

Etc. Section

+

This section will be implemented later.

+
+ + + ); +}; + +export default EtcTab; diff --git a/src/pages/Assignments/tabs/ReviewStrategyTab.tsx b/src/pages/Assignments/tabs/ReviewStrategyTab.tsx new file mode 100644 index 00000000..06cb34e3 --- /dev/null +++ b/src/pages/Assignments/tabs/ReviewStrategyTab.tsx @@ -0,0 +1,16 @@ +import { Col, Row } from "react-bootstrap"; + +const ReviewStrategyTab = () => { + return ( + + +
+

Review Strategy Section

+

This section will be implemented later.

+
+ + + ); +}; + +export default ReviewStrategyTab; diff --git a/src/pages/Assignments/tabs/RubricsTab.tsx b/src/pages/Assignments/tabs/RubricsTab.tsx new file mode 100644 index 00000000..78c1b49a --- /dev/null +++ b/src/pages/Assignments/tabs/RubricsTab.tsx @@ -0,0 +1,16 @@ +import { Col, Row } from "react-bootstrap"; + +const RubricsTab = () => { + return ( + + +
+

Rubrics Section

+

This section will be implemented later.

+
+ + + ); +}; + +export default RubricsTab; diff --git a/src/pages/Assignments/tabs/TopicsTab.tsx b/src/pages/Assignments/tabs/TopicsTab.tsx new file mode 100644 index 00000000..d27ebe73 --- /dev/null +++ b/src/pages/Assignments/tabs/TopicsTab.tsx @@ -0,0 +1,166 @@ +import { Col, Row, Form, Table as BootstrapTable } from "react-bootstrap"; +import { BsInfoCircle, BsCheck, BsX, BsBookmark, BsPencil } from "react-icons/bs"; + +interface TopicSettings { + allowTopicSuggestions: boolean; + enableBidding: boolean; + enableAuthorsReview: boolean; + allowReviewerChoice: boolean; + allowBookmarks: boolean; + allowBiddingForReviewers: boolean; +} + +interface TopicData { + id: string; + name: string; + students: string[]; + questionnaire: string; + numSlots: number; + availableSlots: number; + waitlist: number; +} + +interface TopicsTabProps { + topicSettings: TopicSettings; + topicsData: TopicData[]; + onTopicSettingChange: (setting: string, value: boolean) => void; +} + +const TopicsTab = ({ topicSettings, topicsData, onTopicSettingChange }: TopicsTabProps) => { + return ( + + +

Topics for OSS project & documentation assignment

+ + {/* Topic Settings */} +
+
+ onTopicSettingChange('allowTopicSuggestions', e.target.checked)} + /> + + onTopicSettingChange('enableBidding', e.target.checked)} + > + + + + onTopicSettingChange('enableAuthorsReview', e.target.checked)} + > + + + + onTopicSettingChange('allowReviewerChoice', e.target.checked)} + /> + + onTopicSettingChange('allowBookmarks', e.target.checked)} + /> + + onTopicSettingChange('allowBiddingForReviewers', e.target.checked)} + > + + + +
+ + {/* Hide all teams link */} + + + {/* Topics Table */} + + + + + + + + + + + + + + + + {topicsData.map((topic, index) => ( + + + + + + + + + + + + ))} + + + + + ); +}; + +export default TopicsTab; From 8210deae3f209fc7cf325a58c0412425201c9c1c Mon Sep 17 00:00:00 2001 From: Deekshtih Anantha Date: Sun, 26 Oct 2025 00:09:09 -0400 Subject: [PATCH 06/34] check boxes in the Assignemnt-topic tab fixed --- src/pages/Assignments/Assignment.tsx | 4 ++-- src/pages/Assignments/tabs/TopicsTab.tsx | 12 +++--------- 2 files changed, 5 insertions(+), 11 deletions(-) diff --git a/src/pages/Assignments/Assignment.tsx b/src/pages/Assignments/Assignment.tsx index 6808764b..6b7e9f49 100644 --- a/src/pages/Assignments/Assignment.tsx +++ b/src/pages/Assignments/Assignment.tsx @@ -42,8 +42,8 @@ const Assignments = () => { const [topicSettings, setTopicSettings] = useState({ allowTopicSuggestions: false, enableBidding: false, - enableAuthorsReview: true, - allowReviewerChoice: true, + enableAuthorsReview: false, + allowReviewerChoice: false, allowBookmarks: false, allowBiddingForReviewers: false, }); diff --git a/src/pages/Assignments/tabs/TopicsTab.tsx b/src/pages/Assignments/tabs/TopicsTab.tsx index d27ebe73..2b9394c9 100644 --- a/src/pages/Assignments/tabs/TopicsTab.tsx +++ b/src/pages/Assignments/tabs/TopicsTab.tsx @@ -49,9 +49,7 @@ const TopicsTab = ({ topicSettings, topicsData, onTopicSettingChange }: TopicsTa label="Enable bidding for topics?" checked={topicSettings.enableBidding} onChange={(e) => onTopicSettingChange('enableBidding', e.target.checked)} - > - - + /> onTopicSettingChange('enableAuthorsReview', e.target.checked)} - > - - + /> onTopicSettingChange('allowBiddingForReviewers', e.target.checked)} - > - - + /> From f401fc00fee3bccec8033d5bcc26358c07fa81ca Mon Sep 17 00:00:00 2001 From: Deekshtih Anantha Date: Sun, 26 Oct 2025 00:53:25 -0400 Subject: [PATCH 07/34] footer buttons added --- src/pages/Assignments/tabs/TopicsTab.tsx | 25 ++++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/src/pages/Assignments/tabs/TopicsTab.tsx b/src/pages/Assignments/tabs/TopicsTab.tsx index 2b9394c9..a7a71dde 100644 --- a/src/pages/Assignments/tabs/TopicsTab.tsx +++ b/src/pages/Assignments/tabs/TopicsTab.tsx @@ -1,4 +1,4 @@ -import { Col, Row, Form, Table as BootstrapTable } from "react-bootstrap"; +import { Col, Row, Form, Table as BootstrapTable, Button } from "react-bootstrap"; import { BsInfoCircle, BsCheck, BsX, BsBookmark, BsPencil } from "react-icons/bs"; interface TopicSettings { @@ -152,9 +152,30 @@ const TopicsTab = ({ topicSettings, topicsData, onTopicSettingChange }: TopicsTa ))} + + {/* --- NEWLY ADDED BUTTONS --- */} +
+ + + + + +
+ {/* --- END OF NEWLY ADDED BUTTONS --- */} + ); }; -export default TopicsTab; +export default TopicsTab; \ No newline at end of file From 906120563d5eb8a7863e531dfeb38aa7b56e69ec Mon Sep 17 00:00:00 2001 From: Deekshtih Anantha Date: Sun, 26 Oct 2025 01:30:30 -0400 Subject: [PATCH 08/34] Code modified by LLM after doing a verification of the requiremts check on what is satisfied and what is not satisfied --- src/pages/Assignments/tabs/TopicsTab.tsx | 424 +++++++++++++++++------ 1 file changed, 314 insertions(+), 110 deletions(-) diff --git a/src/pages/Assignments/tabs/TopicsTab.tsx b/src/pages/Assignments/tabs/TopicsTab.tsx index a7a71dde..df97a7e6 100644 --- a/src/pages/Assignments/tabs/TopicsTab.tsx +++ b/src/pages/Assignments/tabs/TopicsTab.tsx @@ -1,6 +1,55 @@ -import { Col, Row, Form, Table as BootstrapTable, Button } from "react-bootstrap"; -import { BsInfoCircle, BsCheck, BsX, BsBookmark, BsPencil } from "react-icons/bs"; +import React, { useState } from "react"; +import { Col, Row, Form, Table as BootstrapTable, Button, Modal, FloatingLabel, Badge, Stack } from "react-bootstrap"; +// Reverting to the standard import path for react-icons/bs +import { BsInfoCircle, BsCheck, BsX, BsBookmark, BsPencil, BsLink45Deg, BsPersonPlusFill, BsFillBookmarkPlusFill } from "react-icons/bs"; +// --- Interface Modifications --- +// Assuming these interfaces are defined elsewhere and imported +// They are redefined here for clarity based on requirements + +interface TeamMember { + id: string; // User ID + name: string; // User's full name +} + +interface AssignedTeam { + teamId: string; + members: TeamMember[]; +} + +interface WaitlistedTeam { + teamId: string; + members: TeamMember[]; +} + +interface PartnerAd { + text: string; + // link?: string; // Optional: Link to a separate page if not using modal +} + +interface BookmarkData { + id: string; + url: string; + title: string; +} + +// Updated TopicData interface +interface TopicData { + id: string; // Topic ID + name: string; // Topic Name + url?: string; // Optional URL for the topic name + description?: string; // Optional short description + assignedTeams: AssignedTeam[]; // Teams/Students assigned to this topic + waitlistedTeams: WaitlistedTeam[]; // Teams/Students waitlisted + questionnaire: string; // Associated questionnaire name + numSlots: number; // Total number of slots + availableSlots: number; // Number of available slots + // waitlist: number; // Redundant now, can derive from waitlistedTeams.length + bookmarks: BookmarkData[]; // Array of bookmarks for this topic + partnerAd?: PartnerAd; // Optional partner advertisement details +} + +// Same as before interface TopicSettings { allowTopicSuggestions: boolean; enableBidding: boolean; @@ -10,151 +59,279 @@ interface TopicSettings { allowBiddingForReviewers: boolean; } -interface TopicData { - id: string; - name: string; - students: string[]; - questionnaire: string; - numSlots: number; - availableSlots: number; - waitlist: number; -} - interface TopicsTabProps { topicSettings: TopicSettings; - topicsData: TopicData[]; + topicsData: TopicData[]; // Ensure the data passed matches the updated TopicData interface onTopicSettingChange: (setting: string, value: boolean) => void; + // Add handlers for actions like drop team, delete topic, edit topic, create bookmark etc. + onDropTeam: (topicId: string, teamId: string) => void; + onDeleteTopic: (topicId: string) => void; + onEditTopic: (topicId: string) => void; + onCreateBookmark: (topicId: string) => void; // Function to handle opening a create bookmark UI/modal + // Handler for partner ad application submission + onApplyPartnerAd: (topicId: string, applicationText: string) => void; } -const TopicsTab = ({ topicSettings, topicsData, onTopicSettingChange }: TopicsTabProps) => { +// --- Component Implementation --- + +const TopicsTab = ({ + topicSettings, + topicsData, + onTopicSettingChange, + onDropTeam, + onDeleteTopic, + onEditTopic, + onCreateBookmark, + onApplyPartnerAd, +}: TopicsTabProps) => { + const [displayUserNames, setDisplayUserNames] = useState(false); // State for toggling user name/ID display + const [showPartnerAdModal, setShowPartnerAdModal] = useState(false); + const [selectedPartnerAdTopic, setSelectedPartnerAdTopic] = useState(null); + const [partnerAdApplication, setPartnerAdApplication] = useState(""); + + // --- Partner Ad Modal Handlers --- + const handleShowPartnerAd = (topic: TopicData) => { + setSelectedPartnerAdTopic(topic); + setPartnerAdApplication(""); // Reset text area + setShowPartnerAdModal(true); + }; + + const handleClosePartnerAd = () => { + setShowPartnerAdModal(false); + setSelectedPartnerAdTopic(null); + }; + + const handleSubmitPartnerAd = () => { + if (selectedPartnerAdTopic) { + onApplyPartnerAd(selectedPartnerAdTopic.id, partnerAdApplication); + // Optional: Show success message or handle response + } + handleClosePartnerAd(); + }; + + // --- Render Helper Functions --- + const renderTeamMembers = (members: TeamMember[]) => { + // Basic check for members array + if (!Array.isArray(members)) { + return ''; + } + return members.map(member => (displayUserNames ? member.name : member.id)).join(', '); + }; + return (

Topics for OSS project & documentation assignment

- + {/* Topic Settings */}
- onTopicSettingChange('allowTopicSuggestions', e.target.checked)} - /> + onTopicSettingChange('allowTopicSuggestions', e.target.checked)} + /> - onTopicSettingChange('enableBidding', e.target.checked)} - /> + onTopicSettingChange('enableBidding', e.target.checked)} + /> - onTopicSettingChange('enableAuthorsReview', e.target.checked)} - /> + onTopicSettingChange('enableAuthorsReview', e.target.checked)} + /> - onTopicSettingChange('allowReviewerChoice', e.target.checked)} - /> + onTopicSettingChange('allowReviewerChoice', e.target.checked)} + /> - onTopicSettingChange('allowBookmarks', e.target.checked)} - /> + onTopicSettingChange('allowBookmarks', e.target.checked)} + /> - onTopicSettingChange('allowBiddingForReviewers', e.target.checked)} - /> + onTopicSettingChange('allowBiddingForReviewers', e.target.checked)} + />
- {/* Hide all teams link */} -
- Hide all teams + {/* View Options */} +
+ {/* Hide all teams link - Functionality TBD */} + {/* Hide all teams */} +
{/* Placeholder for alignment */} + setDisplayUserNames(e.target.checked)} + />
+ {/* Topics Table */} - + {/* Added responsive */}
- - + {/* Topic Name column with max-width */} + - + - + - {topicsData.map((topic, index) => ( - - - - + {/* Checkbox cell */} + + {/* Topic ID */} + + {/* Topic Name, Description, Teams, Waitlist, Partner Ad */} + - + {/* Questionnaire */} + - - - - - - - ))} + + {/* Slots */} + + + {/* Waitlist count */} + + {/* Bookmarks */} + + {/* Actions */} + + + )) + ) : ( + /* Add row for empty state */ + + + + )} - {/* --- NEWLY ADDED BUTTONS --- */} -
+ {/* Action Buttons */} +
{/* Added flex-wrap */} @@ -171,11 +348,38 @@ const TopicsTab = ({ topicSettings, topicsData, onTopicSettingChange }: TopicsTa Back
- {/* --- END OF NEWLY ADDED BUTTONS --- */} - + + {/* Partner Advertisement Modal */} + + + Partner Advertisement: {selectedPartnerAdTopic?.name} + + +

{selectedPartnerAdTopic?.partnerAd?.text}

+
+ + setPartnerAdApplication(e.target.value)} + /> + +
+ + + + +
); }; -export default TopicsTab; \ No newline at end of file +export default TopicsTab; + From 89d8baa294e7834189e21c9e2907a61dd8291072 Mon Sep 17 00:00:00 2001 From: Deekshtih Anantha Date: Sun, 26 Oct 2025 02:02:56 -0400 Subject: [PATCH 09/34] StudentTask page implemented, values are hardcoded need to be implemented to the backend data --- src/pages/StudentTasks/StudentTasks.tsx | 82 +++++++++++++++++++++++++ 1 file changed, 82 insertions(+) create mode 100644 src/pages/StudentTasks/StudentTasks.tsx diff --git a/src/pages/StudentTasks/StudentTasks.tsx b/src/pages/StudentTasks/StudentTasks.tsx new file mode 100644 index 00000000..ed69ddd8 --- /dev/null +++ b/src/pages/StudentTasks/StudentTasks.tsx @@ -0,0 +1,82 @@ +import React, { useEffect, useState } from "react"; +import { Container, Table } from "react-bootstrap"; + +interface Topic { + id: string; + name: string; + availableSlots: number; + waitlist: number; +} + +const StudentTasks: React.FC = () => { + const [topics, setTopics] = useState([]); + + useEffect(() => { + // Temporary hardcoded data (replace later with API call) + const mockData: Topic[] = [ + { id: "E2450", name: "Refactor assignments_controller.rb", availableSlots: 0, waitlist: 0 }, + { id: "E2451", name: "Reimplement feedback_response_map.rb", availableSlots: 1, waitlist: 0 }, + { id: "E2452", name: "Refactor review_mapping_controller.rb", availableSlots: 0, waitlist: 0 }, + { id: "E2453", name: "Refactor review_mapping_helper.rb", availableSlots: 0, waitlist: 0 }, + { id: "E2454", name: "Refactor student_task.rb", availableSlots: 0, waitlist: 0 }, + { id: "E2455", name: "Refactor sign_up_sheet_controller.rb", availableSlots: 0, waitlist: 0 }, + { id: "E2456", name: "Refactor teams_user.rb", availableSlots: 0, waitlist: 0 }, + { id: "E2457", name: "Github metrics integration", availableSlots: 1, waitlist: 0 }, + { id: "E2458", name: "User management and users table", availableSlots: 0, waitlist: 0 }, + { id: "E2459", name: "View for results of bidding", availableSlots: 0, waitlist: 0 }, + ]; + setTopics(mockData); + }, []); + + return ( + +
+

+ Signup sheet for OSS project & documentation assignment +

+

+ Your topic(s): Refactor review_mapping_controller.rb +

+
+ +
+
+ + Topic IDTopic name(s)QuestionnaireNum. of slotsAvailable slotsNum. on waitlistBookmarksActions
+ + {topic.id} +
+
+ {topic.name} + +
+
+ + {topic.students.join(' ')} + + +
+
+
+
+
Review Round 1:
+ + + +
+
{topic.numSlots}{topic.availableSlots}{topic.waitlist} + + +
+ + +
+
- + {/* Adjusted width for checkbox column */} + + {/* Consider select all functionality */} Topic IDTopic name(s)Topic name(s) QuestionnaireNum. of slotsNum. slots Available slotsNum. on waitlistWaitlist Bookmarks Actions
- - {topic.id} -
-
- {topic.name} - -
-
- - {topic.students.join(' ')} - - + {/* Ensure topicsData is not null or undefined before mapping */} + {topicsData && topicsData.length > 0 ? ( + topicsData.map((topic) => ( +
+ + {topic.id} +
+ {/* Topic Name (as link if URL exists) */} + {topic.url ? ( + {topic.name} + ) : ( + {topic.name} + )} + {/* Topic Description */} + {topic.description && ( +
{topic.description}
+ )}
- -
-
+ {/* Assigned Teams */} + {topic.assignedTeams && topic.assignedTeams.map((team) => ( +
+ Assigned + {renderTeamMembers(team.members)} + {/* Drop Team Icon - Requires handler */} + onDropTeam(topic.id, team.teamId)} + title={`Drop team ${team.teamId}`} + /> +
+ ))} + {/* Waitlisted Teams */} + {topic.waitlistedTeams && topic.waitlistedTeams.map((team) => ( +
+ Waitlisted + {renderTeamMembers(team.members)} + {/* Optional: Add icon/action for waitlisted teams if needed */} +
+ ))} + {/* Partner Advertisement */} + {topic.partnerAd && ( +
+ +
+ )} +
+ {/* This might need adjustment based on how questionnaires are handled (e.g., multiple rounds) */}
Review Round 1:
- - + {/* Assuming display only */} + - -
{topic.numSlots}{topic.availableSlots}{topic.waitlist} - - -
- - -
-
{topic.numSlots}{topic.availableSlots}{topic.waitlistedTeams ? topic.waitlistedTeams.length : 0} + + {/* Display existing bookmarks */} + {topic.bookmarks && topic.bookmarks.length > 0 ? ( + topic.bookmarks.map(bm => ( + + {bm.title || bm.url} + + )) + ) : ( + None + )} + {/* Create Bookmark Button */} + {topicSettings.allowBookmarks && ( + + )} + + +
+ {/* Edit Icon */} + onEditTopic(topic.id)} + title="Edit Topic" + /> + {/* Delete Icon */} + onDeleteTopic(topic.id)} + title="Delete Topic" + /> +
+
No topics found.
+ + + + + + + + + + {topics.map((topic) => ( + + + + + + + ))} + +
Topic IDTopic name(s)Available slotsNum. on waitlist
{topic.id}{topic.name}{topic.availableSlots}{topic.waitlist}
+ +
+ ); +}; + +export default StudentTasks; From bfcb3657a55e8c9797d5c5976004c594fe80a86f Mon Sep 17 00:00:00 2001 From: Deekshtih Anantha Date: Sun, 26 Oct 2025 21:36:21 -0400 Subject: [PATCH 10/34] Assignment tab added --- src/App.tsx | 5 ++ src/pages/Assignments/Assignment.tsx | 108 +++++++++++++++++++++++++++ 2 files changed, 113 insertions(+) diff --git a/src/App.tsx b/src/App.tsx index 27736ba3..e031fef5 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -35,6 +35,7 @@ import EditProfile from "pages/Profile/Edit"; import Reviews from "pages/Reviews/reviews"; import Email_the_author from "./pages/Email_the_author/email_the_author"; import CreateTeams from "pages/Assignments/CreateTeams"; +import StudentTasks from "pages/StudentTasks/StudentTasks"; import AssignReviewer from "pages/Assignments/AssignReviewer"; import ViewSubmissions from "pages/Assignments/ViewSubmissions"; import ViewScores from "pages/Assignments/ViewScores"; @@ -198,6 +199,10 @@ function App() { path: "email_the_author", element: , }, + { + path: "student_tasks", + element: } />, + }, // Fixed the missing comma and added an opening curly brace { path: "courses", diff --git a/src/pages/Assignments/Assignment.tsx b/src/pages/Assignments/Assignment.tsx index 6b7e9f49..36a969ee 100644 --- a/src/pages/Assignments/Assignment.tsx +++ b/src/pages/Assignments/Assignment.tsx @@ -77,6 +77,114 @@ const Assignments = () => { availableSlots: 0, waitlist: 0, }, + { + id: "E2552", + name: "ProjectTopic and SignedUpTeam", + students: ["Student 10905", "Student 10915"], + questionnaire: "--Default rubric--", + numSlots: 1, + availableSlots: 0, + waitlist: 0, + }, + { + id: "E2552", + name: "ProjectTopic and SignedUpTeam", + students: ["Student 10905", "Student 10915"], + questionnaire: "--Default rubric--", + numSlots: 1, + availableSlots: 0, + waitlist: 0, + }, + { + id: "E2552", + name: "ProjectTopic and SignedUpTeam", + students: ["Student 10905", "Student 10915"], + questionnaire: "--Default rubric--", + numSlots: 1, + availableSlots: 0, + waitlist: 0, + }, + { + id: "E2552", + name: "ProjectTopic and SignedUpTeam", + students: ["Student 10905", "Student 10915"], + questionnaire: "--Default rubric--", + numSlots: 1, + availableSlots: 0, + waitlist: 0, + }, + { + id: "E2552", + name: "ProjectTopic and SignedUpTeam", + students: ["Student 10905", "Student 10915"], + questionnaire: "--Default rubric--", + numSlots: 1, + availableSlots: 0, + waitlist: 0, + }, + { + id: "E2552", + name: "ProjectTopic and SignedUpTeam", + students: ["Student 10905", "Student 10915"], + questionnaire: "--Default rubric--", + numSlots: 1, + availableSlots: 0, + waitlist: 0, + }, + { + id: "E2552", + name: "ProjectTopic and SignedUpTeam", + students: ["Student 10905", "Student 10915"], + questionnaire: "--Default rubric--", + numSlots: 1, + availableSlots: 0, + waitlist: 0, + }, + { + id: "E2552", + name: "ProjectTopic and SignedUpTeam", + students: ["Student 10905", "Student 10915"], + questionnaire: "--Default rubric--", + numSlots: 1, + availableSlots: 0, + waitlist: 0, + }, + { + id: "E2552", + name: "ProjectTopic and SignedUpTeam", + students: ["Student 10905", "Student 10915"], + questionnaire: "--Default rubric--", + numSlots: 1, + availableSlots: 0, + waitlist: 0, + }, + { + id: "E2552", + name: "ProjectTopic and SignedUpTeam", + students: ["Student 10905", "Student 10915"], + questionnaire: "--Default rubric--", + numSlots: 1, + availableSlots: 0, + waitlist: 0, + }, + { + id: "E2552", + name: "ProjectTopic and SignedUpTeam", + students: ["Student 10905", "Student 10915"], + questionnaire: "--Default rubric--", + numSlots: 1, + availableSlots: 0, + waitlist: 0, + }, + { + id: "E2552", + name: "ProjectTopic and SignedUpTeam", + students: ["Student 10905", "Student 10915"], + questionnaire: "--Default rubric--", + numSlots: 1, + availableSlots: 0, + waitlist: 0, + }, ]; From 867db842ffab886cd51a7ea902030e58ac2ffd04 Mon Sep 17 00:00:00 2001 From: Srinidhi Shivashankar Date: Sun, 26 Oct 2025 23:24:20 -0400 Subject: [PATCH 11/34] Add dynamic topic fetching and management in StudentTasks page - Implemented fetching of topics based on the selected assignment ID. - Enhanced the UI to display loading states and error messages for topic retrieval. - Updated the table to show available topics with their respective slots and waitlist counts. - Added functionality for user-selected topics display and highlighting. - Refactored code for better readability and maintainability. --- src/App.tsx | 4 + src/hooks/useAPI.ts | 7 +- src/pages/Assignments/Assignment.tsx | 320 +++++++------- src/pages/Assignments/tabs/TopicsTab.tsx | 528 ++++++++++++++++++++++- src/pages/StudentTasks/StudentTasks.tsx | 206 +++++++-- 5 files changed, 857 insertions(+), 208 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index e031fef5..44bdd812 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -203,6 +203,10 @@ function App() { path: "student_tasks", element: } />, }, + { + path: "student_tasks/:assignmentId", + element: } />, + }, // Fixed the missing comma and added an opening curly brace { path: "courses", diff --git a/src/hooks/useAPI.ts b/src/hooks/useAPI.ts index cb112bfe..e58e5c8e 100644 --- a/src/hooks/useAPI.ts +++ b/src/hooks/useAPI.ts @@ -32,7 +32,10 @@ const useAPI = () => { let errorMessage = ""; axios(requestConfig) - .then((response) => setData(response)) + .then((response) => { + setData(response); + setIsLoading(false); + }) .catch((err) => { if (err.response) { const errors = err.response.data; @@ -51,8 +54,8 @@ const useAPI = () => { } if (errorMessage) setError(errorMessage); + setIsLoading(false); }); - setIsLoading(false); }, []); return { data, setData, isLoading, error, sendRequest }; diff --git a/src/pages/Assignments/Assignment.tsx b/src/pages/Assignments/Assignment.tsx index 36a969ee..c22dc464 100644 --- a/src/pages/Assignments/Assignment.tsx +++ b/src/pages/Assignments/Assignment.tsx @@ -20,6 +20,7 @@ import EtcTab from "./tabs/EtcTab"; const Assignments = () => { const { error, isLoading, data: assignmentResponse, sendRequest: fetchAssignments } = useAPI(); const { data: coursesResponse, sendRequest: fetchCourses } = useAPI(); + const { data: topicsResponse, error: topicsError, isLoading: topicsLoading, sendRequest: fetchTopicsAPI } = useAPI(); const auth = useSelector( @@ -48,157 +49,60 @@ const Assignments = () => { allowBiddingForReviewers: false, }); - // Sample topics data - const topicsData = [ - { - id: "E2550", - name: "Response hierarchy and responses_controller back end", - students: ["Student 10929", "Student 10913", "Student 10912"], - questionnaire: "--Default rubric--", - numSlots: 1, - availableSlots: 0, - waitlist: 0, - }, - { - id: "E2551", - name: "Reimplementing SubmittedContentController", - students: ["Student 10904", "Student 10922", "Student 10924"], - questionnaire: "--Default rubric--", - numSlots: 1, - availableSlots: 0, - waitlist: 0, - }, - { - id: "E2552", - name: "ProjectTopic and SignedUpTeam", - students: ["Student 10905", "Student 10915"], - questionnaire: "--Default rubric--", - numSlots: 1, - availableSlots: 0, - waitlist: 0, - }, - { - id: "E2552", - name: "ProjectTopic and SignedUpTeam", - students: ["Student 10905", "Student 10915"], - questionnaire: "--Default rubric--", - numSlots: 1, - availableSlots: 0, - waitlist: 0, - }, - { - id: "E2552", - name: "ProjectTopic and SignedUpTeam", - students: ["Student 10905", "Student 10915"], - questionnaire: "--Default rubric--", - numSlots: 1, - availableSlots: 0, - waitlist: 0, - }, - { - id: "E2552", - name: "ProjectTopic and SignedUpTeam", - students: ["Student 10905", "Student 10915"], - questionnaire: "--Default rubric--", - numSlots: 1, - availableSlots: 0, - waitlist: 0, - }, - { - id: "E2552", - name: "ProjectTopic and SignedUpTeam", - students: ["Student 10905", "Student 10915"], - questionnaire: "--Default rubric--", - numSlots: 1, - availableSlots: 0, - waitlist: 0, - }, - { - id: "E2552", - name: "ProjectTopic and SignedUpTeam", - students: ["Student 10905", "Student 10915"], - questionnaire: "--Default rubric--", - numSlots: 1, - availableSlots: 0, - waitlist: 0, - }, - { - id: "E2552", - name: "ProjectTopic and SignedUpTeam", - students: ["Student 10905", "Student 10915"], - questionnaire: "--Default rubric--", - numSlots: 1, - availableSlots: 0, - waitlist: 0, - }, - { - id: "E2552", - name: "ProjectTopic and SignedUpTeam", - students: ["Student 10905", "Student 10915"], - questionnaire: "--Default rubric--", - numSlots: 1, - availableSlots: 0, - waitlist: 0, - }, - { - id: "E2552", - name: "ProjectTopic and SignedUpTeam", - students: ["Student 10905", "Student 10915"], - questionnaire: "--Default rubric--", - numSlots: 1, - availableSlots: 0, - waitlist: 0, - }, - { - id: "E2552", - name: "ProjectTopic and SignedUpTeam", - students: ["Student 10905", "Student 10915"], - questionnaire: "--Default rubric--", - numSlots: 1, - availableSlots: 0, - waitlist: 0, - }, - { - id: "E2552", - name: "ProjectTopic and SignedUpTeam", - students: ["Student 10905", "Student 10915"], - questionnaire: "--Default rubric--", - numSlots: 1, - availableSlots: 0, - waitlist: 0, - }, - { - id: "E2552", - name: "ProjectTopic and SignedUpTeam", - students: ["Student 10905", "Student 10915"], - questionnaire: "--Default rubric--", - numSlots: 1, - availableSlots: 0, - waitlist: 0, - }, - { - id: "E2552", - name: "ProjectTopic and SignedUpTeam", - students: ["Student 10905", "Student 10915"], - questionnaire: "--Default rubric--", - numSlots: 1, - availableSlots: 0, - waitlist: 0, - }, - ]; - - - const fetchData = useCallback(async () => { - try { - const [assignments, courses] = await Promise.all([ - fetchAssignments({ url: `/assignments` }), - fetchCourses({ url: '/courses' }), - ]); - // Handle the responses as needed - } catch (err) { - // Handle any errors that occur during the fetch - console.error("Error fetching data:", err); + // Fetch topics for the current assignment + const fetchTopics = useCallback((assignmentId: number) => { + if (!assignmentId) return; + console.log('Fetching topics for assignment:', assignmentId); + fetchTopicsAPI({ + url: `/project_topics?assignment_id=${assignmentId}`, + method: 'GET' + }); + }, [fetchTopicsAPI]); + + // Debug logging + useEffect(() => { + console.log('Topics response:', topicsResponse); + console.log('Topics response data:', topicsResponse?.data); + console.log('Topics response data type:', typeof topicsResponse?.data); + console.log('Is topics response data array:', Array.isArray(topicsResponse?.data)); + console.log('Topics error:', topicsError); + console.log('Topics loading:', topicsLoading); + }, [topicsResponse, topicsError, topicsLoading]); + + // Transform topics data to match expected format + const topicsData = useMemo(() => { + // If there's an error or no response, return empty array + if (topicsError || !topicsResponse?.data) { + console.log('No topics data available:', { topicsError, topicsResponse }); + return []; } + + // Check if data is an array, if not, return empty array + const topics = Array.isArray(topicsResponse.data) ? topicsResponse.data : []; + + console.log('Processing topics:', topics); + + return topics.map((topic: any) => ({ + id: topic.topic_identifier || topic.id?.toString() || 'unknown', + name: topic.topic_name || 'Unnamed Topic', + assignedTeams: topic.confirmed_teams || [], + waitlistedTeams: topic.waitlisted_teams || [], + questionnaire: "--Default rubric--", // This would need to be fetched separately + numSlots: topic.max_choosers || 1, + availableSlots: topic.available_slots || 0, + bookmarks: [], // This would need to be fetched separately + category: topic.category || '', + description: topic.description || '', + link: topic.link || '', + micropayment: topic.micropayment || 0, + private_to: topic.private_to || null + })); + }, [topicsResponse, topicsError]); + + const fetchData = useCallback(() => { + // Trigger the API calls - they will update the state + fetchAssignments({ url: `/assignments` }); + fetchCourses({ url: '/courses' }); }, [fetchAssignments, fetchCourses]); useEffect(() => { @@ -207,6 +111,14 @@ const Assignments = () => { } }, [fetchData, showDeleteConfirmation.visible, auth.user.id]); + // Fetch topics when assignment data is available + useEffect(() => { + if (assignmentResponse && assignmentResponse.data && assignmentResponse.data.length > 0) { + const firstAssignment = assignmentResponse.data[0]; + fetchTopics(firstAssignment.id); + } + }, [assignmentResponse, fetchTopics]); + let mergedData: Array = []; if (assignmentResponse && coursesResponse) { @@ -250,6 +162,106 @@ const Assignments = () => { })); }; + // Handler functions for TopicsTab + const handleDropTeam = async (topicId: string, teamId: string) => { + try { + // This would require a specific API endpoint for dropping teams from topics + // For now, we'll just refresh the topics data + console.log(`Dropping team ${teamId} from topic ${topicId}`); + // TODO: Implement team dropping API call when endpoint is available + + // Refresh topics data + if (assignmentResponse && assignmentResponse.data && assignmentResponse.data.length > 0) { + fetchTopics(assignmentResponse.data[0].id); + } + } catch (err) { + console.error("Error dropping team:", err); + } + }; + + const handleDeleteTopic = async (topicId: string) => { + try { + if (!assignmentResponse || !assignmentResponse.data || assignmentResponse.data.length === 0) { + throw new Error('No assignment found'); + } + + fetchTopicsAPI({ + url: `/project_topics?assignment_id=${assignmentResponse.data[0].id}&topic_ids[]=${topicId}`, + method: 'DELETE' + }); + + // Refresh topics data after a short delay + setTimeout(() => { + fetchTopics(assignmentResponse.data[0].id); + }, 500); + + console.log(`Topic ${topicId} deleted successfully`); + } catch (err) { + console.error("Error deleting topic:", err); + } + }; + + const handleEditTopic = async (topicId: string, updatedData: any) => { + try { + fetchTopicsAPI({ + url: `/project_topics/${topicId}`, + method: 'PATCH', + data: { + project_topic: updatedData + } + }); + + // Refresh topics data after a short delay + if (assignmentResponse && assignmentResponse.data && assignmentResponse.data.length > 0) { + setTimeout(() => { + fetchTopics(assignmentResponse.data[0].id); + }, 500); + } + + console.log(`Topic ${topicId} updated successfully`); + } catch (err) { + console.error("Error updating topic:", err); + } + }; + + const handleCreateTopic = async (topicData: any) => { + try { + if (!assignmentResponse || !assignmentResponse.data || assignmentResponse.data.length === 0) { + throw new Error('No assignment found'); + } + + fetchTopicsAPI({ + url: `/project_topics`, + method: 'POST', + data: { + project_topic: { + ...topicData, + assignment_id: assignmentResponse.data[0].id + } + } + }); + + // Refresh topics data after a short delay + setTimeout(() => { + fetchTopics(assignmentResponse.data[0].id); + }, 500); + + console.log(`Topic created successfully`); + } catch (err) { + console.error("Error creating topic:", err); + } + }; + + const handleCreateBookmark = (topicId: string) => { + console.log(`Creating bookmark for topic ${topicId}`); + // TODO: Implement bookmark creation logic + }; + + const handleApplyPartnerAd = (topicId: string, applicationText: string) => { + console.log(`Applying to partner ad for topic ${topicId}: ${applicationText}`); + // TODO: Implement partner ad application logic + }; + const renderTabContent = () => { switch (activeTab) { case 'general': @@ -269,7 +281,15 @@ const Assignments = () => { ); diff --git a/src/pages/Assignments/tabs/TopicsTab.tsx b/src/pages/Assignments/tabs/TopicsTab.tsx index df97a7e6..b8586af9 100644 --- a/src/pages/Assignments/tabs/TopicsTab.tsx +++ b/src/pages/Assignments/tabs/TopicsTab.tsx @@ -39,6 +39,7 @@ interface TopicData { name: string; // Topic Name url?: string; // Optional URL for the topic name description?: string; // Optional short description + category?: string; // Optional category assignedTeams: AssignedTeam[]; // Teams/Students assigned to this topic waitlistedTeams: WaitlistedTeam[]; // Teams/Students waitlisted questionnaire: string; // Associated questionnaire name @@ -62,11 +63,14 @@ interface TopicSettings { interface TopicsTabProps { topicSettings: TopicSettings; topicsData: TopicData[]; // Ensure the data passed matches the updated TopicData interface + topicsLoading?: boolean; + topicsError?: string | null; onTopicSettingChange: (setting: string, value: boolean) => void; // Add handlers for actions like drop team, delete topic, edit topic, create bookmark etc. onDropTeam: (topicId: string, teamId: string) => void; onDeleteTopic: (topicId: string) => void; - onEditTopic: (topicId: string) => void; + onEditTopic: (topicId: string, updatedData?: any) => void; + onCreateTopic?: (topicData: any) => void; onCreateBookmark: (topicId: string) => void; // Function to handle opening a create bookmark UI/modal // Handler for partner ad application submission onApplyPartnerAd: (topicId: string, applicationText: string) => void; @@ -77,10 +81,13 @@ interface TopicsTabProps { const TopicsTab = ({ topicSettings, topicsData, + topicsLoading = false, + topicsError = null, onTopicSettingChange, onDropTeam, onDeleteTopic, onEditTopic, + onCreateTopic, onCreateBookmark, onApplyPartnerAd, }: TopicsTabProps) => { @@ -88,6 +95,41 @@ const TopicsTab = ({ const [showPartnerAdModal, setShowPartnerAdModal] = useState(false); const [selectedPartnerAdTopic, setSelectedPartnerAdTopic] = useState(null); const [partnerAdApplication, setPartnerAdApplication] = useState(""); + + // New topic modal state + const [showNewTopicModal, setShowNewTopicModal] = useState(false); + const [newTopicData, setNewTopicData] = useState({ + topic_name: '', + topic_identifier: '', + category: '', + max_choosers: 1, + description: '', + link: '' + }); + + // Selected topics state + const [selectedTopics, setSelectedTopics] = useState>(new Set()); + const [selectAll, setSelectAll] = useState(false); + + // Import topics modal state + const [showImportModal, setShowImportModal] = useState(false); + const [importData, setImportData] = useState(''); + + // Delete confirmation modal state + const [showDeleteModal, setShowDeleteModal] = useState(false); + const [deleteType, setDeleteType] = useState<'selected' | 'all'>('selected'); + + // Edit topic modal state + const [showEditModal, setShowEditModal] = useState(false); + const [editingTopic, setEditingTopic] = useState(null); + const [editTopicData, setEditTopicData] = useState({ + topic_name: '', + topic_identifier: '', + category: '', + max_choosers: 1, + description: '', + link: '' + }); // --- Partner Ad Modal Handlers --- const handleShowPartnerAd = (topic: TopicData) => { @@ -109,6 +151,169 @@ const TopicsTab = ({ handleClosePartnerAd(); }; + // --- New Topic Modal Handlers --- + const handleShowNewTopic = () => { + setNewTopicData({ + topic_name: '', + topic_identifier: '', + category: '', + max_choosers: 1, + description: '', + link: '' + }); + setShowNewTopicModal(true); + }; + + const handleCloseNewTopic = () => { + setShowNewTopicModal(false); + }; + + const handleSubmitNewTopic = () => { + if (onCreateTopic) { + onCreateTopic(newTopicData); + handleCloseNewTopic(); + } + }; + + const handleInputChange = (field: string, value: string | number) => { + setNewTopicData(prev => ({ + ...prev, + [field]: value + })); + }; + + // --- Edit Topic Modal Handlers --- + const handleShowEditTopic = (topic: TopicData) => { + setEditingTopic(topic); + setEditTopicData({ + topic_name: topic.name || '', + topic_identifier: topic.id || '', + category: topic.category || '', + max_choosers: topic.numSlots || 1, + description: topic.description || '', + link: topic.url || '' + }); + setShowEditModal(true); + }; + + const handleCloseEditTopic = () => { + setShowEditModal(false); + setEditingTopic(null); + }; + + const handleSubmitEditTopic = () => { + if (editingTopic && onEditTopic) { + onEditTopic(editingTopic.id, editTopicData); + handleCloseEditTopic(); + } + }; + + const handleEditInputChange = (field: string, value: string | number) => { + setEditTopicData(prev => ({ + ...prev, + [field]: value + })); + }; + + // --- Selection Handlers --- + const handleSelectAll = () => { + if (selectAll) { + setSelectedTopics(new Set()); + setSelectAll(false); + } else { + const allTopicIds = new Set(topicsData.map(topic => topic.id)); + setSelectedTopics(allTopicIds); + setSelectAll(true); + } + }; + + const handleSelectTopic = (topicId: string) => { + const newSelected = new Set(selectedTopics); + if (newSelected.has(topicId)) { + newSelected.delete(topicId); + } else { + newSelected.add(topicId); + } + setSelectedTopics(newSelected); + setSelectAll(newSelected.size === topicsData.length); + }; + + // --- Import Topics Handlers --- + const handleShowImport = () => { + setImportData(''); + setShowImportModal(true); + }; + + const handleCloseImport = () => { + setShowImportModal(false); + }; + + const handleImportTopics = () => { + try { + // Parse CSV or JSON data + const lines = importData.trim().split('\n'); + const topics = lines.map((line, index) => { + const [topic_name, topic_identifier, category, max_choosers, description, link] = line.split(','); + return { + topic_name: topic_name?.trim() || `Imported Topic ${index + 1}`, + topic_identifier: topic_identifier?.trim() || `IMP${index + 1}`, + category: category?.trim() || '', + max_choosers: parseInt(max_choosers?.trim()) || 1, + description: description?.trim() || '', + link: link?.trim() || '' + }; + }); + + // Create each topic + topics.forEach(topic => { + if (onCreateTopic) { + onCreateTopic(topic); + } + }); + + handleCloseImport(); + } catch (error) { + console.error('Error importing topics:', error); + } + }; + + // --- Delete Handlers --- + const handleDeleteSelected = () => { + setDeleteType('selected'); + setShowDeleteModal(true); + }; + + const handleDeleteAll = () => { + setDeleteType('all'); + setShowDeleteModal(true); + }; + + const handleConfirmDelete = () => { + if (deleteType === 'selected') { + selectedTopics.forEach(topicId => { + onDeleteTopic(topicId); + }); + setSelectedTopics(new Set()); + setSelectAll(false); + } else { + // Delete all topics + topicsData.forEach(topic => { + onDeleteTopic(topic.id); + }); + } + setShowDeleteModal(false); + }; + + const handleCloseDelete = () => { + setShowDeleteModal(false); + }; + + // --- Back Handler --- + const handleBack = () => { + // Navigate back to assignments list + window.history.back(); + }; + // --- Render Helper Functions --- const renderTeamMembers = (members: TeamMember[]) => { // Basic check for members array @@ -191,13 +396,29 @@ const TopicsTab = ({ + {/* Error Message */} + {topicsError && ( +
+ Error loading topics: { + typeof topicsError === 'string' + ? topicsError + : JSON.stringify(topicsError) + } +
+ )} + {/* Topics Table */} {/* Added responsive */} {/* Adjusted width for checkbox column */} - {/* Consider select all functionality */} + Topic ID {/* Topic Name column with max-width */} @@ -211,13 +432,29 @@ const TopicsTab = ({ - {/* Ensure topicsData is not null or undefined before mapping */} - {topicsData && topicsData.length > 0 ? ( + {/* Loading State */} + {topicsLoading ? ( + + +
+
+ Loading... +
+ Loading topics... +
+ + + ) : topicsData && topicsData.length > 0 ? ( topicsData.map((topic) => ( {/* Checkbox cell */} - + handleSelectTopic(topic.id)} + /> {/* Topic ID */} {topic.id} @@ -307,7 +544,7 @@ const TopicsTab = ({ onEditTopic(topic.id)} + onClick={() => handleShowEditTopic(topic)} title="Edit Topic" /> {/* Delete Icon */} @@ -332,19 +569,36 @@ const TopicsTab = ({ {/* Action Buttons */}
{/* Added flex-wrap */} - - - - -
@@ -377,6 +631,256 @@ const TopicsTab = ({ + + {/* New Topic Modal */} + + + Create New Topic + + +
+ + + + handleInputChange('topic_name', e.target.value)} + required + /> + + + + + handleInputChange('topic_identifier', e.target.value)} + required + /> + + + + + + + handleInputChange('category', e.target.value)} + /> + + + + + handleInputChange('max_choosers', parseInt(e.target.value) || 1)} + required + /> + + + + + + + handleInputChange('description', e.target.value)} + /> + + + + + + + handleInputChange('link', e.target.value)} + /> + + + +
+
+ + + + +
+ + {/* Import Topics Modal */} + + + Import Topics + + +
+

Import topics from CSV format. Each line should contain:

+

Topic Name, Topic Identifier, Category, Max Choosers, Description, Link

+

Example: "Database Design, DB001, Technical, 2, Design database schema, https://example.com"

+
+ + setImportData(e.target.value)} + /> + +
+ + + + +
+ + {/* Delete Confirmation Modal */} + + + Confirm Delete + + +

+ {deleteType === 'selected' + ? `Are you sure you want to delete ${selectedTopics.size} selected topic(s)? This action cannot be undone.` + : `Are you sure you want to delete ALL topics (${topicsData.length} total)? This action cannot be undone.` + } +

+
+ + + + +
+ + {/* Edit Topic Modal */} + + + Edit Topic + + +
+ + + + handleEditInputChange('topic_name', e.target.value)} + required + /> + + + + + handleEditInputChange('topic_identifier', e.target.value)} + required + /> + + + + + + + handleEditInputChange('category', e.target.value)} + /> + + + + + handleEditInputChange('max_choosers', parseInt(e.target.value) || 1)} + required + /> + + + + + + + handleEditInputChange('description', e.target.value)} + /> + + + + + + + handleEditInputChange('link', e.target.value)} + /> + + + +
+
+ + + + +
); }; diff --git a/src/pages/StudentTasks/StudentTasks.tsx b/src/pages/StudentTasks/StudentTasks.tsx index ed69ddd8..d4dce3ba 100644 --- a/src/pages/StudentTasks/StudentTasks.tsx +++ b/src/pages/StudentTasks/StudentTasks.tsx @@ -1,5 +1,7 @@ -import React, { useEffect, useState } from "react"; -import { Container, Table } from "react-bootstrap"; +import React, { useEffect, useCallback, useMemo } from "react"; +import { Container, Table, Spinner, Alert } from "react-bootstrap"; +import { useParams } from "react-router-dom"; +import useAPI from "../../hooks/useAPI"; interface Topic { id: string; @@ -9,25 +11,128 @@ interface Topic { } const StudentTasks: React.FC = () => { - const [topics, setTopics] = useState([]); + const { assignmentId } = useParams<{ assignmentId?: string }>(); + const { data: topicsResponse, error: topicsError, isLoading: topicsLoading, sendRequest: fetchTopicsAPI } = useAPI(); + const { data: assignmentResponse, sendRequest: fetchAssignment } = useAPI(); + // Fetch assignment data first to get the assignment ID + const fetchAssignmentData = useCallback(() => { + if (assignmentId) { + fetchAssignment({ + url: `/assignments/${assignmentId}`, + method: 'GET' + }); + } else { + // If no assignment ID in URL, fetch all assignments and use the first one + fetchAssignment({ + url: `/assignments`, + method: 'GET' + }); + } + }, [assignmentId, fetchAssignment]); + + // Fetch topics for the current assignment + const fetchTopics = useCallback((assignmentId: number) => { + if (!assignmentId) return; + console.log('Fetching topics for assignment:', assignmentId); + fetchTopicsAPI({ + url: `/project_topics?assignment_id=${assignmentId}`, + method: 'GET' + }); + }, [fetchTopicsAPI]); + + // Load assignment data on component mount useEffect(() => { - // Temporary hardcoded data (replace later with API call) - const mockData: Topic[] = [ - { id: "E2450", name: "Refactor assignments_controller.rb", availableSlots: 0, waitlist: 0 }, - { id: "E2451", name: "Reimplement feedback_response_map.rb", availableSlots: 1, waitlist: 0 }, - { id: "E2452", name: "Refactor review_mapping_controller.rb", availableSlots: 0, waitlist: 0 }, - { id: "E2453", name: "Refactor review_mapping_helper.rb", availableSlots: 0, waitlist: 0 }, - { id: "E2454", name: "Refactor student_task.rb", availableSlots: 0, waitlist: 0 }, - { id: "E2455", name: "Refactor sign_up_sheet_controller.rb", availableSlots: 0, waitlist: 0 }, - { id: "E2456", name: "Refactor teams_user.rb", availableSlots: 0, waitlist: 0 }, - { id: "E2457", name: "Github metrics integration", availableSlots: 1, waitlist: 0 }, - { id: "E2458", name: "User management and users table", availableSlots: 0, waitlist: 0 }, - { id: "E2459", name: "View for results of bidding", availableSlots: 0, waitlist: 0 }, - ]; - setTopics(mockData); + fetchAssignmentData(); + }, [fetchAssignmentData]); + + // Fetch topics when assignment data is available + useEffect(() => { + if (assignmentResponse?.data) { + let targetAssignmentId: number; + + if (assignmentId) { + // If assignment ID is in URL, use it + targetAssignmentId = parseInt(assignmentId); + } else if (Array.isArray(assignmentResponse.data) && assignmentResponse.data.length > 0) { + // If no assignment ID in URL, use the first assignment + targetAssignmentId = assignmentResponse.data[0].id; + } else { + // Single assignment object + targetAssignmentId = assignmentResponse.data.id; + } + + fetchTopics(targetAssignmentId); + } + }, [assignmentResponse, assignmentId, fetchTopics]); + + // Transform topics data to match expected format + const topics = useMemo(() => { + // If there's an error or no response, return empty array + if (topicsError || !topicsResponse?.data) { + console.log('No topics data available:', { topicsError, topicsResponse }); + return []; + } + + // Check if data is an array, if not, return empty array + const topicsData = Array.isArray(topicsResponse.data) ? topicsResponse.data : []; + + console.log('Processing topics for StudentTasks:', topicsData); + + return topicsData.map((topic: any) => ({ + id: topic.topic_identifier || topic.id?.toString() || 'unknown', + name: topic.topic_name || 'Unnamed Topic', + availableSlots: topic.available_slots || 0, + waitlist: topic.waitlisted_teams?.length || 0 + })); + }, [topicsResponse, topicsError]); + + // Get assignment name for display + const assignmentName = useMemo(() => { + if (!assignmentResponse?.data) return 'OSS project & documentation assignment'; + + if (Array.isArray(assignmentResponse.data) && assignmentResponse.data.length > 0) { + return assignmentResponse.data[0].name || 'OSS project & documentation assignment'; + } else { + return assignmentResponse.data.name || 'OSS project & documentation assignment'; + } + }, [assignmentResponse]); + + // Get user's selected topics (this would need to be implemented based on user's selections) + const userSelectedTopics: Topic[] = useMemo(() => { + // For now, return empty array - this would need to be fetched from user's topic selections + return []; }, []); + // Show loading spinner while data is being fetched + if (topicsLoading) { + return ( + + + Loading topics... + +

Loading topics...

+
+ ); + } + + // Show error message if there's an error + if (topicsError) { + return ( + + + Error Loading Topics +

+ {typeof topicsError === 'string' + ? topicsError + : JSON.stringify(topicsError) + } +

+
+
+ ); + } + return ( { >

- Signup sheet for OSS project & documentation assignment + Signup sheet for {assignmentName}

- Your topic(s): Refactor review_mapping_controller.rb + Your topic(s): { + userSelectedTopics.length > 0 + ? userSelectedTopics.map(topic => topic.name).join(', ') + : 'No topics selected yet' + }

- - - - - - - - - - - {topics.map((topic) => ( - - - - - + {topics.length === 0 ? ( + + No Topics Available +

There are no topics available for this assignment yet.

+
+ ) : ( +
Topic IDTopic name(s)Available slotsNum. on waitlist
{topic.id}{topic.name}{topic.availableSlots}{topic.waitlist}
+ + + + + + - ))} - -
Topic IDTopic name(s)Available slotsNum. on waitlist
+ + + {topics.map((topic) => ( + selected.id === topic.id) + ? "#fff8c4" + : "white", // highlight selected row + }} + > + {topic.id} + {topic.name} + {topic.availableSlots} + {topic.waitlist} + + ))} + + + )}
); From 850edf0be77a0d09080be9e66a7d908e27d62ec4 Mon Sep 17 00:00:00 2001 From: Srinidhi Shivashankar Date: Sun, 26 Oct 2025 23:41:56 -0400 Subject: [PATCH 12/34] Enhance topic management with database ID integration and error handling - Added functionality to store and utilize the database ID for topics in API calls. - Implemented success and error handling for topic edits, including alerts for users. - Updated the UI to log actions and provide feedback during topic editing and deletion. - Refactored code for improved clarity and maintainability in topic management. --- src/pages/Assignments/Assignment.tsx | 55 ++++++++++++++++++------ src/pages/Assignments/tabs/TopicsTab.tsx | 15 ++++++- 2 files changed, 56 insertions(+), 14 deletions(-) diff --git a/src/pages/Assignments/Assignment.tsx b/src/pages/Assignments/Assignment.tsx index c22dc464..d85a4237 100644 --- a/src/pages/Assignments/Assignment.tsx +++ b/src/pages/Assignments/Assignment.tsx @@ -21,6 +21,7 @@ const Assignments = () => { const { error, isLoading, data: assignmentResponse, sendRequest: fetchAssignments } = useAPI(); const { data: coursesResponse, sendRequest: fetchCourses } = useAPI(); const { data: topicsResponse, error: topicsError, isLoading: topicsLoading, sendRequest: fetchTopicsAPI } = useAPI(); + const { data: editResponse, error: editError, sendRequest: editTopicAPI } = useAPI(); const auth = useSelector( @@ -84,6 +85,7 @@ const Assignments = () => { return topics.map((topic: any) => ({ id: topic.topic_identifier || topic.id?.toString() || 'unknown', + databaseId: topic.id, // Store the actual database ID for API calls name: topic.topic_name || 'Unnamed Topic', assignedTeams: topic.confirmed_teams || [], waitlistedTeams: topic.waitlisted_teams || [], @@ -119,6 +121,23 @@ const Assignments = () => { } }, [assignmentResponse, fetchTopics]); + // Refresh topics when edit is successful + useEffect(() => { + if (editResponse && assignmentResponse && assignmentResponse.data && assignmentResponse.data.length > 0) { + console.log('Edit successful, refreshing topics...'); + dispatch(alertActions.showAlert({ variant: "success", message: "Topic updated successfully!" })); + fetchTopics(assignmentResponse.data[0].id); + } + }, [editResponse, assignmentResponse, fetchTopics, dispatch]); + + // Handle edit errors + useEffect(() => { + if (editError) { + console.error('Edit error:', editError); + dispatch(alertActions.showAlert({ variant: "danger", message: `Edit failed: ${editError}` })); + } + }, [editError, dispatch]); + let mergedData: Array = []; if (assignmentResponse && coursesResponse) { @@ -185,8 +204,16 @@ const Assignments = () => { throw new Error('No assignment found'); } + // Find the topic to get the database ID + const topic = topicsData.find(t => t.id === topicId); + if (!topic || !topic.databaseId) { + console.error('Topic not found or missing database ID:', topicId); + dispatch(alertActions.showAlert({ variant: "danger", message: "Topic not found" })); + return; + } + fetchTopicsAPI({ - url: `/project_topics?assignment_id=${assignmentResponse.data[0].id}&topic_ids[]=${topicId}`, + url: `/project_topics?assignment_id=${assignmentResponse.data[0].id}&topic_ids[]=${topic.databaseId}`, method: 'DELETE' }); @@ -195,7 +222,7 @@ const Assignments = () => { fetchTopics(assignmentResponse.data[0].id); }, 500); - console.log(`Topic ${topicId} deleted successfully`); + console.log(`Topic ${topicId} (database ID: ${topic.databaseId}) deleted successfully`); } catch (err) { console.error("Error deleting topic:", err); } @@ -203,24 +230,28 @@ const Assignments = () => { const handleEditTopic = async (topicId: string, updatedData: any) => { try { - fetchTopicsAPI({ - url: `/project_topics/${topicId}`, + // Find the topic to get the database ID + const topic = topicsData.find(t => t.id === topicId); + if (!topic || !topic.databaseId) { + console.error('Topic not found or missing database ID:', topicId); + dispatch(alertActions.showAlert({ variant: "danger", message: "Topic not found" })); + return; + } + + console.log('Starting edit for topic:', topicId, 'database ID:', topic.databaseId, 'with data:', updatedData); + + editTopicAPI({ + url: `/project_topics/${topic.databaseId}`, method: 'PATCH', data: { project_topic: updatedData } }); - // Refresh topics data after a short delay - if (assignmentResponse && assignmentResponse.data && assignmentResponse.data.length > 0) { - setTimeout(() => { - fetchTopics(assignmentResponse.data[0].id); - }, 500); - } - - console.log(`Topic ${topicId} updated successfully`); + console.log(`Edit request sent for topic ${topicId} (database ID: ${topic.databaseId})`); } catch (err) { console.error("Error updating topic:", err); + dispatch(alertActions.showAlert({ variant: "danger", message: "Failed to update topic" })); } }; diff --git a/src/pages/Assignments/tabs/TopicsTab.tsx b/src/pages/Assignments/tabs/TopicsTab.tsx index b8586af9..0355e1b6 100644 --- a/src/pages/Assignments/tabs/TopicsTab.tsx +++ b/src/pages/Assignments/tabs/TopicsTab.tsx @@ -35,7 +35,8 @@ interface BookmarkData { // Updated TopicData interface interface TopicData { - id: string; // Topic ID + id: string; // Topic ID (topic_identifier for display) + databaseId: number; // Database ID for API calls name: string; // Topic Name url?: string; // Optional URL for the topic name description?: string; // Optional short description @@ -184,6 +185,7 @@ const TopicsTab = ({ // --- Edit Topic Modal Handlers --- const handleShowEditTopic = (topic: TopicData) => { + console.log('Edit button clicked for topic:', topic); setEditingTopic(topic); setEditTopicData({ topic_name: topic.name || '', @@ -194,6 +196,7 @@ const TopicsTab = ({ link: topic.url || '' }); setShowEditModal(true); + console.log('Edit modal should be opening now'); }; const handleCloseEditTopic = () => { @@ -202,9 +205,14 @@ const TopicsTab = ({ }; const handleSubmitEditTopic = () => { + console.log('Submitting edit for topic:', editingTopic); + console.log('Edit data:', editTopicData); if (editingTopic && onEditTopic) { + console.log('Calling onEditTopic with:', editingTopic.id, editTopicData); onEditTopic(editingTopic.id, editTopicData); handleCloseEditTopic(); + } else { + console.log('Missing editingTopic or onEditTopic:', { editingTopic, onEditTopic }); } }; @@ -544,7 +552,10 @@ const TopicsTab = ({ handleShowEditTopic(topic)} + onClick={() => { + console.log('Edit icon clicked for topic:', topic); + handleShowEditTopic(topic); + }} title="Edit Topic" /> {/* Delete Icon */} From 4a0bbc47e3354de9b3f49c5a0b1b0477adac120e Mon Sep 17 00:00:00 2001 From: Srinidhi Shivashankar Date: Mon, 27 Oct 2025 00:16:55 -0400 Subject: [PATCH 13/34] Implement bookmarking and selection features in StudentTasks page - Added state management for bookmarking and selecting topics. - Introduced UI elements for toggling bookmarks and selections with appropriate icons. - Updated topic rendering to reflect bookmark and selection states. - Enhanced user experience by visually distinguishing taken, selected, and bookmarked topics. --- src/pages/StudentTasks/StudentTasks.tsx | 92 ++++++++++++++++++++++--- 1 file changed, 81 insertions(+), 11 deletions(-) diff --git a/src/pages/StudentTasks/StudentTasks.tsx b/src/pages/StudentTasks/StudentTasks.tsx index d4dce3ba..a1664175 100644 --- a/src/pages/StudentTasks/StudentTasks.tsx +++ b/src/pages/StudentTasks/StudentTasks.tsx @@ -1,6 +1,7 @@ -import React, { useEffect, useCallback, useMemo } from "react"; -import { Container, Table, Spinner, Alert } from "react-bootstrap"; +import React, { useEffect, useCallback, useMemo, useState } from "react"; +import { Container, Table, Spinner, Alert, Button } from "react-bootstrap"; import { useParams } from "react-router-dom"; +import { BsBookmark, BsBookmarkFill, BsCheck, BsX } from "react-icons/bs"; import useAPI from "../../hooks/useAPI"; interface Topic { @@ -8,12 +9,19 @@ interface Topic { name: string; availableSlots: number; waitlist: number; + isBookmarked?: boolean; + isSelected?: boolean; + isTaken?: boolean; } const StudentTasks: React.FC = () => { const { assignmentId } = useParams<{ assignmentId?: string }>(); const { data: topicsResponse, error: topicsError, isLoading: topicsLoading, sendRequest: fetchTopicsAPI } = useAPI(); const { data: assignmentResponse, sendRequest: fetchAssignment } = useAPI(); + + // State for bookmarks and selections + const [bookmarkedTopics, setBookmarkedTopics] = useState>(new Set()); + const [selectedTopics, setSelectedTopics] = useState>(new Set()); // Fetch assignment data first to get the assignment ID const fetchAssignmentData = useCallback(() => { @@ -83,9 +91,12 @@ const StudentTasks: React.FC = () => { id: topic.topic_identifier || topic.id?.toString() || 'unknown', name: topic.topic_name || 'Unnamed Topic', availableSlots: topic.available_slots || 0, - waitlist: topic.waitlisted_teams?.length || 0 + waitlist: topic.waitlisted_teams?.length || 0, + isBookmarked: bookmarkedTopics.has(topic.topic_identifier || topic.id?.toString() || 'unknown'), + isSelected: selectedTopics.has(topic.topic_identifier || topic.id?.toString() || 'unknown'), + isTaken: (topic.available_slots || 0) <= 0 })); - }, [topicsResponse, topicsError]); + }, [topicsResponse, topicsError, bookmarkedTopics, selectedTopics]); // Get assignment name for display const assignmentName = useMemo(() => { @@ -100,9 +111,34 @@ const StudentTasks: React.FC = () => { // Get user's selected topics (this would need to be implemented based on user's selections) const userSelectedTopics: Topic[] = useMemo(() => { - // For now, return empty array - this would need to be fetched from user's topic selections - return []; - }, []); + return topics.filter(topic => topic.isSelected); + }, [topics]); + + // Handle bookmark toggle + const handleBookmarkToggle = (topicId: string) => { + setBookmarkedTopics(prev => { + const newSet = new Set(prev); + if (newSet.has(topicId)) { + newSet.delete(topicId); + } else { + newSet.add(topicId); + } + return newSet; + }); + }; + + // Handle topic selection toggle + const handleTopicSelect = (topicId: string) => { + setSelectedTopics(prev => { + const newSet = new Set(prev); + if (newSet.has(topicId)) { + newSet.delete(topicId); + } else { + newSet.add(topicId); + } + return newSet; + }); + }; // Show loading spinner while data is being fetched if (topicsLoading) { @@ -170,6 +206,8 @@ const StudentTasks: React.FC = () => { Topic name(s) Available slots Num. on waitlist + Bookmarks + Select @@ -177,16 +215,48 @@ const StudentTasks: React.FC = () => { selected.id === topic.id) - ? "#fff8c4" - : "white", // highlight selected row + backgroundColor: topic.isTaken + ? "#fff8c4" // Yellow for taken topics + : topic.isSelected + ? "#e3f2fd" // Light blue for selected topics + : "white" }} > {topic.id} {topic.name} {topic.availableSlots} {topic.waitlist} + + + + + + ))} From 7d21b816d721543ae470d022605c8dbb8c6453f9 Mon Sep 17 00:00:00 2001 From: Srinidhi Shivashankar Date: Mon, 27 Oct 2025 01:04:55 -0400 Subject: [PATCH 14/34] Enhance team management and UI feedback in Assignments and StudentTasks - Implemented API call for dropping teams from topics with error handling and success alerts. - Updated UI to reflect team assignment status, including improved rendering for assigned and waitlisted teams. - Enhanced topic selection logic in StudentTasks to support single selection and automatic drop of previously selected topics. - Added loading indicators during sign-up processes and improved user feedback for actions taken on topics. --- src/pages/Assignments/Assignment.tsx | 35 +++- src/pages/Assignments/tabs/TopicsTab.tsx | 77 ++++++--- src/pages/StudentTasks/StudentTasks.tsx | 207 ++++++++++++++++++++--- 3 files changed, 258 insertions(+), 61 deletions(-) diff --git a/src/pages/Assignments/Assignment.tsx b/src/pages/Assignments/Assignment.tsx index d85a4237..3028aaf7 100644 --- a/src/pages/Assignments/Assignment.tsx +++ b/src/pages/Assignments/Assignment.tsx @@ -184,17 +184,40 @@ const Assignments = () => { // Handler functions for TopicsTab const handleDropTeam = async (topicId: string, teamId: string) => { try { - // This would require a specific API endpoint for dropping teams from topics - // For now, we'll just refresh the topics data console.log(`Dropping team ${teamId} from topic ${topicId}`); - // TODO: Implement team dropping API call when endpoint is available - // Refresh topics data - if (assignmentResponse && assignmentResponse.data && assignmentResponse.data.length > 0) { - fetchTopics(assignmentResponse.data[0].id); + // Find the topic to get the database ID + const topic = topicsData.find(t => t.id === topicId); + if (!topic || !topic.databaseId) { + console.error('Topic not found or missing database ID:', topicId); + dispatch(alertActions.showAlert({ variant: "danger", message: "Topic not found" })); + return; } + + // Call the drop team from topic API + fetchTopicsAPI({ + url: `/signed_up_teams/drop_team_from_topic`, + method: 'DELETE', + data: { + topic_id: topic.databaseId, + team_id: teamId + } + }); + + // Show success message + dispatch(alertActions.showAlert({ variant: "success", message: `Team ${teamId} dropped from topic ${topicId}` })); + + // Refresh topics data after a short delay + setTimeout(() => { + if (assignmentResponse && assignmentResponse.data && assignmentResponse.data.length > 0) { + fetchTopics(assignmentResponse.data[0].id); + } + }, 500); + + console.log(`Team ${teamId} dropped from topic ${topicId} (database ID: ${topic.databaseId})`); } catch (err) { console.error("Error dropping team:", err); + dispatch(alertActions.showAlert({ variant: "danger", message: "Failed to drop team from topic" })); } }; diff --git a/src/pages/Assignments/tabs/TopicsTab.tsx b/src/pages/Assignments/tabs/TopicsTab.tsx index 0355e1b6..6e785121 100644 --- a/src/pages/Assignments/tabs/TopicsTab.tsx +++ b/src/pages/Assignments/tabs/TopicsTab.tsx @@ -325,10 +325,13 @@ const TopicsTab = ({ // --- Render Helper Functions --- const renderTeamMembers = (members: TeamMember[]) => { // Basic check for members array - if (!Array.isArray(members)) { - return ''; + if (!Array.isArray(members) || members.length === 0) { + return 'No members'; } - return members.map(member => (displayUserNames ? member.name : member.id)).join(', '); + return members.map(member => { + const displayName = displayUserNames ? member.name : member.id; + return displayName || 'Unknown Member'; + }).join(', '); }; return ( @@ -480,36 +483,56 @@ const TopicsTab = ({
{topic.description}
)} - {/* Assigned Teams */} - {topic.assignedTeams && topic.assignedTeams.map((team) => ( -
- Assigned - {renderTeamMembers(team.members)} - {/* Drop Team Icon - Requires handler */} - onDropTeam(topic.id, team.teamId)} - title={`Drop team ${team.teamId}`} - /> + + {/* Assigned Teams (Confirmed) */} + {topic.assignedTeams && topic.assignedTeams.length > 0 && ( +
+ {topic.assignedTeams.map((team) => ( +
+ Assigned + + {renderTeamMembers(team.members)} + + {/* Drop Team Icon */} + onDropTeam(topic.id, team.teamId)} + title={`Drop team ${team.teamId} from topic`} + /> +
+ ))}
- ))} - {/* Waitlisted Teams */} - {topic.waitlistedTeams && topic.waitlistedTeams.map((team) => ( -
- Waitlisted - {renderTeamMembers(team.members)} - {/* Optional: Add icon/action for waitlisted teams if needed */} + )} + + {/* Waitlisted Teams */} + {topic.waitlistedTeams && topic.waitlistedTeams.length > 0 && ( +
+ {topic.waitlistedTeams.map((team) => ( +
+ Waitlisted + + {renderTeamMembers(team.members)} (waitlisted) + +
+ ))}
- ))} - {/* Partner Advertisement */} - {topic.partnerAd && ( + )} + + {/* Show message if no teams assigned or waitlisted */} + {(!topic.assignedTeams || topic.assignedTeams.length === 0) && + (!topic.waitlistedTeams || topic.waitlistedTeams.length === 0) && ( +
No teams assigned
+ )} + + {/* Partner Advertisement */} + {topic.partnerAd && (
- )} + )} {/* Questionnaire */} diff --git a/src/pages/StudentTasks/StudentTasks.tsx b/src/pages/StudentTasks/StudentTasks.tsx index a1664175..58619aa6 100644 --- a/src/pages/StudentTasks/StudentTasks.tsx +++ b/src/pages/StudentTasks/StudentTasks.tsx @@ -3,6 +3,8 @@ import { Container, Table, Spinner, Alert, Button } from "react-bootstrap"; import { useParams } from "react-router-dom"; import { BsBookmark, BsBookmarkFill, BsCheck, BsX } from "react-icons/bs"; import useAPI from "../../hooks/useAPI"; +import { useSelector } from "react-redux"; +import { RootState } from "../../store/store"; interface Topic { id: string; @@ -18,10 +20,17 @@ const StudentTasks: React.FC = () => { const { assignmentId } = useParams<{ assignmentId?: string }>(); const { data: topicsResponse, error: topicsError, isLoading: topicsLoading, sendRequest: fetchTopicsAPI } = useAPI(); const { data: assignmentResponse, sendRequest: fetchAssignment } = useAPI(); + const { data: signUpResponse, error: signUpError, sendRequest: signUpAPI } = useAPI(); + const { data: dropResponse, error: dropError, sendRequest: dropAPI } = useAPI(); + + // Get current user from Redux store + const auth = useSelector((state: RootState) => state.authentication); + const currentUser = auth.user; // State for bookmarks and selections const [bookmarkedTopics, setBookmarkedTopics] = useState>(new Set()); - const [selectedTopics, setSelectedTopics] = useState>(new Set()); + const [selectedTopic, setSelectedTopic] = useState(null); + const [isSigningUp, setIsSigningUp] = useState(false); // Fetch assignment data first to get the assignment ID const fetchAssignmentData = useCallback(() => { @@ -74,6 +83,62 @@ const StudentTasks: React.FC = () => { } }, [assignmentResponse, assignmentId, fetchTopics]); + // Handle successful sign up + useEffect(() => { + if (signUpResponse) { + console.log('Successfully signed up for topic:', signUpResponse); + setIsSigningUp(false); + // Refresh topics to show updated available slots + if (assignmentResponse?.data) { + let targetAssignmentId: number; + if (assignmentId) { + targetAssignmentId = parseInt(assignmentId); + } else if (Array.isArray(assignmentResponse.data) && assignmentResponse.data.length > 0) { + targetAssignmentId = assignmentResponse.data[0].id; + } else { + targetAssignmentId = assignmentResponse.data.id; + } + fetchTopics(targetAssignmentId); + } + } + }, [signUpResponse, assignmentResponse, assignmentId, fetchTopics]); + + // Handle sign up error + useEffect(() => { + if (signUpError) { + console.error('Error signing up for topic:', signUpError); + setIsSigningUp(false); + // You might want to show an error message to the user here + } + }, [signUpError]); + + // Handle successful drop + useEffect(() => { + if (dropResponse) { + console.log('Successfully dropped topic:', dropResponse); + // Refresh topics to show updated available slots + if (assignmentResponse?.data) { + let targetAssignmentId: number; + if (assignmentId) { + targetAssignmentId = parseInt(assignmentId); + } else if (Array.isArray(assignmentResponse.data) && assignmentResponse.data.length > 0) { + targetAssignmentId = assignmentResponse.data[0].id; + } else { + targetAssignmentId = assignmentResponse.data.id; + } + fetchTopics(targetAssignmentId); + } + } + }, [dropResponse, assignmentResponse, assignmentId, fetchTopics]); + + // Handle drop error + useEffect(() => { + if (dropError) { + console.error('Error dropping topic:', dropError); + // You might want to show an error message to the user here + } + }, [dropError]); + // Transform topics data to match expected format const topics = useMemo(() => { // If there's an error or no response, return empty array @@ -93,10 +158,10 @@ const StudentTasks: React.FC = () => { availableSlots: topic.available_slots || 0, waitlist: topic.waitlisted_teams?.length || 0, isBookmarked: bookmarkedTopics.has(topic.topic_identifier || topic.id?.toString() || 'unknown'), - isSelected: selectedTopics.has(topic.topic_identifier || topic.id?.toString() || 'unknown'), + isSelected: selectedTopic === (topic.topic_identifier || topic.id?.toString() || 'unknown'), isTaken: (topic.available_slots || 0) <= 0 })); - }, [topicsResponse, topicsError, bookmarkedTopics, selectedTopics]); + }, [topicsResponse, topicsError, bookmarkedTopics, selectedTopic]); // Get assignment name for display const assignmentName = useMemo(() => { @@ -127,17 +192,89 @@ const StudentTasks: React.FC = () => { }); }; - // Handle topic selection toggle - const handleTopicSelect = (topicId: string) => { - setSelectedTopics(prev => { - const newSet = new Set(prev); - if (newSet.has(topicId)) { - newSet.delete(topicId); + // Handle topic selection (single selection only) + const handleTopicSelect = async (topicId: string) => { + console.log('Topic clicked:', topicId, 'Current selected:', selectedTopic); + + if (!currentUser?.id) { + console.error('No user logged in'); + return; + } + + if (selectedTopic === topicId) { + // If clicking the same topic, deselect it (drop the topic) + console.log('Deselecting topic:', topicId); + setSelectedTopic(null); + + // Find the topic to get its database ID + const topic = topics.find(t => t.id === topicId); + if (topic) { + // We need to find the database ID from the topics response + const topicData = topicsResponse?.data?.find((t: any) => + t.topic_identifier === topicId || t.id?.toString() === topicId + ); + + if (topicData?.id) { + dropAPI({ + url: '/signed_up_teams/drop_topic', + method: 'DELETE', + data: { + user_id: currentUser.id, + topic_id: topicData.id + } + }); + } + } + } else { + // Select the new topic (automatically deselects the previous one) + console.log('Selecting topic:', topicId); + + // If there's a previously selected topic, drop it first + if (selectedTopic) { + console.log('Dropping previously selected topic:', selectedTopic); + const previousTopicData = topicsResponse?.data?.find((t: any) => + t.topic_identifier === selectedTopic || t.id?.toString() === selectedTopic + ); + + if (previousTopicData?.id) { + // Drop the previous topic + dropAPI({ + url: '/signed_up_teams/drop_topic', + method: 'DELETE', + data: { + user_id: currentUser.id, + topic_id: previousTopicData.id + } + }); + } + } + + // Update UI state immediately + setSelectedTopic(topicId); + setIsSigningUp(true); + + // Find the new topic to get its database ID + const topicData = topicsResponse?.data?.find((t: any) => + t.topic_identifier === topicId || t.id?.toString() === topicId + ); + + if (topicData?.id) { + // Add a small delay to ensure the drop operation completes first + setTimeout(() => { + signUpAPI({ + url: '/signed_up_teams/sign_up_student', + method: 'POST', + data: { + user_id: currentUser.id, + topic_id: topicData.id + } + }); + }, 100); // Small delay to ensure drop completes first } else { - newSet.add(topicId); + console.error('Topic not found in response data'); + setIsSigningUp(false); } - return newSet; - }); + } }; // Show loading spinner while data is being fetched @@ -211,22 +348,33 @@ const StudentTasks: React.FC = () => { - {topics.map((topic) => ( + {topics.map((topic) => { + const isSelected = topic.isSelected; + const isTaken = topic.isTaken; + const backgroundColor = isSelected + ? "#ffff00" // Very bright yellow highlighter for selected topics + : isTaken + ? "#f8f9fa" // Light gray for taken topics + : "white"; + + console.log(`Topic ${topic.id}: isSelected=${isSelected}, isTaken=${isTaken}, backgroundColor=${backgroundColor}`); + + return ( - {topic.id} - {topic.name} - {topic.availableSlots} - {topic.waitlist} - + {topic.id} + {topic.name} + {topic.availableSlots} + {topic.waitlist} + - + - ))} + ); + })} )} From 5aa998c56407a51c13568519087da11d917a1ac9 Mon Sep 17 00:00:00 2001 From: Srinidhi Shivashankar Date: Mon, 27 Oct 2025 01:36:21 -0400 Subject: [PATCH 15/34] Refactor TopicsTab component for improved team display and UI consistency - Removed unused imports and streamlined icon usage. - Updated member display logic to always show names in admin view. - Enhanced rendering of assigned and waitlisted teams, including clearer messaging for unassigned topics. - Improved overall layout and readability of team information in the UI. --- src/pages/Assignments/tabs/TopicsTab.tsx | 87 ++++++++++++------------ 1 file changed, 43 insertions(+), 44 deletions(-) diff --git a/src/pages/Assignments/tabs/TopicsTab.tsx b/src/pages/Assignments/tabs/TopicsTab.tsx index 6e785121..1b425440 100644 --- a/src/pages/Assignments/tabs/TopicsTab.tsx +++ b/src/pages/Assignments/tabs/TopicsTab.tsx @@ -1,7 +1,7 @@ import React, { useState } from "react"; -import { Col, Row, Form, Table as BootstrapTable, Button, Modal, FloatingLabel, Badge, Stack } from "react-bootstrap"; +import { Col, Row, Form, Table as BootstrapTable, Button, Modal, FloatingLabel, Stack } from "react-bootstrap"; // Reverting to the standard import path for react-icons/bs -import { BsInfoCircle, BsCheck, BsX, BsBookmark, BsPencil, BsLink45Deg, BsPersonPlusFill, BsFillBookmarkPlusFill } from "react-icons/bs"; +import { BsX, BsBookmark, BsPencil, BsLink45Deg, BsPersonPlusFill, BsFillBookmarkPlusFill } from "react-icons/bs"; // --- Interface Modifications --- // Assuming these interfaces are defined elsewhere and imported @@ -329,7 +329,8 @@ const TopicsTab = ({ return 'No members'; } return members.map(member => { - const displayName = displayUserNames ? member.name : member.id; + // Always show names in admin view, regardless of toggle setting + const displayName = member.name || member.id; return displayName || 'Unknown Member'; }).join(', '); }; @@ -482,49 +483,47 @@ const TopicsTab = ({ {topic.description && (
{topic.description}
)} + + {/* Student Names - Display directly under description */} + {topic.assignedTeams && topic.assignedTeams.length > 0 && ( +
+ {topic.assignedTeams.map((team) => ( +
+ + {renderTeamMembers(team.members)} + + {/* Drop Team Icon */} + onDropTeam(topic.id, team.teamId)} + title={`Drop team ${team.teamId} from topic`} + /> +
+ ))} +
+ )} + + {/* Waitlisted Students */} + {topic.waitlistedTeams && topic.waitlistedTeams.length > 0 && ( +
+ {topic.waitlistedTeams.map((team) => ( +
+ + {renderTeamMembers(team.members)} (waitlisted) + +
+ ))} +
+ )} + + {/* Show message if no teams assigned or waitlisted */} + {(!topic.assignedTeams || topic.assignedTeams.length === 0) && + (!topic.waitlistedTeams || topic.waitlistedTeams.length === 0) && ( +
No students assigned
+ )}
- {/* Assigned Teams (Confirmed) */} - {topic.assignedTeams && topic.assignedTeams.length > 0 && ( -
- {topic.assignedTeams.map((team) => ( -
- Assigned - - {renderTeamMembers(team.members)} - - {/* Drop Team Icon */} - onDropTeam(topic.id, team.teamId)} - title={`Drop team ${team.teamId} from topic`} - /> -
- ))} -
- )} - - {/* Waitlisted Teams */} - {topic.waitlistedTeams && topic.waitlistedTeams.length > 0 && ( -
- {topic.waitlistedTeams.map((team) => ( -
- Waitlisted - - {renderTeamMembers(team.members)} (waitlisted) - -
- ))} -
- )} - - {/* Show message if no teams assigned or waitlisted */} - {(!topic.assignedTeams || topic.assignedTeams.length === 0) && - (!topic.waitlistedTeams || topic.waitlistedTeams.length === 0) && ( -
No teams assigned
- )} - {/* Partner Advertisement */} {topic.partnerAd && (
From 9921adb960435a6b6060e1fadf57c846ffd0342a Mon Sep 17 00:00:00 2001 From: Deekshtih Anantha Date: Tue, 28 Oct 2025 08:56:39 -0400 Subject: [PATCH 16/34] border line fixed on student task page --- src/pages/StudentTasks/StudentTasks.tsx | 133 +++++++++++++----------- 1 file changed, 70 insertions(+), 63 deletions(-) diff --git a/src/pages/StudentTasks/StudentTasks.tsx b/src/pages/StudentTasks/StudentTasks.tsx index 58619aa6..18093973 100644 --- a/src/pages/StudentTasks/StudentTasks.tsx +++ b/src/pages/StudentTasks/StudentTasks.tsx @@ -348,69 +348,76 @@ const StudentTasks: React.FC = () => { - {topics.map((topic) => { - const isSelected = topic.isSelected; - const isTaken = topic.isTaken; - const backgroundColor = isSelected - ? "#ffff00" // Very bright yellow highlighter for selected topics - : isTaken - ? "#f8f9fa" // Light gray for taken topics - : "white"; - - console.log(`Topic ${topic.id}: isSelected=${isSelected}, isTaken=${isTaken}, backgroundColor=${backgroundColor}`); - - return ( - - {topic.id} - {topic.name} - {topic.availableSlots} - {topic.waitlist} - - - - - - - - ); - })} - + {topics.map((topic) => { + const isSelected = topic.isSelected; + const isTaken = topic.isTaken; + + // Use Bootstrap's built-in table variants for highlighting + // This will work correctly with 'bordered' and 'striped' + const rowClass = isSelected + ? "table-warning" // Bootstrap's standard yellow + : isTaken + ? "table-light" // Bootstrap's standard gray + : ""; + + // You can still keep the 'bold' style if you like + const rowStyle = { + fontWeight: isSelected ? 'bold' : 'normal', + }; + + console.log(`Topic ${topic.id}: isSelected=${isSelected}, isTaken=${isTaken}, rowClass=${rowClass}`); + + return ( + + {/* REMOVE ALL INLINE 'style' PROPS FROM ALL TAGS. + This is the most important step. + */} + + {topic.id} + {topic.name} + {topic.availableSlots} + {topic.waitlist} + + + + + + + + ); + })} + )}
From 0f83e042c734c64eac818393ecbb23cf14f8ff6a Mon Sep 17 00:00:00 2001 From: Deekshtih Anantha Date: Tue, 28 Oct 2025 09:22:36 -0400 Subject: [PATCH 17/34] Refactor StudentTasks layout for improved UI consistency - Updated alignment of task items to center for better visual appeal. - Changed width of the topics container to fit-content for a more responsive design. - Added padding to table headers and cells for enhanced readability. - Removed inline styles from table cells to streamline styling. --- src/pages/StudentTasks/StudentTasks.tsx | 43 ++++++++++++------------- 1 file changed, 20 insertions(+), 23 deletions(-) diff --git a/src/pages/StudentTasks/StudentTasks.tsx b/src/pages/StudentTasks/StudentTasks.tsx index 18093973..fa062c42 100644 --- a/src/pages/StudentTasks/StudentTasks.tsx +++ b/src/pages/StudentTasks/StudentTasks.tsx @@ -312,7 +312,7 @@ const StudentTasks: React.FC = () => { style={{ display: "flex", flexDirection: "column", - alignItems: "flex-start", + alignItems: "center", justifyContent: "flex-start", }} > @@ -329,7 +329,7 @@ const StudentTasks: React.FC = () => {

-
+
{topics.length === 0 ? ( No Topics Available @@ -337,16 +337,16 @@ const StudentTasks: React.FC = () => { ) : ( - - - - - - - - - - + + + + + + + + + + {topics.map((topic) => { const isSelected = topic.isSelected; @@ -370,18 +370,15 @@ const StudentTasks: React.FC = () => { return ( - {/* REMOVE ALL INLINE 'style' PROPS FROM ALL - - - - + + + + - + {topics.length === 0 ? ( + + No Topics Available +

There are no topics available for this assignment yet.

+
+ ) : ( + + )} + + ); }; -export default StudentTasks; \ No newline at end of file +export default StudentTasks; From 7a0fdb209840fd459a7cfc0ed4f6088497d92729 Mon Sep 17 00:00:00 2001 From: abhira0 Date: Tue, 28 Oct 2025 17:16:55 -0400 Subject: [PATCH 23/34] Enhance topic management and UI feedback in AssignmentEditPage and StudentTasks - Implemented API calls for creating, updating, and deleting topics with success and error alerts. - Added optimistic UI updates in StudentTasks for better user experience during topic selection and dropping. - Introduced new state management for optimistic slot changes to reflect real-time availability of topics. - Improved styling for selected rows in the table to enhance visual feedback for user interactions. --- src/components/Table/Table.tsx | 12 ++- src/pages/Assignments/AssignmentEditPage.tsx | 99 ++++++++++++++++++-- src/pages/StudentTasks/StudentTasks.tsx | 89 +++++++++++++----- 3 files changed, 171 insertions(+), 29 deletions(-) diff --git a/src/components/Table/Table.tsx b/src/components/Table/Table.tsx index 01c39c2e..e83a14df 100644 --- a/src/components/Table/Table.tsx +++ b/src/components/Table/Table.tsx @@ -155,6 +155,16 @@ import { return ( <> + @@ -202,7 +212,7 @@ import { {table.getRowModel().rows.map((row) => ( - + {row.getVisibleCells().map((cell) => ( {table.getRowModel().rows.map((row) => ( - - {row.getVisibleCells().map((cell) => ( - - ))} + + {row.getVisibleCells().map((cell) => { + const selected = !!row.original.isSelected; + return ( + + ); + })} {row.getIsExpanded() && renderSubComponent && ( diff --git a/src/pages/Assignments/components/TopicsTable.tsx b/src/pages/Assignments/components/TopicsTable.tsx index 58d186d5..6b0d9da6 100644 --- a/src/pages/Assignments/components/TopicsTable.tsx +++ b/src/pages/Assignments/components/TopicsTable.tsx @@ -9,6 +9,7 @@ export interface Team { teamId: string; members: TeamMember[] } export interface TopicRow { id: string; + databaseId?: number; name: string; url?: string; description?: string; @@ -133,7 +134,7 @@ const TopicsTable: React.FC = ({ header: "Select", cell: ({ row }) => { const t = row.original; - const disabled = t.isTaken || !!isSigningUp; + const disabled = !!isSigningUp || (!t.isSelected && t.isTaken); const isThisSigning = !!isSigningUp && selectedTopicId === t.id; return (
diff --git a/src/pages/StudentTasks/StudentTasks.tsx b/src/pages/StudentTasks/StudentTasks.tsx index 1e068e12..9ab1f9c7 100644 --- a/src/pages/StudentTasks/StudentTasks.tsx +++ b/src/pages/StudentTasks/StudentTasks.tsx @@ -27,9 +27,13 @@ const StudentTasks: React.FC = () => { const currentUser = auth.user; const [bookmarkedTopics, setBookmarkedTopics] = useState>(new Set()); - const [selectedTopic, setSelectedTopic] = useState(null); + // UI-selected topic override for instant icon/row updates + const [uiSelectedTopic, setUiSelectedTopic] = useState(null); const [isSigningUp, setIsSigningUp] = useState(false); const [optimisticSlotChanges, setOptimisticSlotChanges] = useState>(new Map()); + const [optimisticSelection, setOptimisticSelection] = useState>(new Map()); + const [pendingDeselections, setPendingDeselections] = useState>(new Set()); + const [lastSignedDbTopicId, setLastSignedDbTopicId] = useState(null); const fetchAssignmentData = useCallback(() => { if (assignmentId) { @@ -65,6 +69,8 @@ const StudentTasks: React.FC = () => { useEffect(() => { if (signUpResponse) { setIsSigningUp(false); + const dbTopicId = (signUpResponse as any)?.data?.signed_up_team?.project_topic_id; + if (dbTopicId) setLastSignedDbTopicId(Number(dbTopicId)); // Clear optimistic updates since we'll get real data setOptimisticSlotChanges(new Map()); if (assignmentResponse?.data) { @@ -113,30 +119,106 @@ const StudentTasks: React.FC = () => { console.error('Error dropping topic:', dropError); // Clear optimistic updates on error to restore actual values setOptimisticSlotChanges(new Map()); + setPendingDeselections(new Set()); } }, [dropError]); + const isUserOnTopic = useCallback((topic: any) => { + if (!topic) return false; + const matches = (teams: any[]) => Array.isArray(teams) + ? teams.some((team: any) => + Array.isArray(team.members) && + team.members.some((m: any) => String(m.id) === String(currentUser?.id))) + : false; + return matches(topic.confirmed_teams) || matches(topic.waitlisted_teams); + }, [currentUser?.id]); + const topics = useMemo(() => { if (topicsError || !topicsResponse?.data) return []; const topicsData = Array.isArray(topicsResponse.data) ? topicsResponse.data : []; return topicsData.map((topic: any) => { const topicId = topic.topic_identifier || topic.id?.toString() || 'unknown'; + const dbId = Number(topic.id); const baseSlots = topic.available_slots || 0; const adjustedSlots = optimisticSlotChanges.has(topicId) ? optimisticSlotChanges.get(topicId)! : baseSlots; + // Determine if current user is on a team for this topic (confirmed or waitlisted) + const userOnTopic = isUserOnTopic(topic); + const pendingDrop = pendingDeselections.has(topicId); + const selectionOverride = optimisticSelection.get(topicId); + const isSelected = pendingDrop + ? false + : selectionOverride === 'selected' + ? true + : selectionOverride === 'deselected' + ? false + : uiSelectedTopic !== null + ? uiSelectedTopic === topicId + : userOnTopic; return { id: topicId, + databaseId: isNaN(dbId) ? undefined : dbId, name: topic.topic_name || 'Unnamed Topic', availableSlots: adjustedSlots, waitlist: topic.waitlisted_teams?.length || 0, isBookmarked: bookmarkedTopics.has(topicId), - isSelected: selectedTopic === topicId, + isSelected, isTaken: adjustedSlots <= 0 }; }); - }, [topicsResponse, topicsError, bookmarkedTopics, selectedTopic, optimisticSlotChanges]); + }, [topicsResponse, topicsError, bookmarkedTopics, uiSelectedTopic, optimisticSlotChanges, optimisticSelection, isUserOnTopic, pendingDeselections]); + + // Initialize or reconcile selectedTopic from backend data after fetch + useEffect(() => { + if (Array.isArray(topicsResponse?.data)) { + // Priority 1: if we have lastSignedDbTopicId, map it to identifier and select + if (lastSignedDbTopicId) { + const t = topicsResponse.data.find((x: any) => Number(x.id) === Number(lastSignedDbTopicId)); + const key = t?.topic_identifier || t?.id?.toString(); + if (key) setUiSelectedTopic(key); + setLastSignedDbTopicId(null); + return; + } + // Priority 2: use membership lists + if (uiSelectedTopic === null) { + const found = topicsResponse.data.find((topic: any) => { + const topicKey = topic.topic_identifier || topic.id?.toString(); + if (!topicKey || pendingDeselections.has(topicKey)) return false; + return isUserOnTopic(topic); + }); + if (found) { + const key = found.topic_identifier || found.id?.toString(); + if (key) setUiSelectedTopic(key); + } + } + } + if (optimisticSelection.size > 0) { + setOptimisticSelection(new Map()); + } + }, [topicsResponse?.data, currentUser?.id, uiSelectedTopic, lastSignedDbTopicId, optimisticSelection.size, pendingDeselections, isUserOnTopic]); + + useEffect(() => { + if (!Array.isArray(topicsResponse?.data)) return; + setPendingDeselections(prev => { + if (prev.size === 0) return prev; + const next = new Set(prev); + let changed = false; + prev.forEach(topicId => { + const topic = topicsResponse.data.find((t: any) => { + const key = t.topic_identifier || t.id?.toString(); + return key === topicId; + }); + const stillAssigned = topic ? isUserOnTopic(topic) : false; + if (!stillAssigned) { + next.delete(topicId); + changed = true; + } + }); + return changed ? next : prev; + }); + }, [topicsResponse?.data, isUserOnTopic]); const assignmentName = useMemo(() => { if (!assignmentResponse?.data) return 'OSS project & documentation assignment'; @@ -175,27 +257,38 @@ const StudentTasks: React.FC = () => { const handleTopicSelect = useCallback(async (topicId: string) => { if (!currentUser?.id) return; + // Treat as deselect if either local selection matches or backend indicates selection + const topicEntry = topics.find(t => t.id === topicId); + const isCurrentlyOnThisTopic = !!topicEntry?.isSelected; - if (selectedTopic === topicId) { + if (uiSelectedTopic === topicId || (uiSelectedTopic === null && isCurrentlyOnThisTopic)) { // Deselecting current topic - optimistically increment available slots - const topic = topics.find(t => t.id === topicId); - if (topic) { + if (topicEntry) { setOptimisticSlotChanges(prev => { const newMap = new Map(prev); - newMap.set(topicId, topic.availableSlots + 1); + newMap.set(topicId, topicEntry.availableSlots + 1); return newMap; }); } + setPendingDeselections(prev => { + if (prev.has(topicId)) return prev; + const next = new Set(prev); + next.add(topicId); + return next; + }); - setSelectedTopic(null); - const topicData = topicsResponse?.data?.find((t: any) => - t.topic_identifier === topicId || t.id?.toString() === topicId - ); - if (topicData?.id) { + setUiSelectedTopic(null); + setOptimisticSelection(prev => { + const next = new Map(prev); + next.set(topicId, 'deselected'); + return next; + }); + const dbId = topicEntry?.databaseId || topicsResponse?.data?.find((t: any) => t.topic_identifier === topicId || t.id?.toString() === topicId)?.id; + if (dbId) { dropAPI({ url: '/signed_up_teams/drop_topic', method: 'DELETE', - data: { user_id: currentUser.id, topic_id: topicData.id } + data: { user_id: currentUser.id, topic_id: dbId } }); } } else { @@ -205,53 +298,67 @@ const StudentTasks: React.FC = () => { setOptimisticSlotChanges(prev => { const newMap = new Map(prev); newMap.set(topicId, Math.max(0, topic.availableSlots - 1)); - + // If there's a previously selected topic, increment its slots - if (selectedTopic) { - const prevTopic = topics.find(t => t.id === selectedTopic); + if (uiSelectedTopic) { + const prevTopic = topics.find(t => t.id === uiSelectedTopic); if (prevTopic) { - newMap.set(selectedTopic, prevTopic.availableSlots + 1); + newMap.set(uiSelectedTopic, prevTopic.availableSlots + 1); } } - + return newMap; }); } + + setOptimisticSelection(prev => { + const next = new Map(prev); + next.set(topicId, 'selected'); + if (uiSelectedTopic) { + next.set(uiSelectedTopic, 'deselected'); + } + return next; + }); + setPendingDeselections(prev => { + const next = new Set(prev); + next.delete(topicId); + if (uiSelectedTopic) { + next.add(uiSelectedTopic); + } + return next; + }); - if (selectedTopic) { + if (uiSelectedTopic) { // Drop previous topic first - const previousTopicData = topicsResponse?.data?.find((t: any) => - t.topic_identifier === selectedTopic || t.id?.toString() === selectedTopic - ); - if (previousTopicData?.id) { + const prev = topics.find(t => t.id === uiSelectedTopic); + const prevDbId = prev?.databaseId || topicsResponse?.data?.find((t: any) => t.topic_identifier === uiSelectedTopic || t.id?.toString() === uiSelectedTopic)?.id; + if (prevDbId) { dropAPI({ url: '/signed_up_teams/drop_topic', method: 'DELETE', - data: { user_id: currentUser.id, topic_id: previousTopicData.id } + data: { user_id: currentUser.id, topic_id: prevDbId } }); } } - setSelectedTopic(topicId); + setUiSelectedTopic(topicId); setIsSigningUp(true); - const topicData = topicsResponse?.data?.find((t: any) => - t.topic_identifier === topicId || t.id?.toString() === topicId - ); - - if (topicData?.id) { + const topicData = topics.find(t => t.id === topicId); + const dbId = topicData?.databaseId || topicsResponse?.data?.find((t: any) => t.topic_identifier === topicId || t.id?.toString() === topicId)?.id; + if (dbId) { setTimeout(() => { signUpAPI({ url: '/signed_up_teams/sign_up_student', method: 'POST', - data: { user_id: currentUser.id, topic_id: topicData.id } + data: { user_id: currentUser.id, topic_id: dbId } }); }, 100); } else { setIsSigningUp(false); } } - }, [currentUser?.id, dropAPI, selectedTopic, signUpAPI, topics, topicsResponse?.data]); + }, [currentUser?.id, dropAPI, uiSelectedTopic, signUpAPI, topics, topicsResponse?.data]); // Table columns (declare before any conditional returns to satisfy hooks rules) const topicRows: TopicRow[] = useMemo(() => topics.map(t => ({ @@ -325,7 +432,7 @@ const StudentTasks: React.FC = () => { onBookmarkToggle={handleBookmarkToggle} onSelectTopic={handleTopicSelect} isSigningUp={isSigningUp} - selectedTopicId={selectedTopic} + selectedTopicId={uiSelectedTopic} showBookmarks={allowBookmarks} showPaginationThreshold={10} tableSize={{ span: 12, offset: 0 }} From 89f73451458d8e832b66bf483eb4153a5bbb3bef Mon Sep 17 00:00:00 2001 From: abhira0 Date: Thu, 30 Oct 2025 15:19:25 -0400 Subject: [PATCH 28/34] Add drop team functionality and improve TopicsTab structure - Implemented drop team request in AssignmentEditPage, allowing teams to be removed from topics. - Added success and error handling for drop team actions with user alerts. - Refactored TopicsTab to utilize database IDs for topic management, enhancing data accuracy. - Removed unused state for displaying user names and cleaned up commented code for better readability. --- src/pages/Assignments/AssignmentEditPage.tsx | 29 ++++++++- src/pages/Assignments/tabs/TopicsTab.tsx | 65 ++++++-------------- 2 files changed, 45 insertions(+), 49 deletions(-) diff --git a/src/pages/Assignments/AssignmentEditPage.tsx b/src/pages/Assignments/AssignmentEditPage.tsx index a1a9ef16..6b126f1b 100644 --- a/src/pages/Assignments/AssignmentEditPage.tsx +++ b/src/pages/Assignments/AssignmentEditPage.tsx @@ -71,6 +71,7 @@ const AssignmentEditPage = () => { const { data: deleteResponse, error: deleteError, sendRequest: deleteTopic } = useAPI(); const { data: createResponse, error: createError, sendRequest: createTopic } = useAPI(); const { data: updateTopicResponse, error: updateTopicError, sendRequest: updateTopic } = useAPI(); + const { data: dropTeamResponse, error: dropTeamError, sendRequest: dropTeamRequest } = useAPI(); useEffect(() => { if (id) { @@ -154,6 +155,21 @@ const AssignmentEditPage = () => { } }, [updateTopicError, dispatch]); + useEffect(() => { + if (dropTeamResponse) { + dispatch(alertActions.showAlert({ variant: "success", message: "Team removed from topic successfully" })); + if (id) { + fetchTopics({ url: `/project_topics?assignment_id=${id}` }); + } + } + }, [dropTeamResponse, dispatch, id, fetchTopics]); + + useEffect(() => { + if (dropTeamError) { + dispatch(alertActions.showAlert({ variant: "danger", message: dropTeamError })); + } + }, [dropTeamError, dispatch]); + // Load topics for this assignment useEffect(() => { if (id) { @@ -214,9 +230,16 @@ const AssignmentEditPage = () => { }, [id, updateAssignment]); const handleDropTeam = useCallback((topicId: string, teamId: string) => { - console.log(`Drop team ${teamId} from topic ${topicId}`); - // TODO: Implement drop team logic - }, []); + if (!topicId || !teamId) return; + dropTeamRequest({ + url: `/signed_up_teams/drop_team_from_topic`, + method: 'DELETE', + params: { + topic_id: topicId, + team_id: teamId, + }, + }); + }, [dropTeamRequest]); const handleDeleteTopic = useCallback((topicIdentifier: string) => { console.log(`Delete topic ${topicIdentifier}`); diff --git a/src/pages/Assignments/tabs/TopicsTab.tsx b/src/pages/Assignments/tabs/TopicsTab.tsx index 1e65ea83..c7c9ae80 100644 --- a/src/pages/Assignments/tabs/TopicsTab.tsx +++ b/src/pages/Assignments/tabs/TopicsTab.tsx @@ -100,7 +100,6 @@ const TopicsTab = ({ onApplyPartnerAd, onTopicsChanged, }: TopicsTabProps) => { - const [displayUserNames, setDisplayUserNames] = useState(false); // State for toggling user name/ID display const [showPartnerAdModal, setShowPartnerAdModal] = useState(false); const [selectedPartnerAdTopic, setSelectedPartnerAdTopic] = useState(null); const [partnerAdApplication, setPartnerAdApplication] = useState(""); @@ -367,20 +366,6 @@ const TopicsTab = ({
- {/* View Options */} -
- {/* Hide all teams link - Functionality TBD */} - {/* Hide all teams */} -
{/* Placeholder for alignment */} - setDisplayUserNames(e.target.checked)} - /> -
- {/* Error Message */} {topicsError && ( @@ -396,6 +381,7 @@ const TopicsTab = ({ ({ id: t.id, + databaseId: t.databaseId, name: t.name, url: t.url, description: t.description, @@ -416,22 +402,6 @@ const TopicsTab = ({ header: "Questionnaire", cell: ({ row }) => {(topicsData.find(t => t.id === row.original.id)?.questionnaire) || "--Default rubric--"}, }, - { - id: "createdAt", - header: "Creation Date", - cell: ({ row }) => { - const t = topicsData.find(t => t.id === row.original.id); - return {t?.createdAt || ''}; - }, - }, - { - id: "updatedAt", - header: "Updated Date", - cell: ({ row }) => { - const t = topicsData.find(t => t.id === row.original.id); - return {t?.updatedAt || ''}; - }, - }, { id: "numSlots", header: "Num. of Slots", @@ -465,21 +435,24 @@ const TopicsTab = ({
{row.assignedTeams && row.assignedTeams.length > 0 && (
- {row.assignedTeams.map((team) => ( -
- - {team.members.map(m => m.name || m.id).join(", ")} - - Drop team onDropTeam(row.id, team.teamId)} - /> -
- ))} + {row.assignedTeams.map((team) => { + const topicDbId = row.databaseId?.toString() ?? row.id; + return ( +
+ + {team.members.map(m => m.name || m.id).join(", ")} + + Drop team onDropTeam(topicDbId, team.teamId)} + /> +
+ ); + })}
)} {row.waitlistedTeams && row.waitlistedTeams.length > 0 && ( From c75d8465c9d853b7b3a0c63410b8ada099d605ed Mon Sep 17 00:00:00 2001 From: abhira0 Date: Thu, 30 Oct 2025 15:45:45 -0400 Subject: [PATCH 29/34] Enhance TopicsTable and StudentTasks for waitlist management - Added waitlist functionality to TopicsTable, displaying a "Waitlisted" badge for students. - Updated selection logic in StudentTasks to account for waitlisted status, improving user feedback. - Enhanced aria-labels for buttons to provide clearer accessibility information regarding topic selection and waitlisting. - Refactored state management to ensure accurate representation of user status on topics. --- .../Assignments/components/TopicsTable.tsx | 24 ++++++++---- src/pages/StudentTasks/StudentTasks.tsx | 37 +++++++++++++------ 2 files changed, 42 insertions(+), 19 deletions(-) diff --git a/src/pages/Assignments/components/TopicsTable.tsx b/src/pages/Assignments/components/TopicsTable.tsx index 6b0d9da6..91fa4a0a 100644 --- a/src/pages/Assignments/components/TopicsTable.tsx +++ b/src/pages/Assignments/components/TopicsTable.tsx @@ -1,7 +1,7 @@ import React, { useMemo } from "react"; import { ColumnDef } from "@tanstack/react-table"; import Table from "components/Table/Table"; -import { Button, Spinner } from "react-bootstrap"; +import { Badge, Button, Spinner } from "react-bootstrap"; import { BsBookmark, BsBookmarkFill } from "react-icons/bs"; export interface TeamMember { id: string; name?: string } @@ -20,6 +20,7 @@ export interface TopicRow { isTaken?: boolean; isBookmarked?: boolean; isSelected?: boolean; + isWaitlisted?: boolean; } type Mode = "student" | "instructor"; @@ -79,10 +80,15 @@ const TopicsTable: React.FC = ({ accessorKey: "name", header: "Topic Names", cell: ({ row }) => ( - {row.original.name} + + {row.original.name} + {mode === "student" && row.original.isWaitlisted && ( + Waitlisted + )} + ), }, - ], []); + ], [mode]); const studentColumns: ColumnDef[] = useMemo(() => { return [ @@ -134,8 +140,11 @@ const TopicsTable: React.FC = ({ header: "Select", cell: ({ row }) => { const t = row.original; - const disabled = !!isSigningUp || (!t.isSelected && t.isTaken); + const disabled = !!isSigningUp; const isThisSigning = !!isSigningUp && selectedTopicId === t.id; + const ariaLabel = t.isSelected + ? (t.isWaitlisted ? "Leave waitlist" : "Deselect topic") + : (t.isTaken ? "Join waitlist" : "Select topic"); return (
diff --git a/src/pages/StudentTasks/StudentTasks.tsx b/src/pages/StudentTasks/StudentTasks.tsx index 9ab1f9c7..f37cdb22 100644 --- a/src/pages/StudentTasks/StudentTasks.tsx +++ b/src/pages/StudentTasks/StudentTasks.tsx @@ -8,12 +8,14 @@ import TopicsTable, { TopicRow } from "pages/Assignments/components/TopicsTable" interface Topic { id: string; + databaseId?: number; name: string; availableSlots: number; waitlist: number; isBookmarked?: boolean; isSelected?: boolean; isTaken?: boolean; + isWaitlisted?: boolean; } const StudentTasks: React.FC = () => { @@ -144,19 +146,28 @@ const StudentTasks: React.FC = () => { ? optimisticSlotChanges.get(topicId)! : baseSlots; // Determine if current user is on a team for this topic (confirmed or waitlisted) - const userOnTopic = isUserOnTopic(topic); + const matches = (teams: any[]) => { + if (!currentUser?.id || !Array.isArray(teams)) return false; + return teams.some((team: any) => + Array.isArray(team.members) && + team.members.some((m: any) => String(m.id) === String(currentUser.id)) + ); + }; + const userWaitlisted = matches(topic.waitlisted_teams); + const userConfirmed = matches(topic.confirmed_teams); + const userOnTopic = userConfirmed || userWaitlisted; const pendingDrop = pendingDeselections.has(topicId); const selectionOverride = optimisticSelection.get(topicId); const isSelected = pendingDrop ? false : selectionOverride === 'selected' - ? true - : selectionOverride === 'deselected' - ? false - : uiSelectedTopic !== null - ? uiSelectedTopic === topicId - : userOnTopic; + ? true + : selectionOverride === 'deselected' + ? false + : uiSelectedTopic !== null + ? uiSelectedTopic === topicId + : userOnTopic; return { id: topicId, databaseId: isNaN(dbId) ? undefined : dbId, @@ -165,10 +176,11 @@ const StudentTasks: React.FC = () => { waitlist: topic.waitlisted_teams?.length || 0, isBookmarked: bookmarkedTopics.has(topicId), isSelected, - isTaken: adjustedSlots <= 0 + isTaken: adjustedSlots <= 0, + isWaitlisted: userWaitlisted }; }); - }, [topicsResponse, topicsError, bookmarkedTopics, uiSelectedTopic, optimisticSlotChanges, optimisticSelection, isUserOnTopic, pendingDeselections]); + }, [topicsResponse, topicsError, bookmarkedTopics, uiSelectedTopic, optimisticSlotChanges, optimisticSelection, pendingDeselections, currentUser?.id]); // Initialize or reconcile selectedTopic from backend data after fetch useEffect(() => { @@ -262,8 +274,8 @@ const StudentTasks: React.FC = () => { const isCurrentlyOnThisTopic = !!topicEntry?.isSelected; if (uiSelectedTopic === topicId || (uiSelectedTopic === null && isCurrentlyOnThisTopic)) { - // Deselecting current topic - optimistically increment available slots - if (topicEntry) { + // Deselecting current topic - optimistically increment available slots when confirmed + if (topicEntry && !topicEntry.isWaitlisted) { setOptimisticSlotChanges(prev => { const newMap = new Map(prev); newMap.set(topicId, topicEntry.availableSlots + 1); @@ -369,6 +381,7 @@ const StudentTasks: React.FC = () => { isTaken: t.isTaken, isBookmarked: t.isBookmarked, isSelected: t.isSelected, + isWaitlisted: t.isWaitlisted, })), [topics]); if (topicsLoading) { @@ -412,7 +425,7 @@ const StudentTasks: React.FC = () => {

Your topic(s): {userSelectedTopics.length > 0 - ? userSelectedTopics.map((topic) => topic.name).join(", ") + ? userSelectedTopics.map((topic) => topic.isWaitlisted ? `${topic.name} (waitlisted)` : topic.name).join(", ") : "No topics selected yet"}

From 3640ce1696687281ad5c6971f11305387995976e Mon Sep 17 00:00:00 2001 From: abhira0 Date: Thu, 30 Oct 2025 15:49:28 -0400 Subject: [PATCH 30/34] Refactor team display and interaction in TopicsTab - Updated team display structure to improve layout and spacing. - Replaced the delete icon with a button for better accessibility and user experience. - Enhanced button functionality with a tooltip for clearer action indication. --- src/pages/Assignments/tabs/TopicsTab.tsx | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/pages/Assignments/tabs/TopicsTab.tsx b/src/pages/Assignments/tabs/TopicsTab.tsx index c7c9ae80..a40cb853 100644 --- a/src/pages/Assignments/tabs/TopicsTab.tsx +++ b/src/pages/Assignments/tabs/TopicsTab.tsx @@ -438,18 +438,18 @@ const TopicsTab = ({ {row.assignedTeams.map((team) => { const topicDbId = row.databaseId?.toString() ?? row.id; return ( -
- +
+ {team.members.map(m => m.name || m.id).join(", ")} - Drop team onDropTeam(topicDbId, team.teamId)} - /> + title="Remove signup team from topic" + > + Remove team +
); })} From b822ea62128c304afc321f864d181f2d66149cec Mon Sep 17 00:00:00 2001 From: Deekshtih Anantha Date: Thu, 30 Oct 2025 17:54:54 -0400 Subject: [PATCH 31/34] Remove Student View link from Header navigation --- src/layout/Header.tsx | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/layout/Header.tsx b/src/layout/Header.tsx index 02708b43..b051f40c 100644 --- a/src/layout/Header.tsx +++ b/src/layout/Header.tsx @@ -151,9 +151,6 @@ const Header: React.FC = () => { Profile - - Student View - Grades View From 0aa8c1d07dca1fc5f2f49b2662fb2df85bcbc270 Mon Sep 17 00:00:00 2001 From: Deekshtih Anantha Date: Fri, 31 Oct 2025 01:10:13 -0400 Subject: [PATCH 32/34] Add GeneralTab and EtcTab components to AssignmentEditPage; remove unused FontAwesome icons from AssignmentEditor --- src/pages/Assignments/AssignmentEditPage.tsx | 5 ++ src/pages/Assignments/AssignmentEditor.tsx | 43 +------------- src/pages/Assignments/tabs/EtcTab.tsx | 62 +++++++++++++++++--- src/pages/Assignments/tabs/GeneralTab.tsx | 62 ++++---------------- 4 files changed, 71 insertions(+), 101 deletions(-) diff --git a/src/pages/Assignments/AssignmentEditPage.tsx b/src/pages/Assignments/AssignmentEditPage.tsx index 6b126f1b..6afd2881 100644 --- a/src/pages/Assignments/AssignmentEditPage.tsx +++ b/src/pages/Assignments/AssignmentEditPage.tsx @@ -7,6 +7,7 @@ import { alertActions } from "store/slices/alertSlice"; import useAPI from "hooks/useAPI"; // Import tab components +import GeneralTab from "./tabs/GeneralTab"; import TopicsTab from "./tabs/TopicsTab"; import RubricsTab from "./tabs/RubricsTab"; import ReviewStrategyTab from "./tabs/ReviewStrategyTab"; @@ -303,6 +304,10 @@ const AssignmentEditPage = () => { const renderTabContent = () => { switch (activeTab) { + + case "general": + return ; + case "topics": return ( = ({ mode }) => { -
-
navigate(`participants`)}> - - Add Participant -
-
navigate(`/assignments/edit/${assignmentData.id}/createteams`)}> - - Create Teams -
- -
navigate(`/assignments/edit/${assignmentData.id}/assignreviewer`)}> - - Assign Reviewer -
-
navigate(`/assignments/edit/${assignmentData.id}/viewsubmissions`)}> - - View Submissions -
-
navigate(`/assignments/edit/${assignmentData.id}/viewscores`)}> - - View Scores -
-
navigate(`/assignments/edit/${assignmentData.id}/viewreports`)}> - - View Reports -
-
navigate(`/assignments/edit/${assignmentData.id}/viewdelayedjobs`)}> - - View Delayed Jobs -
-
- +
diff --git a/src/pages/Assignments/tabs/EtcTab.tsx b/src/pages/Assignments/tabs/EtcTab.tsx index a7b2c95c..10676db5 100644 --- a/src/pages/Assignments/tabs/EtcTab.tsx +++ b/src/pages/Assignments/tabs/EtcTab.tsx @@ -1,15 +1,59 @@ -import { Col, Row } from "react-bootstrap"; +import React from 'react'; +import { useNavigate } from 'react-router-dom'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { + faUser, + faUserCheck, + faClock, + faFileAlt, + faChartBar, + faUsers, + faClipboardList +} from '@fortawesome/free-solid-svg-icons'; -const EtcTab = () => { +interface EtcTabProps { + assignmentId?: number; +} + +const EtcTab: React.FC = ({ assignmentId }) => { + const navigate = useNavigate(); + return ( - -
-
-

Etc. Section

-

This section will be implemented later.

+
+
+

Assignment Actions

+
+
navigate(`participants`)}> + + Add Participant +
+
navigate(`/assignments/edit/${assignmentId}/createteams`)}> + + Create Teams +
+
navigate(`/assignments/edit/${assignmentId}/assignreviewer`)}> + + Assign Reviewer +
+
navigate(`/assignments/edit/${assignmentId}/viewsubmissions`)}> + + View Submissions +
+
navigate(`/assignments/edit/${assignmentId}/viewscores`)}> + + View Scores +
+
navigate(`/assignments/edit/${assignmentId}/viewreports`)}> + + View Reports +
+
navigate(`/assignments/edit/${assignmentId}/viewdelayedjobs`)}> + + View Delayed Jobs +
- - +
+
); }; diff --git a/src/pages/Assignments/tabs/GeneralTab.tsx b/src/pages/Assignments/tabs/GeneralTab.tsx index 35334628..6776d05e 100644 --- a/src/pages/Assignments/tabs/GeneralTab.tsx +++ b/src/pages/Assignments/tabs/GeneralTab.tsx @@ -1,58 +1,16 @@ -import { Button, Col, Row } from "react-bootstrap"; -import { useNavigate } from "react-router-dom"; -import { BsFileText } from "react-icons/bs"; -import DeleteAssignment from "../AssignmentDelete"; -import { IAssignmentResponse } from "../../../utils/interfaces"; -import { Row as TRow } from "@tanstack/react-table"; -import Table from "components/Table/Table"; -import { assignmentColumns as ASSIGNMENT_COLUMNS } from "../AssignmentColumns"; -interface GeneralTabProps { - tableData: any[]; - isLoading: boolean; - showDeleteConfirmation: { - visible: boolean; - data?: IAssignmentResponse; - }; - onDeleteAssignmentHandler: () => void; - onEditHandle: (row: TRow) => void; - onDeleteHandle: (row: TRow) => void; -} - -const GeneralTab = ({ - tableData, - isLoading, - showDeleteConfirmation, - onDeleteAssignmentHandler, - onEditHandle, - onDeleteHandle, -}: GeneralTabProps) => { - const navigate = useNavigate(); - - const tableColumns = ASSIGNMENT_COLUMNS(onEditHandle, onDeleteHandle); +import { Col, Row } from "react-bootstrap"; +const GeneralTab = () => { return ( - <> - -
- - - {showDeleteConfirmation.visible && ( - - )} - - -
Topic IDTopic name(s)Available slotsNum. on waitlistBookmarksSelect
Topic IDTopic name(s)Available slotsNum. on waitlistBookmarksSelect
TAGS. - This is the most important step. - */} - {topic.id}{topic.name}{topic.availableSlots}{topic.waitlist} + {topic.id}{topic.name}{topic.availableSlots}{topic.waitlist} {/* Added p-3 */} + {/* Added p-3 */}
{flexRender(cell.column.columnDef.cell, cell.getContext())} diff --git a/src/pages/Assignments/AssignmentEditPage.tsx b/src/pages/Assignments/AssignmentEditPage.tsx index 30e1e309..90083aba 100644 --- a/src/pages/Assignments/AssignmentEditPage.tsx +++ b/src/pages/Assignments/AssignmentEditPage.tsx @@ -66,6 +66,9 @@ const AssignmentEditPage = () => { const { data: assignmentResponse, error: assignmentError, sendRequest: fetchAssignment } = useAPI(); const { data: topicsResponse, error: topicsApiError, sendRequest: fetchTopics } = useAPI(); const { data: updateResponse, error: updateError, sendRequest: updateAssignment } = useAPI(); + const { data: deleteResponse, error: deleteError, sendRequest: deleteTopic } = useAPI(); + const { data: createResponse, error: createError, sendRequest: createTopic } = useAPI(); + const { data: updateTopicResponse, error: updateTopicError, sendRequest: updateTopic } = useAPI(); useEffect(() => { if (id) { @@ -101,6 +104,54 @@ const AssignmentEditPage = () => { } }, [updateError, dispatch]); + useEffect(() => { + if (deleteResponse) { + dispatch(alertActions.showAlert({ variant: "success", message: "Topic deleted successfully" })); + // Refresh topics data + if (id) { + fetchTopics({ url: `/project_topics?assignment_id=${id}` }); + } + } + }, [deleteResponse, dispatch, id, fetchTopics]); + + useEffect(() => { + if (deleteError) { + dispatch(alertActions.showAlert({ variant: "danger", message: deleteError })); + } + }, [deleteError, dispatch]); + + useEffect(() => { + if (createResponse) { + dispatch(alertActions.showAlert({ variant: "success", message: "Topic created successfully" })); + // Refresh topics data + if (id) { + fetchTopics({ url: `/project_topics?assignment_id=${id}` }); + } + } + }, [createResponse, dispatch, id, fetchTopics]); + + useEffect(() => { + if (createError) { + dispatch(alertActions.showAlert({ variant: "danger", message: createError })); + } + }, [createError, dispatch]); + + useEffect(() => { + if (updateTopicResponse) { + dispatch(alertActions.showAlert({ variant: "success", message: "Topic updated successfully" })); + // Refresh topics data + if (id) { + fetchTopics({ url: `/project_topics?assignment_id=${id}` }); + } + } + }, [updateTopicResponse, dispatch, id, fetchTopics]); + + useEffect(() => { + if (updateTopicError) { + dispatch(alertActions.showAlert({ variant: "danger", message: updateTopicError })); + } + }, [updateTopicError, dispatch]); + // Load topics for this assignment useEffect(() => { if (id) { @@ -165,19 +216,53 @@ const AssignmentEditPage = () => { const handleDeleteTopic = useCallback((topicId: string) => { console.log(`Delete topic ${topicId}`); - // TODO: Implement delete topic logic - setTopicsData((prev) => prev.filter((t) => t.id !== topicId)); - }, []); + if (id) { + deleteTopic({ + url: `/project_topics`, + method: 'DELETE', + data: { + assignment_id: id, + topic_ids: [topicId] + } + }); + } + }, [id, deleteTopic]); const handleEditTopic = useCallback((topicId: string, updatedData: any) => { console.log(`Edit topic ${topicId}`, updatedData); - // TODO: Implement edit topic logic - }, []); + updateTopic({ + url: `/project_topics/${topicId}`, + method: 'PATCH', + data: { + project_topic: { + topic_identifier: updatedData.topic_identifier || updatedData.id, + topic_name: updatedData.name, + category: updatedData.category, + max_choosers: updatedData.numSlots, + assignment_id: id + } + } + }); + }, [id, updateTopic]); const handleCreateTopic = useCallback((topicData: any) => { console.log(`Create topic`, topicData); - // TODO: Implement create topic logic - }, []); + if (id) { + createTopic({ + url: `/project_topics`, + method: 'POST', + data: { + project_topic: { + topic_identifier: topicData.topic_identifier || topicData.id, + topic_name: topicData.name, + category: topicData.category, + max_choosers: topicData.numSlots, + assignment_id: id + } + } + }); + } + }, [id, createTopic]); const handleApplyPartnerAd = useCallback((topicId: string, applicationText: string) => { console.log(`Applying to partner ad for topic ${topicId}: ${applicationText}`); diff --git a/src/pages/StudentTasks/StudentTasks.tsx b/src/pages/StudentTasks/StudentTasks.tsx index 05736de9..1e068e12 100644 --- a/src/pages/StudentTasks/StudentTasks.tsx +++ b/src/pages/StudentTasks/StudentTasks.tsx @@ -29,6 +29,7 @@ const StudentTasks: React.FC = () => { const [bookmarkedTopics, setBookmarkedTopics] = useState>(new Set()); const [selectedTopic, setSelectedTopic] = useState(null); const [isSigningUp, setIsSigningUp] = useState(false); + const [optimisticSlotChanges, setOptimisticSlotChanges] = useState>(new Map()); const fetchAssignmentData = useCallback(() => { if (assignmentId) { @@ -64,6 +65,8 @@ const StudentTasks: React.FC = () => { useEffect(() => { if (signUpResponse) { setIsSigningUp(false); + // Clear optimistic updates since we'll get real data + setOptimisticSlotChanges(new Map()); if (assignmentResponse?.data) { let targetAssignmentId: number; if (assignmentId) { @@ -82,11 +85,15 @@ const StudentTasks: React.FC = () => { if (signUpError) { console.error('Error signing up for topic:', signUpError); setIsSigningUp(false); + // Clear optimistic updates on error to restore actual values + setOptimisticSlotChanges(new Map()); } }, [signUpError]); useEffect(() => { if (dropResponse) { + // Clear optimistic updates since we'll get real data + setOptimisticSlotChanges(new Map()); if (assignmentResponse?.data) { let targetAssignmentId: number; if (assignmentId) { @@ -104,22 +111,32 @@ const StudentTasks: React.FC = () => { useEffect(() => { if (dropError) { console.error('Error dropping topic:', dropError); + // Clear optimistic updates on error to restore actual values + setOptimisticSlotChanges(new Map()); } }, [dropError]); const topics = useMemo(() => { if (topicsError || !topicsResponse?.data) return []; const topicsData = Array.isArray(topicsResponse.data) ? topicsResponse.data : []; - return topicsData.map((topic: any) => ({ - id: topic.topic_identifier || topic.id?.toString() || 'unknown', - name: topic.topic_name || 'Unnamed Topic', - availableSlots: topic.available_slots || 0, - waitlist: topic.waitlisted_teams?.length || 0, - isBookmarked: bookmarkedTopics.has(topic.topic_identifier || topic.id?.toString() || 'unknown'), - isSelected: selectedTopic === (topic.topic_identifier || topic.id?.toString() || 'unknown'), - isTaken: (topic.available_slots || 0) <= 0 - })); - }, [topicsResponse, topicsError, bookmarkedTopics, selectedTopic]); + return topicsData.map((topic: any) => { + const topicId = topic.topic_identifier || topic.id?.toString() || 'unknown'; + const baseSlots = topic.available_slots || 0; + const adjustedSlots = optimisticSlotChanges.has(topicId) + ? optimisticSlotChanges.get(topicId)! + : baseSlots; + + return { + id: topicId, + name: topic.topic_name || 'Unnamed Topic', + availableSlots: adjustedSlots, + waitlist: topic.waitlisted_teams?.length || 0, + isBookmarked: bookmarkedTopics.has(topicId), + isSelected: selectedTopic === topicId, + isTaken: adjustedSlots <= 0 + }; + }); + }, [topicsResponse, topicsError, bookmarkedTopics, selectedTopic, optimisticSlotChanges]); const assignmentName = useMemo(() => { if (!assignmentResponse?.data) return 'OSS project & documentation assignment'; @@ -160,22 +177,49 @@ const StudentTasks: React.FC = () => { if (!currentUser?.id) return; if (selectedTopic === topicId) { - setSelectedTopic(null); + // Deselecting current topic - optimistically increment available slots const topic = topics.find(t => t.id === topicId); if (topic) { - const topicData = topicsResponse?.data?.find((t: any) => - t.topic_identifier === topicId || t.id?.toString() === topicId - ); - if (topicData?.id) { - dropAPI({ - url: '/signed_up_teams/drop_topic', - method: 'DELETE', - data: { user_id: currentUser.id, topic_id: topicData.id } - }); - } + setOptimisticSlotChanges(prev => { + const newMap = new Map(prev); + newMap.set(topicId, topic.availableSlots + 1); + return newMap; + }); + } + + setSelectedTopic(null); + const topicData = topicsResponse?.data?.find((t: any) => + t.topic_identifier === topicId || t.id?.toString() === topicId + ); + if (topicData?.id) { + dropAPI({ + url: '/signed_up_teams/drop_topic', + method: 'DELETE', + data: { user_id: currentUser.id, topic_id: topicData.id } + }); } } else { + // Selecting new topic - optimistically decrement available slots + const topic = topics.find(t => t.id === topicId); + if (topic) { + setOptimisticSlotChanges(prev => { + const newMap = new Map(prev); + newMap.set(topicId, Math.max(0, topic.availableSlots - 1)); + + // If there's a previously selected topic, increment its slots + if (selectedTopic) { + const prevTopic = topics.find(t => t.id === selectedTopic); + if (prevTopic) { + newMap.set(selectedTopic, prevTopic.availableSlots + 1); + } + } + + return newMap; + }); + } + if (selectedTopic) { + // Drop previous topic first const previousTopicData = topicsResponse?.data?.find((t: any) => t.topic_identifier === selectedTopic || t.id?.toString() === selectedTopic ); @@ -187,11 +231,14 @@ const StudentTasks: React.FC = () => { }); } } + setSelectedTopic(topicId); setIsSigningUp(true); + const topicData = topicsResponse?.data?.find((t: any) => t.topic_identifier === topicId || t.id?.toString() === topicId ); + if (topicData?.id) { setTimeout(() => { signUpAPI({ From 5d156fb446ec399ba0e4849beb5f91aac6b46e29 Mon Sep 17 00:00:00 2001 From: abhira0 Date: Thu, 30 Oct 2025 13:12:22 -0400 Subject: [PATCH 24/34] Update topic handling in AssignmentEditPage and TopicsTab for improved data management - Modified topic ID handling to ensure consistent data types across API calls. - Enhanced the delete and edit topic functions to utilize database IDs for better accuracy. - Updated UI interactions in TopicsTab to streamline topic editing and partner ad applications. - Improved comments and code clarity for better maintainability. --- package.json | 2 +- src/pages/Assignments/AssignmentEditPage.tsx | 42 ++++++++------- src/pages/Assignments/tabs/TopicsTab.tsx | 56 ++++++++------------ 3 files changed, 47 insertions(+), 53 deletions(-) diff --git a/package.json b/package.json index 8ca6caed..7f42db45 100644 --- a/package.json +++ b/package.json @@ -42,7 +42,7 @@ "yup": "^1.4.0" }, "scripts": { - "start": "react-scripts start", + "start": "HOST=0.0.0.0 react-scripts start", "build": "react-scripts build", "test": "react-scripts test", "eject": "react-scripts eject" diff --git a/src/pages/Assignments/AssignmentEditPage.tsx b/src/pages/Assignments/AssignmentEditPage.tsx index 90083aba..c7891ec8 100644 --- a/src/pages/Assignments/AssignmentEditPage.tsx +++ b/src/pages/Assignments/AssignmentEditPage.tsx @@ -165,8 +165,8 @@ const AssignmentEditPage = () => { useEffect(() => { if (topicsResponse?.data) { const transformedTopics: TopicData[] = (topicsResponse.data || []).map((topic: any) => ({ - id: topic.topic_identifier || topic.id?.toString(), - databaseId: topic.id, + id: topic.topic_identifier?.toString?.() || topic.topic_identifier || topic.id?.toString?.() || String(topic.id), + databaseId: Number(topic.id), name: topic.topic_name, url: topic.link, description: topic.description, @@ -214,32 +214,34 @@ const AssignmentEditPage = () => { // TODO: Implement drop team logic }, []); - const handleDeleteTopic = useCallback((topicId: string) => { - console.log(`Delete topic ${topicId}`); + const handleDeleteTopic = useCallback((topicIdentifier: string) => { + console.log(`Delete topic ${topicIdentifier}`); if (id) { deleteTopic({ url: `/project_topics`, method: 'DELETE', - data: { - assignment_id: id, - topic_ids: [topicId] + params: { + assignment_id: Number(id), + 'topic_ids[]': [topicIdentifier] } }); } }, [id, deleteTopic]); - const handleEditTopic = useCallback((topicId: string, updatedData: any) => { - console.log(`Edit topic ${topicId}`, updatedData); + const handleEditTopic = useCallback((dbId: string, updatedData: any) => { + console.log(`Edit topic DB id ${dbId}`, updatedData); updateTopic({ - url: `/project_topics/${topicId}`, + url: `/project_topics/${dbId}`, method: 'PATCH', data: { project_topic: { - topic_identifier: updatedData.topic_identifier || updatedData.id, - topic_name: updatedData.name, + topic_identifier: updatedData.topic_identifier, + topic_name: updatedData.topic_name, category: updatedData.category, - max_choosers: updatedData.numSlots, - assignment_id: id + max_choosers: updatedData.max_choosers, + assignment_id: id, + description: updatedData.description, + link: updatedData.link } } }); @@ -254,11 +256,14 @@ const AssignmentEditPage = () => { data: { project_topic: { topic_identifier: topicData.topic_identifier || topicData.id, - topic_name: topicData.name, + topic_name: topicData.topic_name || topicData.name, category: topicData.category, - max_choosers: topicData.numSlots, - assignment_id: id - } + max_choosers: topicData.max_choosers ?? topicData.numSlots, + assignment_id: id, + description: topicData.description, + link: topicData.link + }, + micropayment: topicData.micropayment ?? 0 } }); } @@ -472,4 +477,3 @@ const AssignmentEditPage = () => { }; export default AssignmentEditPage; - diff --git a/src/pages/Assignments/tabs/TopicsTab.tsx b/src/pages/Assignments/tabs/TopicsTab.tsx index 553d0785..c722530c 100644 --- a/src/pages/Assignments/tabs/TopicsTab.tsx +++ b/src/pages/Assignments/tabs/TopicsTab.tsx @@ -36,7 +36,7 @@ interface BookmarkData { // Updated TopicData interface interface TopicData { - id: string; // Topic ID (topic_identifier for display) + id: string; // topic_identifier for display/selection databaseId: number; // Database ID for API calls name: string; // Topic Name url?: string; // Optional URL for the topic name @@ -208,8 +208,8 @@ const TopicsTab = ({ console.log('Submitting edit for topic:', editingTopic); console.log('Edit data:', editTopicData); if (editingTopic && onEditTopic) { - console.log('Calling onEditTopic with:', editingTopic.id, editTopicData); - onEditTopic(editingTopic.id, editTopicData); + console.log('Calling onEditTopic with DB id:', editingTopic.databaseId, editTopicData); + onEditTopic(String(editingTopic.databaseId), editTopicData); handleCloseEditTopic(); } else { console.log('Missing editingTopic or onEditTopic:', { editingTopic, onEditTopic }); @@ -484,41 +484,31 @@ const TopicsTab = ({ )} renderInstructorActions={(topic) => ( - - From f215e5aad7b159a4bbfc1ae0d81482ab6aa34fb2 Mon Sep 17 00:00:00 2001 From: abhira0 Date: Thu, 30 Oct 2025 13:18:59 -0400 Subject: [PATCH 25/34] Enhance TopicsTab and AssignmentEditPage for improved topic management and deletion handling --- src/pages/Assignments/AssignmentEditPage.tsx | 2 + src/pages/Assignments/TopicDelete.tsx | 77 ++++++++++++++++++++ src/pages/Assignments/tabs/TopicsTab.tsx | 62 +++++++--------- 3 files changed, 105 insertions(+), 36 deletions(-) create mode 100644 src/pages/Assignments/TopicDelete.tsx diff --git a/src/pages/Assignments/AssignmentEditPage.tsx b/src/pages/Assignments/AssignmentEditPage.tsx index c7891ec8..6f9fc550 100644 --- a/src/pages/Assignments/AssignmentEditPage.tsx +++ b/src/pages/Assignments/AssignmentEditPage.tsx @@ -280,6 +280,7 @@ const AssignmentEditPage = () => { return ( { onEditTopic={handleEditTopic} onCreateTopic={handleCreateTopic} onApplyPartnerAd={handleApplyPartnerAd} + onTopicsChanged={() => id && fetchTopics({ url: `/project_topics?assignment_id=${id}` })} /> ); diff --git a/src/pages/Assignments/TopicDelete.tsx b/src/pages/Assignments/TopicDelete.tsx new file mode 100644 index 00000000..5e26f3e0 --- /dev/null +++ b/src/pages/Assignments/TopicDelete.tsx @@ -0,0 +1,77 @@ +import { Button, Modal } from "react-bootstrap"; +import React, { useEffect, useState } from "react"; +import { useDispatch } from "react-redux"; +import useAPI from "hooks/useAPI"; +import { alertActions } from "store/slices/alertSlice"; + +interface DeleteTopicsProps { + assignmentId: string; + topicIds: string[]; // topic_identifier values + topicNames?: string[]; // optional display names + onClose: () => void; + onDeleted?: () => void; +} + +const DeleteTopics: React.FC = ({ assignmentId, topicIds, topicNames = [], onClose, onDeleted }) => { + const { data: deleteResp, error: deleteError, sendRequest: deleteTopics } = useAPI(); + const [show, setShow] = useState(true); + const dispatch = useDispatch(); + + const deleteHandler = () => { + deleteTopics({ + url: `/project_topics`, + method: 'DELETE', + params: { + assignment_id: Number(assignmentId), + 'topic_ids[]': topicIds, + } + }); + }; + + useEffect(() => { + if (deleteError) { + dispatch(alertActions.showAlert({ variant: "danger", message: deleteError })); + } + }, [deleteError, dispatch]); + + useEffect(() => { + if (deleteResp?.status && deleteResp.status >= 200 && deleteResp.status < 300) { + setShow(false); + const label = topicIds.length === 1 ? (topicNames[0] || topicIds[0]) : `${topicIds.length} topics`; + dispatch(alertActions.showAlert({ variant: "success", message: `Deleted ${label} successfully.` })); + onClose(); + onDeleted && onDeleted(); + } + }, [deleteResp?.status, dispatch, onClose, topicIds, topicNames]); + + const closeHandler = () => { + setShow(false); + onClose(); + }; + + const title = topicIds.length === 1 ? 'Delete Topic' : 'Delete Topics'; + const body = topicIds.length === 1 + ? <>Are you sure you want to delete topic {topicNames[0] || topicIds[0]}? + : <>Are you sure you want to delete {topicIds.length} selected topics?; + + return ( + + + {title} + + +

{body}

+
+ + + + +
+ ); +}; + +export default DeleteTopics; diff --git a/src/pages/Assignments/tabs/TopicsTab.tsx b/src/pages/Assignments/tabs/TopicsTab.tsx index c722530c..b092099d 100644 --- a/src/pages/Assignments/tabs/TopicsTab.tsx +++ b/src/pages/Assignments/tabs/TopicsTab.tsx @@ -3,6 +3,7 @@ import { Col, Row, Form, Button, Modal, FloatingLabel, Stack } from "react-boots // Reverting to the standard import path for react-icons/bs import { BsPersonPlusFill, BsBookmark, BsBookmarkFill } from "react-icons/bs"; import TopicsTable from "pages/Assignments/components/TopicsTable"; +import DeleteTopics from "../TopicDelete"; // --- Interface Modifications --- // Assuming these interfaces are defined elsewhere and imported @@ -64,6 +65,7 @@ interface TopicSettings { interface TopicsTabProps { assignmentName?: string; + assignmentId: string; topicSettings: TopicSettings; topicsData: TopicData[]; // Ensure the data passed matches the updated TopicData interface topicsLoading?: boolean; @@ -76,12 +78,14 @@ interface TopicsTabProps { onCreateTopic?: (topicData: any) => void; // Handler for partner ad application submission onApplyPartnerAd: (topicId: string, applicationText: string) => void; + onTopicsChanged?: () => void; } // --- Component Implementation --- const TopicsTab = ({ assignmentName = "Assignment", + assignmentId, topicSettings, topicsData, topicsLoading = false, @@ -92,6 +96,7 @@ const TopicsTab = ({ onEditTopic, onCreateTopic, onApplyPartnerAd, + onTopicsChanged, }: TopicsTabProps) => { const [displayUserNames, setDisplayUserNames] = useState(false); // State for toggling user name/ID display const [showPartnerAdModal, setShowPartnerAdModal] = useState(false); @@ -117,8 +122,8 @@ const TopicsTab = ({ const [showImportModal, setShowImportModal] = useState(false); const [importData, setImportData] = useState(''); - // Delete confirmation modal state - const [showDeleteModal, setShowDeleteModal] = useState(false); + // Delete modal state (repo-standard) + const [deleteState, setDeleteState] = useState<{ visible: boolean; ids: string[]; names: string[] }>({ visible: false, ids: [], names: [] }); // Edit topic modal state const [showEditModal, setShowEditModal] = useState(false); @@ -287,20 +292,10 @@ const TopicsTab = ({ // --- Delete Handlers --- const handleDeleteSelected = () => { - setShowDeleteModal(true); - }; - - const handleConfirmDelete = () => { - selectedTopics.forEach(topicId => { - onDeleteTopic(topicId); - }); - setSelectedTopics(new Set()); - setSelectAll(false); - setShowDeleteModal(false); - }; - - const handleCloseDelete = () => { - setShowDeleteModal(false); + if (selectedTopics.size === 0) return; + const ids = Array.from(selectedTopics); + const names = ids.map(id => topicsData.find(t => t.id === id)?.name || id); + setDeleteState({ visible: true, ids, names }); }; // --- Back Handler --- @@ -496,7 +491,12 @@ const TopicsTab = ({ > Edit - - - - + {deleteState.visible && ( + setDeleteState({ visible: false, ids: [], names: [] })} + onDeleted={onTopicsChanged} + /> + )} {/* Edit Topic Modal */} From 701badcbcbe6cb6ad3dcca242c4c9ae368d4e836 Mon Sep 17 00:00:00 2001 From: abhira0 Date: Thu, 30 Oct 2025 13:29:37 -0400 Subject: [PATCH 26/34] enabled pagination --- src/pages/Assignments/AssignmentEditPage.tsx | 4 ++++ .../Assignments/components/TopicsTable.tsx | 4 ++-- src/pages/Assignments/tabs/TopicsTab.tsx | 18 ++++++++++++++++++ 3 files changed, 24 insertions(+), 2 deletions(-) diff --git a/src/pages/Assignments/AssignmentEditPage.tsx b/src/pages/Assignments/AssignmentEditPage.tsx index 6f9fc550..a1a9ef16 100644 --- a/src/pages/Assignments/AssignmentEditPage.tsx +++ b/src/pages/Assignments/AssignmentEditPage.tsx @@ -36,6 +36,8 @@ interface TopicData { availableSlots: number; bookmarks: any[]; partnerAd?: any; + createdAt?: string; + updatedAt?: string; } const AssignmentEditPage = () => { @@ -178,6 +180,8 @@ const AssignmentEditPage = () => { availableSlots: topic.available_slots || 0, bookmarks: [], partnerAd: undefined, + createdAt: topic.created_at, + updatedAt: topic.updated_at, })); setTopicsData(transformedTopics); setTopicsLoading(false); diff --git a/src/pages/Assignments/components/TopicsTable.tsx b/src/pages/Assignments/components/TopicsTable.tsx index 949f4bbc..58d186d5 100644 --- a/src/pages/Assignments/components/TopicsTable.tsx +++ b/src/pages/Assignments/components/TopicsTable.tsx @@ -211,8 +211,8 @@ const TopicsTable: React.FC = ({ data={data} columns={columns as ColumnDef[]} showGlobalFilter={false} - showColumnFilter={false} - showPagination={data.length >= showPaginationThreshold} + showColumnFilter={true} + showPagination={true} renderSubComponent={renderDetails ? ({ row }) => renderDetails(row.original as TopicRow) : undefined} getRowCanExpand={renderDetails ? (row) => { const r = row.original as TopicRow; diff --git a/src/pages/Assignments/tabs/TopicsTab.tsx b/src/pages/Assignments/tabs/TopicsTab.tsx index b092099d..1e65ea83 100644 --- a/src/pages/Assignments/tabs/TopicsTab.tsx +++ b/src/pages/Assignments/tabs/TopicsTab.tsx @@ -51,6 +51,8 @@ interface TopicData { // waitlist: number; // Redundant now, can derive from waitlistedTeams.length bookmarks: BookmarkData[]; // Array of bookmarks for this topic partnerAd?: PartnerAd; // Optional partner advertisement details + createdAt?: string; + updatedAt?: string; } // Same as before @@ -414,6 +416,22 @@ const TopicsTab = ({ header: "Questionnaire", cell: ({ row }) => {(topicsData.find(t => t.id === row.original.id)?.questionnaire) || "--Default rubric--"}, }, + { + id: "createdAt", + header: "Creation Date", + cell: ({ row }) => { + const t = topicsData.find(t => t.id === row.original.id); + return {t?.createdAt || ''}; + }, + }, + { + id: "updatedAt", + header: "Updated Date", + cell: ({ row }) => { + const t = topicsData.find(t => t.id === row.original.id); + return {t?.updatedAt || ''}; + }, + }, { id: "numSlots", header: "Num. of Slots", From 62f8bd099596cedf16f2b076fd4a6531ffe679a2 Mon Sep 17 00:00:00 2001 From: abhira0 Date: Thu, 30 Oct 2025 15:12:03 -0400 Subject: [PATCH 27/34] Enhance topic selection and management in StudentTasks and related components - Introduced optimistic UI updates for topic selection and deselection, improving user experience during interactions. - Updated state management to handle UI-selected topics, pending deselections, and optimistic selections for better data consistency. - Modified table rendering logic to apply background color styling for selected rows, enhancing visual feedback. - Ensured database IDs are used for API calls related to topic management, improving accuracy in data handling. --- src/components/Table/Table.tsx | 18 +- .../Assignments/components/TopicsTable.tsx | 3 +- src/pages/StudentTasks/StudentTasks.tsx | 173 ++++++++++++++---- 3 files changed, 154 insertions(+), 40 deletions(-) diff --git a/src/components/Table/Table.tsx b/src/components/Table/Table.tsx index e83a14df..beabe119 100644 --- a/src/components/Table/Table.tsx +++ b/src/components/Table/Table.tsx @@ -212,12 +212,18 @@ import {
- {flexRender(cell.column.columnDef.cell, cell.getContext())} -
+ {flexRender(cell.column.columnDef.cell, cell.getContext())} +
- - + + +
+

General Section

+

This section will be implemented later.

+
+ + ); }; From 89558826c4bae5729f849c3d8feed7840291f87e Mon Sep 17 00:00:00 2001 From: Deekshtih Anantha Date: Fri, 31 Oct 2025 01:23:23 -0400 Subject: [PATCH 33/34] General tab fixed --- src/pages/Assignments/tabs/GeneralTab.tsx | 174 +++++++++++++++++++++- 1 file changed, 170 insertions(+), 4 deletions(-) diff --git a/src/pages/Assignments/tabs/GeneralTab.tsx b/src/pages/Assignments/tabs/GeneralTab.tsx index 6776d05e..02b91447 100644 --- a/src/pages/Assignments/tabs/GeneralTab.tsx +++ b/src/pages/Assignments/tabs/GeneralTab.tsx @@ -1,14 +1,180 @@ - import { Col, Row } from "react-bootstrap"; const GeneralTab = () => { return ( -
-

General Section

-

This section will be implemented later.

+ {/* This form is a direct conversion of your HTML. + It assumes a and
component are wrapped + around this GeneralTab component by its parent. + */} + +
+ {/* Column 1: Text & Number Inputs + This maps to your components + */} +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + {/* Column 2: Checkboxes + This maps to your components + */} +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + {/* Footer / Buttons + This maps to your + */} +
+ + +
+ ); From 53908e3a8023a28e42c05a69278d3caafb1cb522 Mon Sep 17 00:00:00 2001 From: Deekshtih Anantha Date: Fri, 31 Oct 2025 01:27:39 -0400 Subject: [PATCH 34/34] cyan color removed --- src/pages/Assignments/tabs/EtcTab.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/Assignments/tabs/EtcTab.tsx b/src/pages/Assignments/tabs/EtcTab.tsx index 10676db5..d27f85e9 100644 --- a/src/pages/Assignments/tabs/EtcTab.tsx +++ b/src/pages/Assignments/tabs/EtcTab.tsx @@ -20,7 +20,7 @@ const EtcTab: React.FC = ({ assignmentId }) => { return (
-
+

Assignment Actions

navigate(`participants`)}>