diff --git a/support-portal-backend/src/main/java/net/shyshkin/study/fullstack/supportportal/backend/controller/ProfessorResource.java b/support-portal-backend/src/main/java/net/shyshkin/study/fullstack/supportportal/backend/controller/ProfessorResource.java index 868265c..bafafb9 100644 --- a/support-portal-backend/src/main/java/net/shyshkin/study/fullstack/supportportal/backend/controller/ProfessorResource.java +++ b/support-portal-backend/src/main/java/net/shyshkin/study/fullstack/supportportal/backend/controller/ProfessorResource.java @@ -14,6 +14,7 @@ import org.springframework.web.bind.annotation.*; import org.springframework.web.multipart.MultipartFile; import javax.validation.Valid; +import java.util.List; import java.util.UUID; import static org.springframework.http.HttpStatus.OK; @@ -33,19 +34,19 @@ public class ProfessorResource { @PostMapping("register") public Professor register(@RequestBody Professor professor) { - return professorService.register(professor.getFirstName(), professor.getLastName(), professor.getEmail(), professor.getDepartment(), professor.getPosition()); + return professorService.register(professor.getFirstName(), professor.getLastName(), + professor.getEmail(), professor.getDepartment(), professor.getPosition()); } @PostMapping("add") - public ResponseEntity addNewProfessor(@Valid ProfessorDto professorDto) { + public ResponseEntity addNewProfessor(@Valid ProfessorDto professorDto) { log.debug("Professor DTO: {}", professorDto); Professor professor = professorService.addNewProfessor(professorDto); return ResponseEntity.ok(professor); } - @PutMapping("{professorId}") - public Professor updateProfessor(@PathVariable UUID professorId, @Valid ProfessorDto professorDto) { + public Professor updateProfessor(@PathVariable UUID professorId, @Valid ProfessorDto professorDto) { log.debug("Professor DTO: {}", professorDto); return professorService.updateProfessor(professorId, professorDto); } @@ -77,12 +78,14 @@ public class ProfessorResource { } @PutMapping("{professorId}/profile-image") - public Professor updateProfileImage(@PathVariable UUID professorId, @RequestParam MultipartFile profileImage) { + public Professor updateProfileImage(@PathVariable UUID professorId, + @RequestParam MultipartFile profileImage) { return professorService.updateProfileImage(professorId, profileImage); } @GetMapping(path = "{professorId}/profile-image/{filename}", produces = MediaType.IMAGE_JPEG_VALUE) - public byte[] getProfileImageByProfessorId(@PathVariable UUID professorId, @PathVariable String filename) { + public byte[] getProfileImageByProfessorId(@PathVariable UUID professorId, + @PathVariable String filename) { return professorService.getImageByProfessorId(professorId, filename); } @@ -90,4 +93,24 @@ public class ProfessorResource { public byte[] getDefaultProfileImage(@PathVariable UUID professorId) { return professorService.getDefaultProfileImage(professorId); } -} + + /** + * Bulk-update displayOrder. + * + * Accepts a JSON array of professor UUIDs in the desired display order. + * The array index becomes each professor's displayOrder value. + * + * Example request body: + * ["uuid-A", "uuid-B", "uuid-C"] + * + * Returns the full professor list sorted by the new displayOrder. + * + * Requires ADMIN or MANAGER role (configure in your SecurityConfig). + */ + @PutMapping("order") + public ResponseEntity> updateDisplayOrder(@RequestBody List orderedIds) { + log.info("Updating display order for {} professors", orderedIds.size()); + List updated = professorService.updateDisplayOrder(orderedIds); + return ResponseEntity.ok(updated); + } +} \ No newline at end of file diff --git a/support-portal-backend/src/main/java/net/shyshkin/study/fullstack/supportportal/backend/domain/Professor.java b/support-portal-backend/src/main/java/net/shyshkin/study/fullstack/supportportal/backend/domain/Professor.java index 24f586e..1ecf19a 100644 --- a/support-portal-backend/src/main/java/net/shyshkin/study/fullstack/supportportal/backend/domain/Professor.java +++ b/support-portal-backend/src/main/java/net/shyshkin/study/fullstack/supportportal/backend/domain/Professor.java @@ -2,8 +2,6 @@ package net.shyshkin.study.fullstack.supportportal.backend.domain; import com.fasterxml.jackson.annotation.JsonIgnore; import lombok.*; -import org.hibernate.annotations.Fetch; -import org.hibernate.annotations.FetchMode; import org.hibernate.annotations.Type; import javax.persistence.*; @@ -46,35 +44,40 @@ public class Professor implements Serializable { @Enumerated(EnumType.STRING) private ProfessorCategory category; + /** + * Controls display order within each category section on the public frontend. + * Lower values appear first. Defaults to 0 (new professors appear at the top). + * Admins can drag-and-drop rows in the management UI to reorder. + */ + @Column(nullable = false, columnDefinition = "INT DEFAULT 0") + @Builder.Default + private Integer displayOrder = 0; + // Additional fields for Next.js integration private String phone; private String specialty; - + @Column(columnDefinition = "TEXT") private String certification; - + @Column(columnDefinition = "TEXT") private String training; - + private String experience; - + @Column(columnDefinition = "TEXT") private String description; - + private String designation; - + @ElementCollection(fetch = FetchType.EAGER) @CollectionTable(name = "professor_work_days", joinColumns = @JoinColumn(name = "professor_id")) @Column(name = "work_day") private List workDays; - - // ✅ CRITICAL FIX: Added orphanRemoval = true - // This tells JPA to DELETE skills that are removed from the collection + @OneToMany(mappedBy = "professor", cascade = CascadeType.ALL, fetch = FetchType.EAGER, orphanRemoval = true) private Set skills; - - // ✅ CRITICAL FIX: Added orphanRemoval = true - // This tells JPA to DELETE awards that are removed from the collection + @OneToMany(mappedBy = "professor", cascade = CascadeType.ALL, fetch = FetchType.EAGER, orphanRemoval = true) private Set awards; @@ -82,7 +85,6 @@ public class Professor implements Serializable { @JsonIgnore private List posts; - // Convenience method to get full name public String getName() { return firstName + " " + lastName; } diff --git a/support-portal-backend/src/main/java/net/shyshkin/study/fullstack/supportportal/backend/repository/ProfessorRepository.java b/support-portal-backend/src/main/java/net/shyshkin/study/fullstack/supportportal/backend/repository/ProfessorRepository.java index cd055be..e30deca 100644 --- a/support-portal-backend/src/main/java/net/shyshkin/study/fullstack/supportportal/backend/repository/ProfessorRepository.java +++ b/support-portal-backend/src/main/java/net/shyshkin/study/fullstack/supportportal/backend/repository/ProfessorRepository.java @@ -3,9 +3,11 @@ package net.shyshkin.study.fullstack.supportportal.backend.repository; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Modifying; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; +import java.util.List; import java.util.Optional; import java.util.UUID; @@ -25,15 +27,35 @@ public interface ProfessorRepository extends JpaRepository { @Query("SELECT p FROM Professor p WHERE p.professorId = :professorId") Optional findByProfessorId(@Param("professorId") UUID professorId); - - @Query("SELECT p FROM Professor p WHERE p.status = :status") + + // ── Status / category filters, ordered by displayOrder then lastName ──────── + + @Query("SELECT p FROM Professor p WHERE p.status = :status ORDER BY p.displayOrder ASC, p.lastName ASC") Page findByStatus(@Param("status") WorkingStatus status, Pageable pageable); - - @Query("SELECT p FROM Professor p WHERE p.category = :category") + + @Query("SELECT p FROM Professor p WHERE p.category = :category ORDER BY p.displayOrder ASC, p.lastName ASC") Page findByCategory(@Param("category") ProfessorCategory category, Pageable pageable); - - @Query("SELECT p FROM Professor p WHERE p.status = :status AND p.category = :category") - Page findByStatusAndCategory(@Param("status") WorkingStatus status, - @Param("category") ProfessorCategory category, - Pageable pageable); + + @Query("SELECT p FROM Professor p WHERE p.status = :status AND p.category = :category ORDER BY p.displayOrder ASC, p.lastName ASC") + Page findByStatusAndCategory(@Param("status") WorkingStatus status, + @Param("category") ProfessorCategory category, + Pageable pageable); + + // ── Bulk display-order update ──────────────────────────────────────────────── + + /** + * Sets displayOrder for a single professor identified by professorId. + * Used by the bulk-reorder service method. + */ + @Modifying + @Query("UPDATE Professor p SET p.displayOrder = :displayOrder WHERE p.professorId = :professorId") + void updateDisplayOrder(@Param("professorId") UUID professorId, + @Param("displayOrder") int displayOrder); + + /** + * Returns all professors ordered by displayOrder ASC, then lastName ASC. + * Used by the admin reorder endpoint to return the updated list. + */ + @Query("SELECT p FROM Professor p ORDER BY p.displayOrder ASC, p.lastName ASC") + List findAllOrderedByDisplayOrder(); } \ No newline at end of file diff --git a/support-portal-backend/src/main/java/net/shyshkin/study/fullstack/supportportal/backend/service/ProfessorService.java b/support-portal-backend/src/main/java/net/shyshkin/study/fullstack/supportportal/backend/service/ProfessorService.java index 37d5d36..ec665f8 100644 --- a/support-portal-backend/src/main/java/net/shyshkin/study/fullstack/supportportal/backend/service/ProfessorService.java +++ b/support-portal-backend/src/main/java/net/shyshkin/study/fullstack/supportportal/backend/service/ProfessorService.java @@ -7,6 +7,7 @@ import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.web.multipart.MultipartFile; +import java.util.List; import java.util.UUID; public interface ProfessorService { @@ -30,15 +31,20 @@ public interface ProfessorService { byte[] getImageByProfessorId(UUID professorId, String filename); byte[] getDefaultProfileImage(UUID professorId); - - // Existing method for active professors + Page findActiveProfessors(Pageable pageable); - - // New methods for category-based filtering + Page findByCategory(ProfessorCategory category, Pageable pageable); - + Page findActiveProfessorsByCategory(ProfessorCategory category, Pageable pageable); - - // Method to find professor with details + Professor findProfessorWithDetailsById(UUID professorId); + + /** + * Bulk-update displayOrder for all professors. + * + * @param orderedIds Professor UUIDs in the desired display order (index 0 = first). + * @return All professors sorted by their new displayOrder. + */ + List updateDisplayOrder(List orderedIds); } \ No newline at end of file diff --git a/support-portal-backend/src/main/java/net/shyshkin/study/fullstack/supportportal/backend/service/ProfessorServiceImpl.java b/support-portal-backend/src/main/java/net/shyshkin/study/fullstack/supportportal/backend/service/ProfessorServiceImpl.java index 3383767..533562e 100644 --- a/support-portal-backend/src/main/java/net/shyshkin/study/fullstack/supportportal/backend/service/ProfessorServiceImpl.java +++ b/support-portal-backend/src/main/java/net/shyshkin/study/fullstack/supportportal/backend/service/ProfessorServiceImpl.java @@ -35,6 +35,7 @@ import java.util.List; import java.util.Set; import java.util.UUID; import java.util.stream.Collectors; +import java.util.stream.IntStream; import static net.shyshkin.study.fullstack.supportportal.backend.constant.FileConstant.*; import static org.springframework.http.MediaType.*; @@ -63,10 +64,6 @@ public class ProfessorServiceImpl implements ProfessorService { .build(); } - /** - * Parses ISO date string from frontend (e.g. "2026-04-22T23:17:58.831Z") - * into LocalDateTime. Falls back to now() if null/blank/invalid. - */ private LocalDateTime parseJoinDate(String joinDate) { if (joinDate == null || joinDate.isBlank()) { return LocalDateTime.now(); @@ -179,6 +176,12 @@ public class ProfessorServiceImpl implements ProfessorService { professor.setJoinDate(parseJoinDate(professorDto.getJoinDate())); professor.setProfileImageUrl(generateDefaultProfileImageUrl(professor.getProfessorId())); + // New professors get displayOrder = 0 by default (appear first). + // Admins can re-order via the drag-and-drop endpoint. + if (professor.getDisplayOrder() == null) { + professor.setDisplayOrder(0); + } + Professor savedProfessor = professorRepository.save(professor); if (professorDto.getSkills() != null && !professorDto.getSkills().isEmpty()) { @@ -238,17 +241,15 @@ public class ProfessorServiceImpl implements ProfessorService { professor.setDescription(professorDto.getDescription()); professor.setDesignation(professorDto.getDesignation()); professor.setWorkDays(professorDto.getWorkDays()); + // displayOrder is intentionally NOT updated here — only via the reorder endpoint. - // Parse joinDate string safely if (professorDto.getJoinDate() != null && !professorDto.getJoinDate().isBlank()) { professor.setJoinDate(parseJoinDate(professorDto.getJoinDate())); } final Professor professorRef = professor; - if (professor.getSkills() == null) { - professor.setSkills(new HashSet<>()); - } + if (professor.getSkills() == null) professor.setSkills(new HashSet<>()); professor.getSkills().clear(); if (professorDto.getSkills() != null && !professorDto.getSkills().isEmpty()) { @@ -263,9 +264,7 @@ public class ProfessorServiceImpl implements ProfessorService { professor.getSkills().addAll(newSkills); } - if (professor.getAwards() == null) { - professor.setAwards(new HashSet<>()); - } + if (professor.getAwards() == null) professor.setAwards(new HashSet<>()); professor.getAwards().clear(); if (professorDto.getAwards() != null && !professorDto.getAwards().isEmpty()) { @@ -329,4 +328,19 @@ public class ProfessorServiceImpl implements ProfessorService { var responseEntity = restTemplate.exchange(requestEntity, new ParameterizedTypeReference() {}); return responseEntity.getBody(); } + + /** + * Persists a new displayOrder for every professor in the given list. + * The list position (index) becomes the professor's displayOrder value. + * + * @param orderedIds UUIDs in desired display order; index 0 → displayOrder=0, etc. + * @return All professors sorted by their updated displayOrder. + */ + @Override + @Transactional + public List updateDisplayOrder(List orderedIds) { + IntStream.range(0, orderedIds.size()) + .forEach(i -> professorRepository.updateDisplayOrder(orderedIds.get(i), i)); + return professorRepository.findAllOrderedByDisplayOrder(); + } } \ No newline at end of file diff --git a/support-portal-frontend/src/app/component/professor/professor.component.css b/support-portal-frontend/src/app/component/professor/professor.component.css index 3f90d00..f23238c 100644 --- a/support-portal-frontend/src/app/component/professor/professor.component.css +++ b/support-portal-frontend/src/app/component/professor/professor.component.css @@ -71,6 +71,7 @@ display: flex; gap: 12px; align-items: center; + flex-wrap: wrap; } /* Search Box */ @@ -162,6 +163,109 @@ color: #3b82f6; } +/* ─── Reorder toolbar buttons ──────────────────────────────────────────────── */ + +.btn-reorder { + display: flex; + align-items: center; + gap: 8px; + padding: 10px 20px; + border-radius: 8px; + font-size: 14px; + font-weight: 500; + cursor: pointer; + transition: all 0.2s ease; + border: 1px solid #d1d5db; + background: white; + color: #374151; +} + +.btn-reorder:hover { + background: #f0f9ff; + border-color: #3b82f6; + color: #3b82f6; +} + +.btn-save-order { + display: flex; + align-items: center; + gap: 8px; + padding: 10px 20px; + border-radius: 8px; + font-size: 14px; + font-weight: 500; + cursor: pointer; + transition: all 0.2s ease; + border: none; + background: #16a34a; + color: white; +} + +.btn-save-order:hover:not(:disabled) { + background: #15803d; + transform: translateY(-1px); +} + +.btn-save-order:disabled { + opacity: 0.6; + cursor: not-allowed; +} + +.btn-cancel-reorder { + display: flex; + align-items: center; + gap: 8px; + padding: 10px 20px; + border-radius: 8px; + font-size: 14px; + font-weight: 500; + cursor: pointer; + transition: all 0.2s ease; + border: 1px solid #d1d5db; + background: white; + color: #6b7280; +} + +.btn-cancel-reorder:hover:not(:disabled) { + background: #fef2f2; + border-color: #dc2626; + color: #dc2626; +} + +.btn-cancel-reorder:disabled { + opacity: 0.5; + cursor: not-allowed; +} + +/* Instructional hint badge shown while in reorder mode */ +.reorder-hint-badge { + display: inline-flex; + align-items: center; + gap: 8px; + padding: 8px 14px; + background: #eff6ff; + color: #1d4ed8; + border: 1px solid #bfdbfe; + border-radius: 8px; + font-size: 13px; + font-weight: 500; +} + +/* Tiny spinner inside Save button */ +.spinner-sm { + display: inline-block; + width: 14px; + height: 14px; + border: 2px solid rgba(255, 255, 255, 0.4); + border-top-color: white; + border-radius: 50%; + animation: spin 0.7s linear infinite; +} + +@keyframes spin { + to { transform: rotate(360deg); } +} + /* Table Container */ .table-container { animation: fadeIn 0.3s ease; @@ -205,9 +309,16 @@ letter-spacing: 0.5px; } +/* Compact header columns for drag handle and order number */ +.professors-table thead th.th-handle, +.professors-table thead th.th-order { + padding: 16px 8px; + width: auto; +} + .professors-table tbody tr { border-bottom: 1px solid #f3f4f6; - transition: background 0.2s ease; + transition: background 0.15s ease; cursor: pointer; } @@ -226,6 +337,93 @@ vertical-align: middle; } +/* ─── Table in reorder mode ────────────────────────────────────────────────── */ + +/* Subtle amber top-border to signal reorder mode */ +.professors-table.reorder-active { + border-top: 3px solid #f59e0b; +} + +/* All rows become draggable */ +tr.reorder-row { + cursor: grab; + user-select: none; + transition: background-color 0.15s ease, opacity 0.15s ease; +} + +tr.reorder-row:active { + cursor: grabbing; +} + +/* Row being dragged — ghost placeholder */ +tr.row-dragging { + opacity: 0.35; + background-color: #fef9c3 !important; + outline: 2px dashed #f59e0b; +} + +/* Row that is the current drop target */ +tr.row-drag-over { + background-color: #dbeafe !important; + border-top: 3px solid #3b82f6 !important; +} + +/* Drag-handle cell */ +.drag-handle-cell { + width: 28px; + padding: 0 6px !important; + text-align: center; + color: #9ca3af; +} + +.drag-handle-icon { + font-size: 1.1rem; + cursor: grab; + transition: color 0.15s ease; +} + +.drag-handle-icon:hover { + color: #374151; +} + +/* Position-number cell */ +.order-number-cell { + width: 36px; + padding: 0 8px !important; + text-align: center; +} + +.order-badge { + display: inline-block; + min-width: 24px; + padding: 3px 7px; + background: #f3f4f6; + color: #6b7280; + border-radius: 4px; + font-size: 12px; + font-weight: 700; + text-align: center; +} + +/* Dim action buttons while in reorder mode */ +.actions-disabled { + opacity: 0.3; + pointer-events: none; +} + +/* Display order indicator in the view modal badges row */ +.order-indicator { + display: inline-flex; + align-items: center; + gap: 5px; + padding: 4px 10px; + background: #f3f4f6; + color: #6b7280; + border-radius: 6px; + font-size: 12px; + font-weight: 600; +} + /* Professor Avatar */ .professor-avatar { width: 40px; @@ -286,40 +484,13 @@ font-weight: 600; } -.badge-faculty { - background: #dbeafe; - color: #1e40af; -} - -.badge-support { - background: #e0e7ff; - color: #3730a3; -} - -.badge-trainee { - background: #fef3c7; - color: #92400e; -} - -.badge-resigned { - background: #f3f4f6; - color: #374151; -} - -.badge-guides { - background: #dbeafe; - color: #1e3a8a; -} - -.badge-friends { - background: #dcfce7; - color: #14532d; -} - -.badge-patrons { - background: #fae8ff; - color: #6b21a8; -} +.badge-faculty { background: #dbeafe; color: #1e40af; } +.badge-support { background: #e0e7ff; color: #3730a3; } +.badge-trainee { background: #fef3c7; color: #92400e; } +.badge-resigned { background: #f3f4f6; color: #374151; } +.badge-guides { background: #dbeafe; color: #1e3a8a; } +.badge-friends { background: #dcfce7; color: #14532d; } +.badge-patrons { background: #fae8ff; color: #6b21a8; } /* Status Badge */ .status-badge { @@ -332,20 +503,10 @@ font-weight: 600; } -.status-active { - background: #d1fae5; - color: #065f46; -} - -.status-leave { - background: #fef3c7; - color: #92400e; -} - -.status-retired { - background: #fee2e2; - color: #991b1b; -} +.status-active { background: #d1fae5; color: #065f46; } +.status-leave { background: #fef3c7; color: #92400e; } +.status-retired { background: #fee2e2; color: #991b1b; } +.status-inactive { background: #f3f4f6; color: #6b7280; } /* Action Buttons */ .action-buttons { @@ -422,6 +583,24 @@ margin: 0; } +/* Loading State */ +.loading-state { + padding: 60px 20px; + text-align: center; + color: #6b7280; +} + +.spinner-border-lg { + display: inline-block; + width: 40px; + height: 40px; + border: 4px solid #e5e7eb; + border-top-color: #3b82f6; + border-radius: 50%; + animation: spin 0.8s linear infinite; + margin-bottom: 12px; +} + /* Modal Styles */ .modal-overlay { position: fixed; @@ -454,14 +633,8 @@ } @keyframes slideUp { - from { - opacity: 0; - transform: translateY(20px); - } - to { - opacity: 1; - transform: translateY(0); - } + from { opacity: 0; transform: translateY(20px); } + to { opacity: 1; transform: translateY(0); } } .modal-header { @@ -564,6 +737,7 @@ gap: 8px; margin-bottom: 12px; flex-wrap: wrap; + align-items: center; } .info-grid { @@ -699,6 +873,25 @@ color: #9ca3af; } +.form-textarea { + width: 100%; + padding: 12px 16px; + border: 1px solid #d1d5db; + border-radius: 8px; + font-size: 14px; + color: #1a1a1a; + transition: all 0.2s ease; + background: white; + font-family: inherit; + resize: vertical; +} + +.form-textarea:focus { + outline: none; + border-color: #3b82f6; + box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1); +} + /* File Upload */ .file-upload-wrapper { position: relative; @@ -728,6 +921,11 @@ background: #f0f9ff; } +.file-label.disabled { + opacity: 0.5; + cursor: not-allowed; +} + .file-label i { font-size: 20px; color: #6b7280; @@ -738,7 +936,7 @@ color: #374151; } -/* Checkbox */ +/* Form Section */ .form-section { margin: 24px 0; padding: 20px; @@ -746,6 +944,7 @@ border-radius: 8px; } +/* Checkbox */ .checkbox-wrapper { display: flex; align-items: center; @@ -820,100 +1019,6 @@ color: #6b7280; } -/* Responsive Design */ -@media (max-width: 992px) { - .user-content { - margin-left: 0; - width: 100%; - padding: 24px; - } - - .sidebar { - transform: translateX(-100%); - transition: transform 0.3s ease; - } - - .sidebar.open { - transform: translateX(0); - } - - .user-header { - flex-direction: column; - align-items: flex-start; - } - - .header-actions { - width: 100%; - flex-wrap: wrap; - } - - .search-input { - width: 100%; - } -} - -@media (max-width: 768px) { - .user-content { - padding: 20px; - } - - .page-title { - font-size: 24px; - } - - .table-wrapper { - overflow-x: auto; - } - - .users-table { - min-width: 800px; - } - - .modal-container { - width: 95%; - } - - .modal-header, - .modal-body, - .modal-footer { - padding: 20px; - } - - .form-row { - grid-template-columns: 1fr; - } -} - -@media (max-width: 576px) { - .user-content { - padding: 16px; - } - - .header-actions { - flex-direction: column; - } - - .header-actions > * { - width: 100%; - } -} - -/* Print Styles */ -@media print { - .sidebar, - .header-actions, - .action-buttons { - display: none; - } - - .user-content { - margin-left: 0; - width: 100%; - } -} - -/* Professor-Specific Styles */ - /* Section Title */ .section-title { font-size: 16px; @@ -1090,7 +1195,7 @@ line-height: 1.5; } -/* Responsive Adjustments for Professor Component */ +/* Responsive Design */ @media (max-width: 992px) { .professor-content { margin-left: 0; @@ -1098,11 +1203,28 @@ padding: 24px; } + .sidebar { + transform: translateX(-100%); + transition: transform 0.3s ease; + } + + .sidebar.open { + transform: translateX(0); + } + .professor-header { flex-direction: column; align-items: flex-start; } + .header-actions { + width: 100%; + } + + .search-input { + width: 100%; + } + .award-fields { grid-template-columns: 1fr; } @@ -1117,10 +1239,32 @@ padding: 20px; } + .page-title { + font-size: 24px; + } + + .table-wrapper { + overflow-x: auto; + } + .professors-table { min-width: 900px; } + .modal-container { + width: 95%; + } + + .modal-header, + .modal-body, + .modal-footer { + padding: 20px; + } + + .form-row { + grid-template-columns: 1fr; + } + .work-days-grid { grid-template-columns: 1fr; } @@ -1132,6 +1276,11 @@ .detail-row { grid-template-columns: 1fr; } + + .reorder-hint-badge { + font-size: 12px; + padding: 6px 10px; + } } @media (max-width: 576px) { @@ -1139,6 +1288,15 @@ padding: 16px; } + .header-actions { + flex-direction: column; + } + + .header-actions > * { + width: 100%; + justify-content: center; + } + .professor-detail-header { flex-direction: column; text-align: center; @@ -1149,7 +1307,7 @@ } } -/* Print Styles for Professor */ +/* Print Styles */ @media print { .sidebar, .header-actions, diff --git a/support-portal-frontend/src/app/component/professor/professor.component.html b/support-portal-frontend/src/app/component/professor/professor.component.html index f85dcab..7a2b4d3 100644 --- a/support-portal-frontend/src/app/component/professor/professor.component.html +++ b/support-portal-frontend/src/app/component/professor/professor.component.html @@ -4,6 +4,7 @@
+
@@ -11,17 +12,49 @@

Manage faculty, support team, and trainee/fellow profiles

-
@@ -29,43 +62,80 @@
- +
+ + + + - + - + + - - + + + + + + + + + - - - - - - + +
#PhotoProfessor IDProfessor ID NameEmailEmail Department Category Status Actions
+
+ + + {{ i + 1 }} +
+ + + {{ professor?.professorId }} + + +
{{ professor?.firstName }} {{ professor?.lastName }} {{ professor?.specialty }}
+ + + + + + {{ professor?.department || 'N/A' }} - + + - + + - {{ professor?.status }} -
- -
@@ -111,6 +189,12 @@

No professors found

Try adjusting your search or add a new professor

+ + +
+
+

Loading professors…

+
@@ -121,7 +205,10 @@ - + +