-
Notifications
You must be signed in to change notification settings - Fork 15
Fix/filtering UI #418
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: staged
Are you sure you want to change the base?
Fix/filtering UI #418
Conversation
…ndStaff component
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
This pull request refactors the faculty and staff filtering UI by extracting filter logic into reusable client-side components and introducing a mobile-optimized filter panel. The changes improve code organization and enhance the mobile user experience.
Key Changes:
- Extracted department and designation filter logic from
page.tsxinto new reusable client components (DepartmentsClient,DesignationsClient,MobileFilters) - Introduced a full-screen slide-in mobile filter panel with backdrop and scroll locking
- Replaced inline mobile filter implementations with the new modular components
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 9 comments.
| File | Description |
|---|---|
| app/[locale]/faculty-and-staff/client-components.tsx | Added three new client components for mobile filters, department filtering, and designation filtering with improved UI features like "View more" functionality |
| app/[locale]/faculty-and-staff/page.tsx | Updated to use new client components, added department data fetching for mobile filters, and contains large blocks of commented-out code that should be removed |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| import { FaFilter, FaSlidersH, FaTimes, FaChevronLeft, FaChevronRight } from 'react-icons/fa'; | ||
| import { | ||
| Select, | ||
| SelectContent, | ||
| SelectItem, |
Copilot
AI
Nov 26, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unused imports detected. The icons FaFilter, FaSlidersH, FaChevronLeft, and FaChevronRight are imported but never used in the component. Only FaTimes is actually used (line 114). Additionally, SelectItem is imported but never used. Remove these unused imports to improve code clarity and reduce bundle size.
| import { FaFilter, FaSlidersH, FaTimes, FaChevronLeft, FaChevronRight } from 'react-icons/fa'; | |
| import { | |
| Select, | |
| SelectContent, | |
| SelectItem, | |
| import { FaTimes } from 'react-icons/fa'; | |
| import { | |
| Select, | |
| SelectContent, |
| )} | ||
| > | ||
| <MdFilterList className="text-primary-700 text-xl"/> | ||
| <span className='text-primary-700'>Filters</span> |
Copilot
AI
Nov 26, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Inconsistent quote style. This line uses single quotes for the className string, while line 72 uses double quotes. For consistency with the rest of the file, use double quotes here as well.
| <span className='text-primary-700'>Filters</span> | |
| <span className="text-primary-700">Filters</span> |
| <div > | ||
| <div className=" bg-white p-5"> | ||
| <ScrollArea className="h-[calc(100vh-200px)]"> | ||
| <div className="rounded-lg "> |
Copilot
AI
Nov 26, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Empty div wrapper without purpose. Line 103 contains an empty <div> tag that wraps the content but doesn't add any classes or attributes. This adds unnecessary nesting to the DOM. Consider removing it or adding necessary attributes if it serves a purpose.
| <div > | |
| <div className=" bg-white p-5"> | |
| <ScrollArea className="h-[calc(100vh-200px)]"> | |
| <div className="rounded-lg "> | |
| <div className=" bg-white p-5"> | |
| <ScrollArea className="h-[calc(100vh-200px)]"> | |
| <div className="rounded-lg "> |
| <ol className="w-full space-y-4"> | ||
| {/* Constrain the list height and allow internal scrolling */} | ||
| <div | ||
| className={cn( | ||
| 'overflow-y-auto', | ||
| showAll ? 'max-h-[calc(100vh-200px)]' : 'max-h-[280px]' | ||
| )} | ||
| > | ||
| {visible.map(({ name, urlName }, index) => ( | ||
| <li key={index}> | ||
| <PreserveParamsLink | ||
| paramToUpdate="department" | ||
| value={getUpdatedDepartments(urlName)} | ||
| className={cn( | ||
| 'flex w-full items-center rounded border p-3', | ||
| selectedDepartments.includes(urlName) | ||
| ? 'bg-primary-50 border-primary-700' | ||
| : 'border-neutral-300' | ||
| )} | ||
| > | ||
| <div className="flex w-full items-center"> | ||
| <div className="mr-2"> | ||
| <input | ||
| type="checkbox" | ||
| id={`department-${urlName}`} | ||
| className="h-4 w-4 rounded border-neutral-300 text-primary-700 focus:ring-primary-700" | ||
| checked={selectedDepartments.includes(urlName)} | ||
| readOnly | ||
| /> | ||
| </div> | ||
| <span className="font-semibold text-shade-dark">{name}</span> | ||
| </div> | ||
| </PreserveParamsLink> | ||
| </li> | ||
| ))} | ||
| </div> |
Copilot
AI
Nov 26, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Incorrect semantic structure. The <ol> element on line 272 wraps a <div> on line 274, but the list items (<li>) are inside the div. This creates invalid HTML since a <div> cannot be a direct child of <ol>. The div should be removed and the overflow/max-height styles should be applied directly to the <ol> element, or the structure should be reorganized.
| <ol className="w-full space-y-4"> | |
| {/* Constrain the list height and allow internal scrolling */} | |
| <div | |
| className={cn( | |
| 'overflow-y-auto', | |
| showAll ? 'max-h-[calc(100vh-200px)]' : 'max-h-[280px]' | |
| )} | |
| > | |
| {visible.map(({ name, urlName }, index) => ( | |
| <li key={index}> | |
| <PreserveParamsLink | |
| paramToUpdate="department" | |
| value={getUpdatedDepartments(urlName)} | |
| className={cn( | |
| 'flex w-full items-center rounded border p-3', | |
| selectedDepartments.includes(urlName) | |
| ? 'bg-primary-50 border-primary-700' | |
| : 'border-neutral-300' | |
| )} | |
| > | |
| <div className="flex w-full items-center"> | |
| <div className="mr-2"> | |
| <input | |
| type="checkbox" | |
| id={`department-${urlName}`} | |
| className="h-4 w-4 rounded border-neutral-300 text-primary-700 focus:ring-primary-700" | |
| checked={selectedDepartments.includes(urlName)} | |
| readOnly | |
| /> | |
| </div> | |
| <span className="font-semibold text-shade-dark">{name}</span> | |
| </div> | |
| </PreserveParamsLink> | |
| </li> | |
| ))} | |
| </div> | |
| <ol | |
| className={cn( | |
| 'w-full space-y-4 overflow-y-auto', | |
| showAll ? 'max-h-[calc(100vh-200px)]' : 'max-h-[280px]' | |
| )} | |
| > | |
| {/* Constrain the list height and allow internal scrolling */} | |
| {visible.map(({ name, urlName }, index) => ( | |
| <li key={index}> | |
| <PreserveParamsLink | |
| paramToUpdate="department" | |
| value={getUpdatedDepartments(urlName)} | |
| className={cn( | |
| 'flex w-full items-center rounded border p-3', | |
| selectedDepartments.includes(urlName) | |
| ? 'bg-primary-50 border-primary-700' | |
| : 'border-neutral-300' | |
| )} | |
| > | |
| <div className="flex w-full items-center"> | |
| <div className="mr-2"> | |
| <input | |
| type="checkbox" | |
| id={`department-${urlName}`} | |
| className="h-4 w-4 rounded border-neutral-300 text-primary-700 focus:ring-primary-700" | |
| checked={selectedDepartments.includes(urlName)} | |
| readOnly | |
| /> | |
| </div> | |
| <span className="font-semibold text-shade-dark">{name}</span> | |
| </div> | |
| </PreserveParamsLink> | |
| </li> | |
| ))} |
| <button | ||
| onClick={() => setOpen(false)} | ||
| aria-label="Close filters" | ||
| className=" hover:bg-black/5" | ||
| > | ||
| <FaTimes className="text-primary-700 size-7" /> | ||
| </button> |
Copilot
AI
Nov 26, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing accessibility label. The close button for the mobile filter panel should have an aria-label attribute to provide a descriptive label for screen readers. While line 111 has aria-label="Close filters", this button is missing additional context. Consider adding title="Close filters" as well for tooltip support.
| aria-controls="mobile-filters-panel" | ||
| onClick={() => setOpen((s) => !s)} | ||
| className={cn( | ||
| 'flex items-center gap-2 px-3 py-2 rounded border border-primary-100 bg-neutral-50 ', |
Copilot
AI
Nov 26, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing spacing in className. There's a trailing space in the className string that should be removed. Change 'flex items-center gap-2 px-3 py-2 rounded border border-primary-100 bg-neutral-50 ' to 'flex items-center gap-2 px-3 py-2 rounded border border-primary-100 bg-neutral-50'.
| 'flex items-center gap-2 px-3 py-2 rounded border border-primary-100 bg-neutral-50 ', | |
| 'flex items-center gap-2 px-3 py-2 rounded border border-primary-100 bg-neutral-50', |
| SelectValue, | ||
| } from '~/components/inputs'; | ||
| import { cn } from '~/lib/utils'; | ||
| import { MdFilterList } from "react-icons/md"; |
Copilot
AI
Nov 26, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Inconsistent quote style. This line uses double quotes for the import, while most other imports in the file use single quotes. For consistency with the rest of the file (lines 1-15), use single quotes here as well.
| import { MdFilterList } from "react-icons/md"; | |
| import { MdFilterList } from 'react-icons/md'; |
| // const selectedDepartments = Array.isArray(department) | ||
| // ? department | ||
| // : department | ||
| // ? [department] | ||
| // : []; | ||
|
|
||
| // Define the updated department value based on selection | ||
| const getUpdatedDepartments = (urlName: string) => { | ||
| return selectedDepartments.includes(urlName) | ||
| ? selectedDepartments.filter((d) => d !== urlName) | ||
| : [...selectedDepartments, urlName]; | ||
| }; | ||
| // // Define the updated department value based on selection | ||
| // const getUpdatedDepartments = (urlName: string) => { | ||
| // return selectedDepartments.includes(urlName) | ||
| // ? selectedDepartments.filter((d) => d !== urlName) | ||
| // : [...selectedDepartments, urlName]; | ||
| // }; | ||
|
|
||
| return select ? ( | ||
| <Select navigate> | ||
| <SelectTrigger className="px-4 py-5 sm:w-1/2 lg:w-1/3 xl:hidden"> | ||
| <SelectValue | ||
| placeholder={ | ||
| selectedDepartments.length | ||
| ? `${selectedDepartments.length} selected` | ||
| : 'Choose a department' | ||
| } | ||
| /> | ||
| </SelectTrigger> | ||
| <SelectContent> | ||
| {departments.map(({ name, urlName }, index) => ( | ||
| <div key={index} className="flex items-center px-2 py-1"> | ||
| <input | ||
| type="checkbox" | ||
| id={`mobile-department-${urlName}`} | ||
| className="h-4 w-4 rounded border-neutral-300 text-primary-700 focus:ring-primary-700" | ||
| checked={selectedDepartments.includes(urlName)} | ||
| readOnly | ||
| /> | ||
| <PreserveParamsLink | ||
| paramToUpdate="department" | ||
| value={getUpdatedDepartments(urlName)} | ||
| className="ml-2 w-full py-1" | ||
| > | ||
| {name} | ||
| </PreserveParamsLink> | ||
| </div> | ||
| ))} | ||
| </SelectContent> | ||
| </Select> | ||
| ) : ( | ||
| <ol className="w-full space-y-4"> | ||
| {departments.map(({ name, urlName }, index) => ( | ||
| <li key={index}> | ||
| <PreserveParamsLink | ||
| paramToUpdate="department" | ||
| value={getUpdatedDepartments(urlName)} | ||
| className={cn( | ||
| 'flex w-full items-center rounded border p-3', | ||
| selectedDepartments.includes(urlName) | ||
| ? 'bg-primary-50 border-primary-700' | ||
| : 'border-neutral-300' | ||
| )} | ||
| > | ||
| <div className="flex w-full items-center"> | ||
| <div className="mr-2"> | ||
| <input | ||
| type="checkbox" | ||
| id={`department-${urlName}`} | ||
| className="h-4 w-4 rounded border-neutral-300 text-primary-700 focus:ring-primary-700" | ||
| checked={selectedDepartments.includes(urlName)} | ||
| readOnly | ||
| /> | ||
| </div> | ||
| <span className="font-semibold text-shade-dark">{name}</span> | ||
| </div> | ||
| </PreserveParamsLink> | ||
| </li> | ||
| ))} | ||
| </ol> | ||
| // return select ? ( | ||
| // <Select navigate> | ||
| // <SelectTrigger className="px-4 py-5 sm:w-1/2 lg:w-1/3 xl:hidden"> | ||
| // <SelectValue | ||
| // placeholder={ | ||
| // selectedDepartments.length | ||
| // ? `${selectedDepartments.length} selected` | ||
| // : 'Choose a department' | ||
| // } | ||
| // /> | ||
| // </SelectTrigger> | ||
| // <SelectContent> | ||
| // {departments.map(({ name, urlName }, index) => ( | ||
| // <div key={index} className="flex items-center px-2 py-1"> | ||
| // <input | ||
| // type="checkbox" | ||
| // id={`mobile-department-${urlName}`} | ||
| // className="h-4 w-4 rounded border-neutral-300 text-primary-700 focus:ring-primary-700" | ||
| // checked={selectedDepartments.includes(urlName)} | ||
| // readOnly | ||
| // /> | ||
| // <PreserveParamsLink | ||
| // paramToUpdate="department" | ||
| // value={getUpdatedDepartments(urlName)} | ||
| // className="ml-2 w-full py-1" | ||
| // > | ||
| // {name} | ||
| // </PreserveParamsLink> | ||
| // </div> | ||
| // ))} | ||
| // </SelectContent> | ||
| // </Select> | ||
| // ) : ( | ||
| // <ol className="w-full space-y-4"> | ||
| // {departments.map(({ name, urlName }, index) => ( | ||
| // <li key={index}> | ||
| // <PreserveParamsLink | ||
| // paramToUpdate="department" | ||
| // value={getUpdatedDepartments(urlName)} | ||
| // className={cn( | ||
| // 'flex w-full items-center rounded border p-3', | ||
| // selectedDepartments.includes(urlName) | ||
| // ? 'bg-primary-50 border-primary-700' | ||
| // : 'border-neutral-300' | ||
| // )} | ||
| // > | ||
| // <div className="flex w-full items-center"> | ||
| // <div className="mr-2"> | ||
| // <input | ||
| // type="checkbox" | ||
| // id={`department-${urlName}`} | ||
| // className="h-4 w-4 rounded border-neutral-300 text-primary-700 focus:ring-primary-700" | ||
| // checked={selectedDepartments.includes(urlName)} | ||
| // readOnly | ||
| // /> | ||
| // </div> | ||
| // <span className="font-semibold text-shade-dark">{name}</span> | ||
| // </div> | ||
| // </PreserveParamsLink> | ||
| // </li> | ||
| // ))} | ||
| // </ol> | ||
| // ); |
Copilot
AI
Nov 26, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Large block of commented-out code. Lines 231-306 contain a significant amount of commented-out code that has been moved to the client component. This dead code should be removed to improve code maintainability and clarity. If this code needs to be preserved for reference, it should be documented in version control history instead.
| interface Dept { | ||
| id: number; | ||
| name: string; | ||
| urlName: string; | ||
| } | ||
|
|
Copilot
AI
Nov 26, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Duplicate interface definition. The Dept interface is defined twice in this file (lines 20-24 and lines 202-206). This duplication can lead to maintenance issues if the interface needs to be updated. Consider defining it once at the top of the file and reusing it throughout.
| interface Dept { | |
| id: number; | |
| name: string; | |
| urlName: string; | |
| } |
This pull request refactors the faculty and staff filtering UI to improve code organization, reusability, and mobile user experience. The main changes include extracting filter components into reusable client-side components, introducing a new mobile filter panel, and updating the main page to use these components for both desktop and mobile layouts.
Component extraction and UI refactor:
page.tsxinto new reusable components (DepartmentsClient,DesignationsClient, andMobileFilters) inclient-components.tsx, simplifying and centralizing filter UI code. (app/[locale]/faculty-and-staff/client-components.tsxR4-R145, app/[locale]/faculty-and-staff/client-components.tsxR201-R363)page.tsxwith the newDepartmentsClientcomponent, removing duplicated logic and improving maintainability. (app/[locale]/faculty-and-staff/page.tsxL224-L298)Mobile filter improvements:
MobileFilterscomponent for mobile devices, providing a slide-in filter panel with designation and department filters, and handling scroll locking and outside click to close. (app/[locale]/faculty-and-staff/client-components.tsxR4-R145)MobileFiltersfor mobile filter UI, replacing previous mobile-specific filter implementations. (app/[locale]/faculty-and-staff/page.tsxL76-L94)General code and UI improvements:
page.tsxto use the new filter components. (app/[locale]/faculty-and-staff/page.tsxL23-R28)page.tsxto support the new mobile filter panel. (app/[locale]/faculty-and-staff/page.tsxR43-R46)These changes make the filter UI more modular, maintainable, and user-friendly, especially for mobile users.