Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ import ProtectedRoute from "./router/ProtectedRoute";
import { ROLE } from "./utils/interfaces";
import AssignReviewer from "./pages/Assignments/AssignReviewer";
import StudentTasks from "./pages/StudentTasks/StudentTasks";
import StudentTaskDetail from "pages/StudentTasks/StudentTaskDetail";
import StudentTeams from "./pages/Student Teams/StudentTeamView";
import StudentTeamView from "./pages/Student Teams/StudentTeamView";
import NewTeammateAdvertisement from './pages/Student Teams/NewTeammateAdvertisement';
Expand Down Expand Up @@ -75,7 +76,12 @@ function App() {
path: "edit-questionnaire",
element: <ProtectedRoute element={<Questionnaire />} />,
},

{
path: "student_task_detail/:id",
element: (
<ProtectedRoute element={<StudentTaskDetail />} leastPrivilegeRole={ROLE.STUDENT} />
),
},
{
path: "assignments/edit/:id",
element: <AssignmentEditor mode="update" />,
Expand Down
146 changes: 70 additions & 76 deletions src/components/Table/Table.tsx
Original file line number Diff line number Diff line change
@@ -1,53 +1,58 @@
import {
ColumnDef,
ColumnFiltersState,
ExpandedState,
flexRender,
getCoreRowModel,
getExpandedRowModel,
getFilteredRowModel,
getPaginationRowModel,
getSortedRowModel,
SortingState,
useReactTable,
ExpandedState,
getExpandedRowModel,
} from "@tanstack/react-table";
import React, { useEffect, useMemo, useRef, useState } from "react";
import { Table as BTable, Col, Container, Row } from "react-bootstrap";
import { BsChevronDown, BsChevronRight } from "react-icons/bs";
import { FaSearch } from "react-icons/fa";
import { Col, Container, Row, Table as BTable } from "react-bootstrap";
import { BsChevronRight, BsChevronDown } from "react-icons/bs";
import ColumnFilter from "./ColumnFilter";
import GlobalFilter from "./GlobalFilter";
import Pagination from "./Pagination";
import RowSelectCheckBox from "./RowSelectCheckBox";
import { FaSearch } from "react-icons/fa";
import ToolTip from "components/ToolTip";

interface TableProps {
data: Record<string, any>[];
columns: ColumnDef<any, any>[];
disableGlobalFilter?: boolean;
showGlobalFilter?: boolean;
showColumnFilter?: boolean;
showPagination?: boolean;
tableSize?: { span: number; offset: number };
columnVisibility?: Record<string, boolean>;
onSelectionChange?: (selectedData: Record<any, any>[]) => void;
onRowClick?: (row: any) => void;
renderSubComponent?: (props: { row: any }) => React.ReactNode;
getRowCanExpand?: (row: any) => boolean;
disableGlobalFilter?: boolean;
// Maps column header text to a tooltip string shown as an info icon in the header.
headingComments?: Record<string, string>;
// When true, passes fluid to Container so the table spans the full viewport width.
fluid?: boolean;
}

const Table: React.FC<TableProps> = ({
data: initialData,
columns,
disableGlobalFilter = false,
showGlobalFilter = false,
showColumnFilter = true,
showPagination = true,
onSelectionChange,
onRowClick,
columnVisibility = {},
tableSize = { span: 12, offset: 0 },
renderSubComponent,
getRowCanExpand,
disableGlobalFilter = false, // Disable the Global Search
headingComments = {},
fluid = false,
}) => {
const [rowSelection, setRowSelection] = useState({});
const [sorting, setSorting] = useState<SortingState>([]);
Expand Down Expand Up @@ -82,7 +87,6 @@ const Table: React.FC<TableProps> = ({
</button>
);
},
size: 40,
enableSorting: false,
enableColumnFilter: false,
};
Expand Down Expand Up @@ -110,7 +114,6 @@ const Table: React.FC<TableProps> = ({
}}
/>
),
size: 40,
enableSorting: false,
enableFilter: false,
},
Expand Down Expand Up @@ -145,12 +148,6 @@ const Table: React.FC<TableProps> = ({
getExpandedRowModel: getExpandedRowModel(),
});


