Skip to content
This repository was archived by the owner on Mar 13, 2025. It is now read-only.

Commit 593f99e

Browse files
authored
Merge pull request #21 from MadOPcode/feature/work-periods-part-2
Final fixes and new features
2 parents bfdc589 + cb25463 commit 593f99e

File tree

43 files changed

+866
-345
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

43 files changed

+866
-345
lines changed

src/components/Page/index.jsx

+6-1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import React from "react";
22
import PT from "prop-types";
33
import cn from "classnames";
44
import ReduxToastr from "react-redux-toastr";
5+
import { TOAST_DEFAULT_TIMEOUT } from "constants/index.js";
56
import styles from "./styles.module.scss";
67

78
/**
@@ -16,8 +17,12 @@ const Page = ({ className, children }) => (
1617
<div className={cn(styles.container, className)}>
1718
{children}
1819
<ReduxToastr
19-
timeOut={60000}
20+
timeOut={TOAST_DEFAULT_TIMEOUT}
2021
position="top-right"
22+
newestOnTop={true}
23+
removeOnHover={false}
24+
removeOnHoverTimeOut={TOAST_DEFAULT_TIMEOUT}
25+
closeOnToastrClick={false}
2126
transitionIn="fadeIn"
2227
transitionOut="fadeOut"
2328
/>

src/components/ProjectName/index.jsx

+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import React, { useContext, useEffect } from "react";
2+
import PT from "prop-types";
3+
import cn from "classnames";
4+
import { ProjectNameContext } from "components/ProjectNameContextProvider";
5+
import styles from "./styles.module.scss";
6+
7+
const ProjectName = ({ className, projectId }) => {
8+
const [getName, fetchName] = useContext(ProjectNameContext);
9+
useEffect(() => {
10+
fetchName(projectId);
11+
}, [fetchName, projectId]);
12+
13+
return (
14+
<span className={cn(styles.container, className)}>
15+
{getName(projectId) || projectId}
16+
</span>
17+
);
18+
};
19+
20+
ProjectName.propTypes = {
21+
className: PT.string,
22+
projectId: PT.number.isRequired,
23+
};
24+
25+
export default ProjectName;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
@import "styles/mixins";
2+
3+
.container {
4+
@include roboto-medium;
5+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import React, { createContext, useCallback, useState } from "react";
2+
import PT from "prop-types";
3+
import { fetchProject } from "services/workPeriods";
4+
import { increment, noop } from "utils/misc";
5+
6+
const names = {};
7+
const promises = {};
8+
9+
const getName = (id) => names[id];
10+
11+
export const ProjectNameContext = createContext([
12+
getName,
13+
(id) => {
14+
`${id}`;
15+
},
16+
]);
17+
18+
const ProjectNameProvider = ({ children }) => {
19+
const [, setCount] = useState(Number.MIN_SAFE_INTEGER);
20+
21+
const fetchName = useCallback((id) => {
22+
if (id in names || id in promises) {
23+
return;
24+
}
25+
promises[id] = fetchProject(id)
26+
.then((data) => {
27+
names[id] = data.name;
28+
setCount(increment);
29+
})
30+
.catch(noop)
31+
.finally(() => {
32+
delete promises[id];
33+
});
34+
}, []);
35+
36+
return (
37+
<ProjectNameContext.Provider value={[getName, fetchName]}>
38+
{children}
39+
</ProjectNameContext.Provider>
40+
);
41+
};
42+
43+
ProjectNameProvider.propTypes = {
44+
children: PT.node,
45+
};
46+
47+
export default ProjectNameProvider;
+51-38
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,19 @@
1-
import React, { useCallback, useState } from "react";
1+
import React, { useCallback } from "react";
22
import PT from "prop-types";
33
import cn from "classnames";
4-
import _ from "lodash";
54
import AsyncSelect from "react-select/async";
65
import { getMemberSuggestions } from "services/teams";
7-
// import { getOptionByValue } from "utils/misc";
86
import styles from "./styles.module.scss";
97

108
const selectComponents = {
119
DropdownIndicator: () => null,
1210
IndicatorSeparator: () => null,
1311
};
1412

13+
const loadingMessage = () => "Loading...";
14+
15+
const noOptionsMessage = () => "No suggestions";
16+
1517
/**
1618
* Displays search input field.
1719
*
@@ -25,20 +27,31 @@ const selectComponents = {
2527
* @param {string} props.value input value
2628
* @returns {JSX.Element}
2729
*/
28-
const SearchAutocomplete = ({
30+
const SearchHandleField = ({
2931
className,
3032
id,
33+
name,
3134
size = "medium",
3235
onChange,
3336
placeholder,
3437
value,
3538
}) => {
36-
// const option = getOptionByValue(options, value);
37-
const [savedInput, setSavedInput] = useState("");
38-
3939
const onValueChange = useCallback(
40-
(option) => {
41-
onChange(option.value);
40+
(option, { action }) => {
41+
if (action === "clear") {
42+
onChange("");
43+
} else {
44+
onChange(option.value);
45+
}
46+
},
47+
[onChange]
48+
);
49+
50+
const onInputChange = useCallback(
51+
(value, { action }) => {
52+
if (action === "input-change") {
53+
onChange(value);
54+
}
4255
},
4356
[onChange]
4457
);
@@ -51,52 +64,52 @@ const SearchAutocomplete = ({
5164
classNamePrefix="custom"
5265
components={selectComponents}
5366
id={id}
67+
name={name}
68+
isClearable={true}
5469
isSearchable={true}
5570
// menuIsOpen={true} // for debugging
56-
// onChange={onOptionChange}
57-
// onMenuOpen={onMenuOpen}
58-
// onMenuClose={onMenuClose}
59-
value={{ value, label: value }}
60-
onInputChange={setSavedInput}
61-
onFocus={() => {
62-
setSavedInput("");
63-
onChange(savedInput);
64-
}}
65-
placeholder={placeholder}
71+
value={null}
72+
inputValue={value}
6673
onChange={onValueChange}
67-
noOptionsMessage={() => "No options"}
68-
loadingMessage={() => "Loading..."}
74+
onInputChange={onInputChange}
75+
openMenuOnClick={false}
76+
placeholder={placeholder}
77+
noOptionsMessage={noOptionsMessage}
78+
loadingMessage={loadingMessage}
6979
loadOptions={loadSuggestions}
70-
blurInputOnSelect
80+
cacheOptions
7181
/>
7282
</div>
7383
);
7484
};
7585

76-
const loadSuggestions = (inputVal) => {
77-
return getMemberSuggestions(inputVal)
78-
.then((res) => {
79-
const users = _.get(res, "data.result.content", []);
80-
return users.map((user) => ({
81-
label: user.handle,
82-
value: user.handle,
83-
}));
84-
})
85-
.catch(() => {
86-
console.warn("could not get suggestions");
87-
return [];
88-
});
86+
const loadSuggestions = async (inputVal) => {
87+
let options = [];
88+
if (inputVal.length < 3) {
89+
return options;
90+
}
91+
try {
92+
const res = await getMemberSuggestions(inputVal);
93+
const users = res.data.result.content;
94+
for (let i = 0, len = users.length; i < len; i++) {
95+
let value = users[i].handle;
96+
options.push({ value, label: value });
97+
}
98+
} catch (error) {
99+
console.error(error);
100+
console.warn("could not get suggestions");
101+
}
102+
return options;
89103
};
90104

91-
SearchAutocomplete.propTypes = {
105+
SearchHandleField.propTypes = {
92106
className: PT.string,
93107
id: PT.string.isRequired,
94108
size: PT.oneOf(["medium", "small"]),
95109
name: PT.string.isRequired,
96110
onChange: PT.func.isRequired,
97-
options: PT.array,
98111
placeholder: PT.string,
99112
value: PT.oneOfType([PT.number, PT.string]),
100113
};
101114

102-
export default SearchAutocomplete;
115+
export default SearchHandleField;

src/components/SearchHandleField/styles.module.scss

+20-5
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
@import "styles/mixins";
33

44
.container {
5+
position: relative;
56
display: flex;
67
align-items: center;
78
border: 1px solid $control-border-color;
@@ -18,6 +19,11 @@
1819
}
1920

2021
.icon {
22+
display: block;
23+
position: absolute;
24+
top: 0;
25+
bottom: 0;
26+
left: 0;
2127
margin: auto 10px;
2228
width: 16px;
2329
height: 16px;
@@ -42,7 +48,7 @@ input.input {
4248
display: flex;
4349
margin: 0;
4450
border: none !important;
45-
padding: 8px 16px 8px 0;
51+
padding: 8px 16px 8px 36px;
4652
line-height: 22px;
4753
background: none;
4854
outline: none !important;
@@ -68,6 +74,7 @@ input.input {
6874
margin: 0;
6975
border: none;
7076
padding: 0;
77+
max-width: none !important;
7178

7279
> * {
7380
display: flex;
@@ -79,19 +86,23 @@ input.input {
7986
margin: 0;
8087
border: none;
8188
padding: 0;
89+
max-width: none !important;
90+
transform: none !important;
8291
}
8392

8493
input {
8594
flex: 1 1 0;
8695
margin: 0 !important;
8796
padding: 0 !important;
8897
border: none !important;
98+
max-width: none !important;
8999
width: auto !important;
90100
height: 22px !important;
91101
outline: none !important;
92102
box-shadow: none !important;
93103
line-height: 22px;
94104
color: inherit;
105+
opacity: 1 !important;
95106
}
96107
}
97108

@@ -103,7 +114,8 @@ input.input {
103114

104115
:global(.custom__input) {
105116
flex: 1 1 0;
106-
display: flex;
117+
display: flex !important;
118+
max-width: none !important;
107119
}
108120

109121
:global(.custom__placeholder) {
@@ -115,9 +127,12 @@ input.input {
115127
}
116128

117129
:global(.custom__menu) {
118-
margin: 1px 0 0;
130+
left: -1px;
131+
right: -1px;
132+
margin: 2px 0 0;
119133
border: 1px solid $control-border-color;
120134
border-radius: 0;
135+
width: auto;
121136
box-shadow: none;
122137
}
123138

@@ -141,8 +156,8 @@ input.input {
141156
}
142157
}
143158

144-
:global(.custom__option--is-selected) {
145-
background-color: #229174 !important;
159+
:global(.custom__option--is-focused) {
160+
background-color: $primary-text-color !important;
146161
color: #fff;
147162
}
148163
}

src/components/ToastrMessage/index.jsx

+18
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import React from "react";
2+
import { toastr } from "react-redux-toastr";
23
import PT from "prop-types";
34
import cn from "classnames";
5+
import { TOAST_DEFAULT_TIMEOUT } from "constants/index.js";
46
import styles from "./styles.module.scss";
57

68
/**
@@ -38,3 +40,19 @@ ToastrMessage.propTypes = {
3840
};
3941

4042
export default ToastrMessage;
43+
44+
/**
45+
* Creates a redux toastr message with the specified type and contents.
46+
*
47+
* @param {string|Object} message
48+
* @param {'info'|'success'|'warning'|'error'} type
49+
*/
50+
export function makeToast(message, type = "error") {
51+
const component =
52+
typeof message === "string" ? (
53+
<ToastrMessage message={message} type={type} />
54+
) : (
55+
<ToastrMessage type={type}>{message}</ToastrMessage>
56+
);
57+
toastr[type]("", { component, options: { timeOut: TOAST_DEFAULT_TIMEOUT } });
58+
}

0 commit comments

Comments
 (0)