Skip to content

Commit cb50a82

Browse files
authored
SUBMIT-699 EAO - Generating Invite URL for Eligible Entities (Frontend) (#759)
* Adds frontend page for proponent projects and invitation view * Fix linting * Fix unit tests * Update ProjectsTable imports
1 parent eb79621 commit cb50a82

17 files changed

Lines changed: 446 additions & 504 deletions

File tree

submit-api/src/submit_api/models/proponent.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ def get_proponent_by_id(cls, proponent_id, include_invitations=False, include_pr
6868
proponent_dict = {
6969
"id": proponent.id,
7070
"name": proponent.name,
71+
"status": proponent.status.value if proponent.status else None
7172
}
7273
if not include_invitations and not include_projects:
7374
return proponent_dict

submit-web/src/components/Shared/ContentBox/index.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ type ContentBoxProps = {
88
mainLabel: React.ReactNode;
99
topLabel?: string;
1010
bottomLabel?: string;
11+
statusChip?: React.ReactNode;
1112
children?: React.ReactNode;
1213
contentBoxVariant?: ContentBoxVariant;
1314
} & PaperProps;
@@ -16,6 +17,7 @@ export const ContentBox = ({
1617
mainLabel = "",
1718
topLabel,
1819
bottomLabel,
20+
statusChip,
1921
contentBoxVariant = "primary",
2022
...rest
2123
}: ContentBoxProps) => {
@@ -82,6 +84,7 @@ export const ContentBox = ({
8284
</Typography>
8385
</Stack>
8486
)}
87+
{statusChip}
8588
</Box>
8689
<Box
8790
sx={{

submit-web/src/components/Shared/DataTable/DataTable.tsx

Lines changed: 125 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import {
1010
TableProps,
1111
TableRow,
1212
LinearProgress,
13+
Checkbox,
1314
} from "@mui/material";
1415
import TableSortLabel from "@mui/material/TableSortLabel";
1516
import { useEffect, useMemo, useState, ReactNode, useCallback } from "react";
@@ -39,8 +40,12 @@ export type DataTableProps<T> = Readonly<{
3940
defaultSortKey?: string;
4041
defaultSortOrder?: "asc" | "desc";
4142
onSortChange?: (sortKey: string, sortOrder: "asc" | "desc") => void;
43+
paginated?: boolean;
4244
rowsPerPageOptions?: readonly number[];
4345
tableProps?: TableProps;
46+
selectable?: boolean;
47+
selected?: (string | number)[];
48+
onSelectionChange?: (selected: (string | number)[]) => void;
4449
}>;
4550

4651
export function DataTable<T>({
@@ -55,8 +60,12 @@ export function DataTable<T>({
5560
defaultSortKey,
5661
defaultSortOrder = "asc",
5762
onSortChange,
63+
paginated = true,
5864
rowsPerPageOptions = [10, 25],
5965
tableProps,
66+
selectable = false,
67+
selected = [],
68+
onSelectionChange,
6069
}: DataTableProps<T>) {
6170
const [page, setPage] = useState(DEFAULT_PAGE);
6271
const [rowsPerPage, setRowsPerPage] = useState(DEFAULT_ROWS_PER_PAGE);
@@ -117,6 +126,34 @@ export function DataTable<T>({
117126
return sortOrder === "asc" ? sorted : sorted.reverse();
118127
}, [data, sortKey, sortOrder, columns, sortable]);
119128

129+
const handleSelectAllClick = useCallback(
130+
(event: React.ChangeEvent<HTMLInputElement>) => {
131+
if (event.target.checked) {
132+
const newSelected = sortedData.map((row) => getRowId(row));
133+
onSelectionChange?.(newSelected);
134+
} else {
135+
onSelectionChange?.([]);
136+
}
137+
},
138+
[sortedData, getRowId, onSelectionChange],
139+
);
140+
141+
const handleRowClick = useCallback(
142+
(rowId: string | number) => {
143+
const selectedIndex = selected.indexOf(rowId);
144+
let newSelected: (string | number)[] = [];
145+
146+
if (selectedIndex === -1) {
147+
newSelected = [...selected, rowId];
148+
} else {
149+
newSelected = selected.filter((id) => id !== rowId);
150+
}
151+
152+
onSelectionChange?.(newSelected);
153+
},
154+
[selected, onSelectionChange],
155+
);
156+
120157
useEffect(() => {
121158
const totalPages = Math.max(1, Math.ceil(sortedData.length / rowsPerPage));
122159
if (sortedData.length > 0 && page >= totalPages) {
@@ -128,14 +165,25 @@ export function DataTable<T>({
128165
if (sortedData.length === 0) {
129166
return [];
130167
}
168+
if (!paginated) {
169+
return sortedData
170+
}
131171

132172
const startIndex = page * rowsPerPage;
133173
const endIndex = startIndex + rowsPerPage;
134174
return sortedData.slice(startIndex, endIndex);
135-
}, [sortedData, page, rowsPerPage]);
175+
}, [sortedData, page, rowsPerPage, paginated]);
136176

137177
const emptyRows = Math.max(0, rowsPerPage - paginatedData.length);
138178

179+
const isSelected = useCallback(
180+
(rowId: string | number) => selected.indexOf(rowId) !== -1,
181+
[selected],
182+
);
183+
184+
const numSelected = selected.length;
185+
const rowCount = sortedData.length;
186+
139187
return (
140188
<Box>
141189
<TableContainer>
@@ -145,6 +193,20 @@ export function DataTable<T>({
145193
>
146194
<TableHead>
147195
<TableRow>
196+
{selectable && (
197+
<SubmitTableHeadCell padding="checkbox">
198+
<Checkbox
199+
color="primary"
200+
indeterminate={numSelected > 0 && numSelected < rowCount}
201+
checked={rowCount > 0 && numSelected === rowCount}
202+
onChange={handleSelectAllClick}
203+
inputProps={{
204+
"aria-label": "select all",
205+
}}
206+
sx={{ p: 0, ml: 0.5 }}
207+
/>
208+
</SubmitTableHeadCell>
209+
)}
148210
{columns.map((column) => (
149211
<SubmitTableHeadCell
150212
key={column.id}
@@ -169,7 +231,7 @@ export function DataTable<T>({
169231
<TableBody key={`table-body-${page}-${rowsPerPage}`}>
170232
{isError && (
171233
<TableRow>
172-
<TableCell colSpan={columns.length} align="center">
234+
<TableCell colSpan={columns.length + (selectable ? 1 : 0)} align="center">
173235
{errorMessage}
174236
</TableCell>
175237
</TableRow>
@@ -179,7 +241,7 @@ export function DataTable<T>({
179241
<>
180242
<TableRow>
181243
<TableCell
182-
colSpan={columns.length}
244+
colSpan={columns.length + (selectable ? 1 : 0)}
183245
sx={{
184246
border: "none",
185247
}}
@@ -189,7 +251,7 @@ export function DataTable<T>({
189251
</TableCell>
190252
</TableRow>
191253
<TableRow>
192-
<TableCell colSpan={columns.length}>
254+
<TableCell colSpan={columns.length + (selectable ? 1 : 0)}>
193255
<LinearProgress />
194256
</TableCell>
195257
</TableRow>
@@ -198,46 +260,80 @@ export function DataTable<T>({
198260

199261
{!isLoading && !isError && paginatedData.length === 0 && (
200262
<TableRow>
201-
<TableCell colSpan={columns.length} align="center">
263+
<TableCell colSpan={columns.length + (selectable ? 1 : 0)} align="center">
202264
{emptyMessage}
203265
</TableCell>
204266
</TableRow>
205267
)}
206268

207269
{!isLoading &&
208270
!isError &&
209-
paginatedData.map((row) => (
210-
<TableRow key={getRowId(row)}>
211-
{columns.map((column) => {
212-
const cellContent =
213-
column.renderCell?.(row) ?? column.getValue?.(row) ?? null;
214-
return (
215-
<PlainTableCell key={column.id}>
216-
{cellContent}
271+
paginatedData.map((row) => {
272+
const rowId = getRowId(row);
273+
const isItemSelected = isSelected(rowId);
274+
275+
return (
276+
<TableRow
277+
key={rowId}
278+
hover={selectable}
279+
onClick={() => selectable && handleRowClick(rowId)}
280+
role={selectable ? "checkbox" : undefined}
281+
aria-checked={selectable ? isItemSelected : undefined}
282+
selected={isItemSelected}
283+
sx={{
284+
cursor: selectable ? "pointer" : "default",
285+
"&.Mui-selected": {
286+
backgroundColor: "transparent",
287+
},
288+
"&.Mui-selected:hover": {
289+
backgroundColor: "rgba(0, 0, 0, 0.04)",
290+
},
291+
}}
292+
>
293+
{selectable && (
294+
<PlainTableCell padding="checkbox">
295+
<Checkbox
296+
color="primary"
297+
checked={isItemSelected}
298+
inputProps={{
299+
"aria-labelledby": `checkbox-${rowId}`,
300+
}}
301+
sx={{ p: 0 }}
302+
/>
217303
</PlainTableCell>
218-
);
219-
})}
220-
</TableRow>
221-
))}
304+
)}
305+
{columns.map((column) => {
306+
const cellContent =
307+
column.renderCell?.(row) ?? column.getValue?.(row) ?? null;
308+
return (
309+
<PlainTableCell key={column.id}>
310+
{cellContent}
311+
</PlainTableCell>
312+
);
313+
})}
314+
</TableRow>
315+
);
316+
})}
222317

223-
{!isLoading && !isError && emptyRows > 0 && (
318+
{paginated && !isLoading && !isError && emptyRows > 0 && (
224319
<TableRow style={{ height: ROW_HEIGHT * emptyRows }}>
225-
<TableCell colSpan={columns.length} sx={{ border: "none" }} />
320+
<TableCell colSpan={columns.length + (selectable ? 1 : 0)} sx={{ border: "none" }} />
226321
</TableRow>
227322
)}
228323
</TableBody>
229324
</Table>
230325
</TableContainer>
231-
<TablePagination
232-
component="div"
233-
count={sortedData.length}
234-
page={page}
235-
rowsPerPage={rowsPerPage}
236-
onPageChange={handleChangePage}
237-
onRowsPerPageChange={handleChangeRowsPerPage}
238-
rowsPerPageOptions={rowsPerPageOptions}
239-
/>
326+
{paginated && (
327+
<TablePagination
328+
component="div"
329+
count={sortedData.length}
330+
page={page}
331+
rowsPerPage={rowsPerPage}
332+
onPageChange={handleChangePage}
333+
onRowsPerPageChange={handleChangeRowsPerPage}
334+
rowsPerPageOptions={rowsPerPageOptions}
335+
/>
336+
)}
240337
</Box>
241338
);
242339
}
243-

submit-web/src/components/Shared/Text/BarTitle.tsx

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,15 +14,19 @@ export default function BarTitle({ title }: { title: string }) {
1414
export function BarBlueTitle({
1515
title,
1616
fullWidth,
17+
tooltip,
1718
bold = true,
19+
variant = "h4",
1820
}: {
1921
title: string;
2022
fullWidth?: boolean;
23+
tooltip?: React.ReactNode;
2124
bold?: boolean;
25+
variant?: "h1" | "h2" | "h3" | "h4" | "h5" | "h6";
2226
}) {
2327
return (
2428
<Typography
25-
variant="h4"
29+
variant={variant}
2630
color={BCDesignTokens.themeBlue100}
2731
sx={{
2832
mt: BCDesignTokens.layoutMarginSmall,
@@ -31,7 +35,10 @@ export function BarBlueTitle({
3135
fontWeight: bold ? "bold" : "normal",
3236
}}
3337
>
34-
{title}
38+
<>
39+
{title}
40+
{tooltip}
41+
</>
3542
</Typography>
3643
);
3744
}

submit-web/src/components/UserManagement/staff/EntityTable/EntityTableBody.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ export const EntityTableBody = ({
2525

2626
const handleRowClick = (id: number) => {
2727
navigate({
28-
to: `/staff/invitations/entities/${id}`,
28+
to: `/staff/proponents/${id}`,
2929
});
3030
};
3131

0 commit comments

Comments
 (0)