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
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import NumberPopup from "./NumberPopup";
import BooleanPopup from "./BooleanPopup";
import VisibilityPopup from "./VisibilityPopup";
import StatusPopup from "./StatusPopup";
import VersionPopup from "./VersionPopup";
import { useTheme } from "react-jss";

const useStyles = createUseStyles(theme => ({
Expand Down Expand Up @@ -107,7 +108,8 @@ const ProjectTableColumnHeader = ({
setSort,
setCheckedProjectIds,
setSelectAllChecked,
droOptions
droOptions,
calculations
}) => {
const theme = useTheme();
const classes = useStyles(theme);
Expand All @@ -133,7 +135,8 @@ const ProjectTableColumnHeader = ({
header.popupType === "text" ||
header.popupType === "string" ||
header.popupType === "number" ||
header.popupType === "stringList"
header.popupType === "stringList" ||
header.popupType === "version"
) {
const listPropertyName = propertyName + "List";
const listValue = criteria[listPropertyName];
Expand Down Expand Up @@ -356,6 +359,21 @@ const ProjectTableColumnHeader = ({
setCheckedProjectIds={setCheckedProjectIds}
setSelectAllChecked={setSelectAllChecked}
/>
) : header.popupType === "version" ? (
<VersionPopup
close={() => handlePopoverToggle(false)}
header={header}
criteria={criteria}
setCriteria={setCriteria}
order={order}
orderBy={orderBy}
setSort={setSort}
setCheckedProjectIds={setCheckedProjectIds}
setSelectAllChecked={setSelectAllChecked}
projects={projects}
filter={filter}
calculations={calculations}
/>
) : null}
</div>
}
Expand Down
319 changes: 319 additions & 0 deletions client/src/components/Projects/ColumnHeaderPopups/VersionPopup.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,319 @@
import React, { useState, useContext } from "react";
import PropTypes from "prop-types";
import Button from "../../Button/Button";
import RadioButton from "../../UI/RadioButton";
import "react-datepicker/dist/react-datepicker.css";
import { MdClose } from "react-icons/md";
import { MdOutlineSearch } from "react-icons/md";
import { createUseStyles } from "react-jss";
import ToggleCheckbox from "components/UI/ToggleCheckbox";
import { selectAllCheckboxes } from "helpers/util";

const useStyles = createUseStyles(theme => ({
container: {
display: "flex",
flexDirection: "column",
maxWidth: "25rem",
color: theme.colors.secondary.darkNavy
},
searchBarWrapper: {
width: "100%",
position: "relative",
alignSelf: "center",
marginBottom: "0.5rem"
},
searchBar: {
maxWidth: "100%",
width: "100%",
padding: "12px 12px 12px 12px",
boxSizing: "border-box"
// marginRight: "0.5rem"
},
searchIcon: {
position: "absolute",
right: "16px",
top: "14px"
},
listItem: {
display: "flex",
flexDirection: "row",
alignItems: "center",
height: "2rem",
gap: "0.2em",
"&:hover": {
backgroundColor: "lightblue"
},
"& span": {
maxWidth: "25ch",
whiteSpace: "nowrap",
overflow: "hidden",
textOverflow: "ellipsis"
}
},
toggleButton: {
marginRight: "0",
marginTop: "4px",
marginBottom: "4px",
backgroundColor: "transparent",
border: "0",
cursor: "pointer",
textDecoration: "underline",
display: "flex",
fontWeight: "normal",
color: theme.colors.secondary.darkNavy
}
}));

const VersionPopup = ({
projects,
filter,
close,
header,
criteria,
setCriteria,
order,
orderBy,
setSort,
setCheckedProjectIds,
setSelectAllChecked,
calculations
}) => {
const property = header.id;

const classes = useStyles();

const [newOrder, setNewOrder] = useState(
header.id !== orderBy ? null : order
);
const [selectedListItems, setSelectedListItems] = useState(
(criteria[header.id + "List"] || []).map(s => ({
value: s,
label: s
}))
);
const [searchString, setSearchString] = useState("");

const initiallyChecked = o => criteria[header.id + "List"].includes(o);

const listCriteria = { ...criteria, [header.id + "List"]: [] };
const filteredProjects = projects.filter(p => filter(p, listCriteria));

const getValue = p => {
if (property === "calculationId") {
return calculations?.[p.calculationId]?.version ?? "Beta";
}
return p[property];
};

let filteredOptions;

const compareVersions = (a, b) => {
const aChecked = initiallyChecked(a);
const bChecked = initiallyChecked(b);

// Keep checked items at the top
if (aChecked !== bChecked) {
return bChecked - aChecked;
}

// Beta is newest → always FIRST
if (a === "Beta" && b === "Beta") return 0;
if (a === "Beta") return -1;
if (b === "Beta") return 1;

const aParts = String(a).split(".").map(Number);
const bParts = String(b).split(".").map(Number);

const maxLength = Math.max(aParts.length, bParts.length);

for (let i = 0; i < maxLength; i++) {
const aPart = aParts[i] ?? 0;
const bPart = bParts[i] ?? 0;

if (aPart !== bPart) {
return bPart - aPart;
}
}

return 0;
};

filteredOptions = [...new Set(filteredProjects.map(getValue))]
.filter(value => value !== null && value !== "")
.filter(value => value.toLowerCase().includes(searchString.toLowerCase()))
.sort(compareVersions);

const onChangeSearchString = e => {
setSearchString(e.target.value);
};

const handleCheckboxChange = e => {
const optionValue = e.target.name;
if (!e.target.checked) {
const newSelectedListItems = selectedListItems.filter(
selectedOption => selectedOption.value !== optionValue
);
setSelectedListItems(newSelectedListItems);
} else {
const newSelectedListItems = [
...selectedListItems,
{ value: optionValue, label: optionValue }
];
setSelectedListItems(newSelectedListItems);
}
};

const isChecked = optionValue => {
const checked = selectedListItems.find(
option => option.value === optionValue
);
return !!checked;
};

const applyChanges = () => {
let selectedValues = selectedListItems.map(sli => sli.value);

setCriteria({
...criteria,
[header.id + "List"]: selectedValues
});

if (newOrder) {
setSort(header.id, newOrder);
}
if (setCheckedProjectIds) setCheckedProjectIds([]);
if (setSelectAllChecked) setSelectAllChecked(false);
close();
};

const setDefault = () => {
setNewOrder(null);
setSelectedListItems([]);
if (setCheckedProjectIds) setCheckedProjectIds([]);
if (setSelectAllChecked) setSelectAllChecked(false);
};

return (
<div className={classes.container}>
<div style={{ display: "flex", justifyContent: "flex-end" }}>
<MdClose
style={{
backgroundColor: "transparent",
color: "black",
position: "absolute",
top: "0.5rem",
right: "0.5rem"
}}
alt={`Close popup`}
onClick={close}
/>
</div>
<div style={{ display: "flex", flexDirection: "column" }}>
<RadioButton
label="Sort Newest to Oldest"
value="asc"
checked={newOrder === "asc"}
onChange={() => setNewOrder("asc")}
/>
<RadioButton
label="Sort Oldest to Newest"
value="desc"
checked={newOrder === "desc"}
onChange={() => setNewOrder("desc")}
/>
<hr style={{ width: "100%" }} />
</div>

<div
style={{
display: "flex",
justifyContent: "space-between",
alignItems: "baseline"
}}
>
<div style={{ display: "flex" }}>
<button
className={classes.toggleButton}
onClick={() =>
selectAllCheckboxes(filteredOptions, setSelectedListItems)
}
>
Select all {filteredOptions.length}
</button>
<div style={{ display: "flex", alignItems: "center" }}>-</div>
<button
className={classes.toggleButton}
onClick={() => setSelectedListItems([])}
>
Clear
</button>
</div>
<div>{`${selectedListItems.length} selected`}</div>
</div>
<div className={classes.searchBarWrapper}>
<input
type="text"
value={searchString}
onChange={onChangeSearchString}
className={classes.searchBar}
/>
<MdOutlineSearch className={classes.searchIcon} alt="Search Icon" />
</div>

<div style={{ overflow: "auto", maxHeight: "12rem" }}>
{filteredOptions.map(o => {
const checked = isChecked(o);

return (
<div key={o} className={classes.listItem}>
<ToggleCheckbox
checked={checked}
onChange={() =>
handleCheckboxChange({
target: {
name: o,
checked: !isChecked(o)
}
})
}
label={o}
/>
<span>{o}</span>
</div>
);
})}
</div>

<hr style={{ width: "100%" }} />
<div style={{ display: "flex", justifyContent: "center" }}>
<Button onClick={setDefault} variant="outlined">
Reset
</Button>
<Button
onClick={applyChanges}
variant="contained"
color={"colorPrimary"}
>
Apply
</Button>
</div>
</div>
);
};

VersionPopup.propTypes = {
projects: PropTypes.any,
filter: PropTypes.func,
close: PropTypes.func,
header: PropTypes.any,
criteria: PropTypes.any,
setCriteria: PropTypes.func,
order: PropTypes.string,
orderBy: PropTypes.string,
setSort: PropTypes.func,
setCheckedProjectIds: PropTypes.func,
setSelectAllChecked: PropTypes.func,
droOptions: PropTypes.array
};

export default VersionPopup;
Loading
Loading