Skip to content

Commit 4f7a026

Browse files
committed
Merge branch 'develop' of github.com:hackforla/tdm-calculator into 2630-add-select-all-option
2 parents 474081e + c473c70 commit 4f7a026

10 files changed

Lines changed: 312 additions & 40 deletions

File tree

client/package-lock.json

Lines changed: 7 additions & 8 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

client/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@
8585
"require-context.macro": "^1.2.2",
8686
"sass": "^1.51.0",
8787
"typescript": "^5.8.2",
88-
"vite": "^6.3.4",
88+
"vite": "^6.3.6",
8989
"vite-plugin-svgr": "^4.3.0",
9090
"vite-tsconfig-paths": "^5.1.4"
9191
},
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
import React from "react";
2+
import PropTypes from "prop-types";
3+
import { createUseStyles, useTheme } from "react-jss";
4+
import { MdLaunch, MdLocationCity } from "react-icons/md";
5+
import ModalDialog from "../UI/Modal";
6+
import RadioButton from "../UI/RadioButton";
7+
import Button from "../Button/Button";
8+
9+
const useStyles = createUseStyles(theme => ({
10+
container: {
11+
display: "flex",
12+
flexDirection: "column",
13+
alignItems: "center",
14+
width: "40rem",
15+
gap: "1rem",
16+
padding: "1rem"
17+
},
18+
buttonFlexBox: {
19+
display: "flex",
20+
flexDirection: "row",
21+
justifyContent: "center",
22+
margin: 0
23+
},
24+
heading1: theme.typography.heading1,
25+
heading2: theme.typography.heading2,
26+
subheading: {
27+
...theme.typography.subHeading,
28+
width: "85%"
29+
},
30+
externalLinkIcon: {
31+
fontSize: "20px",
32+
padding: " 0 0.4em",
33+
color: "#00F",
34+
verticalAlign: "middle"
35+
},
36+
icon: {
37+
height: "40px",
38+
width: "40px",
39+
color: theme.colorBlack,
40+
marginBottom: "0",
41+
verticalAlign: "middle",
42+
marginRight: "1rem"
43+
}
44+
}));
45+
46+
export default function DROSelectionModal({
47+
mounted,
48+
onClose,
49+
onConfirm,
50+
selectedDro,
51+
droOptions,
52+
onChange
53+
}) {
54+
const theme = useTheme();
55+
const classes = useStyles({ theme });
56+
57+
return (
58+
<ModalDialog
59+
mounted={mounted}
60+
onClose={onClose}
61+
onConfirm={onConfirm}
62+
onChange={onChange}
63+
>
64+
<div className={classes.container}>
65+
<div className={classes.heading1}>
66+
<MdLocationCity className={classes.icon} />
67+
Select Development Review Office
68+
</div>
69+
<div className={classes.subheading}>
70+
To submit your snapshot, select a Development Review Office based on
71+
the location of your project
72+
</div>
73+
<div className={classes.heading2}>
74+
Map of Development Review Offices
75+
<a
76+
href="https://ladot.lacity.gov/sites/default/files/documents/ladot-development-review-counter-info.pdf"
77+
target="_blank"
78+
className={classes.glossaryLink}
79+
rel="noreferrer"
80+
>
81+
<MdLaunch className={classes.externalLinkIcon} />
82+
</a>
83+
</div>
84+
{droOptions.map(dro => (
85+
<div
86+
key={dro.id}
87+
style={{
88+
display: "flex",
89+
flexDirection: "column",
90+
width: "100px",
91+
margin: "auto"
92+
}}
93+
>
94+
<RadioButton
95+
label={dro.name}
96+
value={dro.id}
97+
checked={selectedDro === dro.id}
98+
onChange={onChange}
99+
/>
100+
</div>
101+
))}
102+
<div className={classes.buttonFlexBox}>
103+
<Button onClick={onClose} variant="secondary">
104+
CANCEL
105+
</Button>
106+
<Button
107+
onClick={() => onConfirm("ok", selectedDro)}
108+
variant="primary"
109+
>
110+
CONFIRM
111+
</Button>
112+
</div>
113+
</div>
114+
</ModalDialog>
115+
);
116+
}
117+
118+
DROSelectionModal.propTypes = {
119+
mounted: PropTypes.bool.isRequired,
120+
onClose: PropTypes.func.isRequired,
121+
onConfirm: PropTypes.func.isRequired,
122+
selectedDro: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
123+
droOptions: PropTypes.array.isRequired,
124+
onChange: PropTypes.func.isRequired
125+
};

client/src/components/ProjectWizard/Common/RuleLabel.jsx

Lines changed: 20 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
1-
import React from "react";
1+
import React, { useContext } from "react";
22
import PropTypes from "prop-types";
33
import { MdLink } from "react-icons/md";
44
import { createUseStyles, useTheme } from "react-jss";
55
import clsx from "clsx";
66
import { MdInfo, MdAddCircle } from "react-icons/md";
7+
import UserContext from "contexts/UserContext";
78

