-
Notifications
You must be signed in to change notification settings - Fork 51
Updates for anticipated start, payment popup changes #1721
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -23,13 +23,26 @@ const STATUS_UPDATE_OPTIONS = STATUS_OPTIONS.filter(option => option.value !== ' | |
| const INPUT_DATE_FORMAT = 'MM/dd/yyyy' | ||
| const INPUT_TIME_FORMAT = 'HH:mm' | ||
|
|
||
| const ANTICIPATED_START_LABELS = { | ||
| IMMEDIATE: 'Immediate', | ||
| FEW_DAYS: 'In a few days', | ||
| FEW_WEEKS: 'In a few weeks' | ||
| } | ||
|
|
||
| const formatDateTime = (value) => { | ||
| if (!value) { | ||
| return '-' | ||
| } | ||
| return moment(value).format('MMM DD, YYYY HH:mm') | ||
| } | ||
|
|
||
| const formatAnticipatedStart = (value) => { | ||
| if (!value) { | ||
| return '-' | ||
| } | ||
| return ANTICIPATED_START_LABELS[value] || value | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [ |
||
| } | ||
|
|
||
| const getStatusClass = (status) => { | ||
| const normalized = (status || '').toString().toLowerCase().replace(/\s+/g, '_') | ||
| if (normalized === 'submitted') { | ||
|
|
@@ -286,8 +299,8 @@ const ApplicationsList = ({ | |
| <span>{engagement && engagement.status ? engagement.status : '-'}</span> | ||
| </div> | ||
| <div className={styles.metaItem}> | ||
| <span className={styles.metaLabel}>Application Deadline:</span> | ||
| <span>{formatDateTime(engagement && engagement.applicationDeadline)}</span> | ||
| <span className={styles.metaLabel}>Anticipated Start:</span> | ||
| <span>{formatAnticipatedStart(engagement && engagement.anticipatedStart)}</span> | ||
| </div> | ||
| </div> | ||
| </div> | ||
|
|
@@ -387,7 +400,7 @@ ApplicationsList.propTypes = { | |
| id: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), | ||
| title: PropTypes.string, | ||
| description: PropTypes.string, | ||
| applicationDeadline: PropTypes.any, | ||
| anticipatedStart: PropTypes.string, | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [ |
||
| assignedMembers: PropTypes.arrayOf( | ||
| PropTypes.oneOfType([PropTypes.string, PropTypes.number]) | ||
| ), | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -5,7 +5,6 @@ import moment from 'moment-timezone' | |
| import cn from 'classnames' | ||
| import { PrimaryButton, OutlineButton } from '../Buttons' | ||
| import DescriptionField from './DescriptionField' | ||
| import DateInput from '../DateInput' | ||
| import Select from '../Select' | ||
| import SkillsField from '../ChallengeEditor/SkillsField' | ||
| import ConfirmationModal from '../Modal/ConfirmationModal' | ||
|
|
@@ -16,8 +15,11 @@ import { formatTimeZoneLabel, formatTimeZoneList } from '../../util/timezones' | |
| import styles from './EngagementEditor.module.scss' | ||
|
|
||
| const ANY_OPTION = { label: 'Any', value: 'Any' } | ||
| const INPUT_DATE_FORMAT = 'MM/dd/yyyy' | ||
| const INPUT_TIME_FORMAT = 'HH:mm' | ||
| const ANTICIPATED_START_OPTIONS = [ | ||
| { label: 'Immediate', value: 'Immediate' }, | ||
| { label: 'In a few days', value: 'In a few days' }, | ||
| { label: 'In a few weeks', value: 'In a few weeks' } | ||
| ] | ||
|
|
||
| const getEmptyEngagement = () => ({ | ||
| title: '', | ||
|
|
@@ -29,6 +31,7 @@ const getEmptyEngagement = () => ({ | |
| role: null, | ||
| workload: null, | ||
| compensationRange: '', | ||
| anticipatedStart: null, | ||
| status: 'Open', | ||
| isPrivate: false, | ||
| requiredMemberCount: '', | ||
|
|
@@ -78,7 +81,6 @@ const EngagementEditor = ({ | |
| onUpdateInput, | ||
| onUpdateDescription, | ||
| onUpdateSkills, | ||
| onUpdateDate, | ||
| onSavePublish, | ||
| onCancel, | ||
| onDelete, | ||
|
|
@@ -169,17 +171,6 @@ const EngagementEditor = ({ | |
| const selectedCountries = (engagement.countries || []).map(code => { | ||
| return countryOptionsByValue[code] || { label: code, value: code } | ||
| }) | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [💡 |
||
| const getMinApplicationDeadline = () => { | ||
| return moment().add(1, 'minute').startOf('minute').toDate() | ||
| } | ||
| const isValidApplicationDeadlineDate = (currentDate) => { | ||
| if (!currentDate) { | ||
| return false | ||
| } | ||
| const minDateTime = getMinApplicationDeadline() | ||
| return moment(currentDate).isSameOrAfter(minDateTime, 'day') | ||
| } | ||
|
|
||
| const selectedRoleOption = useMemo(() => { | ||
| if (!engagement.role) { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [ |
||
| return null | ||
|
|
@@ -194,8 +185,16 @@ const EngagementEditor = ({ | |
| return JOB_WORKLOAD_OPTIONS.find(option => option.value === engagement.workload || option.label === engagement.workload) || null | ||
| }, [engagement.workload]) | ||
|
|
||
| const selectedAnticipatedStartOption = useMemo(() => { | ||
| if (!engagement.anticipatedStart) { | ||
| return null | ||
| } | ||
| return ANTICIPATED_START_OPTIONS.find(option => option.value === engagement.anticipatedStart || option.label === engagement.anticipatedStart) || null | ||
| }, [engagement.anticipatedStart]) | ||
|
|
||
| const roleLabel = selectedRoleOption ? selectedRoleOption.label : engagement.role | ||
| const workloadLabel = selectedWorkloadOption ? selectedWorkloadOption.label : engagement.workload | ||
| const anticipatedStartLabel = selectedAnticipatedStartOption ? selectedAnticipatedStartOption.label : engagement.anticipatedStart | ||
| const assignments = Array.isArray(engagement.assignments) ? engagement.assignments : [] | ||
| const assignedMembers = Array.isArray(engagement.assignedMembers) ? engagement.assignedMembers : [] | ||
| const assignedMemberHandles = Array.isArray(engagement.assignedMemberHandles) ? engagement.assignedMemberHandles : [] | ||
|
|
@@ -572,28 +571,30 @@ const EngagementEditor = ({ | |
|
|
||
| <div className={styles.row}> | ||
| <div className={cn(styles.field, styles.col1)}> | ||
| <label>Application Deadline <span>*</span> :</label> | ||
| <label>Anticipated Start <span>*</span> :</label> | ||
| </div> | ||
| <div className={cn(styles.field, styles.col2)}> | ||
| {canEdit ? ( | ||
| <DateInput | ||
| <Select | ||
| className={styles.selectInput} | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [💡 |
||
| value={engagement.applicationDeadline} | ||
| dateFormat={INPUT_DATE_FORMAT} | ||
| timeFormat={INPUT_TIME_FORMAT} | ||
| onChange={value => onUpdateDate('applicationDeadline', value)} | ||
| isValidDate={isValidApplicationDeadlineDate} | ||
| minDateTime={getMinApplicationDeadline} | ||
| useBottomBorder | ||
| options={ANTICIPATED_START_OPTIONS} | ||
| value={selectedAnticipatedStartOption} | ||
| onChange={(option) => onUpdateInput({ | ||
| target: { | ||
| name: 'anticipatedStart', | ||
| value: option ? option.value : null | ||
| } | ||
| })} | ||
| isClearable={false} | ||
| /> | ||
| ) : ( | ||
| <div className={styles.readOnlyValue}> | ||
| {engagement.applicationDeadline | ||
| ? moment(engagement.applicationDeadline).format('MMM DD, YYYY HH:mm') | ||
| : '-'} | ||
| {anticipatedStartLabel || '-'} | ||
| </div> | ||
| )} | ||
| {submitTriggered && validationErrors.applicationDeadline && ( | ||
| <div className={styles.error}>{validationErrors.applicationDeadline}</div> | ||
| {submitTriggered && validationErrors.anticipatedStart && ( | ||
| <div className={styles.error}>{validationErrors.anticipatedStart}</div> | ||
| )} | ||
| </div> | ||
| </div> | ||
|
|
@@ -776,7 +777,6 @@ EngagementEditor.defaultProps = { | |
| onUpdateInput: () => {}, | ||
| onUpdateDescription: () => {}, | ||
| onUpdateSkills: () => {}, | ||
| onUpdateDate: () => {}, | ||
| onSavePublish: () => {}, | ||
| onCancel: () => {}, | ||
| onDelete: () => {}, | ||
|
|
@@ -820,7 +820,7 @@ EngagementEditor.propTypes = { | |
| timezones: PropTypes.arrayOf(PropTypes.string), | ||
| countries: PropTypes.arrayOf(PropTypes.string), | ||
| skills: PropTypes.arrayOf(PropTypes.shape()), | ||
| applicationDeadline: PropTypes.any, | ||
| anticipatedStart: PropTypes.string, | ||
| status: PropTypes.string | ||
| }), | ||
| projectId: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), | ||
|
|
@@ -833,7 +833,7 @@ EngagementEditor.propTypes = { | |
| title: PropTypes.string, | ||
| description: PropTypes.string, | ||
| durationWeeks: PropTypes.string, | ||
| applicationDeadline: PropTypes.string, | ||
| anticipatedStart: PropTypes.string, | ||
| skills: PropTypes.string, | ||
| timezones: PropTypes.string, | ||
| countries: PropTypes.string, | ||
|
|
@@ -846,7 +846,6 @@ EngagementEditor.propTypes = { | |
| onUpdateInput: PropTypes.func, | ||
| onUpdateDescription: PropTypes.func, | ||
| onUpdateSkills: PropTypes.func, | ||
| onUpdateDate: PropTypes.func, | ||
| onSavePublish: PropTypes.func, | ||
| onCancel: PropTypes.func, | ||
| onDelete: PropTypes.func, | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -20,7 +20,7 @@ const STATUS_OPTIONS = [ | |
| ] | ||
|
|
||
| const SORT_OPTIONS = [ | ||
| { label: 'Application Deadline', value: 'deadline' }, | ||
| { label: 'Anticipated Start', value: 'anticipatedStart' }, | ||
| { label: 'Created Date', value: 'createdAt' } | ||
| ] | ||
|
|
||
|
|
@@ -31,20 +31,58 @@ const SORT_ORDER_OPTIONS = [ | |
|
|
||
| const DEFAULT_STATUS_OPTION = STATUS_OPTIONS.find((option) => option.value === 'Open') || STATUS_OPTIONS[0] | ||
|
|
||
| const ANTICIPATED_START_LABELS = { | ||
| IMMEDIATE: 'Immediate', | ||
| FEW_DAYS: 'In a few days', | ||
| FEW_WEEKS: 'In a few weeks' | ||
| } | ||
|
|
||
| const ANTICIPATED_START_ORDER = { | ||
| Immediate: 1, | ||
| 'In a few days': 2, | ||
| 'In a few weeks': 3, | ||
| ...Object.keys(ANTICIPATED_START_LABELS).reduce((acc, key, index) => { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [ |
||
| acc[key] = index + 1 | ||
| return acc | ||
| }, {}) | ||
| } | ||
|
|
||
| const formatDate = (value) => { | ||
| if (!value) { | ||
| return '-' | ||
| } | ||
| return moment(value).format('MMM DD, YYYY') | ||
| } | ||
|
|
||
| const formatAnticipatedStart = (value) => { | ||
| if (!value) { | ||
| return '-' | ||
| } | ||
| return ANTICIPATED_START_LABELS[value] || value | ||
| } | ||
|
|
||
| const getSortValue = (engagement, sortBy) => { | ||
| if (sortBy === 'deadline') { | ||
| return engagement.applicationDeadline || engagement.application_deadline || null | ||
| if (sortBy === 'anticipatedStart') { | ||
| const anticipatedStart = engagement.anticipatedStart || engagement.anticipated_start || null | ||
| if (!anticipatedStart) { | ||
| return null | ||
| } | ||
| return ANTICIPATED_START_ORDER[anticipatedStart] || 0 | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [ |
||
| } | ||
| return engagement.createdAt || engagement.createdOn || engagement.created || null | ||
| } | ||
|
|
||
| const getSortComparable = (value) => { | ||
| if (value == null) { | ||
| return null | ||
| } | ||
| if (typeof value === 'number') { | ||
| return value | ||
| } | ||
| const parsed = new Date(value).getTime() | ||
| return Number.isNaN(parsed) ? null : parsed | ||
| } | ||
|
|
||
| const getDurationLabel = (engagement) => { | ||
| if (!engagement) { | ||
| return '-' | ||
|
|
@@ -214,9 +252,11 @@ const EngagementsList = ({ | |
| const sorted = [...results].sort((a, b) => { | ||
| const valueA = getSortValue(a, sortBy.value) | ||
| const valueB = getSortValue(b, sortBy.value) | ||
| const dateA = valueA ? new Date(valueA).getTime() : 0 | ||
| const dateB = valueB ? new Date(valueB).getTime() : 0 | ||
| return sortOrder.value === 'asc' ? dateA - dateB : dateB - dateA | ||
| const comparableA = getSortComparable(valueA) | ||
| const comparableB = getSortComparable(valueB) | ||
| const normalizedA = comparableA == null ? 0 : comparableA | ||
| const normalizedB = comparableB == null ? 0 : comparableB | ||
| return sortOrder.value === 'asc' ? normalizedA - normalizedB : normalizedB - normalizedA | ||
| }) | ||
| return sorted | ||
| }, [engagements, statusFilter, searchText, sortBy, sortOrder]) | ||
|
|
@@ -309,7 +349,7 @@ const EngagementsList = ({ | |
| <th>Title</th> | ||
| <th>Duration</th> | ||
| <th>Location</th> | ||
| <th>Application Deadline</th> | ||
| <th>Anticipated Start</th> | ||
| {canManage && <th>Applications</th>} | ||
| {canManage && <th>Visibility</th>} | ||
| {canManage && <th>Members Required</th>} | ||
|
|
@@ -341,7 +381,7 @@ const EngagementsList = ({ | |
| <td>{engagement.title || '-'}</td> | ||
| <td>{duration}</td> | ||
| <td>{location}</td> | ||
| <td>{formatDate(engagement.applicationDeadline)}</td> | ||
| <td>{formatAnticipatedStart(engagement.anticipatedStart)}</td> | ||
| {canManage && ( | ||
| <td> | ||
| {engagement.id ? ( | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[⚠️
correctness]The
trimmedDescriptionvariable is assigned a trimmed version ofdescriptiononly if it is a string. Ifdescriptionis not a string, it defaults to an empty string. Consider handling non-string values more explicitly, or validating the input type earlier to ensuredescriptionis always a string.