//Enable search filters for columns based on page size
const totalItems = initialData.length;
const pageSize = table.getState().pagination.pageSize;
const shouldShowColumnFilters = showColumnFilter && totalItems > pageSize;

const flatRows = table.getSelectedRowModel().flatRows;

useEffect(() => {
Expand All @@ -175,76 +172,72 @@ const Table: React.FC<TableProps> = ({

return (
<>
{!disableGlobalFilter && (
<Container>
<Row className="mb-md-2" style={{ flex: 1 }}>
<Col md={{ span: 12 }}>
{isGlobalFilterVisible && (
<GlobalFilter filterValue={globalFilter} setFilterValue={setGlobalFilter} />
)}
</Col>
{/*<span style={{ marginLeft: "5px" }} onClick={toggleGlobalFilter}>
<Container fluid={fluid}>
<Row className="mb-md-2">
<Col md={{ span: 12 }}>
{isGlobalFilterVisible && (
<GlobalFilter filterValue={globalFilter} setFilterValue={setGlobalFilter} />
)}
</Col>
{!disableGlobalFilter && (
<span style={{ marginLeft: "5px" }} onClick={toggleGlobalFilter}>
<FaSearch style={{ cursor: "pointer" }} />
{isGlobalFilterVisible ? " Hide" : " Show"}
</span>*/}
</Row>
</Container>
)}
<Container>
<Row style={{ flex: 1 }}>
</span>
)}
Comment on lines +183 to +187

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Use a semantic button for the global-search toggle.

The clickable <span> is not keyboard accessible, so keyboard-only users cannot toggle global search.

Proposed fix
-          {!disableGlobalFilter && (
-            <span style={{ marginLeft: "5px" }} onClick={toggleGlobalFilter}>
-              <FaSearch style={{ cursor: "pointer" }} />
-              {isGlobalFilterVisible ? " Hide" : " Show"}
-            </span>
-          )}
+          {!disableGlobalFilter && (
+            <button
+              type="button"
+              className="btn btn-link p-0 ms-1"
+              onClick={toggleGlobalFilter}
+              aria-expanded={isGlobalFilterVisible}
+              aria-label={`${isGlobalFilterVisible ? "Hide" : "Show"} global search`}
+            >
+              <FaSearch aria-hidden="true" />
+              {isGlobalFilterVisible ? " Hide" : " Show"}
+            </button>
+          )}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/Table/Table.tsx` around lines 183 - 187, Replace the `<span>`
element containing the global filter toggle with a semantic `<button>` element.
The button should maintain the same onClick handler (toggleGlobalFilter), style
attribute with marginLeft, and the conditional text render logic with the
FaSearch icon. This will make the toggle keyboard accessible while preserving
the existing functionality.

</Row>
</Container>
<Container fluid={fluid}>
<Row>
<Col md={tableSize}>
<BTable striped hover responsive size="sm" className="custom-table-layout">
<BTable striped hover responsive size="sm">
<thead className="table-secondary">
{table.getHeaderGroups().map((headerGroup) => (
<tr key={headerGroup.id}>
{headerGroup.headers.map((header) => (
<th key={header.id} colSpan={header.colSpan} style={{ width: `${header.getSize()}px` }}>
{header.isPlaceholder ? null : (
<>
<div
{...{
className: header.column.getCanSort()
? "cursor-pointer select-none"
: "",
onClick: header.column.getToggleSortingHandler(),
}}
>
{flexRender(header.column.columnDef.header, header.getContext())}
{{
asc: " 🔼",
desc: " 🔽",
}[header.column.getIsSorted() as string] ?? null}
</div>
{shouldShowColumnFilters && header.column.getCanFilter() ? (
<ColumnFilter column={header.column} />
) : null}
</>
)}
</th>
))}
{headerGroup.headers.map((header) => {
// Add info icon to Heading if comment exists.
const comment = headingComments[header.column.columnDef.header as string];
return (
<th key={header.id} colSpan={header.colSpan}>
{header.isPlaceholder ? null : (
<>
<div
{...{
className: header.column.getCanSort()
? "cursor-pointer select-none"
: "",
onClick: header.column.getToggleSortingHandler(),
}}
>
{flexRender(header.column.columnDef.header, header.getContext())}
{comment && <ToolTip id="" info={comment} />}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Pass a unique tooltip id per header cell.

Using id="" for every tooltip duplicates ids and can break tooltip/ARIA associations.

Proposed fix
-                                {comment && <ToolTip id="" info={comment} />}
+                                {comment && <ToolTip id={header.id} info={comment} />}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
{comment && <ToolTip id="" info={comment} />}
{comment && <ToolTip id={header.id} info={comment} />}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/Table/Table.tsx` at line 213, The ToolTip component on line
213 is being rendered with an empty id attribute, which causes duplicate ids and
breaks tooltip/ARIA associations. Generate a unique identifier for each header
cell (based on the column key, index, or header name) and pass this unique value
to the ToolTip's id prop instead of the empty string, ensuring each tooltip
instance has a distinct id.

{{
asc: " 🔼",
desc: " 🔽",
}[header.column.getIsSorted() as string] ?? null}
</div>
{/* Previously hidden when data fit a single page; now always driven by the showColumnFilter prop. */}
{showColumnFilter && header.column.getCanFilter() ? (
<ColumnFilter column={header.column} />
) : null}
</>
)}
</th>
);
})}
</tr>
))}
</thead>
<tbody>
{table.getRowModel().rows.map((row) => (
<React.Fragment key={row.id}>
<tr
className={row.original.isSelected ? 'selected-topic-row' : ''}
onClick={() => onRowClick?.(row.original)}
style={{
cursor: onRowClick ? 'pointer' : 'default',
backgroundColor: row.original.isSelected ? '#fff3cd' : undefined
}}
>
{row.getVisibleCells().map((cell) => {
const selected = !!row.original.isSelected;
return (
<td key={cell.id} style={selected ? { backgroundColor: '#fff3cd' } : undefined}>
{flexRender(cell.column.columnDef.cell, cell.getContext())}
</td>
);
})}
</tr>
<tr>
{row.getVisibleCells().map((cell) => (
<td key={cell.id}>
{flexRender(cell.column.columnDef.cell, cell.getContext())}
</td>
))}
</tr>
{row.getIsExpanded() && renderSubComponent && (
<tr>
<td colSpan={row.getVisibleCells().length}>
Expand All @@ -266,7 +259,8 @@ const Table: React.FC<TableProps> = ({
setPageSize={table.setPageSize}
getPageCount={table.getPageCount}
getState={table.getState}
totalItems={initialData.length}
// Use pre-pagination row count so the total reflects active filters, not raw data length.
totalItems={table.getPrePaginationRowModel().rows.length}
/>
)}
</Col>
Expand Down
21 changes: 21 additions & 0 deletions src/css-modules.d.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,25 @@
/**
* Type declarations for CSS Modules.
*
* Vite processes any file named *.module.css or *.module.scss as a CSS Module,
* hashing all local class names so they are scoped to the importing component.
* These declarations tell TypeScript that `import styles from "*.module.css/scss"`
* returns a plain object mapping original class names to their hashed equivalents,
* e.g. styles.tbl_heat → "tbl_heat_a1b2c3".
*
* Classes that must remain globally named (used as plain strings by components that
* don't import the module) should be wrapped in `:global { }` inside the SCSS file.
*
* Note: do NOT add `declare module "*.scss"` or `declare module "*.css"` here —
* those blanket declarations would allow side-effect imports of non-module files
* without type checking and mask missing-file errors.
*/
declare module "*.module.css" {
const classes: { readonly [key: string]: string };
export default classes;
}

declare module "*.module.scss" {
const classes: { readonly [key: string]: string };
export default classes;
}
3 changes: 1 addition & 2 deletions src/pages/ReviewTableau/ScoreWidgets.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import React from 'react';
import { getColorClass } from '../ViewTeamGrades/utils';
import { getColorClass } from '../ViewTeamGrades/heatgridUtils';

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== Locate class definitions =="
rg -n --type=scss -C2 '\.(grade-circle|underlined|circle-container|weight-circle|circle|tick|c0|c1|c2|c3|c4|c5|cf)\b'

echo
echo "== Locate SCSS imports on ReviewTableau path =="
rg -n --type=tsx -C3 'import .*\.s?css' src/pages/ReviewTableau

echo
echo "== Locate module/global style imports that could provide these classes =="
rg -n --type=tsx -C3 'ViewTeamGrades.*(module\.scss|grades\.scss)' src

echo
echo "== Confirm ScoreWidgets class usage =="
rg -n --type=tsx -C2 'grade-circle|underlined|circle-container|weight-circle|tick|getColorClass' src/pages/ReviewTableau/ScoreWidgets.tsx

Repository: expertiza/reimplementation-front-end

Length of output: 158


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== Find SCSS files in the repo =="
fd -e scss | head -20

echo
echo "== Find CSS files in the repo =="
fd -e css | head -20

echo
echo "== Check what's exported from heatgridUtils =="
cat -n src/pages/ViewTeamGrades/heatgridUtils.ts 2>/dev/null | head -50

echo
echo "== Check imports and class usage in ScoreWidgets =="
cat -n src/pages/ReviewTableau/ScoreWidgets.tsx

Repository: expertiza/reimplementation-front-end

Length of output: 6306


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== Search for getColorClass definition in heatgridUtils =="
rg -n 'getColorClass' src/pages/ViewTeamGrades/heatgridUtils.ts

echo
echo "== Check full heatgridUtils file =="
wc -l src/pages/ViewTeamGrades/heatgridUtils.ts
tail -100 src/pages/ViewTeamGrades/heatgridUtils.ts

echo
echo "== Search for getColorClass anywhere in the codebase =="
rg -n 'getColorClass' src

echo
echo "== Search for grade-circle class definition =="
rg -n 'grade-circle' src --type=scss
rg -n 'grade-circle' src --type=css

echo
echo "== Check ViewTeamGrades.module.scss =="
cat -n src/pages/ViewTeamGrades/ViewTeamGrades.module.scss

echo
echo "== Check ReviewTableau.scss =="
cat -n src/pages/ReviewTableau/ReviewTableau.scss

echo
echo "== Check if ReviewTableau imports the SCSS =="
rg -n 'import' src/pages/ReviewTableau/ReviewTableau.tsx | head -20

Repository: expertiza/reimplementation-front-end

Length of output: 5437


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== Full ViewTeamGrades.module.scss =="
cat -n src/pages/ViewTeamGrades/ViewTeamGrades.module.scss

echo
echo "== Full ReviewTableau.scss =="
cat -n src/pages/ReviewTableau/ReviewTableau.scss

echo
echo "== Search for color classes c1-c5, cf =="
rg -n '\bc[0-5]\b|cf' src/pages/ViewTeamGrades/ViewTeamGrades.module.scss

echo
echo "== Search for grade-circle, underlined, weight-circle, circle, tick =="
rg -n 'grade-circle|underlined|weight-circle|^.*circle|^.*tick' src/pages/ViewTeamGrades/ViewTeamGrades.module.scss

echo
echo "== Check ReviewTableau.tsx imports =="
cat -n src/pages/ReviewTableau/ReviewTableau.tsx | head -50

Repository: expertiza/reimplementation-front-end

Length of output: 20932


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== Verify if ReviewTableau.tsx imports ViewTeamGrades styles =="
rg -n 'ViewTeamGrades' src/pages/ReviewTableau/ReviewTableau.tsx

echo
echo "== Check if there's a global stylesheet import that loads ViewTeamGrades styles =="
rg -n 'ViewTeamGrades.module.scss|import.*scss' src/pages/ReviewTableau

echo
echo "== Check main App or index files for global style imports =="
rg -n 'ViewTeamGrades.module.scss' src

echo
echo "== Check if custom.scss (global stylesheet) imports ViewTeamGrades styles =="
cat -n src/custom.scss | head -50

echo
echo "== Check App.tsx for stylesheet imports =="
fd -n 'App.tsx' -x cat -n {} | head -30

Repository: expertiza/reimplementation-front-end

Length of output: 144


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== Full custom.scss content =="
cat -n src/custom.scss

echo
echo "== Find and check index files =="
fd -n 'index.tsx\|index.ts' src | head -5 | xargs -I {} sh -c 'echo "=== {} ===" && head -30 {}'

echo
echo "== Check main App.tsx or entry point =="
rg -n 'import.*module.scss|import.*custom.scss|import.*ViewTeamGrades' src/index.tsx src/App.tsx 2>/dev/null || echo "No matches found"

echo
echo "== List all SCSS/CSS imports at repository entry point =="
grep -n 'import.*\.\(scss\|css\)' src/index.tsx src/App.tsx 2>/dev/null || echo "No direct stylesheet imports"

echo
echo "== Confirm which files import ReviewTableau.scss =="
rg -n 'ReviewTableau.scss' src

Repository: expertiza/reimplementation-front-end

Length of output: 5186


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== Check index.tsx/index.ts entry points =="
find src -maxdepth 1 -name 'index.*' -exec cat -n {} \;

echo
echo "== Check App.tsx for stylesheet imports =="
find src -maxdepth 1 -name 'App.tsx' -exec cat -n {} \;

echo
echo "== Verify ReviewTableau.scss is actually imported in ReviewTableau.tsx =="
grep -n 'ReviewTableau.scss' src/pages/ReviewTableau/ReviewTableau.tsx

echo
echo "== Check if ViewTeamGrades.module.scss is globally imported anywhere =="
rg 'ViewTeamGrades.module.scss' src

echo
echo "== Confirm module.scss scoping - check if styles in ReviewTableau.tsx use :global =="
rg -A5 -B5 'grade-circle|underlined|circle-container|weight-circle|tick|c[0-5]' src/pages/ReviewTableau/ReviewTableau.scss

Repository: expertiza/reimplementation-front-end

Length of output: 23781


Add ViewTeamGrades.module.scss import to ReviewTableau.tsx or ScoreWidgets.tsx.

ScoreWidgets renders CSS classes (grade-circle, underlined, circle-container, weight-circle, circle, tick, and color classes c0c5, cf) that are defined in src/pages/ViewTeamGrades/ViewTeamGrades.module.scss. However, this stylesheet is not imported on the ReviewTableau path, so these styles won't be available at runtime. Either import the stylesheet in ReviewTableau.tsx or move the style definitions to a shared location.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/pages/ReviewTableau/ScoreWidgets.tsx` at line 2, Import the
ViewTeamGrades.module.scss stylesheet in ScoreWidgets.tsx to ensure that the CSS
classes used by the ScoreWidgets component (grade-circle, underlined,
circle-container, weight-circle, circle, tick, and color classes c0 through c5
and cf) are available at runtime when rendered on the ReviewTableau path.

import { ScoreWidgetProps } from '../../types/reviewTableau';
import '../ViewTeamGrades/grades.scss';

/**
* Reusable circular score widget that matches the design used in ViewTeamGrades
Expand Down
Loading