@@ -63,7 +89,7 @@ const TeamResults = () => {
setSearchText(e.target.value);
}}
/>
-
+
{loading
diff --git a/web-ui/src/components/team-results/TeamSummaryCard.jsx b/web-ui/src/components/team-results/TeamSummaryCard.jsx
index 06ff4ec5c8..18a2269d97 100644
--- a/web-ui/src/components/team-results/TeamSummaryCard.jsx
+++ b/web-ui/src/components/team-results/TeamSummaryCard.jsx
@@ -55,10 +55,9 @@ const propTypes = {
const displayName = 'TeamSummaryCard';
-const TeamSummaryCard = ({ team, index }) => {
+const TeamSummaryCard = ({ team, index, onTeamSelect, selectedTeamId }) => {
const { state, dispatch } = useContext(AppContext);
const { teams, userProfile, csrf } = state;
- const [open, setOpen] = useState(false);
const [openDelete, setOpenDelete] = useState(false);
const [tooltipIsOpen, setTooltipIsOpen] = useState(false);
@@ -79,10 +78,8 @@ const TeamSummaryCard = ({ team, index }) => {
? false
: leads.some(lead => lead.memberId === userProfile.memberProfile.id);
- const handleOpen = () => setOpen(true);
const handleOpenDeleteConfirmation = () => setOpenDelete(true);
- const handleClose = () => setOpen(false);
const handleCloseDeleteConfirmation = () => setOpenDelete(false);
const teamId = team?.id;
@@ -113,7 +110,7 @@ const TeamSummaryCard = ({ team, index }) => {
const handleAction = (e, index) => {
if (index === 0) {
- handleOpen();
+ onTeamSelect(team.id);
} else if (index === 1) {
handleOpenDeleteConfirmation();
}
@@ -216,8 +213,8 @@ const TeamSummaryCard = ({ team, index }) => {
onTeamSelect('')}
onSave={async editedTeam => {
const res = await updateTeam(editedTeam, csrf);
const data =
diff --git a/web-ui/src/components/team-results/TeamsActions.jsx b/web-ui/src/components/team-results/TeamsActions.jsx
index 8b21be5650..8db7309d9f 100644
--- a/web-ui/src/components/team-results/TeamsActions.jsx
+++ b/web-ui/src/components/team-results/TeamsActions.jsx
@@ -12,24 +12,18 @@ import './TeamResults.css';
const displayName = 'TeamsActions';
-const TeamsActions = () => {
+const TeamsActions = ({ isOpen, onOpen }) => {
const { state, dispatch } = useContext(AppContext);
- const [open, setOpen] = useState(false);
-
const { csrf } = state;
- const handleOpen = () => setOpen(true);
-
- const handleClose = () => setOpen(false);
-
return (
-
} onClick={handleOpen}>
+
} onClick={() => onOpen(true)}>
Add Team
onOpen(false)}
onSave={async team => {
if (csrf) {
let res = await createTeam(team, csrf);
diff --git a/web-ui/src/helpers/query-parameters.js b/web-ui/src/helpers/query-parameters.js
new file mode 100644
index 0000000000..920add31be
--- /dev/null
+++ b/web-ui/src/helpers/query-parameters.js
@@ -0,0 +1,66 @@
+import { useEffect } from 'react';
+
+/**
+ * @typedef {object} QPBoolean
+ * @property {string} name
+ * @property {boolean} default
+ * @property {boolean} value
+ * @property {(boolean) => void} setter - takes query parameter value and updates state
+ * @property {[(any) => string]} toQP - takes state value and returns query parameter value
+ */
+
+/**
+ * @typedef {object} QPString
+ * @property {string} name
+ * @property {string} default
+ * @property {string} value
+ * @property {(string) => void} setter - takes query parameter value and updates state
+ * @property {[(any) => string]} toQP - takes state value and returns query parameter value
+ */
+
+/**
+ * @param {(QPBoolean | QPString)[]} qps - query parameters
+ */
+export const useQueryParameters = qps => {
+ useEffect(() => {
+ const url = new URL(location.href);
+ const params = url.searchParams;
+ for (const qp of qps) {
+ let v = params.get(qp.name);
+ if (typeof qp.default === 'boolean') {
+ qp.setter(v ? v === 'true' : qp.default);
+ } else {
+ if (v && Array.isArray(qp.default)) v = v.split(',');
+ qp.setter(v || qp.default);
+ }
+ }
+ }, []);
+
+ const dependencies = qps.map(qp => qp.value);
+
+ useEffect(() => {
+ const url = new URL(location.href);
+ let newUrl = url.origin + url.pathname;
+ const params = {};
+
+ // Add query parameters listed in qps that do not have their default value.
+ for (const qp of qps) {
+ let { toQP, value } = qp;
+ if (toQP) value = toQP(value);
+ if (value && !compare(value, qp.default)) params[qp.name] = value;
+ }
+
+ // Add query parameters that are not listed in qps.
+ for (const [k, v] of url.searchParams) {
+ if (!qps.some(qp => qp.name === k)) params[k] = v;
+ }
+
+ if (Object.keys(params).length) {
+ newUrl += '?' + new URLSearchParams(params).toString();
+ }
+ history.replaceState(params, '', newUrl);
+ }, dependencies);
+};
+
+const compare = (a, b) => stringValue(a) === stringValue(b);
+const stringValue = v => (Array.isArray(v) ? v.sort().join(',') : v);
diff --git a/web-ui/src/pages/PermissionsPage.jsx b/web-ui/src/pages/PermissionsPage.jsx
index 0733ce45cc..5ae6ca6305 100644
--- a/web-ui/src/pages/PermissionsPage.jsx
+++ b/web-ui/src/pages/PermissionsPage.jsx
@@ -19,6 +19,7 @@ import {
selectRoles,
selectHasPermissionAssignmentPermission
} from '../context/selectors';
+import { useQueryParameters } from '../helpers/query-parameters';
import './PermissionsPage.css';
@@ -72,21 +73,20 @@ const EditPermissionsPage = () => {
const [rolePermissions, setRolePermissions] = useState([]);
const [refresh, setRefresh] = useState(true);
- useEffect(() => {
- const url = new URL(location.href);
- const roleName = url.searchParams.get('role');
- const role = roles.find(r => r.role === roleName);
- setSelectedRole(role || roles[0]);
- }, []);
-
- useEffect(() => {
- if (!selectedRole) return;
- const url = new URL(location.href);
- const params = { role: selectedRole.role };
- const q = new URLSearchParams(params).toString();
- const newUrl = url.origin + url.pathname + '?' + q;
- history.replaceState(params, '', newUrl);
- }, [selectedRole]);
+ useQueryParameters([
+ {
+ name: 'role',
+ default: roles[0],
+ value: selectedRole,
+ setter(value) {
+ const role = roles.find(r => r.role === value);
+ setSelectedRole(role);
+ },
+ toQP(selectedRole) {
+ return selectedRole?.role ?? '';
+ }
+ }
+ ]);
useEffect(() => {
const getRolePermissions = async () => {
diff --git a/web-ui/src/pages/ViewFeedbackPage.jsx b/web-ui/src/pages/ViewFeedbackPage.jsx
index 004a5872c0..0be9a0567f 100644
--- a/web-ui/src/pages/ViewFeedbackPage.jsx
+++ b/web-ui/src/pages/ViewFeedbackPage.jsx
@@ -33,6 +33,7 @@ import {
} from '../context/selectors';
import { getFeedbackTemplate } from '../api/feedbacktemplate';
import SkeletonLoader from '../components/skeleton_loader/SkeletonLoader';
+import { useQueryParameters } from '../helpers/query-parameters';
const PREFIX = 'ViewFeedbackPage';
const classes = {
@@ -107,6 +108,33 @@ const ViewFeedbackPage = () => {
const [dateRange, setDateRange] = useState(DateRange.THREE_MONTHS);
const [includeAll, setIncludeAll] = useState(false);
+ useQueryParameters([
+ {
+ name: 'dates',
+ default: DateRange.THREE_MONTHS,
+ value: dateRange,
+ setter: setDateRange
+ },
+ {
+ name: 'search',
+ default: '',
+ value: searchText,
+ setter: setSearchText
+ },
+ {
+ name: 'showAll',
+ default: false,
+ value: includeAll,
+ setter: setIncludeAll
+ },
+ {
+ name: 'sort',
+ default: SortOption.SENT_DATE,
+ value: sortValue,
+ setter: setSortValue
+ }
+ ]);
+
useEffect(() => {
if (currentMembers && currentMembers.length > 0) {
isAdmin && includeAll
@@ -380,6 +408,7 @@ const ViewFeedbackPage = () => {
)
}}
+ value={searchText}
/>
{
size="small"
label="Show requests sent within"
onChange={e => setDateRange(e.target.value)}
- defaultValue={DateRange.THREE_MONTHS}
+ value={dateRange}
variant="outlined"
>
@@ -398,7 +427,6 @@ const ViewFeedbackPage = () => {
-
{
size="small"
label="Sort by"
onChange={e => setSortValue(e.target.value)}
- defaultValue={SortOption.SENT_DATE}
+ value={sortValue}
variant="outlined"
>