Skip to content

Commit b388ec7

Browse files
authored
Merge pull request topcoder-archive#64 from MadOPcode/dev
Implemented the upper constraint for the working days
2 parents 714651c + 12e070f commit b388ec7

File tree

12 files changed

+382
-70
lines changed

12 files changed

+382
-70
lines changed

src/constants/workPeriods.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ export const WORK_PERIODS_API_URL = `${API.V5}/work-periods`;
2929
export const TAAS_TEAM_API_URL = `${API.V5}/taas-teams`;
3030

3131
export const DATE_FORMAT_API = "YYYY-MM-DD";
32+
export const DATE_FORMAT_ISO = "YYYY-MM-DD";
3233
export const DATE_FORMAT_UI = "MMM DD, YYYY";
3334

3435
// Field names that are required to be retrieved for display, filtering and sorting.
@@ -52,6 +53,7 @@ export const API_REQUIRED_FIELDS = [
5253
"workPeriods.daysPaid",
5354
"workPeriods.payments.amount",
5455
"workPeriods.payments.challengeId",
56+
"workPeriods.payments.createdAt",
5557
"workPeriods.payments.days",
5658
"workPeriods.payments.id",
5759
"workPeriods.payments.memberRate",

src/routes/WorkPeriods/components/PeriodDetails/index.jsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,8 @@ const PeriodDetails = ({
143143
</span>
144144
</div>
145145
<PeriodsHistory
146+
bookingStart={period.bookingStart}
147+
bookingEnd={period.bookingEnd}
146148
isDisabled={isDisabled}
147149
periods={periodsVisible}
148150
/>
@@ -177,6 +179,8 @@ PeriodDetails.propTypes = {
177179
rbId: PT.string.isRequired,
178180
jobId: PT.string.isRequired,
179181
billingAccountId: PT.number.isRequired,
182+
bookingStart: PT.string.isRequired,
183+
bookingEnd: PT.string.isRequired,
180184
}).isRequired,
181185
};
182186

src/routes/WorkPeriods/components/PeriodItem/index.jsx

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,11 @@ import {
2727
updateWorkPeriodWorkingDays,
2828
} from "store/thunks/workPeriods";
2929
import { useUpdateEffect } from "utils/hooks";
30-
import { formatUserHandleLink, formatWeeklyRate } from "utils/formatters";
30+
import {
31+
formatDate,
32+
formatUserHandleLink,
33+
formatWeeklyRate,
34+
} from "utils/formatters";
3135
import { stopPropagation } from "utils/misc";
3236
import styles from "./styles.module.scss";
3337
import PeriodAlerts from "../PeriodAlerts";
@@ -178,8 +182,8 @@ const PeriodItem = ({
178182
<ProjectName projectId={item.projectId} />
179183
</Tooltip>
180184
</td>
181-
<td className={styles.startDate}>{item.startDate}</td>
182-
<td className={styles.endDate}>{item.endDate}</td>
185+
<td className={styles.startDate}>{formatDate(item.bookingStart)}</td>
186+
<td className={styles.endDate}>{formatDate(item.bookingEnd)}</td>
183187
<td className={styles.alert}>
184188
<PeriodAlerts alerts={alerts} />
185189
</td>
@@ -208,12 +212,14 @@ const PeriodItem = ({
208212
</td>
209213
<td className={styles.daysWorked}>
210214
<PeriodWorkingDays
211-
updateHintTimeout={2000}
215+
bookingStart={item.bookingStart}
216+
bookingEnd={item.bookingEnd}
212217
controlName={`wp_wrk_days_${item.id}`}
213218
data={data}
214219
isDisabled={isDisabled}
215220
onWorkingDaysChange={onWorkingDaysChange}
216221
onWorkingDaysUpdateHintTimeout={onWorkingDaysUpdateHintTimeout}
222+
updateHintTimeout={2000}
217223
/>
218224
</td>
219225
</tr>
@@ -266,14 +272,14 @@ PeriodItem.propTypes = {
266272
projectId: PT.oneOfType([PT.number, PT.string]).isRequired,
267273
userHandle: PT.string.isRequired,
268274
teamName: PT.oneOfType([PT.number, PT.string]).isRequired,
269-
startDate: PT.string.isRequired,
270-
endDate: PT.string.isRequired,
275+
bookingStart: PT.string.isRequired,
276+
bookingEnd: PT.string.isRequired,
271277
weeklyRate: PT.number,
272278
}).isRequired,
273279
alerts: PT.arrayOf(PT.string),
274280
data: PT.shape({
275-
daysWorked: PT.number.isRequired,
276281
daysPaid: PT.number.isRequired,
282+
daysWorked: PT.number.isRequired,
277283
paymentErrorLast: PT.object,
278284
payments: PT.array,
279285
paymentStatus: PT.string.isRequired,

src/routes/WorkPeriods/components/PeriodWorkingDays/index.jsx

Lines changed: 95 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,18 @@
1-
import React from "react";
1+
import React, { useMemo } from "react";
22
import PT from "prop-types";
33
import cn from "classnames";
4-
import IntegerField from "components/IntegerField";
4+
import Tooltip from "components/Tooltip";
55
import IconCheckmarkCircled from "components/Icons/CheckmarkCircled";
6+
import { formatDate } from "utils/formatters";
7+
import { stopPropagation } from "utils/misc";
68
import styles from "./styles.module.scss";
79

810
/**
911
* Displays working days input field with an icon hinting about the update.
1012
*
1113
* @param {Object} props component properties
14+
* @param {string} props.bookingStart resource booking start date
15+
* @param {string} props.bookingEnd resource booking end date
1216
* @param {string} [props.className] class name to be added to root element
1317
* @param {string} props.controlName working days input control name
1418
* @param {Object} props.data working period data object
@@ -22,42 +26,109 @@ import styles from "./styles.module.scss";
2226
* @returns {JSX.Element}
2327
*/
2428
const PeriodWorkingDays = ({
29+
bookingStart,
30+
bookingEnd,
2531
className,
2632
controlName,
27-
data,
33+
data: { daysPaid, daysWorked, daysWorkedMax, daysWorkedIsUpdated },
2834
isDisabled,
2935
onWorkingDaysChange,
3036
onWorkingDaysUpdateHintTimeout,
3137
updateHintTimeout = 2000,
32-
}) => (
33-
<div className={cn(styles.container, className)}>
34-
<span className={styles.iconPlaceholder}>
35-
{data.daysWorkedIsUpdated && (
36-
<IconCheckmarkCircled
37-
className={styles.checkmarkIcon}
38-
onTimeout={onWorkingDaysUpdateHintTimeout}
39-
timeout={updateHintTimeout}
38+
}) => {
39+
const isBtnMinusDisabled = daysWorked > 0 && daysWorked <= daysPaid;
40+
const isBtnPlusDisabled = daysWorked < 5 && daysWorked >= daysWorkedMax;
41+
const decreaseDaysWorkedMessage = useMemo(
42+
() => `Cannot decrease "Working Days" below the number of days already
43+
paid for: ${daysPaid}`,
44+
[daysPaid]
45+
);
46+
const increaseDaysWorkedMessage = useMemo(
47+
() => `Cannot increase "Working Days" because the Resource Booking period
48+
is between ${formatDate(bookingStart)} and ${formatDate(bookingEnd)}`,
49+
[bookingStart, bookingEnd]
50+
);
51+
52+
return (
53+
<div className={cn(styles.container, className)}>
54+
<span className={styles.iconPlaceholder}>
55+
{daysWorkedIsUpdated && (
56+
<IconCheckmarkCircled
57+
className={styles.checkmarkIcon}
58+
onTimeout={onWorkingDaysUpdateHintTimeout}
59+
timeout={updateHintTimeout}
60+
/>
61+
)}
62+
</span>
63+
<div
64+
className={styles.daysWorkedControl}
65+
onClick={stopPropagation}
66+
role="button"
67+
tabIndex={0}
68+
>
69+
<input
70+
disabled={isDisabled}
71+
readOnly
72+
className={styles.input}
73+
name={controlName}
74+
value={daysWorked}
4075
/>
41-
)}
42-
</span>
43-
<IntegerField
44-
className={styles.daysWorkedControl}
45-
isDisabled={isDisabled}
46-
name={controlName}
47-
onChange={onWorkingDaysChange}
48-
maxValue={5}
49-
minValue={data.daysPaid}
50-
value={data.daysWorked}
51-
/>
52-
</div>
53-
);
76+
<Tooltip
77+
className={styles.btnMinus}
78+
targetClassName={cn(styles.tooltipTarget, {
79+
[styles.notAllowed]: isBtnMinusDisabled,
80+
})}
81+
tooltipClassName={styles.tooltip}
82+
content={decreaseDaysWorkedMessage}
83+
isDisabled={!isBtnMinusDisabled || isDisabled}
84+
strategy="fixed"
85+
>
86+
<button
87+
className={styles.btnMinus}
88+
disabled={isBtnMinusDisabled}
89+
onClick={(event) => {
90+
event.stopPropagation();
91+
if (!isDisabled) {
92+
onWorkingDaysChange(Math.max(daysWorked - 1, daysPaid));
93+
}
94+
}}
95+
/>
96+
</Tooltip>
97+
<Tooltip
98+
className={styles.btnPlus}
99+
targetClassName={cn(styles.tooltipTarget, {
100+
[styles.notAllowed]: isBtnPlusDisabled,
101+
})}
102+
tooltipClassName={styles.tooltip}
103+
content={increaseDaysWorkedMessage}
104+
isDisabled={!isBtnPlusDisabled || isDisabled}
105+
strategy="fixed"
106+
>
107+
<button
108+
className={styles.btnPlus}
109+
disabled={isBtnPlusDisabled}
110+
onClick={(event) => {
111+
event.stopPropagation();
112+
if (!isDisabled) {
113+
onWorkingDaysChange(Math.min(daysWorked + 1, daysWorkedMax));
114+
}
115+
}}
116+
/>
117+
</Tooltip>
118+
</div>
119+
</div>
120+
);
121+
};
54122

55123
PeriodWorkingDays.propTypes = {
124+
bookingStart: PT.string.isRequired,
125+
bookingEnd: PT.string.isRequired,
56126
className: PT.string,
57127
controlName: PT.string.isRequired,
58128
data: PT.shape({
59129
daysPaid: PT.number.isRequired,
60130
daysWorked: PT.number.isRequired,
131+
daysWorkedMax: PT.number.isRequired,
61132
daysWorkedIsUpdated: PT.bool.isRequired,
62133
}).isRequired,
63134
isDisabled: PT.bool.isRequired,

src/routes/WorkPeriods/components/PeriodWorkingDays/styles.module.scss

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
@import "styles/variables";
2+
13
.container {
24
display: flex;
35
align-items: baseline;
@@ -17,5 +19,126 @@
1719
}
1820

1921
.daysWorkedControl {
22+
position: relative;
23+
display: inline-flex;
24+
align-items: center;
25+
border: 1px solid $control-border-color;
26+
border-radius: 6px;
2027
width: 100px;
28+
overflow: hidden;
29+
}
30+
31+
input.input {
32+
flex: 1 1 0;
33+
margin: 0;
34+
border: none !important;
35+
padding: 3px 0;
36+
height: 28px;
37+
line-height: 22px;
38+
background: #fff;
39+
outline: none !important;
40+
box-shadow: none !important;
41+
text-align: center;
42+
43+
&:disabled {
44+
border-color: $control-disabled-border-color;
45+
background-color: $control-disabled-bg-color;
46+
color: $control-disabled-text-color;
47+
cursor: not-allowed;
48+
49+
~ .btnMinus,
50+
~ .btnPlus {
51+
cursor: not-allowed;
52+
}
53+
}
54+
}
55+
56+
.tooltip {
57+
max-width: 400px;
58+
}
59+
60+
.tooltipTarget {
61+
display: block;
62+
width: 100%;
63+
height: 100%;
64+
65+
&.notAllowed {
66+
cursor: not-allowed;
67+
}
68+
}
69+
70+
.btnMinus,
71+
.btnPlus {
72+
position: absolute;
73+
top: 0;
74+
bottom: 0;
75+
margin: auto;
76+
width: 30px;
77+
height: 30px;
78+
79+
button {
80+
display: block;
81+
position: relative;
82+
border: none;
83+
padding: 0;
84+
width: 100%;
85+
height: 100%;
86+
background: transparent;
87+
outline: none !important;
88+
cursor: pointer;
89+
90+
&:disabled {
91+
background: #ddd;
92+
opacity: 1;
93+
}
94+
}
95+
}
96+
97+
.btnMinus {
98+
left: 0;
99+
100+
button {
101+
&::before {
102+
content: "";
103+
display: block;
104+
position: absolute;
105+
left: 0;
106+
top: 0;
107+
right: 0;
108+
bottom: 0;
109+
margin: auto;
110+
width: 8px;
111+
height: 1px;
112+
background: #7f7f7f;
113+
}
114+
}
115+
}
116+
117+
.btnPlus {
118+
right: 0;
119+
120+
button {
121+
&::before,
122+
&::after {
123+
content: "";
124+
display: block;
125+
position: absolute;
126+
left: 0;
127+
top: 0;
128+
right: 0;
129+
bottom: 0;
130+
margin: auto;
131+
background: #7f7f7f;
132+
}
133+
134+
&::before {
135+
width: 9px;
136+
height: 1px;
137+
}
138+
139+
&::after {
140+
width: 1px;
141+
height: 9px;
142+
}
143+
}
21144
}

0 commit comments

Comments
 (0)