diff --git a/src/actions/educationPortal/atomActions.js b/src/actions/educationPortal/atomActions.js
new file mode 100644
index 0000000000..87b86703a4
--- /dev/null
+++ b/src/actions/educationPortal/atomActions.js
@@ -0,0 +1,113 @@
+import axios from 'axios';
+import { toast } from 'react-toastify';
+import { ENDPOINTS } from '~/utils/URL';
+import {
+ FETCH_AVAILABLE_ATOMS_START,
+ FETCH_AVAILABLE_ATOMS_SUCCESS,
+ FETCH_AVAILABLE_ATOMS_ERROR,
+ ASSIGN_ATOMS_START,
+ ASSIGN_ATOMS_SUCCESS,
+ ASSIGN_ATOMS_ERROR,
+ SELECT_ATOM,
+ DESELECT_ATOM,
+ CLEAR_SELECTIONS,
+ SET_NOTE,
+ CLEAR_FORM,
+ SHOW_MODAL,
+ HIDE_MODAL,
+} from '~/constants/educationPortal/atom';
+
+// Fetch available atoms
+export const fetchAvailableAtoms = () => {
+ return async dispatch => {
+ dispatch({ type: FETCH_AVAILABLE_ATOMS_START });
+ try {
+ const response = await axios.get(`${ENDPOINTS.APIEndpoint()}/atoms`);
+ dispatch({
+ type: FETCH_AVAILABLE_ATOMS_SUCCESS,
+ payload: response.data,
+ });
+ } catch (error) {
+ dispatch({
+ type: FETCH_AVAILABLE_ATOMS_ERROR,
+ payload: error.message,
+ });
+ toast.error('Failed to load atoms. Please try again.');
+ }
+ };
+};
+
+// Assign atoms to a student
+export const assignAtoms = (studentId, atoms, note) => {
+ return async dispatch => {
+ dispatch({ type: ASSIGN_ATOMS_START });
+ try {
+ const payload = {
+ studentId,
+ atomTypes: atoms || [],
+ note: note || '',
+ };
+
+ const response = await axios.post(ENDPOINTS.EDUCATOR_ASSIGN_ATOMS(), payload);
+
+ dispatch({
+ type: ASSIGN_ATOMS_SUCCESS,
+ payload: response.data,
+ });
+
+ toast.success('Atoms have been assigned', {
+ position: 'top-right',
+ autoClose: 3000,
+ });
+
+ return response.data;
+ } catch (error) {
+ dispatch({
+ type: ASSIGN_ATOMS_ERROR,
+ payload: error.message,
+ });
+
+ toast.error('Failed to assign atoms. Please try again.', {
+ position: 'top-right',
+ autoClose: 5000,
+ });
+
+ throw error;
+ }
+ };
+};
+
+// Selection actions
+export const selectAtom = atomId => ({
+ type: SELECT_ATOM,
+ payload: atomId,
+});
+
+export const deselectAtom = atomId => ({
+ type: DESELECT_ATOM,
+ payload: atomId,
+});
+
+export const clearSelections = () => ({
+ type: CLEAR_SELECTIONS,
+});
+
+// Form actions
+export const setNote = note => ({
+ type: SET_NOTE,
+ payload: note,
+});
+
+export const clearForm = () => ({
+ type: CLEAR_FORM,
+});
+
+// Modal actions
+export const showModal = (studentId, studentName) => ({
+ type: SHOW_MODAL,
+ payload: { studentId, studentName },
+});
+
+export const hideModal = () => ({
+ type: HIDE_MODAL,
+});
diff --git a/src/components/EductionPortal/AssignAtomModal/AssignAtomModal.jsx b/src/components/EductionPortal/AssignAtomModal/AssignAtomModal.jsx
new file mode 100644
index 0000000000..b73dfe2c4d
--- /dev/null
+++ b/src/components/EductionPortal/AssignAtomModal/AssignAtomModal.jsx
@@ -0,0 +1,424 @@
+import React, { useState, useEffect, useCallback, useRef } from 'react';
+import { connect } from 'react-redux';
+import PropTypes from 'prop-types';
+import {
+ Modal,
+ ModalHeader,
+ ModalBody,
+ ModalFooter,
+ Button,
+ FormGroup,
+ Label,
+ Input,
+ FormFeedback,
+ Dropdown,
+ Table,
+ CustomInput,
+} from 'reactstrap';
+import axios from 'axios';
+import { ENDPOINTS } from '~/utils/URL';
+import styles from './AssignAtomModal.module.css';
+import {
+ fetchAvailableAtoms,
+ assignAtoms,
+ selectAtom,
+ deselectAtom,
+ setNote,
+ hideModal,
+ clearForm,
+} from '~/actions/educationPortal/atomActions';
+
+const AssignAtomModal = ({
+ // Redux state
+ isModalOpen,
+ studentId,
+ studentName,
+ availableAtoms,
+ selectedAtoms,
+ note,
+ isLoadingAtoms,
+ isSubmitting,
+ submitError,
+ darkMode,
+
+ // Redux actions
+ fetchAvailableAtoms,
+ assignAtoms,
+ selectAtom,
+ deselectAtom,
+ setNote,
+ hideModal,
+ clearForm,
+}) => {
+ const [localNote, setLocalNote] = useState('');
+ const [validationError, setValidationError] = useState('');
+
+ // Student search state
+ const [searchText, setSearchText] = useState('');
+ const [allUsers, setAllUsers] = useState([]);
+ const [isUserDropdownOpen, setIsUserDropdownOpen] = useState(false);
+ const [isInputFocus, setIsInputFocus] = useState(false);
+ const [selectedStudent, setSelectedStudent] = useState(null);
+ const [isLoadingUsers, setIsLoadingUsers] = useState(false);
+
+ const userSearchRef = useRef();
+
+ // Sync local note with Redux state
+ useEffect(() => {
+ setLocalNote(note);
+ }, [note]);
+
+ // Load atoms when modal opens
+ useEffect(() => {
+ if (isModalOpen && availableAtoms.length === 0) {
+ fetchAvailableAtoms();
+ }
+ }, [isModalOpen, availableAtoms.length, fetchAvailableAtoms]);
+
+ // Load users when modal opens
+ useEffect(() => {
+ if (isModalOpen && allUsers.length === 0) {
+ fetchAllUsers();
+ }
+ }, [isModalOpen, allUsers.length]);
+
+ // Set selected student when studentId changes
+ useEffect(() => {
+ if (studentId && studentName) {
+ setSelectedStudent({ _id: studentId, name: studentName });
+ setSearchText(studentName);
+ }
+ }, [studentId, studentName]);
+
+ // Fetch all users for search
+ const fetchAllUsers = async () => {
+ setIsLoadingUsers(true);
+ try {
+ const response = await axios.get(`${ENDPOINTS.APIEndpoint()}/userprofile`);
+ setAllUsers(response.data);
+ } catch (error) {
+ // Handle error silently or show toast
+ // console.error('Failed to fetch users:', error);
+ } finally {
+ setIsLoadingUsers(false);
+ }
+ };
+
+ const handleAtomToggle = useCallback(
+ atomId => {
+ if (selectedAtoms.includes(atomId)) {
+ deselectAtom(atomId);
+ } else {
+ selectAtom(atomId);
+ }
+ },
+ [selectedAtoms, selectAtom, deselectAtom],
+ );
+
+ const handleNoteChange = useCallback(
+ e => {
+ const value = e.target.value;
+ if (value.length <= 500) {
+ setLocalNote(value);
+ setNote(value);
+ }
+ },
+ [setNote],
+ );
+
+ // Student selection handlers
+ const handleStudentSelect = user => {
+ setSelectedStudent(user);
+ setSearchText(`${user.firstName} ${user.lastName}`);
+ setIsUserDropdownOpen(false);
+ setIsInputFocus(false);
+ };
+
+ const handleSearchChange = value => {
+ setSearchText(value);
+ setIsUserDropdownOpen(true);
+ };
+
+ const handleSubmit = useCallback(async () => {
+ // Validation
+ if (selectedAtoms.length === 0) {
+ setValidationError('Please select at least one atom');
+ return;
+ }
+
+ if (!selectedStudent) {
+ setValidationError('Please select a student');
+ return;
+ }
+
+ setValidationError('');
+
+ try {
+ await assignAtoms(selectedStudent._id, selectedAtoms, localNote);
+ hideModal();
+ } catch (error) {
+ // Error is handled by the action
+ // console.error('Assignment failed:', error);
+ }
+ }, [selectedAtoms, localNote, selectedStudent, assignAtoms, hideModal]);
+
+ const handleCancel = useCallback(() => {
+ if (selectedAtoms.length > 0 || localNote.trim() || selectedStudent) {
+ // eslint-disable-next-line no-alert
+ if (window.confirm('You have unsaved changes. Are you sure you want to cancel?')) {
+ clearForm();
+ setSelectedStudent(null);
+ setSearchText('');
+ hideModal();
+ }
+ } else {
+ hideModal();
+ }
+ }, [selectedAtoms.length, localNote, selectedStudent, clearForm, hideModal]);
+
+ // Filter users based on search text
+ const filteredUsers = allUsers.filter(user => {
+ if (!searchText.trim()) return false;
+ const fullName = `${user.firstName} ${user.lastName}`.toLowerCase();
+ const searchLower = searchText.toLowerCase();
+ return (
+ (user.firstName.toLowerCase().includes(searchLower) ||
+ user.lastName.toLowerCase().includes(searchLower) ||
+ fullName.includes(searchLower)) &&
+ user.isActive
+ );
+ });
+
+ const handleClose = useCallback(() => {
+ handleCancel();
+ }, [handleCancel]);
+
+ const getCharacterCountClass = () => {
+ const count = localNote.length;
+ if (count > 450) return styles.error;
+ if (count > 400) return styles.warning;
+ return '';
+ };
+
+ const isSubmitDisabled = isSubmitting || selectedAtoms.length === 0 || !selectedStudent;
+
+ return (
+
+
+ Assign Atoms
+
+
+
+ {/* Student Selection */}
+
+
+
+
setIsUserDropdownOpen(!isUserDropdownOpen)}
+ style={{ width: '100%', marginRight: '5px' }}
+ >
+ {
+ setIsInputFocus(true);
+ setIsUserDropdownOpen(true);
+ }}
+ onChange={e => handleSearchChange(e.target.value)}
+ placeholder="Search for a student..."
+ className={darkMode ? 'bg-darkmode-liblack text-light border-0' : ''}
+ autoComplete="off"
+ name="student-search"
+ />
+ {isInputFocus || (searchText !== '' && allUsers && allUsers.length > 0) ? (
+
+ {isLoadingUsers ? (
+
Loading users...
+ ) : filteredUsers.length > 0 ? (
+ filteredUsers.map(user => (
+
handleStudentSelect(user)}
+ onKeyDown={e => {
+ if (e.key === 'Enter' || e.key === ' ') {
+ handleStudentSelect(user);
+ }
+ }}
+ >
+ {user.firstName} {user.lastName}
+
+ ))
+ ) : (
+
No users found
+ )}
+
+ ) : null}
+
+
+
+
+ {/* Associated Atoms */}
+
+
+
+ {isLoadingAtoms ? (
+
Loading atoms...
+ ) : (
+
+ {availableAtoms.map(atom => (
+
+ handleAtomToggle(atom._id)}
+ />
+
+ ))}
+
+ )}
+
+
+ {/* Selected Atoms Summary */}
+ {selectedAtoms.length > 0 && (
+
+
Selected Atoms ({selectedAtoms.length}):
+
+ {selectedAtoms.map(atomId => {
+ const atom = availableAtoms.find(a => a._id === atomId);
+ return (
+
+ {atom?.name || atom?.title || atom?.atomName || 'Unknown Atom'}
+
+
+ );
+ })}
+
+
+ )}
+
+
+ {/* Note Field */}
+
+
+
+
+ {localNote.length}/500 characters
+
+
+
+ {/* Error Messages */}
+ {validationError && {validationError}
}
+
+ {submitError && {submitError}
}
+
+
+
+
+
+
+
+ );
+};
+
+AssignAtomModal.propTypes = {
+ // Redux state
+ isModalOpen: PropTypes.bool.isRequired,
+ studentId: PropTypes.string,
+ studentName: PropTypes.string,
+ availableAtoms: PropTypes.array.isRequired,
+ selectedAtoms: PropTypes.array.isRequired,
+ note: PropTypes.string.isRequired,
+ isLoadingAtoms: PropTypes.bool.isRequired,
+ isSubmitting: PropTypes.bool.isRequired,
+ submitError: PropTypes.string,
+ darkMode: PropTypes.bool.isRequired,
+
+ // Redux actions
+ fetchAvailableAtoms: PropTypes.func.isRequired,
+ assignAtoms: PropTypes.func.isRequired,
+ selectAtom: PropTypes.func.isRequired,
+ deselectAtom: PropTypes.func.isRequired,
+ setNote: PropTypes.func.isRequired,
+ hideModal: PropTypes.func.isRequired,
+ clearForm: PropTypes.func.isRequired,
+};
+
+const mapStateToProps = state => ({
+ isModalOpen: state.atom?.isModalOpen || false,
+ studentId: state.atom?.studentId,
+ studentName: state.atom?.studentName,
+ availableAtoms: state.atom?.availableAtoms || [],
+ selectedAtoms: state.atom?.selectedAtoms || [],
+ note: state.atom?.note || '',
+ isLoadingAtoms: state.atom?.isLoadingAtoms || false,
+ isSubmitting: state.atom?.isSubmitting || false,
+ submitError: state.atom?.submitError,
+ darkMode: state.theme?.darkMode || false,
+});
+
+const mapDispatchToProps = {
+ fetchAvailableAtoms,
+ assignAtoms,
+ selectAtom,
+ deselectAtom,
+ setNote,
+ hideModal,
+ clearForm,
+};
+
+export default connect(mapStateToProps, mapDispatchToProps)(AssignAtomModal);
diff --git a/src/components/EductionPortal/AssignAtomModal/AssignAtomModal.module.css b/src/components/EductionPortal/AssignAtomModal/AssignAtomModal.module.css
new file mode 100644
index 0000000000..950b08aee9
--- /dev/null
+++ b/src/components/EductionPortal/AssignAtomModal/AssignAtomModal.module.css
@@ -0,0 +1,418 @@
+/* AssignAtomModal Styles */
+
+.modal {
+ max-width: 600px;
+ width: 90%;
+}
+
+.modalContent {
+ padding: 20px;
+}
+
+.modalHeader {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-bottom: 20px;
+ padding-bottom: 15px;
+ border-bottom: 1px solid #e9ecef;
+}
+
+.modalTitle {
+ margin: 0;
+ font-size: 1.5rem;
+ font-weight: 600;
+ color: #333;
+}
+
+.closeButton {
+ background: none;
+ border: none;
+ font-size: 1.5rem;
+ cursor: pointer;
+ color: #6c757d;
+ padding: 0;
+ width: 30px;
+ height: 30px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+}
+
+.closeButton:hover {
+ color: #333;
+}
+
+.studentInfo {
+ margin-bottom: 20px;
+ padding: 15px;
+ background-color: #f8f9fa;
+ border-radius: 8px;
+ border-left: 4px solid #007bff;
+}
+
+.studentLabel {
+ font-weight: 600;
+ color: #495057;
+ margin-bottom: 5px;
+}
+
+.studentName {
+ font-size: 1.1rem;
+ color: #333;
+}
+
+.formGroup {
+ margin-bottom: 20px;
+}
+
+.formLabel {
+ display: block;
+ margin-bottom: 8px;
+ font-weight: 600;
+ color: #495057;
+}
+
+.dropdownContainer {
+ position: relative;
+}
+
+.dropdown {
+ width: 100%;
+ padding: 10px 15px;
+ border: 1px solid #ced4da;
+ border-radius: 6px;
+ background-color: #fff;
+ font-size: 1rem;
+ cursor: pointer;
+}
+
+.dropdown:focus {
+ outline: none;
+ border-color: #007bff;
+ box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.25);
+}
+
+.dropdownMultiple {
+ min-height: 120px;
+ padding: 10px;
+}
+
+.selectedItems {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 8px;
+ margin-top: 10px;
+}
+
+.selectedItem {
+ display: flex;
+ align-items: center;
+ background-color: #e9ecef;
+ padding: 5px 10px;
+ border-radius: 20px;
+ font-size: 0.9rem;
+}
+
+.removeItem {
+ background: none;
+ border: none;
+ margin-left: 8px;
+ cursor: pointer;
+ color: #6c757d;
+ font-size: 1.2rem;
+ padding: 0;
+ width: 20px;
+ height: 20px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+}
+
+.removeItem:hover {
+ color: #dc3545;
+}
+
+.noteField {
+ width: 100%;
+ min-height: 100px;
+ padding: 10px 15px;
+ border: 1px solid #ced4da;
+ border-radius: 6px;
+ font-size: 1rem;
+ font-family: inherit;
+ resize: vertical;
+}
+
+.noteField:focus {
+ outline: none;
+ border-color: #007bff;
+ box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.25);
+}
+
+.characterCount {
+ text-align: right;
+ font-size: 0.8rem;
+ color: #6c757d;
+ margin-top: 5px;
+}
+
+.characterCount.warning {
+ color: #ffc107;
+}
+
+.characterCount.error {
+ color: #dc3545;
+}
+
+.errorMessage {
+ color: #dc3545;
+ font-size: 0.9rem;
+ margin-top: 5px;
+}
+
+.buttonGroup {
+ display: flex;
+ gap: 10px;
+ justify-content: flex-end;
+ margin-top: 30px;
+ padding-top: 20px;
+ border-top: 1px solid #e9ecef;
+}
+
+.submitButton {
+ background-color: #28a745;
+ color: white;
+ border: none;
+ padding: 10px 20px;
+ border-radius: 6px;
+ font-size: 1rem;
+ font-weight: 600;
+ cursor: pointer;
+ transition: background-color 0.2s;
+}
+
+.submitButton:hover:not(:disabled) {
+ background-color: #218838;
+}
+
+.submitButton:disabled {
+ background-color: #6c757d;
+ cursor: not-allowed;
+}
+
+.cancelButton {
+ background-color: #6c757d;
+ color: white;
+ border: none;
+ padding: 10px 20px;
+ border-radius: 6px;
+ font-size: 1rem;
+ font-weight: 600;
+ cursor: pointer;
+ transition: background-color 0.2s;
+}
+
+.cancelButton:hover {
+ background-color: #5a6268;
+}
+
+.loadingSpinner {
+ display: inline-block;
+ width: 16px;
+ height: 16px;
+ border: 2px solid #ffffff;
+ border-radius: 50%;
+ border-top-color: transparent;
+ animation: spin 1s ease-in-out infinite;
+ margin-right: 8px;
+}
+
+@keyframes spin {
+ to {
+ transform: rotate(360deg);
+ }
+}
+
+/* Student Search Styles */
+.studentSearchContainer {
+ position: relative;
+}
+
+.user__auto-complete {
+ padding: 8px 12px;
+ cursor: pointer;
+ border-bottom: 1px solid #e9ecef;
+ transition: background-color 0.2s;
+}
+
+.user__auto-complete:hover {
+ background-color: #f8f9fa;
+}
+
+.user__auto-complete:last-child {
+ border-bottom: none;
+}
+
+.dropdown__user-perms {
+ max-height: 200px;
+ overflow-y: auto;
+ border: 1px solid #ced4da;
+ border-radius: 6px;
+ box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
+}
+
+/* Atom Selection Styles */
+.atomsContainer {
+ max-height: 300px;
+ overflow-y: auto;
+ border: 1px solid #ced4da;
+ border-radius: 6px;
+ padding: 15px;
+ background-color: #fff;
+}
+
+.atomsList {
+ display: flex;
+ flex-direction: column;
+ gap: 10px;
+}
+
+.atomItem {
+ display: flex;
+ align-items: center;
+ padding: 8px;
+ border-radius: 4px;
+ transition: background-color 0.2s;
+}
+
+.atomItem:hover {
+ background-color: #f8f9fa;
+}
+
+.loadingMessage {
+ text-align: center;
+ color: #6c757d;
+ font-style: italic;
+ padding: 20px;
+}
+
+.selectedSummary {
+ margin-top: 15px;
+ padding: 15px;
+ background-color: #f8f9fa;
+ border-radius: 6px;
+ border-left: 4px solid #28a745;
+}
+
+.selectedSummary strong {
+ color: #495057;
+ display: block;
+ margin-bottom: 10px;
+}
+
+/* Dark mode styles */
+.darkMode .modalTitle {
+ color: #fff;
+}
+
+.darkMode .studentInfo {
+ background-color: #343a40;
+ border-left-color: #007bff;
+}
+
+.darkMode .studentLabel {
+ color: #adb5bd;
+}
+
+.darkMode .studentName {
+ color: #fff;
+}
+
+.darkMode .formLabel {
+ color: #adb5bd;
+}
+
+.darkMode .dropdown {
+ background-color: #495057;
+ border-color: #6c757d;
+ color: #fff;
+}
+
+.darkMode .dropdown:focus {
+ border-color: #007bff;
+ box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.25);
+}
+
+.darkMode .noteField {
+ background-color: #495057;
+ border-color: #6c757d;
+ color: #fff;
+}
+
+.darkMode .noteField:focus {
+ border-color: #007bff;
+ box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.25);
+}
+
+.darkMode .selectedItem {
+ background-color: #495057;
+ color: #fff;
+}
+
+.darkMode .buttonGroup {
+ border-top-color: #6c757d;
+}
+
+.darkMode .user__auto-complete {
+ color: #fff;
+ border-bottom-color: #6c757d;
+}
+
+.darkMode .user__auto-complete:hover {
+ background-color: #495057;
+}
+
+.darkMode .dropdown__user-perms {
+ background-color: #343a40;
+ border-color: #6c757d;
+}
+
+.darkMode .atomsContainer {
+ background-color: #343a40;
+ border-color: #6c757d;
+}
+
+.darkMode .atomItem:hover {
+ background-color: #495057;
+}
+
+.darkMode .selectedSummary {
+ background-color: #343a40;
+ border-left-color: #28a745;
+}
+
+.darkMode .selectedSummary strong {
+ color: #fff;
+}
+
+/* Responsive design */
+@media (max-width: 768px) {
+ .modal {
+ width: 95%;
+ margin: 10px;
+ }
+
+ .modalContent {
+ padding: 15px;
+ }
+
+ .buttonGroup {
+ flex-direction: column;
+ }
+
+ .submitButton,
+ .cancelButton {
+ width: 100%;
+ }
+}
diff --git a/src/components/EductionPortal/AssignAtomModal/index.jsx b/src/components/EductionPortal/AssignAtomModal/index.jsx
new file mode 100644
index 0000000000..3462a448fa
--- /dev/null
+++ b/src/components/EductionPortal/AssignAtomModal/index.jsx
@@ -0,0 +1 @@
+export { default } from './AssignAtomModal';
diff --git a/src/components/EductionPortal/AssignAtoms/AssignAtoms.jsx b/src/components/EductionPortal/AssignAtoms/AssignAtoms.jsx
new file mode 100644
index 0000000000..e09e0d960c
--- /dev/null
+++ b/src/components/EductionPortal/AssignAtoms/AssignAtoms.jsx
@@ -0,0 +1,50 @@
+import React from 'react';
+import { Container, Button } from 'reactstrap';
+import { useSelector, useDispatch } from 'react-redux';
+import styles from './AssignAtoms.module.css';
+import AssignAtomModal from '../AssignAtomModal';
+import { showModal } from '~/actions/educationPortal/atomActions';
+import NavigationBar from '../StudentDashboard/NavigationBar';
+
+const AssignAtoms = () => {
+ const dispatch = useDispatch();
+ const authUser = useSelector(state => state.auth.user);
+
+ // Handle opening assign atoms modal
+ const handleAssignAtoms = () => {
+ const studentId = authUser?._id || authUser?.id;
+ const studentName =
+ `${authUser?.firstName || ''} ${authUser?.lastName || ''}`.trim() || 'Student';
+ dispatch(showModal(studentId, studentName));
+ };
+
+ return (
+
+
+
+
+ {/* Header */}
+
+
+
+
Assign Atoms
+
Assign learning atoms to students
+
+
+
+
+ {/* Assign Atoms Button */}
+
+
+
+
+
+ {/* Assign Atoms Modal */}
+
+
+ );
+};
+
+export default AssignAtoms;
diff --git a/src/components/EductionPortal/AssignAtoms/AssignAtoms.module.css b/src/components/EductionPortal/AssignAtoms/AssignAtoms.module.css
new file mode 100644
index 0000000000..2567f84993
--- /dev/null
+++ b/src/components/EductionPortal/AssignAtoms/AssignAtoms.module.css
@@ -0,0 +1,58 @@
+.assignAtomsPage {
+ min-height: 100vh;
+ background-color: #f8f9fa;
+}
+
+.mainContainer {
+ padding: 2rem 1rem;
+ max-width: 1200px;
+}
+
+.header {
+ margin-bottom: 2rem;
+}
+
+.headerContent {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ flex-wrap: wrap;
+ gap: 1rem;
+}
+
+.headerText {
+ flex: 1;
+}
+
+.title {
+ font-size: 2rem;
+ font-weight: 600;
+ color: #2c3e50;
+ margin: 0 0 0.5rem 0;
+}
+
+.subtitle {
+ color: #6c757d;
+ margin: 0;
+ font-size: 1rem;
+}
+
+.buttonContainer {
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ margin-top: 3rem;
+}
+
+.assignAtomsButton {
+ padding: 0.75rem 2rem;
+ font-size: 1.125rem;
+ font-weight: 500;
+ border-radius: 0.375rem;
+ transition: all 0.2s;
+}
+
+.assignAtomsButton:hover {
+ transform: translateY(-1px);
+ box-shadow: 0 4px 12px rgba(0, 123, 255, 0.3);
+}
diff --git a/src/components/EductionPortal/StudentDashboard/StudentDashboard.jsx b/src/components/EductionPortal/StudentDashboard/StudentDashboard.jsx
index 21df510c50..ee306bfdb7 100644
--- a/src/components/EductionPortal/StudentDashboard/StudentDashboard.jsx
+++ b/src/components/EductionPortal/StudentDashboard/StudentDashboard.jsx
@@ -156,8 +156,12 @@ const StudentDashboard = () => {
{/* Header */}
-
Student Dashboard
-
Track your learning progress and manage your logs
+
+
+
Student Dashboard
+
Track your learning progress and manage your logs
+
+
{/* Summary Cards */}
diff --git a/src/constants/educationPortal/atom.js b/src/constants/educationPortal/atom.js
new file mode 100644
index 0000000000..57263e8540
--- /dev/null
+++ b/src/constants/educationPortal/atom.js
@@ -0,0 +1,19 @@
+// Atom Assignment Constants
+
+export const FETCH_AVAILABLE_ATOMS_START = 'FETCH_AVAILABLE_ATOMS_START';
+export const FETCH_AVAILABLE_ATOMS_SUCCESS = 'FETCH_AVAILABLE_ATOMS_SUCCESS';
+export const FETCH_AVAILABLE_ATOMS_ERROR = 'FETCH_AVAILABLE_ATOMS_ERROR';
+
+export const ASSIGN_ATOMS_START = 'ASSIGN_ATOMS_START';
+export const ASSIGN_ATOMS_SUCCESS = 'ASSIGN_ATOMS_SUCCESS';
+export const ASSIGN_ATOMS_ERROR = 'ASSIGN_ATOMS_ERROR';
+
+export const SELECT_ATOM = 'SELECT_ATOM';
+export const DESELECT_ATOM = 'DESELECT_ATOM';
+export const CLEAR_SELECTIONS = 'CLEAR_SELECTIONS';
+
+export const SET_NOTE = 'SET_NOTE';
+export const CLEAR_FORM = 'CLEAR_FORM';
+
+export const SHOW_MODAL = 'SHOW_MODAL';
+export const HIDE_MODAL = 'HIDE_MODAL';
diff --git a/src/reducers/educationPortal/atomReducer.js b/src/reducers/educationPortal/atomReducer.js
new file mode 100644
index 0000000000..7e2eb06c4b
--- /dev/null
+++ b/src/reducers/educationPortal/atomReducer.js
@@ -0,0 +1,145 @@
+import {
+ FETCH_AVAILABLE_ATOMS_START,
+ FETCH_AVAILABLE_ATOMS_SUCCESS,
+ FETCH_AVAILABLE_ATOMS_ERROR,
+ ASSIGN_ATOMS_START,
+ ASSIGN_ATOMS_SUCCESS,
+ ASSIGN_ATOMS_ERROR,
+ SELECT_ATOM,
+ DESELECT_ATOM,
+ CLEAR_SELECTIONS,
+ SET_NOTE,
+ CLEAR_FORM,
+ SHOW_MODAL,
+ HIDE_MODAL,
+} from '~/constants/educationPortal/atom';
+
+const initialState = {
+ // Data
+ availableAtoms: [],
+
+ // Selections
+ selectedAtoms: [],
+
+ // Form
+ note: '',
+
+ // UI State
+ isModalOpen: false,
+ studentId: null,
+ studentName: '',
+
+ // Loading states
+ isLoadingAtoms: false,
+ isSubmitting: false,
+
+ // Error states
+ atomsError: null,
+ submitError: null,
+};
+
+// eslint-disable-next-line default-param-last
+export const atomReducer = (state = initialState, action) => {
+ switch (action.type) {
+ // Fetch atoms
+ case FETCH_AVAILABLE_ATOMS_START:
+ return {
+ ...state,
+ isLoadingAtoms: true,
+ atomsError: null,
+ };
+ case FETCH_AVAILABLE_ATOMS_SUCCESS:
+ return {
+ ...state,
+ isLoadingAtoms: false,
+ availableAtoms: action.payload,
+ atomsError: null,
+ };
+ case FETCH_AVAILABLE_ATOMS_ERROR:
+ return {
+ ...state,
+ isLoadingAtoms: false,
+ atomsError: action.payload,
+ };
+
+ // Assignment
+ case ASSIGN_ATOMS_START:
+ return {
+ ...state,
+ isSubmitting: true,
+ submitError: null,
+ };
+ case ASSIGN_ATOMS_SUCCESS:
+ return {
+ ...state,
+ isSubmitting: false,
+ submitError: null,
+ // Clear form after successful submission
+ selectedAtoms: [],
+ note: '',
+ };
+ case ASSIGN_ATOMS_ERROR:
+ return {
+ ...state,
+ isSubmitting: false,
+ submitError: action.payload,
+ };
+
+ // Selection management
+ case SELECT_ATOM:
+ return {
+ ...state,
+ selectedAtoms: [...state.selectedAtoms, action.payload],
+ };
+ case DESELECT_ATOM:
+ return {
+ ...state,
+ selectedAtoms: state.selectedAtoms.filter(id => id !== action.payload),
+ };
+ case CLEAR_SELECTIONS:
+ return {
+ ...state,
+ selectedAtoms: [],
+ };
+
+ // Form management
+ case SET_NOTE:
+ return {
+ ...state,
+ note: action.payload,
+ };
+ case CLEAR_FORM:
+ return {
+ ...state,
+ selectedAtoms: [],
+ note: '',
+ submitError: null,
+ };
+
+ // Modal management
+ case SHOW_MODAL:
+ return {
+ ...state,
+ isModalOpen: true,
+ studentId: action.payload.studentId,
+ studentName: action.payload.studentName,
+ submitError: null,
+ };
+ case HIDE_MODAL:
+ return {
+ ...state,
+ isModalOpen: false,
+ studentId: null,
+ studentName: '',
+ // Clear form when closing modal
+ selectedAtoms: [],
+ note: '',
+ submitError: null,
+ };
+
+ default:
+ return state;
+ }
+};
+
+export default atomReducer;
diff --git a/src/reducers/index.js b/src/reducers/index.js
index 4e56483575..d7aa0caf14 100644
--- a/src/reducers/index.js
+++ b/src/reducers/index.js
@@ -100,6 +100,9 @@ import reviewsInsightReducer from './prAnalytics/reviewsInsightReducer';
// job analytics
import { hoursPledgedReducer } from './jobAnalytics/hoursPledgedReducer';
import { studentTasksReducer } from './studentTasksReducer';
+
+// education portal
+import { atomReducer } from './educationPortal/atomReducer';
import { weeklySummariesFiltersApi } from '../actions/weeklySummariesFilterAction';
//education portal
@@ -190,6 +193,9 @@ const localReducers = {
studentTasks: studentTasksReducer,
jobApplication: jobApplicationReducer,
+ // education portal
+ atom: atomReducer,
+
// education portal
browseLessonPlan: browseLessonPlanReducer,
};
diff --git a/src/routes.jsx b/src/routes.jsx
index 9d1b20cbf3..2ca6b3d8bb 100644
--- a/src/routes.jsx
+++ b/src/routes.jsx
@@ -180,6 +180,7 @@ import EPProtectedRoute from './components/common/EPDashboard/EPProtectedRoute';
import EPLogin from './components/EductionPortal/Login';
import BrowseLessonPlan from './components/EductionPortal/BrowseLessonPlan/BrowseLP';
import EPDashboard from './components/EductionPortal';
+import AssignAtoms from './components/EductionPortal/AssignAtoms/AssignAtoms';
import ReportDownloadButton from './components/EductionPortal/AnalyticsDashboard/ReportDownloadButton';
import GroupList from './components/EductionPortal/GroupList/GroupList';
import EvaluationResultsWrapper from './components/EductionPortal/EvaluationResultsWrapper';
@@ -838,6 +839,7 @@ export default (
+
diff --git a/src/utils/URL.js b/src/utils/URL.js
index fcbb75b925..3f63247a0d 100644
--- a/src/utils/URL.js
+++ b/src/utils/URL.js
@@ -515,6 +515,9 @@ export const ENDPOINTS = {
//pull requests analysis
PR_REVIEWS_INSIGHTS: `${APIEndpoint}/analytics/pr-review-insights`,
+ // Education Portal endpoints
+ EDUCATOR_ASSIGN_ATOMS: () => `${APIEndpoint}/educator/assign-atoms`,
+
LESSON_PLANS: `${APIEndpoint}/education/lesson-plans`,
SAVE_INTEREST: `${APIEndpoint}/education/student/saved-interests`,
GET_SAVED: `${APIEndpoint}/education/student/saved-interests`,