89
const useStyles = createUseStyles(theme => ({
910
labelWrapper: {
@@ -13,9 +14,11 @@ const useStyles = createUseStyles(theme => ({
1314
"&:hover $iconContainer": {
1415
visibility: "visible"
1516
},
16-
"&:hover": {
17-
cursor: "pointer"
18-
}
17+
cursor: props =>
18+
props.isAdmin || props.description ? "pointer" : "default"
19+
// "&:hover": {
20+
// cursor: "pointer"
21+
// }
1922
},
2023
labelWrapperWithoutDesc: {
2124
flexGrow: "1",
@@ -24,9 +27,8 @@ const useStyles = createUseStyles(theme => ({
2427
"&:hover $iconContainer": {
2528
visibility: "visible"
2629
},
27-
"&:hover": {
28-
cursor: "pointer"
29-
}
30+
cursor: props =>
31+
props.isAdmin || props.description ? "pointer" : "default"
3032
},
3133
tooltipLabel: {
3234
flexGrow: "1",
@@ -91,7 +93,9 @@ const RuleLabel = ({
9193
setIsEditing
9294
}) => {
9395
const theme = useTheme();
94-
const classes = useStyles(theme);
96+
const userContext = useContext(UserContext);
97+
const isAdmin = !!userContext?.account?.isAdmin;
98+
const classes = useStyles({ theme, description, isAdmin });
9599
const requiredStyle = required && classes.requiredInputLabel;
96100
const disabledStyle = !display;
97101

@@ -207,13 +211,14 @@ const RuleLabel = ({
207211
<MdInfo className={classes.infoIcon} />
208212
</span>
209213
) : (
210-
<span
211-
className={clsx("fa-layers fa-fw", classes.iconContainer)}
212-
style={showDescription ? { visibility: "visible" } : {}}
213-
onClick={addDescriptionHandler}
214-
>
215-
<MdAddCircle className={classes.infoIcon} />
216-
</span>
214+
isAdmin && (
215+
<span
216+
className={clsx("fa-layers fa-fw", classes.iconContainer)}
217+
onClick={addDescriptionHandler}
218+
>
219+
<MdAddCircle className={classes.infoIcon} />
220+
</span>
221+
)
217222
)}
218223
</div>
219224
);

client/src/components/Projects/ColumnHeaderPopups/ProjectTableColumnHeader.jsx

Lines changed: 71 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
/* eslint-disable no-unused-vars */
2-
import React, { useState } from "react";
1+
import React, { useState, useRef, useEffect } from "react";
32
import PropTypes from "prop-types";
43
import "react-datepicker/dist/react-datepicker.css";
54
import {
@@ -110,6 +109,18 @@ const ProjectTableColumnHeader = ({
110109
const theme = useTheme();
111110
const classes = useStyles(theme);
112111
const [isPopoverOpen, setIsPopoverOpen] = useState(false);
112+
const columnHeaderRef = useRef(null);
113+
const popoverContentRef = useRef(null);
114+
const suppressNextOutsideClickRef = useRef(false);
115+
const internalInteractionTimeoutRef = useRef(null);
116+
117+
useEffect(() => {
118+
return () => {
119+
if (internalInteractionTimeoutRef.current) {
120+
clearTimeout(internalInteractionTimeoutRef.current);
121+
}
122+
};
123+
}, []);
113124

114125
// Filter is considered Applied if it is not set
115126
// to the default criteria values.
@@ -157,6 +168,56 @@ const ProjectTableColumnHeader = ({
157168
setIsPopoverOpen(flag);
158169
};
159170

171+
const handleClickOutside = event => {
172+
if (suppressNextOutsideClickRef.current) {
173+
suppressNextOutsideClickRef.current = false;
174+
if (internalInteractionTimeoutRef.current) {
175+
clearTimeout(internalInteractionTimeoutRef.current);
176+
internalInteractionTimeoutRef.current = null;
177+
}
178+
return;
179+
}
180+
181+
const target = event?.target;
182+
183+
if (!target) {
184+
handlePopoverToggle(false);
185+
return;
186+
}
187+
188+
const isWithinTrigger = columnHeaderRef.current?.contains(target);
189+
const isWithinContent = popoverContentRef.current?.contains(target);
190+
191+
if (isWithinTrigger || isWithinContent) {
192+
return;
193+
}
194+
195+
if (target instanceof Element) {
196+
const isWithinDatepicker = target.closest(
197+
".react-datepicker, .react-datepicker__portal"
198+
);
199+
200+
if (isWithinDatepicker) {
201+
return;
202+
}
203+
}
204+
205+
handlePopoverToggle(false);
206+
};
207+
208+
const handleInternalInteraction = () => {
209+
suppressNextOutsideClickRef.current = true;
210+
211+
if (internalInteractionTimeoutRef.current) {
212+
clearTimeout(internalInteractionTimeoutRef.current);
213+
}
214+
215+
internalInteractionTimeoutRef.current = setTimeout(() => {
216+
suppressNextOutsideClickRef.current = false;
217+
internalInteractionTimeoutRef.current = null;
218+
}, 150);
219+
};
220+
160221
return (
161222
<div style={{ width: "100%", height: "100%" }}>
162223
{header.id !== "checkAllProjects" &&
@@ -165,13 +226,18 @@ const ProjectTableColumnHeader = ({
165226
<Popover
166227
containerStyle={{ zIndex: 20 }}
167228
isOpen={isPopoverOpen}
168-
onClickOutside={() => handlePopoverToggle(false)}
229+
onClickOutside={handleClickOutside}
169230
clickOutsideCapture={true}
170231
positions={["bottom", "left", "right", "top"]} // preferred positions by priority
171232
align="start"
172233
padding={10}
173234
content={
174-
<div className={classes.popoverContent}>
235+
<div
236+
ref={popoverContentRef}
237+
className={classes.popoverContent}
238+
onMouseDownCapture={handleInternalInteraction}
239+
onTouchStartCapture={handleInternalInteraction}
240+
>
175241
{!header.popupType ? null : header.popupType === "datetime" ? (
176242
<DatePopup
177243
close={() => handlePopoverToggle(false)}
@@ -285,6 +351,7 @@ const ProjectTableColumnHeader = ({
285351
}
286352
>
287353
<ColumnHeader
354+
ref={columnHeaderRef}
288355
onClick={() => setIsPopoverOpen(!isPopoverOpen)}
289356
header={header}
290357
isFilterApplied={() => isFilterApplied()}

0 commit comments

Comments
 (0)