From edea2ce504e5d5eecab83d6157310f50b18ecf42 Mon Sep 17 00:00:00 2001 From: Mike Joyce Date: Mon, 11 Mar 2019 13:42:11 -0400 Subject: [PATCH] dev -> staging (#98) * chore: remove unnecessary css file from the build output * chore: update docs regarding apache compression config * Update bid list due date to match site-wide format * Use position id instead of position_number to query for position details * chore: remove unused props from components * chore: fix err due to incorrect favicon size in manifest * chore: remove unused props from the Home container * chore: remove unused props from the HomePagePositionsContainer component * fix: update profile page based on qa * Update styles and content in Position Details page based on QA feedback * fix: add logo to saved search title * fix: search page updates from qa * chore: linter fix * chore: fix linter issue * fix: search page updates from qa * chore: linter fix * Remove feedback button site-wide * Use object in state instead of array * Minor edits to Homepage based on QA * fix: make the pagination link clickable area larger * fix: use the correct button style * fix: better accessibility for active pagination tab selection * fix: use correct button design * fix: use lodash get so that non-existent nested property doesn't throw error (#45) * Use lodash get so that non-existent nested property doesn't throw error * Check for details.id so that components don't render with an empty object * Align bid count with data points in ResultsCondensedCard * Show "Available" filter to all users, not just CDOs (#46) * Update dashboard styles and content based on QA * Fix style for bid list container * Move Bid Count in-line with data points on ResultsCard * View More -> View more * Add disabled state for BidListButton based on proposed API updates * Use real properties from API PR, combine strings * Use white for button text color * feature: add remove bid to the bid tracker for draft and submitted bids * add additional status to the canDeleteBid function * Add react-toastify and use with bid list additions/removals (#51) * Add react-toastify and use with bid list additions/removals * More tests for toast-related functionality * Check if bid can be deleted and apply disabled status accordingly; update and optimize utility function * feature: use the can_delete property from the bid rather than calculate client side * Add loading spinner to Bid List button (#52) * Compress us-flag.jpg (#53) * Use react-linkify to automatically hyperlink URLs and email addresses in position capsule descriptions * Authorization -> Authentication (#54) * Authorization -> Authentication * Rename import * Update pagination and page size defaults in alignment with designs * Display link, if available, in the glossary * Add ability to edit links from Glossary editor; update styling for glossary links * Reduce code complexity, fix long link styles * Update snapshot * Add bundlesize (#69) * Add bundlsize and command * Refine glob * Refine maxSize target * Chore/linter (#67) * chore: fix linter is scss file * fix: linter issue * End of basic auth (#63) * Delete login form * Update actions * Clean up sagas * Remove isSAML checks * Refactor sagas, CodeClimate fixes * CodeClimate linting * Upate login screen * Simplify index * Change to force external login * get new custom auth working * remove rest of the basic auth refs from server * use env vars for env specific paths * Bidder role - TM-371 (#68) * Create permissions wrapper and conditionally render content based on bidder role * remove default fallback prop * Fix/mock auth (#70) * fix: override some routes for the webpack dev server so auth works for local development * fix: fix the conf so we can use the login.html when running the server in prod mode * Default to true if can_delete property is not found * Update snapshots * fix: remove the auth redirect loop and clean up reamining references to the LOGIN_MODE * TM-410 - display favorites list as 4 across above the large break point (#66) * fix: display favorites list as 4 across above the large break point * fix: display favorites list as 4 across above the large break point * fix: remove the auth redirect loop (#73) * fix: remove the auth redirect loop and clean up reamining references to the LOGIN_MODE * no conditional about url * dev -> staging (#56) (#75) * chore: remove unnecessary css file from the build output * chore: update docs regarding apache compression config * Update bid list due date to match site-wide format * Use position id instead of position_number to query for position details * chore: remove unused props from components * chore: fix err due to incorrect favicon size in manifest * chore: remove unused props from the Home container * chore: remove unused props from the HomePagePositionsContainer component * fix: update profile page based on qa * Update styles and content in Position Details page based on QA feedback * fix: add logo to saved search title * fix: search page updates from qa * chore: linter fix * chore: fix linter issue * fix: search page updates from qa * chore: linter fix * Remove feedback button site-wide * Use object in state instead of array * Minor edits to Homepage based on QA * fix: make the pagination link clickable area larger * fix: use the correct button style * fix: better accessibility for active pagination tab selection * fix: use correct button design * fix: use lodash get so that non-existent nested property doesn't throw error (#45) * Use lodash get so that non-existent nested property doesn't throw error * Check for details.id so that components don't render with an empty object * Align bid count with data points in ResultsCondensedCard * Show "Available" filter to all users, not just CDOs (#46) * Update dashboard styles and content based on QA * Fix style for bid list container * Move Bid Count in-line with data points on ResultsCard * View More -> View more * Add disabled state for BidListButton based on proposed API updates * Use real properties from API PR, combine strings * Use white for button text color * feature: add remove bid to the bid tracker for draft and submitted bids * add additional status to the canDeleteBid function * Add react-toastify and use with bid list additions/removals (#51) * Add react-toastify and use with bid list additions/removals * More tests for toast-related functionality * Check if bid can be deleted and apply disabled status accordingly; update and optimize utility function * feature: use the can_delete property from the bid rather than calculate client side * Add loading spinner to Bid List button (#52) * Compress us-flag.jpg (#53) * Remove use of skill cone/code to skill (#76) * Add Public Profile page - links from CDO portfolio, profile/public/:id route, add Assignments section to public profile, re-use of Profile Dashboard (#71) ## Relies on https://github.com/MetaPhase-Consulting/State-TalentMAP-API/pull/24 * Sort bid cycles alphabetically by name (#78) * feature: download search results in csv format. Resolves TM-439 * chore: use default variable for sort * chore: reformat the date before file creation * Simplified search bar v2 (#81) * Make use of the existing simple search bar throughout site * Offsets for homepage * Redesign compare page (#82) * Redesign compare page * Remove old table styles; handle zero comparisons with route to catch it * feature: move the download button and use the secondary style. resolves TM-511 * fix: set a width on the cards for the favorite positions profile screen (#80) * fix: set a width on the cards for the favorite positions profile screen. resolves TM-410 * fix: no responsiveness for bid count and favorite buttons on position card displays * feature: fixed width for all position cards (homepage, favorites, similar positions). Resolves TM-510 * fix: remove unnecessary class and fix padding on grid for correct wrapping * chore: fixing the wrapping for the larger width * Fix search styles from breaking on the bidder portfolio * Reverting the changes to the glossary card due to the new term dialog breaking * Add link-container class back in * Update snapshots * Trigger circleci build * Code smells (#86) * Update CodeClimate exclusions for local dev * Reduce complexity * Comparison drawer component, add event listeners where needed, remove old comparison UI from results page * Track old compare choices to maintain sorting during an update * Add test coverage, use cancel tokens * Change dropdown menu link name from "Profile" to "Dashboard" * Remove How to Bid section from position details (#89) * Homepage QA (#90) * Remove Inbox icon * Fetch notifications from any screen, since we no longer use a /login route * Remove the BetaHeader * Conditional rendering of Bid Count on Bid Tracker cards, update Results cards data order and style * Break out compare elements into their own rows, add Bid List button to comparisons, use Set() for bidListToggleIsLoading * Remove eslint-disable * feature: include org info for domestic positions (#92) * Remove bidListToggleIsLoading since that is handled in BidListButton container * Display the service needs filter as a pill on the results page (#95) * Add error handling for position details screen (#93) * Add error handling for position details screen * Update call to action * Update based on design feedback * dev -> staging * Make icons consistent throughout profile pages (#102) * Service Needs -> Featured (#100) * Add hover to dropdown (#103) --- .codeclimate.yml | 4 +- package.json | 1 + .../AccountDropdown/AccountDropdown.jsx | 26 +- .../AccountDropdown.test.jsx.snap | 122 +-- .../BidTrackerCard/BidTrackerCard.jsx | 10 +- .../BidTrackerCard.test.jsx.snap | 1 + .../BidTrackerCardBottom.jsx | 6 +- .../BidTrackerCardContainer.jsx | 19 +- .../BidTrackerCardContainer.test.jsx.snap | 2 + .../BidTrackerCardTitle.jsx | 28 +- .../BidTrackerCardTitle.test.jsx.snap | 40 + .../BidTrackerCardTop/BidTrackerCardTop.jsx | 5 +- .../BidTrackerCardTop.test.jsx.snap | 1 + .../PriorityCards/IsOnStandby/IsOnStandby.jsx | 17 +- .../__snapshots__/IsOnStandby.test.jsx.snap | 19 +- .../BidderPortfolioCard.jsx | 2 +- .../BidderPortfolioCard.test.jsx.snap | 1 + .../BidderPortfolioGridItem.jsx | 1 + .../BidderPortfolioGridItem.test.jsx.snap | 1 + .../BidderPortfolioGridListHeader.jsx | 4 +- ...idderPortfolioGridListHeader.test.jsx.snap | 4 +- .../BidderPortfolioSearch.jsx | 2 +- .../BidderPortfolioSearch.test.jsx.snap | 3 +- .../BidderPortfolioViewMore.jsx | 6 +- src/Components/CompareCheck/CompareCheck.jsx | 48 +- src/Components/CompareCheck/index.js | 1 + .../CompareDrawer/CompareDrawer.jsx | 74 ++ .../CompareDrawer/CompareDrawer.test.jsx | 36 + .../CompareDrawer/CompareDrawerContainer.jsx | 117 +++ .../CompareDrawerContainer.test.jsx | 31 + src/Components/CompareDrawer/index.js | 1 + src/Components/CompareList/CompareList.jsx | 158 +++- .../CompareList/CompareList.test.jsx | 1 + .../__snapshots__/CompareList.test.jsx.snap | 757 ++++++++++++++++-- src/Components/Favorite/Favorite.jsx | 2 +- .../FavoritePositions/FavoritePositions.jsx | 1 - .../FavoritePositions.test.jsx.snap | 1 - .../GlossaryEditorCard/GlossaryEditorCard.jsx | 15 +- .../GlossaryEditorSearch.test.jsx.snap | 1 + .../Header/DesktopNav/DesktopNav.jsx | 2 - .../__snapshots__/DesktopNav.test.jsx.snap | 3 - src/Components/Header/Header.jsx | 22 +- src/Components/Header/MobileNav/MobileNav.jsx | 2 +- .../__snapshots__/MobileNav.test.jsx.snap | 4 +- .../Header/Notifications/Notifications.jsx | 12 +- .../Notifications/Notifications.test.jsx | 17 - .../Header/__snapshots__/Header.test.jsx.snap | 2 - .../HomePagePositions/HomePagePositions.jsx | 2 +- .../HomePagePositions.test.jsx.snap | 4 +- .../HomePagePositionsList.jsx | 11 +- .../HomePagePositionsSection.test.jsx.snap | 1 - .../PositionDetails/PositionDetails.jsx | 20 +- .../PositionDetailsItem.jsx | 2 - .../PositionDetailsItem.test.jsx.snap | 3 - .../PositionTitle/PositionTitle.jsx | 6 +- .../__snapshots__/PositionTitle.test.jsx.snap | 1 - .../Assignments/AssignmentsList.jsx | 48 ++ .../Assignments/AssignmentsList.test.jsx | 23 + .../AssignmentsContent/AssignmentsContent.jsx | 47 ++ .../AssignmentsContent.test.jsx | 26 + .../AssignmentsContent.test.jsx.snap | 68 ++ .../AssignmentsContent/index.js | 1 + .../AssignmentsListResultsCard.jsx | 25 + .../AssignmentsListResultsCard.test.jsx | 35 + .../AssignmentsListResultsCard.test.jsx.snap | 48 ++ .../AssignmentsListResultsCard/index.js | 1 + .../AssignmentsList.test.jsx.snap | 90 +++ .../ProfileDashboard/Assignments/index.js | 1 + .../ProfileDashboard/BidList/BidList.jsx | 14 +- .../Favorites/FavoritesList.jsx | 2 +- .../__snapshots__/FavoritesList.test.jsx.snap | 4 +- .../PositionInformation.jsx | 2 +- .../PositionInformation/StartEnd/StartEnd.jsx | 8 +- .../ProfileDashboard/ProfileDashboard.jsx | 99 ++- .../UserProfileContactInformation.jsx | 2 +- ...serProfileContactInformation.test.jsx.snap | 2 +- .../ProfileMenuExpanded.test.jsx.snap | 4 + src/Components/ProfilePage/ProfilePage.jsx | 2 + .../__snapshots__/ProfilePage.test.jsx.snap | 4 + .../ResetComparisons/ResetComparisons.jsx | 10 +- src/Components/ResultsCard/ResultsCard.jsx | 20 +- .../ResultsControls/ResultsControls.jsx | 8 +- .../ResultsControls.test.jsx.snap | 12 +- src/Components/ResultsPage/ResultsPage.jsx | 10 - .../ResultsSearchHeader.jsx | 17 +- .../ResultsSearchHeader.test.jsx | 8 + .../ResultsSearchHeader.test.jsx.snap | 65 +- .../SavedSearches/SavedSearches.jsx | 2 +- .../__snapshots__/SavedSearches.test.jsx.snap | 2 +- .../SearchResultsExportLink.jsx | 82 ++ .../SearchResultsExportLink.test.jsx | 38 + .../SearchResultsExportLink.test.jsx.snap | 12 + .../SearchResultsExportLink/index.js | 1 + .../ViewComparisonLink/ViewComparisonLink.jsx | 2 +- src/Constants/EndpointParams.js | 1 + src/Constants/Menu.js | 1 + src/Constants/PropTypes.js | 3 + src/Constants/PropTypes.test.js | 4 + src/Constants/SetType.js | 56 ++ .../BidListButton/BidListButton.jsx | 12 +- .../BidListButton/BidListButton.test.jsx | 2 +- src/Containers/BidTracker/BidTracker.jsx | 6 +- src/Containers/Compare/Compare.jsx | 50 +- src/Containers/Position/Position.jsx | 10 +- .../ProfilePublic/ProfilePublic.jsx | 72 ++ .../ProfilePublic/ProfilePublic.test.jsx | 47 ++ src/Containers/ProfilePublic/index.js | 1 + src/Containers/Results/Results.jsx | 76 +- src/Containers/Routes/Routes.jsx | 2 +- src/actions/bidList.js | 11 +- src/actions/comparisons.js | 23 +- src/actions/results.js | 14 +- src/actions/userProfilePublic.js | 80 ++ src/actions/userProfilePublic.test.js | 80 ++ src/reducers/bidList/bidList.js | 11 +- src/reducers/bidList/bidList.test.js | 8 +- src/reducers/filters/filters.js | 22 +- src/reducers/index.js | 2 + src/reducers/userProfilePublic/index.js | 5 + .../userProfilePublic/userProfilePublic.js | 26 + .../userProfilePublic.test.js | 15 + src/routes.js | 3 +- src/sass/_bidCount.scss | 21 - src/sass/_bidTracker.scss | 8 + src/sass/_compare.scss | 27 +- src/sass/_compareDrawer.scss | 78 ++ src/sass/_condensedCard.scss | 43 +- src/sass/_details.scss | 17 + src/sass/_dropdown.scss | 5 +- src/sass/_profile.scss | 15 + src/sass/_results.scss | 39 +- src/sass/_resultsSearchBarContainer.scss | 27 +- src/sass/_table.scss | 145 +++- src/sass/styles.scss | 1 + src/utilities.js | 22 + yarn.lock | 5 + 136 files changed, 3056 insertions(+), 568 deletions(-) create mode 100644 src/Components/CompareCheck/index.js create mode 100644 src/Components/CompareDrawer/CompareDrawer.jsx create mode 100644 src/Components/CompareDrawer/CompareDrawer.test.jsx create mode 100644 src/Components/CompareDrawer/CompareDrawerContainer.jsx create mode 100644 src/Components/CompareDrawer/CompareDrawerContainer.test.jsx create mode 100644 src/Components/CompareDrawer/index.js create mode 100644 src/Components/ProfileDashboard/Assignments/AssignmentsList.jsx create mode 100644 src/Components/ProfileDashboard/Assignments/AssignmentsList.test.jsx create mode 100644 src/Components/ProfileDashboard/Assignments/AssignmentsListResultsCard/AssignmentsContent/AssignmentsContent.jsx create mode 100644 src/Components/ProfileDashboard/Assignments/AssignmentsListResultsCard/AssignmentsContent/AssignmentsContent.test.jsx create mode 100644 src/Components/ProfileDashboard/Assignments/AssignmentsListResultsCard/AssignmentsContent/__snapshots__/AssignmentsContent.test.jsx.snap create mode 100644 src/Components/ProfileDashboard/Assignments/AssignmentsListResultsCard/AssignmentsContent/index.js create mode 100644 src/Components/ProfileDashboard/Assignments/AssignmentsListResultsCard/AssignmentsListResultsCard.jsx create mode 100644 src/Components/ProfileDashboard/Assignments/AssignmentsListResultsCard/AssignmentsListResultsCard.test.jsx create mode 100644 src/Components/ProfileDashboard/Assignments/AssignmentsListResultsCard/__snapshots__/AssignmentsListResultsCard.test.jsx.snap create mode 100644 src/Components/ProfileDashboard/Assignments/AssignmentsListResultsCard/index.js create mode 100644 src/Components/ProfileDashboard/Assignments/__snapshots__/AssignmentsList.test.jsx.snap create mode 100644 src/Components/ProfileDashboard/Assignments/index.js create mode 100644 src/Components/SearchResultsExportLink/SearchResultsExportLink.jsx create mode 100644 src/Components/SearchResultsExportLink/SearchResultsExportLink.test.jsx create mode 100644 src/Components/SearchResultsExportLink/__snapshots__/SearchResultsExportLink.test.jsx.snap create mode 100644 src/Components/SearchResultsExportLink/index.js create mode 100644 src/Constants/SetType.js create mode 100644 src/Containers/ProfilePublic/ProfilePublic.jsx create mode 100644 src/Containers/ProfilePublic/ProfilePublic.test.jsx create mode 100644 src/Containers/ProfilePublic/index.js create mode 100644 src/actions/userProfilePublic.js create mode 100644 src/actions/userProfilePublic.test.js create mode 100644 src/reducers/userProfilePublic/index.js create mode 100644 src/reducers/userProfilePublic/userProfilePublic.js create mode 100644 src/reducers/userProfilePublic/userProfilePublic.test.js create mode 100644 src/sass/_compareDrawer.scss diff --git a/.codeclimate.yml b/.codeclimate.yml index 33dca3e66b..dfcfd42400 100644 --- a/.codeclimate.yml +++ b/.codeclimate.yml @@ -26,7 +26,9 @@ plugins: enabled: true exclude_patterns: - node_modules/ -- config/ # build configuration +- build/ # build artifacts +- config/ # build configuration - config/ # build configuration +- coverage/ # test coverage - scripts/ # build & utility scripts - src/__mocks__/ # test objects - src/Constants/PropTypes.js # shape definitions diff --git a/package.json b/package.json index b3512d8733..34fd8518d8 100644 --- a/package.json +++ b/package.json @@ -25,6 +25,7 @@ "rc-steps": "^3.1.0", "react": "^15.6.2", "react-autosuggest": "^9.3.2", + "react-csv": "^1.1.1", "react-dom": "^15.6.2", "react-fontawesome": "^1.6.1", "react-helmet": "^5.2.0", diff --git a/src/Components/AccountDropdown/AccountDropdown.jsx b/src/Components/AccountDropdown/AccountDropdown.jsx index e73d7c1a8e..7c1b838be8 100644 --- a/src/Components/AccountDropdown/AccountDropdown.jsx +++ b/src/Components/AccountDropdown/AccountDropdown.jsx @@ -38,7 +38,13 @@ export class AccountDropdown extends Component { return ( isLoading && - { this.dropdown = dropdown; }} removeElement> + { this.dropdown = dropdown; }} + removeElement + onMouseEnter={() => this.dropdown.show()} + onMouseLeave={() => this.dropdown.hide()} + > { @@ -46,14 +52,16 @@ export class AccountDropdown extends Component { {displayName} } - -
-
Signed in as
- {displayName} -
- Profile - Logout -
+
+ this.dropdown.show()}> +
+
Signed in as
+ {displayName} +
+ Dashboard + Logout +
+
); } diff --git a/src/Components/AccountDropdown/__snapshots__/AccountDropdown.test.jsx.snap b/src/Components/AccountDropdown/__snapshots__/AccountDropdown.test.jsx.snap index b194ceb5a0..9414734834 100644 --- a/src/Components/AccountDropdown/__snapshots__/AccountDropdown.test.jsx.snap +++ b/src/Components/AccountDropdown/__snapshots__/AccountDropdown.test.jsx.snap @@ -3,6 +3,8 @@ exports[`AccountDropdown matches snapshot 1`] = ` - -
-
- Signed in as +
+
+ Signed in as +
+ + John Doe +
- - John Doe - -
- - Profile - - - Logout - - + + Dashboard + + + Logout + + +
`; exports[`AccountDropdown matches snapshot when shouldDisplayName is true 1`] = ` - -
-
- Signed in as +
+
+ Signed in as +
+ + John Doe +
- - John Doe - -
- - Profile - - - Logout - - + + Dashboard + + + Logout + + +
`; diff --git a/src/Components/BidTracker/BidTrackerCard/BidTrackerCard.jsx b/src/Components/BidTracker/BidTrackerCard/BidTrackerCard.jsx index 050039e6b4..fe38cf1a51 100644 --- a/src/Components/BidTracker/BidTrackerCard/BidTrackerCard.jsx +++ b/src/Components/BidTracker/BidTrackerCard/BidTrackerCard.jsx @@ -14,7 +14,8 @@ import { DRAFT_PROP, } from '../../../Constants/BidData'; -const BidTrackerCard = ({ bid, acceptBid, declineBid, submitBid, deleteBid, userProfile }) => { +const BidTrackerCard = ({ bid, acceptBid, declineBid, submitBid, deleteBid, showBidCount, +userProfile }) => { // determine whether we render an alert on top of the card const showAlert = shouldShowAlert(bid); // determine whether we should show the contacts section based on the status @@ -28,6 +29,7 @@ const BidTrackerCard = ({ bid, acceptBid, declineBid, submitBid, deleteBid, user
@@ -66,6 +68,12 @@ BidTrackerCard.propTypes = { submitBid: PropTypes.func.isRequired, deleteBid: PropTypes.func.isRequired, userProfile: USER_PROFILE.isRequired, + showBidCount: PropTypes.bool, }; +BidTrackerCard.defaultProps = { + showBidCount: true, +}; + + export default BidTrackerCard; diff --git a/src/Components/BidTracker/BidTrackerCard/__snapshots__/BidTrackerCard.test.jsx.snap b/src/Components/BidTracker/BidTrackerCard/__snapshots__/BidTrackerCard.test.jsx.snap index 74e94170a9..f99dcce4e4 100644 --- a/src/Components/BidTracker/BidTrackerCard/__snapshots__/BidTrackerCard.test.jsx.snap +++ b/src/Components/BidTracker/BidTrackerCard/__snapshots__/BidTrackerCard.test.jsx.snap @@ -56,6 +56,7 @@ exports[`BidTrackerCardComponent matches snapshot 1`] = ` } } deleteBid={[Function]} + showBidCount={true} showQuestion={true} />
{ }; BidTrackerCardBottom.propTypes = { - reviewer: BID_REVIEWER_OBJECT.isRequired, + reviewer: BID_REVIEWER_OBJECT, bureau: PropTypes.string.isRequired, userProfile: USER_PROFILE.isRequired, }; +BidTrackerCardBottom.defaultProps = { + reviewer: null, +}; + export default BidTrackerCardBottom; diff --git a/src/Components/BidTracker/BidTrackerCardContainer/BidTrackerCardContainer.jsx b/src/Components/BidTracker/BidTrackerCardContainer/BidTrackerCardContainer.jsx index e610605153..5dab0b60d6 100644 --- a/src/Components/BidTracker/BidTrackerCardContainer/BidTrackerCardContainer.jsx +++ b/src/Components/BidTracker/BidTrackerCardContainer/BidTrackerCardContainer.jsx @@ -4,6 +4,12 @@ import { BID_OBJECT, USER_PROFILE } from '../../../Constants/PropTypes'; import BidTrackerCard from '../BidTrackerCard'; import IsPriority from '../PriorityCards/IsPriority'; import IsOnStandby from '../PriorityCards/IsOnStandby'; +import { DRAFT_PROP } from '../../../Constants/BidData'; + +// assign values to constants for equality checks later +const DEFAULT = 'default'; +const PRIORITY = 'priority'; +const STANDBY = 'standby'; // Here we'll figure out which wrapper to use around the BidTrackerCard, if any. // We check two things - one, is there even a priority bid in the list (priorityExists). @@ -12,7 +18,7 @@ import IsOnStandby from '../PriorityCards/IsOnStandby'; // object to the IsOnStandby component. const BidTrackerCardContainer = ({ bid, acceptBid, declineBid, priorityExists, userProfile, submitBid, deleteBid }) => { - const card = ( + const getCard = ({ ...props }) => ( { userProfile={userProfile} submitBid={submitBid} deleteBid={deleteBid} + showBidCount={bid.status !== DRAFT_PROP} + {...props} /> ); - // assign values to constants for equality checks later - const DEFAULT = 'default'; - const PRIORITY = 'priority'; - const STANDBY = 'standby'; - // Set a displayType and change it based on priority. // This way we can ensure that we only have one output in our return let displayType = DEFAULT; @@ -37,13 +40,13 @@ submitBid, deleteBid }) => { let cardComponent; switch (displayType) { case PRIORITY: - cardComponent = ({card}); + cardComponent = ({getCard({ showBidCount: false })}); break; case STANDBY: cardComponent = (); break; default: - cardComponent = card; + cardComponent = getCard(); } return ( diff --git a/src/Components/BidTracker/BidTrackerCardContainer/__snapshots__/BidTrackerCardContainer.test.jsx.snap b/src/Components/BidTracker/BidTrackerCardContainer/__snapshots__/BidTrackerCardContainer.test.jsx.snap index f286ce3195..105e7ab34a 100644 --- a/src/Components/BidTracker/BidTrackerCardContainer/__snapshots__/BidTrackerCardContainer.test.jsx.snap +++ b/src/Components/BidTracker/BidTrackerCardContainer/__snapshots__/BidTrackerCardContainer.test.jsx.snap @@ -57,6 +57,7 @@ exports[`BidTrackerCardContainerComponent matches snapshot when priorityExists i } declineBid={[Function]} deleteBid={[Function]} + showBidCount={true} submitBid={[Function]} userProfile={ Object { @@ -232,6 +233,7 @@ exports[`BidTrackerCardContainerComponent matches snapshot when priorityExists i } declineBid={[Function]} deleteBid={[Function]} + showBidCount={false} submitBid={[Function]} userProfile={ Object { diff --git a/src/Components/BidTracker/BidTrackerCardTitle/BidTrackerCardTitle.jsx b/src/Components/BidTracker/BidTrackerCardTitle/BidTrackerCardTitle.jsx index d2bb379638..e2e1b6d537 100644 --- a/src/Components/BidTracker/BidTrackerCardTitle/BidTrackerCardTitle.jsx +++ b/src/Components/BidTracker/BidTrackerCardTitle/BidTrackerCardTitle.jsx @@ -3,10 +3,9 @@ import PropTypes from 'prop-types'; import { Link } from 'react-router-dom'; import { BID_STATISTICS_OBJECT, POST_DETAILS } from '../../../Constants/PropTypes'; import BidCount from '../../BidCount'; -import { SUBMITTED_PROP } from '../../../Constants/BidData'; import { getPostName } from '../../../utilities'; -const BidTrackerCardTitle = ({ title, id, status, bidStatistics, post }) => ( +const BidTrackerCardTitle = ({ title, id, bidStatistics, post, showBidCount }) => (
{title}
@@ -14,25 +13,30 @@ const BidTrackerCardTitle = ({ title, id, status, bidStatistics, post }) => ( View position
- {status === SUBMITTED_PROP && -
-
- Post: {getPostName(post)} -
- - - +
+
+ Post: {getPostName(post)}
- } + { + showBidCount && + + + + } +
); BidTrackerCardTitle.propTypes = { title: PropTypes.string.isRequired, id: PropTypes.number.isRequired, - status: PropTypes.string.isRequired, bidStatistics: BID_STATISTICS_OBJECT.isRequired, post: POST_DETAILS.isRequired, + showBidCount: PropTypes.bool, +}; + +BidTrackerCardTitle.defaultProps = { + showBidCount: true, }; export default BidTrackerCardTitle; diff --git a/src/Components/BidTracker/BidTrackerCardTitle/__snapshots__/BidTrackerCardTitle.test.jsx.snap b/src/Components/BidTracker/BidTrackerCardTitle/__snapshots__/BidTrackerCardTitle.test.jsx.snap index 080e0ef536..333e220142 100644 --- a/src/Components/BidTracker/BidTrackerCardTitle/__snapshots__/BidTrackerCardTitle.test.jsx.snap +++ b/src/Components/BidTracker/BidTrackerCardTitle/__snapshots__/BidTrackerCardTitle.test.jsx.snap @@ -89,5 +89,45 @@ exports[`BidTrackerCardTitleComponent matches snapshot when status is not "submi
+
+
+ + Post: + + + Maseru, Lesotho +
+ + + +
`; diff --git a/src/Components/BidTracker/BidTrackerCardTop/BidTrackerCardTop.jsx b/src/Components/BidTracker/BidTrackerCardTop/BidTrackerCardTop.jsx index 5448a204cf..40e5ce92e3 100644 --- a/src/Components/BidTracker/BidTrackerCardTop/BidTrackerCardTop.jsx +++ b/src/Components/BidTracker/BidTrackerCardTop/BidTrackerCardTop.jsx @@ -21,7 +21,7 @@ class BidTrackerCardTop extends Component { } render() { - const { bid, showQuestion } = this.props; + const { bid, showBidCount, showQuestion } = this.props; const bidStatistics = get(bid, 'position.bid_statistics[0]', {}); const post = get(bid, 'position.post', {}); return ( @@ -33,6 +33,7 @@ class BidTrackerCardTop extends Component { status={bid.status} bidStatistics={bidStatistics} post={post} + showBidCount={showBidCount} />
@@ -64,10 +65,12 @@ BidTrackerCardTop.propTypes = { bid: BID_OBJECT.isRequired, showQuestion: PropTypes.bool, // Determine whether or not to show the question text deleteBid: PropTypes.func.isRequired, + showBidCount: PropTypes.bool, }; BidTrackerCardTop.defaultProps = { showQuestion: true, + showBidCount: true, }; export default BidTrackerCardTop; diff --git a/src/Components/BidTracker/BidTrackerCardTop/__snapshots__/BidTrackerCardTop.test.jsx.snap b/src/Components/BidTracker/BidTrackerCardTop/__snapshots__/BidTrackerCardTop.test.jsx.snap index b79fb5080b..e09e9e2b9f 100644 --- a/src/Components/BidTracker/BidTrackerCardTop/__snapshots__/BidTrackerCardTop.test.jsx.snap +++ b/src/Components/BidTracker/BidTrackerCardTop/__snapshots__/BidTrackerCardTop.test.jsx.snap @@ -22,6 +22,7 @@ exports[`BidTrackerCardTopComponent matches snapshot 1`] = ` }, } } + showBidCount={true} status="approved" title="POLITICAL/ECONOMIC OFFICER" /> diff --git a/src/Components/BidTracker/PriorityCards/IsOnStandby/IsOnStandby.jsx b/src/Components/BidTracker/PriorityCards/IsOnStandby/IsOnStandby.jsx index 501542adc0..4f829a51af 100644 --- a/src/Components/BidTracker/PriorityCards/IsOnStandby/IsOnStandby.jsx +++ b/src/Components/BidTracker/PriorityCards/IsOnStandby/IsOnStandby.jsx @@ -17,14 +17,21 @@ const IsOnStandby = ({ bid, deleteBid }) => { bid-tracker-standby-container ${useDisabledClass && 'standby-container-disabled'}`} >
-
- { useDisabledClass && } - {bidStatus} +
+
+ { useDisabledClass && } + {bidStatus} +
+ { !useDisabledClass &&
(on-hold)
}
- { !useDisabledClass &&
(on-hold)
}
- +
); diff --git a/src/Components/BidTracker/PriorityCards/IsOnStandby/__snapshots__/IsOnStandby.test.jsx.snap b/src/Components/BidTracker/PriorityCards/IsOnStandby/__snapshots__/IsOnStandby.test.jsx.snap index 4bc89bf541..3e44a928cf 100644 --- a/src/Components/BidTracker/PriorityCards/IsOnStandby/__snapshots__/IsOnStandby.test.jsx.snap +++ b/src/Components/BidTracker/PriorityCards/IsOnStandby/__snapshots__/IsOnStandby.test.jsx.snap @@ -9,14 +9,18 @@ exports[`IsOnStandbyComponent matches snapshot 1`] = ` className="padded-container-inner bid-tracker-standby-title" >
- Approved -
-
- (on-hold) +
+ Approved +
+
+ (on-hold) +
diff --git a/src/Components/BidderPortfolio/BidderPortfolioCard/BidderPortfolioCard.jsx b/src/Components/BidderPortfolio/BidderPortfolioCard/BidderPortfolioCard.jsx index bb29311ac3..04835744c4 100644 --- a/src/Components/BidderPortfolio/BidderPortfolioCard/BidderPortfolioCard.jsx +++ b/src/Components/BidderPortfolio/BidderPortfolioCard/BidderPortfolioCard.jsx @@ -18,7 +18,7 @@ const BidderPortfolioCard = ({ userProfile }) => ( draft={userProfile.bid_statistics[0] ? userProfile.bid_statistics[0].draft : 0} submitted={userProfile.bid_statistics[0] ? userProfile.bid_statistics[0].submitted : 0} /> - + ); diff --git a/src/Components/BidderPortfolio/BidderPortfolioCard/__snapshots__/BidderPortfolioCard.test.jsx.snap b/src/Components/BidderPortfolio/BidderPortfolioCard/__snapshots__/BidderPortfolioCard.test.jsx.snap index fa48d12afe..42939a9466 100644 --- a/src/Components/BidderPortfolio/BidderPortfolioCard/__snapshots__/BidderPortfolioCard.test.jsx.snap +++ b/src/Components/BidderPortfolio/BidderPortfolioCard/__snapshots__/BidderPortfolioCard.test.jsx.snap @@ -123,6 +123,7 @@ exports[`BidderPortfolioCardComponent matches snapshot 1`] = ` /> diff --git a/src/Components/BidderPortfolio/BidderPortfolioGridItem/__snapshots__/BidderPortfolioGridItem.test.jsx.snap b/src/Components/BidderPortfolio/BidderPortfolioGridItem/__snapshots__/BidderPortfolioGridItem.test.jsx.snap index 2135d9da0e..f456ae910e 100644 --- a/src/Components/BidderPortfolio/BidderPortfolioGridItem/__snapshots__/BidderPortfolioGridItem.test.jsx.snap +++ b/src/Components/BidderPortfolio/BidderPortfolioGridItem/__snapshots__/BidderPortfolioGridItem.test.jsx.snap @@ -152,6 +152,7 @@ exports[`BidderPortfolioGridItemComponent matches snapshot 1`] = ` > ( />
- +
- +
diff --git a/src/Components/BidderPortfolio/BidderPortfolioGridListHeader/__snapshots__/BidderPortfolioGridListHeader.test.jsx.snap b/src/Components/BidderPortfolio/BidderPortfolioGridListHeader/__snapshots__/BidderPortfolioGridListHeader.test.jsx.snap index db53754f71..b889158192 100644 --- a/src/Components/BidderPortfolio/BidderPortfolioGridListHeader/__snapshots__/BidderPortfolioGridListHeader.test.jsx.snap +++ b/src/Components/BidderPortfolio/BidderPortfolioGridListHeader/__snapshots__/BidderPortfolioGridListHeader.test.jsx.snap @@ -34,7 +34,7 @@ exports[`BidderPortfolioGridListHeaderComponent matches snapshot 1`] = ` >
@@ -44,7 +44,7 @@ exports[`BidderPortfolioGridListHeaderComponent matches snapshot 1`] = ` >
(
diff --git a/src/Components/BidderPortfolio/BidderPortfolioSearch/__snapshots__/BidderPortfolioSearch.test.jsx.snap b/src/Components/BidderPortfolio/BidderPortfolioSearch/__snapshots__/BidderPortfolioSearch.test.jsx.snap index 812d0db4f3..eb64b0d08d 100644 --- a/src/Components/BidderPortfolio/BidderPortfolioSearch/__snapshots__/BidderPortfolioSearch.test.jsx.snap +++ b/src/Components/BidderPortfolio/BidderPortfolioSearch/__snapshots__/BidderPortfolioSearch.test.jsx.snap @@ -9,10 +9,11 @@ exports[`BidderPortfolioSearchComponent matches snapshot 1`] = ` >
diff --git a/src/Components/BidderPortfolio/BidderPortfolioViewMore/BidderPortfolioViewMore.jsx b/src/Components/BidderPortfolio/BidderPortfolioViewMore/BidderPortfolioViewMore.jsx index 99d601885b..5357095194 100644 --- a/src/Components/BidderPortfolio/BidderPortfolioViewMore/BidderPortfolioViewMore.jsx +++ b/src/Components/BidderPortfolio/BidderPortfolioViewMore/BidderPortfolioViewMore.jsx @@ -3,9 +3,9 @@ import { Link } from 'react-router-dom'; import PropTypes from 'prop-types'; import { EMPTY_FUNCTION } from '../../../Constants/PropTypes'; -const BidderPortfolioViewMore = ({ className, useLink, onClick, isExpanded }) => { +const BidderPortfolioViewMore = ({ className, id, useLink, onClick, isExpanded }) => { const text = isExpanded ? 'Close' : 'View profile'; - const link = '/profile/dashboard/'; + const link = `/profile/public/${id}`; return (
{ @@ -34,6 +34,7 @@ BidderPortfolioViewMore.propTypes = { useLink: PropTypes.bool, onClick: PropTypes.func, isExpanded: PropTypes.bool, + id: PropTypes.number, }; BidderPortfolioViewMore.defaultProps = { @@ -41,6 +42,7 @@ BidderPortfolioViewMore.defaultProps = { useLink: false, onClick: EMPTY_FUNCTION, isExpanded: false, + id: null, }; export default BidderPortfolioViewMore; diff --git a/src/Components/CompareCheck/CompareCheck.jsx b/src/Components/CompareCheck/CompareCheck.jsx index 9320c455dd..c202d00fdb 100644 --- a/src/Components/CompareCheck/CompareCheck.jsx +++ b/src/Components/CompareCheck/CompareCheck.jsx @@ -10,6 +10,7 @@ class CompareCheck extends Component { constructor(props) { super(props); this.toggleSaved = this.toggleSaved.bind(this); + this.eventListener = this.eventListener.bind(this); this.state = { saved: false, localStorageKey: null, @@ -20,12 +21,19 @@ class CompareCheck extends Component { componentWillMount() { const localStorageKey = this.props.type; this.setState({ localStorageKey }); + + // add listener on localStorage 'compare' key + window.addEventListener('compare-ls', this.eventListener); } componentDidMount() { this.getSaved(); } + componentWillUnmount() { + window.removeEventListener('compare-ls', this.eventListener); + } + onToggle() { this.props.onToggle(); } @@ -55,36 +63,54 @@ class CompareCheck extends Component { } } + eventListener() { + this.getSaved(); + } + + // eslint-disable-next-line class-methods-use-this + joinClassNames(className) { + return className + .join(' ') + .trim(); + } + render() { - const { className, as: type } = this.props; + const { className, customElement, as: type, interactiveElementProps } = this.props; const isChecked = this.getSavedState(); - const text = this.isDisabled() ? 'Limit Reached' : 'Compare'; - const icon = isChecked ? 'check-square-o' : 'square-o'; const options = { type, className: [className, 'compare-check-box-container'], onClick: this.toggleSaved, + ...interactiveElementProps, }; + let text = 'Compare'; + let icon = 'square-o'; + if (isChecked) { options.className.push('usa-button-active'); + icon = 'check-square-o'; } if (this.isDisabled()) { options.disabled = true; + text = 'Limit Reached'; } - options.className = options.className - .join(' ') - .trim(); + options.className = this.joinClassNames(options.className); return ( - - { + customElement ? + + {customElement} + + : + + { !this.isDisabled() && } {text} - + ); } } @@ -99,6 +125,8 @@ CompareCheck.propTypes = { type: PropTypes.string, limit: PropTypes.number, onToggle: PropTypes.func, + customElement: PropTypes.node, + interactiveElementProps: PropTypes.shape({}), }; CompareCheck.defaultProps = { @@ -107,6 +135,8 @@ CompareCheck.defaultProps = { type: 'compare', limit: COMPARE_LIMIT, onToggle: EMPTY_FUNCTION, + customElement: null, + interactiveElementProps: {}, }; export default CompareCheck; diff --git a/src/Components/CompareCheck/index.js b/src/Components/CompareCheck/index.js new file mode 100644 index 0000000000..f1543ba1a0 --- /dev/null +++ b/src/Components/CompareCheck/index.js @@ -0,0 +1 @@ +export { default } from './CompareCheck'; diff --git a/src/Components/CompareDrawer/CompareDrawer.jsx b/src/Components/CompareDrawer/CompareDrawer.jsx new file mode 100644 index 0000000000..30e3eeb9c8 --- /dev/null +++ b/src/Components/CompareDrawer/CompareDrawer.jsx @@ -0,0 +1,74 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import FA from 'react-fontawesome'; +import shortid from 'shortid'; +import COMPARE_LIMIT from '../../Constants/Compare'; +import { getPostName } from '../../utilities'; +import { + NO_GRADE, + NO_POST, +} from '../../Constants/SystemMessages'; +import CompareCheck from '../CompareCheck'; +import ViewComparisonLink from '../ViewComparisonLink/ViewComparisonLink'; +import ResetComparisons from '../ResetComparisons/ResetComparisons'; +import { COMPARE_LIST } from '../../Constants/PropTypes'; + +const CompareDrawer = ({ comparisons, isHidden }) => { + const limit = 5; + const compareArray = (comparisons || []).slice(0, COMPARE_LIMIT); + const emptyArray = Array(limit - compareArray.length).fill(); + return ( +
+
+ { + compareArray.map(c => ( +
+
+ } + interactiveElementProps={{ title: 'Remove this comparison' }} + /> +
+ + {c.title} + + + Grade: {c.grade || NO_GRADE} + + + Post: {getPostName(c.post, NO_POST)} + +
+ )) + } + { + emptyArray.map(() => ( +
+ )) + } +
+ + +
+
+
+ ); +}; + +CompareDrawer.propTypes = { + comparisons: COMPARE_LIST, + isHidden: PropTypes.bool, +}; + +CompareDrawer.defaultProps = { + comparisons: [], + isHidden: false, +}; + +export default CompareDrawer; diff --git a/src/Components/CompareDrawer/CompareDrawer.test.jsx b/src/Components/CompareDrawer/CompareDrawer.test.jsx new file mode 100644 index 0000000000..3ee24621f6 --- /dev/null +++ b/src/Components/CompareDrawer/CompareDrawer.test.jsx @@ -0,0 +1,36 @@ +import { shallow } from 'enzyme'; +import React from 'react'; +import CompareDrawer from './CompareDrawer'; +import resultsObject from '../../__mocks__/resultsObject'; + +describe('CompareDrawer', () => { + const props = { + comparisons: resultsObject.results, + isHidden: false, + }; + it('is defined', () => { + const wrapper = shallow(); + expect(wrapper).toBeDefined(); + }); + + it('applies correct css class when isHidden === true', () => { + const wrapper = shallow(); + expect(wrapper.find('.drawer-hidden').exists()).toBe(true); + }); + + it('does not apply new css class when isHidden === false', () => { + const wrapper = shallow(); + expect(wrapper.find('.drawer-hidden').exists()).toBe(false); + }); + + it('displays the correct number of results data cards', () => { + const wrapper = shallow(); + expect(wrapper.find('.check-container').length).toBe(resultsObject.results.length); + }); + + it('displays the correct number of empty cards', () => { + const maxCards = 5; + const wrapper = shallow(); + expect(wrapper.find('.compare-item-empty').length).toBe(maxCards - resultsObject.results.length); + }); +}); diff --git a/src/Components/CompareDrawer/CompareDrawerContainer.jsx b/src/Components/CompareDrawer/CompareDrawerContainer.jsx new file mode 100644 index 0000000000..23c387c587 --- /dev/null +++ b/src/Components/CompareDrawer/CompareDrawerContainer.jsx @@ -0,0 +1,117 @@ +import React, { Component } from 'react'; +import PropTypes from 'prop-types'; +import { connect } from 'react-redux'; +import { isEqual, omit } from 'lodash'; +import { comparisonsFetchData } from '../../actions/comparisons'; +import CompareDrawer from './CompareDrawer'; +import { COMPARE_LIST } from '../../Constants/PropTypes'; +import { getScrollDistanceFromBottom } from '../../utilities'; + +class Compare extends Component { + constructor(props) { + super(props); + this.lsListener = this.lsListener.bind(this); + this.scrollListener = this.scrollListener.bind(this); + + /* set to 0 for now, but could change to the distance in px from the bottom of the screen + that you want the drawer to hide at */ + this.scrollDistance = 0; + + this.state = { + prevComparisons: [], + comparisons: [], + isHidden: false, + }; + } + + componentWillMount() { + // initialize with any existing comparison choices + const ls = localStorage.getItem('compare') || '[]'; + const initialArr = JSON.parse(ls); + this.setState({ comparisons: initialArr }, () => { + this.getComparisons(this.state.comparisons.toString()); + }); + + // add listener on localStorage 'compare' key + window.addEventListener('compare-ls', this.lsListener); + + // add listener for scroll location, to hide the comparison farther down the page + window.addEventListener('scroll', this.scrollListener); + } + + shouldComponentUpdate(nextProps, nextState) { + // we ignore comparisons state, since its just tracking the user's choices + return !(isEqual(nextProps, this.props)) || !(isEqual(omit(nextState, 'comparisons'), omit(this.state, 'comparisons'))); + } + + componentWillUnmount() { + window.removeEventListener('compare-ls', this.lsListener); + } + + getComparisons(ids) { + this.props.fetchData(ids); + } + + lsListener() { + const comparisons = JSON.parse(localStorage.getItem('compare') || []); + this.setState({ prevComparisons: this.state.comparisons, comparisons }, () => { + this.getComparisons(this.state.comparisons.toString()); + }); + } + + scrollListener() { + const { isHidden } = this.state; + + // eslint-disable-next-line no-unused-expressions + getScrollDistanceFromBottom() < this.scrollDistance ? + !isHidden && this.setState({ isHidden: true }) + : + isHidden && this.setState({ isHidden: false }); + } + + render() { + const { isHidden, comparisons: comparisonsState, prevComparisons } = this.state; + const { comparisons, hasErrored } = this.props; + + const comparisonsToUse = comparisonsState.length > prevComparisons.length + ? comparisonsState : prevComparisons; + + /* sort based on any prior compare list, so the cards don't get jumbled + after one is removed, as it persists until the new request completes */ + const sortedComparisons = comparisons.sort((a, b) => + (comparisonsToUse.indexOf(a.position_number) > + comparisonsToUse.indexOf(b.position_number) ? 1 : -1), + ); + + const isHidden$ = isHidden || !sortedComparisons.length; + return ( + + ); + } +} + +Compare.propTypes = { + fetchData: PropTypes.func.isRequired, + hasErrored: PropTypes.bool, + comparisons: COMPARE_LIST, +}; + +Compare.defaultProps = { + comparisons: [], + hasErrored: false, +}; + +const mapStateToProps = state => ({ + comparisons: state.comparisons, + hasErrored: state.comparisonsHasErrored, +}); + +export const mapDispatchToProps = dispatch => ({ + fetchData: url => dispatch(comparisonsFetchData(url)), +}); + +export default connect(mapStateToProps, mapDispatchToProps)(Compare); diff --git a/src/Components/CompareDrawer/CompareDrawerContainer.test.jsx b/src/Components/CompareDrawer/CompareDrawerContainer.test.jsx new file mode 100644 index 0000000000..fb97471994 --- /dev/null +++ b/src/Components/CompareDrawer/CompareDrawerContainer.test.jsx @@ -0,0 +1,31 @@ +import React from 'react'; +import TestUtils from 'react-dom/test-utils'; +import { Provider } from 'react-redux'; +import { MemoryRouter } from 'react-router-dom'; +import configureStore from 'redux-mock-store'; +import thunk from 'redux-thunk'; +import { testDispatchFunctions } from '../../testUtilities/testUtilities'; +import CompareDrawerContainer, { mapDispatchToProps } from './CompareDrawerContainer'; +import resultsObject from '../../__mocks__/resultsObject'; + +const middlewares = [thunk]; +const mockStore = configureStore(middlewares); + +describe('CompareDrawerContainer', () => { + it('is defined', () => { + const compare = TestUtils.renderIntoDocument( + {}} + comparisons={resultsObject.results} + /> + ); + expect(compare).toBeDefined(); + }); +}); + +describe('mapDispatchToProps', () => { + const config = { + fetchData: ['1,2'], + }; + testDispatchFunctions(mapDispatchToProps, config); +}); diff --git a/src/Components/CompareDrawer/index.js b/src/Components/CompareDrawer/index.js new file mode 100644 index 0000000000..bb62ca87f3 --- /dev/null +++ b/src/Components/CompareDrawer/index.js @@ -0,0 +1 @@ +export { default } from './CompareDrawerContainer'; diff --git a/src/Components/CompareList/CompareList.jsx b/src/Components/CompareList/CompareList.jsx index 6b49fb51ac..f668695be9 100644 --- a/src/Components/CompareList/CompareList.jsx +++ b/src/Components/CompareList/CompareList.jsx @@ -2,23 +2,35 @@ import React from 'react'; import PropTypes from 'prop-types'; import { Link } from 'react-router-dom'; import shortId from 'shortid'; +import { get } from 'lodash'; +import FA from 'react-fontawesome'; import BackButton from '../BackButton'; -import { COMPARE_LIST } from '../../Constants/PropTypes'; +import { BID_LIST, COMPARE_LIST, POSITION_SEARCH_RESULTS } from '../../Constants/PropTypes'; +import { POSITION_RESULTS_OBJECT } from '../../Constants/DefaultProps'; import COMPARE_LIMIT from '../../Constants/Compare'; import { NO_POST, NO_TOUR_OF_DUTY, NO_BUREAU, NO_SKILL, NO_DATE, NO_POST_DIFFERENTIAL, NO_DANGER_PAY } from '../../Constants/SystemMessages'; import Spinner from '../Spinner'; import LanguageList from '../LanguageList/LanguageList'; import { propOrDefault, formatDate, getPostName, getDifferentialPercentage, getAccessiblePositionNumber } from '../../utilities'; import OBCUrl from '../OBCUrl'; +import BidCount from '../BidCount'; +import Favorite from '../../Containers/Favorite'; +import CompareCheck from '../CompareCheck'; +import BidListButton from '../../Containers/BidListButton'; +import PermissionsWrapper from '../../Containers/PermissionsWrapper'; -const CompareList = ({ compare, isLoading }) => { +const CompareList = ({ bidList, compare, isLoading, favorites, +onToggle }) => { + const limit = 5; const compareArray = compare.slice(0, COMPARE_LIMIT); + const emptyArray = Array(limit - compareArray.length).fill(); return ( -
+
+

Compare Positions

{ isLoading ? @@ -26,45 +38,90 @@ const CompareList = ({ compare, isLoading }) => {
- + { compareArray.map(c => ( )) } + { + emptyArray.map(() => ()) + } in the */} -
+ Position { compareArray.map(c => (
*/} -
)) } + { + emptyArray.map(() => (
)) + } + + + + { + compareArray.map(c => ( + + )) + } + { + emptyArray.map(() => + + + { + compareArray.map((c) => { + const bidStatistics = get(c, 'bid_statistics[0]', {}); + return ( + + ); + }) + } + { + emptyArray.map(() => @@ -73,6 +130,9 @@ const CompareList = ({ compare, isLoading }) => { )) } + { + emptyArray.map(() => @@ -81,6 +141,9 @@ const CompareList = ({ compare, isLoading }) => { )) } + { + emptyArray.map(() => @@ -103,6 +169,9 @@ const CompareList = ({ compare, isLoading }) => { )) } + { + emptyArray.map(() => @@ -113,6 +182,9 @@ const CompareList = ({ compare, isLoading }) => { )) } + { + emptyArray.map(() => @@ -124,6 +196,9 @@ const CompareList = ({ compare, isLoading }) => { )) } + { + emptyArray.map(() => @@ -147,10 +225,51 @@ const CompareList = ({ compare, isLoading }) => { )) } + { + emptyArray.map(() => + + + { + compareArray.map(c => ( + + )) + } + { + emptyArray.map(() => + + + { + compareArray.map(c => ( + + )) + } + { + emptyArray.map(() =>
Position details comparison:
Position -
{c.title}
-
- View position -
-
+ Return to search results and add more positions to compare. +
- Position number - {/* border-extension-layer-2 border-visible-layer-2 should be inside - of first data point's
+
+
{c.title}
+
+ onToggle(c.position_number)} + refKey={c.position_number} + customElement={} + interactiveElementProps={{ title: 'Remove this comparison' }} + /> +
+
{c.position_number} - {/* border-extension-layer-2 should be inside - of first data point's
in the
+ Return to search results and add more positions to compare. +
Link to details +
+ View position +
+
) + } +
+ Bid Count + + + + + ) + }
Skill{c.skill || NO_SKILL}) + }
Bureau{c.bureau || NO_BUREAU}) + }
@@ -93,6 +156,9 @@ const CompareList = ({ compare, isLoading }) => { )) } + { + emptyArray.map(() => ) + }
Tour of duty) + }
Language) + }
Post differential) + }
@@ -137,6 +212,9 @@ const CompareList = ({ compare, isLoading }) => { )) } + { + emptyArray.map(() => ) + }
TED) + } +
Favorite + + ) + } +
Add to Bid List + + + + ) + }
-
}
@@ -161,11 +280,16 @@ const CompareList = ({ compare, isLoading }) => { CompareList.propTypes = { compare: COMPARE_LIST, isLoading: PropTypes.bool, + favorites: POSITION_SEARCH_RESULTS, + onToggle: PropTypes.func.isRequired, + bidList: BID_LIST, }; CompareList.defaultProps = { compare: [], isLoading: false, + favorites: POSITION_RESULTS_OBJECT, + bidList: { results: [] }, }; export default CompareList; diff --git a/src/Components/CompareList/CompareList.test.jsx b/src/Components/CompareList/CompareList.test.jsx index ddb6d5bc0a..9c001c5831 100644 --- a/src/Components/CompareList/CompareList.test.jsx +++ b/src/Components/CompareList/CompareList.test.jsx @@ -7,6 +7,7 @@ import resultsObject from '../../__mocks__/resultsObject'; describe('CompareListComponent', () => { const props = { goBackLink: { text: 'Go back to search results' }, + onToggle: () => {}, }; it('is defined', () => { const wrapper = shallow(); diff --git a/src/Components/CompareList/__snapshots__/CompareList.test.jsx.snap b/src/Components/CompareList/__snapshots__/CompareList.test.jsx.snap index 7c30509f0d..c9f84468dd 100644 --- a/src/Components/CompareList/__snapshots__/CompareList.test.jsx.snap +++ b/src/Components/CompareList/__snapshots__/CompareList.test.jsx.snap @@ -2,7 +2,7 @@ exports[`CompareListComponent matches snapshot 1`] = `
@@ -10,6 +10,9 @@ exports[`CompareListComponent matches snapshot 1`] = `
+

+ Compare Positions +

@@ -21,15 +24,14 @@ exports[`CompareListComponent matches snapshot 1`] = ` > Position details comparison: - + Position -
OMS (DCM)
+ + +
+ SPECIAL AGENT +
+ + + Return to search results and add more positions to compare. + + + Return to search results and add more positions to compare. + + + Return to search results and add more positions to compare. + + + + + + + Position + + +
+
+ OMS (DCM) +
+
+ + } + interactiveElementProps={ + Object { + "title": "Remove this comparison", + } + } + limit={5} + onToggle={[Function]} + refKey="10034001" + type="compare" + /> +
+
+ + 10034001 + + + +
+
+ SPECIAL AGENT +
+
+ + } + interactiveElementProps={ + Object { + "title": "Remove this comparison", + } + } + limit={5} + onToggle={[Function]} + refKey="56082000" + type="compare" + /> +
+
+ + 56082000 + + + + Return to search results and add more positions to compare. + + + Return to search results and add more positions to compare. + + + Return to search results and add more positions to compare. + + + + + Link to details + +
@@ -47,16 +180,8 @@ exports[`CompareListComponent matches snapshot 1`] = ` View position
-
- - -
- SPECIAL AGENT -
+ +
@@ -67,42 +192,74 @@ exports[`CompareListComponent matches snapshot 1`] = ` View position
-
- + + + + - - - Position number -
+ Bid Count - 10034001 + -
- 56082000 + -
+ + + SECURITY (2501) + + + (WHA) BUREAU OF WESTERN HEMISPHERIC AFFAIRS + + + Chicago, IL + + + 2 YRS (2 R & R) + + + + + + 20% + + + 15% + + + 03/13/2020 + + + + + + + Favorite + + + + + + + + + + + + + + Add to Bid List + + + + + + + + + + + + + + -
@@ -243,7 +543,7 @@ exports[`CompareListComponent matches snapshot 1`] = ` exports[`CompareListComponent matches snapshot when isLoading is true 1`] = `
@@ -251,6 +551,9 @@ exports[`CompareListComponent matches snapshot when isLoading is true 1`] = `
+

+ Compare Positions +

@@ -269,6 +572,9 @@ exports[`CompareListComponent matches snapshot when there is an obc id 1`] = `
+

+ Compare Positions +

@@ -280,15 +586,14 @@ exports[`CompareListComponent matches snapshot when there is an obc id 1`] = ` > Position details comparison: - + Position -
OMS (DCM)
+ + +
+ SPECIAL AGENT +
+ + + Return to search results and add more positions to compare. + + + Return to search results and add more positions to compare. + + + Return to search results and add more positions to compare. + + + + + + + Position + + +
+
+ OMS (DCM) +
+
+ + } + interactiveElementProps={ + Object { + "title": "Remove this comparison", + } + } + limit={5} + onToggle={[Function]} + refKey="10034001" + type="compare" + /> +
+
+ + 10034001 + + + +
+
+ SPECIAL AGENT +
+
+ + } + interactiveElementProps={ + Object { + "title": "Remove this comparison", + } + } + limit={5} + onToggle={[Function]} + refKey="56082000" + type="compare" + /> +
+
+ + 56082000 + + + + Return to search results and add more positions to compare. + + + Return to search results and add more positions to compare. + + + Return to search results and add more positions to compare. + + + + + Link to details + +
@@ -306,16 +742,8 @@ exports[`CompareListComponent matches snapshot when there is an obc id 1`] = ` View position
-
- - -
- SPECIAL AGENT -
+ +
@@ -326,42 +754,74 @@ exports[`CompareListComponent matches snapshot when there is an obc id 1`] = ` View position
-
- + + + + - - - Position number -
+ Bid Count - 10034001 + -
- 56082000 + -
+ + + SECURITY (2501) + + + (WHA) BUREAU OF WESTERN HEMISPHERIC AFFAIRS + + + Chicago, IL + + + 2 YRS (2 R & R) + + + + + + 20% + + + 15% + + + 03/13/2020 + + + + + + + Favorite + + + + + + + + + + + + + + Add to Bid List + + + + + + + + + + + + + + -
diff --git a/src/Components/Favorite/Favorite.jsx b/src/Components/Favorite/Favorite.jsx index 44bfc3d810..04bc6d2491 100644 --- a/src/Components/Favorite/Favorite.jsx +++ b/src/Components/Favorite/Favorite.jsx @@ -82,7 +82,7 @@ class Favorite extends Component { const { hideText, useLongText } = this.props; const checked = this.getSavedState(); const state = checked ? States.CHECKED : States.UNCHECKED; - const type = (useLongText && !enforceShort) ? Types.LONG : Types.SHORT; + const type = (useLongText || !enforceShort) ? Types.LONG : Types.SHORT; return hideText ? null : getText$(state, type); } diff --git a/src/Components/FavoritePositions/FavoritePositions.jsx b/src/Components/FavoritePositions/FavoritePositions.jsx index 997b83c984..a4acdce092 100644 --- a/src/Components/FavoritePositions/FavoritePositions.jsx +++ b/src/Components/FavoritePositions/FavoritePositions.jsx @@ -40,7 +40,6 @@ bidList, onSortChange }) => ( title="favorites" maxLength={300} refreshFavorites - itemsPerRow={4} />
); diff --git a/src/Components/FavoritePositions/__snapshots__/FavoritePositions.test.jsx.snap b/src/Components/FavoritePositions/__snapshots__/FavoritePositions.test.jsx.snap index 513635032a..36f8aa0192 100644 --- a/src/Components/FavoritePositions/__snapshots__/FavoritePositions.test.jsx.snap +++ b/src/Components/FavoritePositions/__snapshots__/FavoritePositions.test.jsx.snap @@ -386,7 +386,6 @@ exports[`FavoritePositionsComponent matches snapshot 1`] = ` ] } isLoading={false} - itemsPerRow={4} maxLength={300} positions={ Array [ diff --git a/src/Components/GlossaryEditor/GlossaryEditorCard/GlossaryEditorCard.jsx b/src/Components/GlossaryEditor/GlossaryEditorCard/GlossaryEditorCard.jsx index 72fa2b6096..97303b9249 100644 --- a/src/Components/GlossaryEditor/GlossaryEditorCard/GlossaryEditorCard.jsx +++ b/src/Components/GlossaryEditor/GlossaryEditorCard/GlossaryEditorCard.jsx @@ -8,14 +8,6 @@ import { isUrl } from '../../../utilities'; const isEmpty = value => (value || '').length === 0; -const getEditorText = (shouldHideEditor) => { - let text = 'Cancel'; - if (shouldHideEditor) { - text = 'Edit'; - } - return text; -}; - class GlossaryEditorCard extends Component { constructor(props) { super(props); @@ -172,10 +164,9 @@ class GlossaryEditorCard extends Component { } = this.state; const renderedTitle = newTitle || term.title; - const renderedLink = newLink || term.link || (There is no link for this term); + const renderedLink = newLink || term.link; const renderedDefinition = newDefinition || term.definition; const shouldHideEditor = editorHidden && !isNewTerm; - const editorText = getEditorText(shouldHideEditor); const { editorHiddenClass, editorContainerHiddenClass, @@ -204,7 +195,7 @@ class GlossaryEditorCard extends Component { !isNewTerm &&
- {editorText} + {shouldHideEditor ? 'Edit' : 'Cancel'}
} @@ -213,7 +204,7 @@ class GlossaryEditorCard extends Component {
{ shouldHideEditor ? -

{renderedLink}

: +

{renderedLink || There is no link for this term}

: - diff --git a/src/Components/Header/DesktopNav/__snapshots__/DesktopNav.test.jsx.snap b/src/Components/Header/DesktopNav/__snapshots__/DesktopNav.test.jsx.snap index b11bb708ef..7df2940e03 100644 --- a/src/Components/Header/DesktopNav/__snapshots__/DesktopNav.test.jsx.snap +++ b/src/Components/Header/DesktopNav/__snapshots__/DesktopNav.test.jsx.snap @@ -119,9 +119,6 @@ exports[`DesktopNav matches snapshot when logged in 1`] = ` className="header-nav-link" > - diff --git a/src/Components/Header/Header.jsx b/src/Components/Header/Header.jsx index 3bb42d5e0f..8f610245ce 100644 --- a/src/Components/Header/Header.jsx +++ b/src/Components/Header/Header.jsx @@ -11,7 +11,6 @@ import { logoutRequest } from '../../login/actions'; import { toggleSearchBar } from '../../actions/showSearchBar'; import { USER_PROFILE, EMPTY_FUNCTION, ROUTER_LOCATION_OBJECT } from '../../Constants/PropTypes'; import StateBanner from './StateBanner/StateBanner'; -import ResultsMultiSearchHeaderContainer from '../ResultsMultiSearchHeader/ResultsMultiSearchContainer'; import ResultsSearchHeader from '../ResultsSearchHeader'; import { isCurrentPath, isCurrentPathIn } from '../ProfileMenu/navigation'; import { searchBarRoutes, searchBarRoutesForce, searchBarRoutesForceHidden } from './searchRoutes'; @@ -19,7 +18,6 @@ import MobileNav from './MobileNav'; import DesktopNav from './DesktopNav'; import { getAssetPath, propOrDefault, focusByFirstOfHeader } from '../../utilities'; import MediaQuery from '../MediaQuery'; -import BetaHeader from './BetaHeader'; import InteractiveElement from '../InteractiveElement'; export class Header extends Component { @@ -135,7 +133,6 @@ export class Header extends Component {
@@ -410,7 +409,6 @@ exports[`PositionDetailsItem matches snapshot when there is an obc id 1`] = ` } userProfile={Object {}} /> -
@@ -611,7 +609,6 @@ exports[`PositionDetailsItem matches snapshot when various data is missing from } userProfile={Object {}} /> -
diff --git a/src/Components/PositionTitle/PositionTitle.jsx b/src/Components/PositionTitle/PositionTitle.jsx index d65971d112..9942c1d28e 100644 --- a/src/Components/PositionTitle/PositionTitle.jsx +++ b/src/Components/PositionTitle/PositionTitle.jsx @@ -1,5 +1,4 @@ import React from 'react'; -import PropTypes from 'prop-types'; import Helmet from 'react-helmet'; import { get } from 'lodash'; import FontAwesome from 'react-fontawesome'; @@ -14,7 +13,7 @@ import PermissionsWrapper from '../../Containers/PermissionsWrapper'; const seal = getAssetPath('/assets/img/us-flag.jpg'); -const PositionTitle = ({ details, bidList, userProfile, bidListToggleIsLoading }) => { +const PositionTitle = ({ details, bidList, userProfile }) => { const obcId = propOrDefault(details, 'post.obc_id'); const availablilityText = get(details, 'availability.reason') ? `${details.availability.reason}${CANNOT_BID_SUFFIX}` : CANNOT_BID_DEFAULT; @@ -77,7 +76,6 @@ const PositionTitle = ({ details, bidList, userProfile, bidListToggleIsLoading } @@ -89,13 +87,11 @@ const PositionTitle = ({ details, bidList, userProfile, bidListToggleIsLoading } PositionTitle.propTypes = { details: POSITION_DETAILS, bidList: BID_LIST.isRequired, - bidListToggleIsLoading: PropTypes.bool, userProfile: USER_PROFILE, }; PositionTitle.defaultProps = { details: null, - bidListToggleIsLoading: false, userProfile: {}, }; diff --git a/src/Components/PositionTitle/__snapshots__/PositionTitle.test.jsx.snap b/src/Components/PositionTitle/__snapshots__/PositionTitle.test.jsx.snap index e441b4619c..df8f34babf 100644 --- a/src/Components/PositionTitle/__snapshots__/PositionTitle.test.jsx.snap +++ b/src/Components/PositionTitle/__snapshots__/PositionTitle.test.jsx.snap @@ -82,7 +82,6 @@ exports[`PositionTitleComponent matches snapshot 1`] = ` compareArray={Array []} disabled={false} id={6} - isLoading={false} />
diff --git a/src/Components/ProfileDashboard/Assignments/AssignmentsList.jsx b/src/Components/ProfileDashboard/Assignments/AssignmentsList.jsx new file mode 100644 index 0000000000..9fb96aa329 --- /dev/null +++ b/src/Components/ProfileDashboard/Assignments/AssignmentsList.jsx @@ -0,0 +1,48 @@ +import React from 'react'; +import { BID_RESULTS } from '../../../Constants/PropTypes'; +import SectionTitle from '../SectionTitle'; +import BorderedList from '../../BorderedList'; +import AssignmentsListResultsCard from './AssignmentsListResultsCard'; + +const AssignmentList = ({ assignments }) => { + const positionArray = []; + assignments.forEach(assignment => ( + positionArray.push( + , + ) + )); + return ( +
+
+
+ +
+
+
+ { + positionArray.length === 0 ? +
+ No assignments associated with this user. +
+ : + + } +
+
+ ); +}; + +AssignmentList.propTypes = { + assignments: BID_RESULTS.isRequired, +}; + +AssignmentList.defaultProps = { + assignments: [], +}; + +export default AssignmentList; diff --git a/src/Components/ProfileDashboard/Assignments/AssignmentsList.test.jsx b/src/Components/ProfileDashboard/Assignments/AssignmentsList.test.jsx new file mode 100644 index 0000000000..e7c31dda31 --- /dev/null +++ b/src/Components/ProfileDashboard/Assignments/AssignmentsList.test.jsx @@ -0,0 +1,23 @@ +import React from 'react'; +import { shallow } from 'enzyme'; +import toJSON from 'enzyme-to-json'; +import AssignmentsList from './AssignmentsList'; +import assignmentObject from '../../../__mocks__/assignmentObject'; + +describe('AssignmentsListComponent', () => { + const positions = [assignmentObject]; + it('is defined', () => { + const wrapper = shallow(); + expect(wrapper).toBeDefined(); + }); + + it('matches snapshot', () => { + const wrapper = shallow(); + expect(toJSON(wrapper)).toMatchSnapshot(); + }); + + it('matches snapshot when there are no bids', () => { + const wrapper = shallow(); + expect(toJSON(wrapper)).toMatchSnapshot(); + }); +}); diff --git a/src/Components/ProfileDashboard/Assignments/AssignmentsListResultsCard/AssignmentsContent/AssignmentsContent.jsx b/src/Components/ProfileDashboard/Assignments/AssignmentsListResultsCard/AssignmentsContent/AssignmentsContent.jsx new file mode 100644 index 0000000000..110c39db27 --- /dev/null +++ b/src/Components/ProfileDashboard/Assignments/AssignmentsListResultsCard/AssignmentsContent/AssignmentsContent.jsx @@ -0,0 +1,47 @@ +import React from 'react'; +import { get } from 'lodash'; +import { Link } from 'react-router-dom'; +import { formatDate, propOrDefault, getPostName } from '../../../../../utilities'; +import { NO_POST, NO_SKILL, NO_POSITION_NUMBER } from '../../../../../Constants/SystemMessages'; +import { POSITION_DETAILS } from '../../../../../Constants/PropTypes'; +import LanguageList from '../../../../LanguageList'; +import StartEnd from '../../../PositionInformation/StartEnd'; + +const AssignmentsContent = ({ assignment }) => ( +
+
+ {get(assignment, 'position.title')} + View Position +
+
+ Position number: + {propOrDefault(assignment, 'position.position_number', NO_POSITION_NUMBER)} +
+
+ Skill: + {propOrDefault(assignment, 'position.skill', NO_SKILL)} +
+
+ Language: + +
+
+ Post: + {getPostName(get(assignment, 'position.post', NO_POST))} +
+
+ Start date and End date: + +
+
+); + +AssignmentsContent.propTypes = { + assignment: POSITION_DETAILS.isRequired, +}; + + +export default AssignmentsContent; diff --git a/src/Components/ProfileDashboard/Assignments/AssignmentsListResultsCard/AssignmentsContent/AssignmentsContent.test.jsx b/src/Components/ProfileDashboard/Assignments/AssignmentsListResultsCard/AssignmentsContent/AssignmentsContent.test.jsx new file mode 100644 index 0000000000..0169e085d0 --- /dev/null +++ b/src/Components/ProfileDashboard/Assignments/AssignmentsListResultsCard/AssignmentsContent/AssignmentsContent.test.jsx @@ -0,0 +1,26 @@ +import React from 'react'; +import { shallow } from 'enzyme'; +import toJSON from 'enzyme-to-json'; +import AssignmentsContent from './AssignmentsContent'; +import assignmentObject from '../../../../../__mocks__/assignmentObject'; + +describe('AssignmentsContentComponent', () => { + const assignment = assignmentObject; + it('is defined', () => { + const wrapper = shallow( + , + ); + expect(wrapper).toBeDefined(); + }); + + it('matches snapshot', () => { + const wrapper = shallow( + , + ); + expect(toJSON(wrapper)).toMatchSnapshot(); + }); +}); diff --git a/src/Components/ProfileDashboard/Assignments/AssignmentsListResultsCard/AssignmentsContent/__snapshots__/AssignmentsContent.test.jsx.snap b/src/Components/ProfileDashboard/Assignments/AssignmentsListResultsCard/AssignmentsContent/__snapshots__/AssignmentsContent.test.jsx.snap new file mode 100644 index 0000000000..14b48bff06 --- /dev/null +++ b/src/Components/ProfileDashboard/Assignments/AssignmentsListResultsCard/AssignmentsContent/__snapshots__/AssignmentsContent.test.jsx.snap @@ -0,0 +1,68 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`AssignmentsContentComponent matches snapshot 1`] = ` +
+
+ + PROJECT DIRECTOR + + + + View Position + +
+
+ + Position number: + + 58348065 +
+
+ + Skill: + + CONSTRUCTION ENGINEERING (6218) +
+
+ + Language: + + +
+
+ + Post: + + Washington, DC +
+
+ + Start date and End date: + + +
+
+`; diff --git a/src/Components/ProfileDashboard/Assignments/AssignmentsListResultsCard/AssignmentsContent/index.js b/src/Components/ProfileDashboard/Assignments/AssignmentsListResultsCard/AssignmentsContent/index.js new file mode 100644 index 0000000000..04a0ca044a --- /dev/null +++ b/src/Components/ProfileDashboard/Assignments/AssignmentsListResultsCard/AssignmentsContent/index.js @@ -0,0 +1 @@ +export { default } from './AssignmentsContent'; diff --git a/src/Components/ProfileDashboard/Assignments/AssignmentsListResultsCard/AssignmentsListResultsCard.jsx b/src/Components/ProfileDashboard/Assignments/AssignmentsListResultsCard/AssignmentsListResultsCard.jsx new file mode 100644 index 0000000000..7f00eec732 --- /dev/null +++ b/src/Components/ProfileDashboard/Assignments/AssignmentsListResultsCard/AssignmentsListResultsCard.jsx @@ -0,0 +1,25 @@ +import React from 'react'; +import { POSITION_DETAILS } from '../../../../Constants/PropTypes'; +import InformationDataPoint from '../../../ProfileDashboard/InformationDataPoint'; +import FavoriteContent from './AssignmentsContent'; + +const FavoritesListResultsCard = ({ assignment }) => ( +
+
+
+ + } + /> +
+
+
+); + +FavoritesListResultsCard.propTypes = { + assignment: POSITION_DETAILS.isRequired, +}; + +export default FavoritesListResultsCard; diff --git a/src/Components/ProfileDashboard/Assignments/AssignmentsListResultsCard/AssignmentsListResultsCard.test.jsx b/src/Components/ProfileDashboard/Assignments/AssignmentsListResultsCard/AssignmentsListResultsCard.test.jsx new file mode 100644 index 0000000000..afb12052c9 --- /dev/null +++ b/src/Components/ProfileDashboard/Assignments/AssignmentsListResultsCard/AssignmentsListResultsCard.test.jsx @@ -0,0 +1,35 @@ +import { shallow } from 'enzyme'; +import React from 'react'; +import toJSON from 'enzyme-to-json'; +import AssignmentsListResultsCard from './AssignmentsListResultsCard'; +import assignmentObject from '../../../../__mocks__/assignmentObject'; + +describe('AssignmentsListResultsCardComponent', () => { + const assignment = assignmentObject; + it('is defined', () => { + const wrapper = shallow( + , + ); + expect(wrapper).toBeDefined(); + }); + + it('can receive props', () => { + const wrapper = shallow( + , + ); + expect(wrapper.instance().props.assignment.id).toBe(assignment.id); + }); + + it('matches snapshot', () => { + const wrapper = shallow( + , + ); + expect(toJSON(wrapper)).toMatchSnapshot(); + }); +}); diff --git a/src/Components/ProfileDashboard/Assignments/AssignmentsListResultsCard/__snapshots__/AssignmentsListResultsCard.test.jsx.snap b/src/Components/ProfileDashboard/Assignments/AssignmentsListResultsCard/__snapshots__/AssignmentsListResultsCard.test.jsx.snap new file mode 100644 index 0000000000..bec3ed3dcf --- /dev/null +++ b/src/Components/ProfileDashboard/Assignments/AssignmentsListResultsCard/__snapshots__/AssignmentsListResultsCard.test.jsx.snap @@ -0,0 +1,48 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`AssignmentsListResultsCardComponent matches snapshot 1`] = ` +
+
+
+ + } + sideBySide={false} + title="" + titleOnBottom={true} + /> +
+
+
+`; diff --git a/src/Components/ProfileDashboard/Assignments/AssignmentsListResultsCard/index.js b/src/Components/ProfileDashboard/Assignments/AssignmentsListResultsCard/index.js new file mode 100644 index 0000000000..694660c24f --- /dev/null +++ b/src/Components/ProfileDashboard/Assignments/AssignmentsListResultsCard/index.js @@ -0,0 +1 @@ +export { default } from './AssignmentsListResultsCard'; diff --git a/src/Components/ProfileDashboard/Assignments/__snapshots__/AssignmentsList.test.jsx.snap b/src/Components/ProfileDashboard/Assignments/__snapshots__/AssignmentsList.test.jsx.snap new file mode 100644 index 0000000000..1c92972251 --- /dev/null +++ b/src/Components/ProfileDashboard/Assignments/__snapshots__/AssignmentsList.test.jsx.snap @@ -0,0 +1,90 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`AssignmentsListComponent matches snapshot 1`] = ` +
+
+
+ +
+
+
+ , + ] + } + /> +
+
+`; + +exports[`AssignmentsListComponent matches snapshot when there are no bids 1`] = ` +
+
+
+ +
+
+
+
+ No assignments associated with this user. +
+
+
+`; diff --git a/src/Components/ProfileDashboard/Assignments/index.js b/src/Components/ProfileDashboard/Assignments/index.js new file mode 100644 index 0000000000..a580d93b9a --- /dev/null +++ b/src/Components/ProfileDashboard/Assignments/index.js @@ -0,0 +1 @@ +export { default } from './AssignmentsList'; diff --git a/src/Components/ProfileDashboard/BidList/BidList.jsx b/src/Components/ProfileDashboard/BidList/BidList.jsx index 21469f18f8..5d0b03046b 100644 --- a/src/Components/ProfileDashboard/BidList/BidList.jsx +++ b/src/Components/ProfileDashboard/BidList/BidList.jsx @@ -1,5 +1,6 @@ import React from 'react'; import { Link } from 'react-router-dom'; +import PropTypes from 'prop-types'; import { BID_RESULTS } from '../../../Constants/PropTypes'; import SectionTitle from '../SectionTitle'; import BorderedList from '../../BorderedList'; @@ -8,7 +9,7 @@ import BidListHeader from './BidListHeader'; import { getStatusProperty } from '../../../Constants/BidStatuses'; import StaticDevContent from '../../StaticDevContent'; -const BidList = ({ bids }) => { +const BidList = ({ bids, showMoreLink }) => { const bidArray = []; bids.slice().forEach(bid => ( bidArray.push( @@ -40,19 +41,24 @@ const BidList = ({ bids }) => { }
-
- Go to Bid Tracker -
+ { + showMoreLink && +
+ Go to Bid Tracker +
+ }
); }; BidList.propTypes = { bids: BID_RESULTS.isRequired, + showMoreLink: PropTypes.bool, }; BidList.defaultProps = { bids: [], + showMoreLink: true, }; export default BidList; diff --git a/src/Components/ProfileDashboard/Favorites/FavoritesList.jsx b/src/Components/ProfileDashboard/Favorites/FavoritesList.jsx index 0af2dd60f3..9449cc400e 100644 --- a/src/Components/ProfileDashboard/Favorites/FavoritesList.jsx +++ b/src/Components/ProfileDashboard/Favorites/FavoritesList.jsx @@ -22,7 +22,7 @@ const FavoriteList = ({ favorites }) => {
- +
diff --git a/src/Components/ProfileDashboard/Favorites/__snapshots__/FavoritesList.test.jsx.snap b/src/Components/ProfileDashboard/Favorites/__snapshots__/FavoritesList.test.jsx.snap index d2c7f134ad..5b88809809 100644 --- a/src/Components/ProfileDashboard/Favorites/__snapshots__/FavoritesList.test.jsx.snap +++ b/src/Components/ProfileDashboard/Favorites/__snapshots__/FavoritesList.test.jsx.snap @@ -12,7 +12,7 @@ exports[`FavoritesListComponent matches snapshot 1`] = ` > { +const PositionInformation = ({ assignment = {} }) => { const assignmentStartDate = assignment.start_date ? formatDate(assignment.start_date) : false; let assignmentEndDate; assignmentEndDate = (assignment.status === 'active') ? assignment.estimated_end_date : assignment.end_date; diff --git a/src/Components/ProfileDashboard/PositionInformation/StartEnd/StartEnd.jsx b/src/Components/ProfileDashboard/PositionInformation/StartEnd/StartEnd.jsx index 268f351579..aa1b57ae49 100644 --- a/src/Components/ProfileDashboard/PositionInformation/StartEnd/StartEnd.jsx +++ b/src/Components/ProfileDashboard/PositionInformation/StartEnd/StartEnd.jsx @@ -3,9 +3,11 @@ import FontAwesome from 'react-fontawesome'; import PropTypes from 'prop-types'; const StartEnd = ({ start, end }) => ( -
- {start} {end} -
+ (start && end) ? +
+ {start} {end} +
+ : null ); StartEnd.propTypes = { diff --git a/src/Components/ProfileDashboard/ProfileDashboard.jsx b/src/Components/ProfileDashboard/ProfileDashboard.jsx index 36c717b359..447f01fcef 100644 --- a/src/Components/ProfileDashboard/ProfileDashboard.jsx +++ b/src/Components/ProfileDashboard/ProfileDashboard.jsx @@ -10,11 +10,13 @@ import ProfileSectionTitle from '../ProfileSectionTitle'; import { Row, Column } from '../Layout'; import MediaQueryWrapper from '../MediaQuery'; import Favorites from './Favorites'; +import Assignments from './Assignments'; import SavedSearches from './SavedSearches/SavedSearchesWrapper'; import PermissionsWrapper from '../../Containers/PermissionsWrapper'; +import BackButton from '../BackButton'; const ProfileDashboard = ({ - userProfile, isLoading, notifications, assignment, assignmentIsLoading, + userProfile, isLoading, notifications, assignment, assignmentIsLoading, isPublic, notificationsIsLoading, bidList, bidListIsLoading, favoritePositions, favoritePositionsIsLoading, }) => (
@@ -24,11 +26,12 @@ const ProfileDashboard = ({ ) : (
- + { isPublic ? : }
{(matches) => { - const columns = !matches ? [3, 4, 5] : [12, 12, 12]; + let columns = !matches ? [3, 4, 5] : [12, 12, 12]; + if (isPublic) { columns = !matches ? [4, 8] : [12, 12, 12]; } return (
- +
- -
- -
-
- -
-
- - -
- -
-
-
- + {isPublic ? +
+ +
+ +
+
+ +
+
- + : +
+ +
+ +
+
+ +
+
+ + +
+ +
+
+
+ +
+
+
} ); }} @@ -75,19 +99,28 @@ const ProfileDashboard = ({ ProfileDashboard.propTypes = { userProfile: USER_PROFILE.isRequired, isLoading: PropTypes.bool.isRequired, - assignment: ASSIGNMENT_OBJECT.isRequired, - assignmentIsLoading: PropTypes.bool.isRequired, - notifications: NOTIFICATION_RESULTS.isRequired, - notificationsIsLoading: PropTypes.bool.isRequired, - bidList: BID_RESULTS.isRequired, - bidListIsLoading: PropTypes.bool.isRequired, + assignment: ASSIGNMENT_OBJECT, + assignmentIsLoading: PropTypes.bool, + notifications: NOTIFICATION_RESULTS, + notificationsIsLoading: PropTypes.bool, + bidList: BID_RESULTS, + bidListIsLoading: PropTypes.bool, favoritePositions: FAVORITE_POSITIONS_ARRAY, favoritePositionsIsLoading: PropTypes.bool, + isPublic: PropTypes.bool, }; ProfileDashboard.defaultProps = { favoritePositions: [], + isLoading: false, + assignment: {}, favoritePositionsIsLoading: false, + assignmentIsLoading: false, + notifications: [], + notificationsIsLoading: false, + bidList: [], + bidListIsLoading: false, + isPublic: false, }; export default ProfileDashboard; diff --git a/src/Components/ProfileDashboard/UserProfile/UserProfileContactInformation/UserProfileContactInformation.jsx b/src/Components/ProfileDashboard/UserProfile/UserProfileContactInformation/UserProfileContactInformation.jsx index 3cb44e8c1f..163d601704 100644 --- a/src/Components/ProfileDashboard/UserProfile/UserProfileContactInformation/UserProfileContactInformation.jsx +++ b/src/Components/ProfileDashboard/UserProfile/UserProfileContactInformation/UserProfileContactInformation.jsx @@ -8,7 +8,7 @@ import StaticDevContent from '../../../StaticDevContent'; const UserProfileContactInformation = ({ userProfile }) => (
- + diff --git a/src/Components/ProfileDashboard/UserProfile/UserProfileContactInformation/__snapshots__/UserProfileContactInformation.test.jsx.snap b/src/Components/ProfileDashboard/UserProfile/UserProfileContactInformation/__snapshots__/UserProfileContactInformation.test.jsx.snap index 6b72837001..550093d5a8 100644 --- a/src/Components/ProfileDashboard/UserProfile/UserProfileContactInformation/__snapshots__/UserProfileContactInformation.test.jsx.snap +++ b/src/Components/ProfileDashboard/UserProfile/UserProfileContactInformation/__snapshots__/UserProfileContactInformation.test.jsx.snap @@ -9,7 +9,7 @@ exports[`UserProfileContactInformationComponent matches snapshot 1`] = ` > diff --git a/src/Components/ProfileMenu/ProfileMenuExpanded/__snapshots__/ProfileMenuExpanded.test.jsx.snap b/src/Components/ProfileMenu/ProfileMenuExpanded/__snapshots__/ProfileMenuExpanded.test.jsx.snap index 0858d756f1..8d1d8bea89 100644 --- a/src/Components/ProfileMenu/ProfileMenuExpanded/__snapshots__/ProfileMenuExpanded.test.jsx.snap +++ b/src/Components/ProfileMenu/ProfileMenuExpanded/__snapshots__/ProfileMenuExpanded.test.jsx.snap @@ -72,6 +72,7 @@ exports[`ProfileMenuExpandedComponent matches snapshot when isCDO is false 1`] = />
diff --git a/src/Components/ProfilePage/__snapshots__/ProfilePage.test.jsx.snap b/src/Components/ProfilePage/__snapshots__/ProfilePage.test.jsx.snap index 674a519265..0c3d394f9c 100644 --- a/src/Components/ProfilePage/__snapshots__/ProfilePage.test.jsx.snap +++ b/src/Components/ProfilePage/__snapshots__/ProfilePage.test.jsx.snap @@ -44,6 +44,10 @@ exports[`ProfilePageComponent matches snapshot 1`] = ` component={[Function]} path="/profile/glossaryeditor" /> +
diff --git a/src/Components/ResetComparisons/ResetComparisons.jsx b/src/Components/ResetComparisons/ResetComparisons.jsx index d1f595e191..b2680f3d75 100644 --- a/src/Components/ResetComparisons/ResetComparisons.jsx +++ b/src/Components/ResetComparisons/ResetComparisons.jsx @@ -1,10 +1,12 @@ import React, { Component } from 'react'; import PropTypes from 'prop-types'; import { EMPTY_FUNCTION } from '../../Constants/PropTypes'; +import { localStorageSetKey } from '../../utilities'; class ResetComparisons extends Component { render() { + const { onToggle, ...rest } = this.props; const exists = () => { let result = false; const retrievedKey = localStorage @@ -16,14 +18,14 @@ class ResetComparisons extends Component { return result; }; const remove = () => { - localStorage.setItem('compare', '[]'); - this.props.onToggle(); + localStorageSetKey('compare', '[]'); + onToggle(); }; const compare = exists() ? - Reset comparison choices + Clear All : null; return ( -
+
{compare}
); diff --git a/src/Components/ResultsCard/ResultsCard.jsx b/src/Components/ResultsCard/ResultsCard.jsx index ef7bc30f6a..76d9aaa844 100644 --- a/src/Components/ResultsCard/ResultsCard.jsx +++ b/src/Components/ResultsCard/ResultsCard.jsx @@ -62,26 +62,24 @@ const ResultsCard = (props) => { const language = (); - const post = getPostName(result.post, NO_POST); + const post = `${getPostName(result.post, NO_POST)}${result.organization ? `: ${result.organization}` : ''}`; const stats = getBidStatisticsObject(result.bid_statistics); const sections = [ /* eslint-disable quote-props */ { - 'Bid Count': , + 'TED': getResult(result, 'current_assignment.estimated_end_date', NO_DATE), 'Bid cycle': getResult(result, 'latest_bidcycle.name', NO_BID_CYCLE), 'Skill': getResult(result, 'skill', NO_SKILL), 'Grade': getResult(result, 'grade', NO_GRADE), 'Bureau': getResult(result, 'bureau', NO_BUREAU), - 'Post': post, }, { 'Tour of duty': getResult(result, 'post.tour_of_duty', NO_TOUR_OF_DUTY), 'Language': language, 'Post differential': getResult(result, 'post.differential_rate', NO_POST_DIFFERENTIAL, true), 'Danger pay': getResult(result, 'post.danger_pay', NO_DANGER_PAY, true), - 'TED': getResult(result, 'current_assignment.estimated_end_date', NO_DATE), 'Incumbent': getResult(result, 'current_assignment.user', NO_USER_LISTED), }, { @@ -110,9 +108,17 @@ const ResultsCard = (props) => { {() => (
- -

{title}

- View position + + +

{title}

+ View position +
+ +
Post:
{post}
+
+
+ +
diff --git a/src/Components/ResultsControls/ResultsControls.jsx b/src/Components/ResultsControls/ResultsControls.jsx index 5d98c1ebf1..42a2e08ddf 100644 --- a/src/Components/ResultsControls/ResultsControls.jsx +++ b/src/Components/ResultsControls/ResultsControls.jsx @@ -2,6 +2,7 @@ import React, { Component } from 'react'; import PropTypes from 'prop-types'; import TotalResults from '../TotalResults/TotalResults'; import SelectForm from '../SelectForm/SelectForm'; +import SearchResultsExportLink from '../SearchResultsExportLink'; import { POSITION_SEARCH_RESULTS, SORT_BY_PARENT_OBJECT } from '../../Constants/PropTypes'; class ResultsControls extends Component { @@ -24,7 +25,7 @@ class ResultsControls extends Component { defaultPageSize, defaultPageNumber, sortBy } = this.props; return (
-
+
{ // if results have loaded, display the total number of results hasLoaded && @@ -35,7 +36,7 @@ class ResultsControls extends Component { /> }
-
+
+
+ +
diff --git a/src/Components/ResultsControls/__snapshots__/ResultsControls.test.jsx.snap b/src/Components/ResultsControls/__snapshots__/ResultsControls.test.jsx.snap index 1749b52336..cc814ba1ef 100644 --- a/src/Components/ResultsControls/__snapshots__/ResultsControls.test.jsx.snap +++ b/src/Components/ResultsControls/__snapshots__/ResultsControls.test.jsx.snap @@ -5,7 +5,7 @@ exports[`ResultsControlsComponent matches snapshot 1`] = ` className="usa-grid-full results-controls" >
+
+ +
diff --git a/src/Components/ResultsPage/ResultsPage.jsx b/src/Components/ResultsPage/ResultsPage.jsx index f1c05e4662..ffc3ec6cdf 100644 --- a/src/Components/ResultsPage/ResultsPage.jsx +++ b/src/Components/ResultsPage/ResultsPage.jsx @@ -5,8 +5,6 @@ ACCORDION_SELECTION_OBJECT, FILTER_ITEMS_ARRAY, USER_PROFILE, BID_RESULTS, SAVED_SEARCH_MESSAGE, SAVED_SEARCH_OBJECT, MISSION_DETAILS_ARRAY, POST_DETAILS_ARRAY, EMPTY_FUNCTION, NEW_SAVED_SEARCH_SUCCESS_OBJECT } from '../../Constants/PropTypes'; import { ACCORDION_SELECTION } from '../../Constants/DefaultProps'; -import ViewComparisonLink from '../ViewComparisonLink/ViewComparisonLink'; -import ResetComparisons from '../ResetComparisons/ResetComparisons'; import ResultsContainer from '../ResultsContainer/ResultsContainer'; import ResultsSearchHeader from '../ResultsSearchHeader/ResultsSearchHeader'; import ResultsFilterContainer from '../ResultsFilterContainer/ResultsFilterContainer'; @@ -49,14 +47,6 @@ class Results extends Component { defaultLocation={defaultLocation} /> } -
-
- -
-
- -
-
+
- Search keyword and location + { + !isHomePage && + Search keyword and location + }
+ { + isHomePage && Find your next position + } + { + isHomePage &&
Example: Abuja, Nigeria, Political Affairs (5505), Russian...
+ }
@@ -70,6 +79,7 @@ ResultsSearchHeader.propTypes = { labelSrOnly: PropTypes.bool, placeholder: PropTypes.string, onFilterChange: PropTypes.func.isRequired, + isHomePage: PropTypes.bool, }; ResultsSearchHeader.defaultProps = { @@ -78,6 +88,7 @@ ResultsSearchHeader.defaultProps = { defaultKeyword: '', labelSrOnly: false, placeholder: 'Location, Skill, Grade, Language, Position number', + isHomePage: false, }; export default ResultsSearchHeader; diff --git a/src/Components/ResultsSearchHeader/ResultsSearchHeader.test.jsx b/src/Components/ResultsSearchHeader/ResultsSearchHeader.test.jsx index c68fe3c9ae..5d4af38a9a 100644 --- a/src/Components/ResultsSearchHeader/ResultsSearchHeader.test.jsx +++ b/src/Components/ResultsSearchHeader/ResultsSearchHeader.test.jsx @@ -91,4 +91,12 @@ describe('ResultsSearchHeaderComponent', () => { />); expect(toJSON(wrapper)).toMatchSnapshot(); }); + + it('matches snapshot when isHomePage is true', () => { + wrapper = shallow(); + expect(toJSON(wrapper)).toMatchSnapshot(); + }); }); diff --git a/src/Components/ResultsSearchHeader/__snapshots__/ResultsSearchHeader.test.jsx.snap b/src/Components/ResultsSearchHeader/__snapshots__/ResultsSearchHeader.test.jsx.snap index 2af2a5b6a0..2d8260e7b7 100644 --- a/src/Components/ResultsSearchHeader/__snapshots__/ResultsSearchHeader.test.jsx.snap +++ b/src/Components/ResultsSearchHeader/__snapshots__/ResultsSearchHeader.test.jsx.snap @@ -2,7 +2,7 @@ exports[`ResultsSearchHeaderComponent matches snapshot 1`] = `
`; + +exports[`ResultsSearchHeaderComponent matches snapshot when isHomePage is true 1`] = ` +
+
+ +
+
+ + Find your next position + + +
+ Example: Abuja, Nigeria, Political Affairs (5505), Russian... +
+
+
+
+ +
+ +
+
+`; diff --git a/src/Components/SavedSearches/SavedSearches.jsx b/src/Components/SavedSearches/SavedSearches.jsx index ce082582b5..291452b2ce 100644 --- a/src/Components/SavedSearches/SavedSearches.jsx +++ b/src/Components/SavedSearches/SavedSearches.jsx @@ -45,7 +45,7 @@ const SavedSearches = (props) => { >
- +
diff --git a/src/Components/SearchResultsExportLink/SearchResultsExportLink.jsx b/src/Components/SearchResultsExportLink/SearchResultsExportLink.jsx new file mode 100644 index 0000000000..2bd4bf7e0e --- /dev/null +++ b/src/Components/SearchResultsExportLink/SearchResultsExportLink.jsx @@ -0,0 +1,82 @@ +import React, { Component } from 'react'; +import PropTypes from 'prop-types'; +import { CSVDownload } from 'react-csv'; +import queryString from 'query-string'; +import { POSITION_SEARCH_SORTS } from '../../Constants/Sort'; +import { fetchResultData } from '../../actions/results'; +import { formatDate } from '../../utilities'; + +// Mapping columns to data fields +const HEADERS = [ + { label: 'Position', key: 'title' }, + { label: 'Position number', key: 'position_number' }, + { label: 'Skill', key: 'skill' }, + { label: 'Grade', key: 'grade' }, + { label: 'Bureau', key: 'bureau' }, + { label: 'Post city', key: 'post.location.city' }, + { label: 'Post country', key: 'post.location.country' }, + { label: 'Tour of duty', key: 'post.tour_of_duty' }, + { label: 'Language', key: 'languages[0].representation' }, + { label: 'Post differential', key: 'post.differential_rate' }, + { label: 'Danger pay', key: 'post.danger_pay' }, + { label: 'TED', key: 'estimated_end_date' }, + { label: 'Incumbent', key: 'current_assignment.user' }, +]; + +// Processes results before sending to the download component to allow for custom formatting. +const processData = data => ( + data.map(entry => ({ + ...entry, + estimated_end_date: formatDate(entry.current_assignment.estimated_end_date), + })) +); + + +class SearchResultsExportLink extends Component { + constructor(props) { + super(props); + this.onClick = this.onClick.bind(this); + this.state = { + data: '', + query: { value: window.location.search.replace('?', '') || '' }, + }; + } + + onClick() { + // reset the state to support multiple clicks + this.setState({ data: '' }); + const query = { + ordering: POSITION_SEARCH_SORTS, + ...queryString.parse(this.state.query.value), + limit: this.props.count, + }; + fetchResultData(queryString.stringify(query)).then((results) => { + const data = processData(results.results); + this.setState({ data }); + }); + } + + render() { + const { data } = this.state; + return ( +
+ + { + data && + } +
+ ); + } +} + +SearchResultsExportLink.propTypes = { + count: PropTypes.number, + filename: PropTypes.string, +}; + +SearchResultsExportLink.defaultProps = { + count: 0, + filename: 'TalentMap_search_export.csv', +}; + +export default SearchResultsExportLink; diff --git a/src/Components/SearchResultsExportLink/SearchResultsExportLink.test.jsx b/src/Components/SearchResultsExportLink/SearchResultsExportLink.test.jsx new file mode 100644 index 0000000000..7958269e88 --- /dev/null +++ b/src/Components/SearchResultsExportLink/SearchResultsExportLink.test.jsx @@ -0,0 +1,38 @@ +import { shallow } from 'enzyme'; +import React from 'react'; +import toJSON from 'enzyme-to-json'; +import sinon from 'sinon'; +import SearchResultsExportLink from './SearchResultsExportLink'; +import * as actions from '../../actions/results'; + +describe('SearchResultsExportLink', () => { + let fetchResultDataStub; + + beforeEach(() => { + fetchResultDataStub = sinon.stub(actions, 'fetchResultData').returns(Promise.resolve('success')); + }); + + afterEach(() => { + actions.fetchResultData.restore(); + }); + + it('gets data when button is clicked', () => { + const wrapper = shallow(); + expect(wrapper.state().data).toBeFalsy(); + const button = wrapper.find('button'); + button.simulate('click'); + expect(fetchResultDataStub.calledOnce).toBe(true); + }); + + it('shows download component when state has data', () => { + const wrapper = shallow(); + wrapper.setState({ data: 'test' }); + wrapper.update(); + expect(wrapper.find('CSVDownload').prop('data')).toBeTruthy(); + }); + + it('matches snapshot', () => { + const wrapper = shallow(); + expect(toJSON(wrapper)).toMatchSnapshot(); + }); +}); diff --git a/src/Components/SearchResultsExportLink/__snapshots__/SearchResultsExportLink.test.jsx.snap b/src/Components/SearchResultsExportLink/__snapshots__/SearchResultsExportLink.test.jsx.snap new file mode 100644 index 0000000000..fb3d0cc497 --- /dev/null +++ b/src/Components/SearchResultsExportLink/__snapshots__/SearchResultsExportLink.test.jsx.snap @@ -0,0 +1,12 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`SearchResultsExportLink matches snapshot 1`] = ` +
+ +
+`; diff --git a/src/Components/SearchResultsExportLink/index.js b/src/Components/SearchResultsExportLink/index.js new file mode 100644 index 0000000000..98c23ced84 --- /dev/null +++ b/src/Components/SearchResultsExportLink/index.js @@ -0,0 +1 @@ +export { default } from './SearchResultsExportLink'; diff --git a/src/Components/ViewComparisonLink/ViewComparisonLink.jsx b/src/Components/ViewComparisonLink/ViewComparisonLink.jsx index 617812b310..4b4870e8ee 100644 --- a/src/Components/ViewComparisonLink/ViewComparisonLink.jsx +++ b/src/Components/ViewComparisonLink/ViewComparisonLink.jsx @@ -17,7 +17,7 @@ class ViewComparisonLink extends Component { // else, parse the key's value to use in the Link const compareArray = JSON.parse(localStorage.getItem('compare')); return ( - Compare positions + Compare ); } } diff --git a/src/Constants/EndpointParams.js b/src/Constants/EndpointParams.js index 65e2b1a794..d8f9905d31 100644 --- a/src/Constants/EndpointParams.js +++ b/src/Constants/EndpointParams.js @@ -15,6 +15,7 @@ export const ENDPOINT_PARAMS = { post: 'post__in', available: 'is_available_in_current_bidcycle', bidCycle: 'is_available_in_bidcycle', + highlighted: 'is_highlighted', }; // any properties that we want to abstract to a common name diff --git a/src/Constants/Menu.js b/src/Constants/Menu.js index b37ee03366..97769139d8 100644 --- a/src/Constants/Menu.js +++ b/src/Constants/Menu.js @@ -82,6 +82,7 @@ export const PROFILE_MENU = MenuConfig([ { text: 'Bidder Portfolio', route: '/profile/bidderportfolio', + icon: 'users', isCDO: true, params: { type: 'all', diff --git a/src/Constants/PropTypes.js b/src/Constants/PropTypes.js index 8a374b88f0..77672a7636 100644 --- a/src/Constants/PropTypes.js +++ b/src/Constants/PropTypes.js @@ -7,6 +7,7 @@ import { IN_PANEL_PROP, DECLINED_PROP, } from './BidData'; +import SetType from './SetType'; export const STRING_OR_BOOL = PropTypes.oneOfType([PropTypes.string, PropTypes.bool]); @@ -544,3 +545,5 @@ export const HIGHLIGHT_POSITION = PropTypes.shape({ success: PropTypes.bool, error: PropTypes.bool, }); + +export { SetType }; diff --git a/src/Constants/PropTypes.test.js b/src/Constants/PropTypes.test.js index 8455d64c18..d09a8cbcf4 100644 --- a/src/Constants/PropTypes.test.js +++ b/src/Constants/PropTypes.test.js @@ -26,6 +26,10 @@ describe('PropTypes', () => { expect(PropTypes.USER_PROFILE).toBeDefined(); }); + it('Should return SetType', () => { + expect(PropTypes.SetType).toBeDefined(); + }); + it('can call PREVENT_DEFAULT', () => { const mock = { fn: PropTypes.PREVENT_DEFAULT }; const spy = sinon.spy(mock, 'fn'); diff --git a/src/Constants/SetType.js b/src/Constants/SetType.js new file mode 100644 index 0000000000..af539cf0d3 --- /dev/null +++ b/src/Constants/SetType.js @@ -0,0 +1,56 @@ +// forked from https://github.com/andrewbranch/es6-set-proptypes + +const methods = [ + 'add', + 'clear', + 'delete', + 'entries', + 'forEach', + 'has', + 'keys', + 'values', +]; + +function error(propName, componentName, property, condition) { + return new Error([ + 'Invalid prop `', + propName, + '` supplied to `', + componentName, + '`. Must be a Set. (`', + propName, + '.', + property, + '` was not ', + condition, + '.)', + ].join('')); +} + +function setType(props, propName, componentName) { + const s = props[propName]; + if (s == null) { return null; } + + if (typeof s.size !== 'number') { + return error(propName, componentName, 'size', 'a number'); + } + + for (let i = 0; i < methods.length; i += 1) { + const method = methods[i]; + if (typeof s[method] !== 'function') { + return error(propName, componentName, method, 'a function'); + } + } + + return null; +} + +setType.isRequired = (props, propName, componentName) => { + if (props[propName] == null) { // null or undefined + return new Error(`Required prop \`${propName}\` was not specified in ${componentName}.`); + } + + return setType(props, propName, componentName); +}; + +module.exports = setType; diff --git a/src/Containers/BidListButton/BidListButton.jsx b/src/Containers/BidListButton/BidListButton.jsx index 29d8e61370..e2f076bd48 100644 --- a/src/Containers/BidListButton/BidListButton.jsx +++ b/src/Containers/BidListButton/BidListButton.jsx @@ -3,18 +3,20 @@ import PropTypes from 'prop-types'; import { connect } from 'react-redux'; import { toggleBidPosition } from '../../actions/bidList'; import BidListButton from '../../Components/BidListButton'; +import { SetType } from '../../Constants/PropTypes'; -const BidListButtonContainer = ({ toggleBid, isLoading, ...rest }) => ( - -); +const BidListButtonContainer = ({ toggleBid, isLoading, id, ...rest }) => ( + + ); BidListButtonContainer.propTypes = { toggleBid: PropTypes.func.isRequired, - isLoading: PropTypes.bool, + isLoading: SetType, + id: PropTypes.number.isRequired, }; BidListButtonContainer.defaultProps = { - isLoading: false, + isLoading: new Set(), }; export const mapStateToProps = state => ({ diff --git a/src/Containers/BidListButton/BidListButton.test.jsx b/src/Containers/BidListButton/BidListButton.test.jsx index 917746ce41..d3db720661 100644 --- a/src/Containers/BidListButton/BidListButton.test.jsx +++ b/src/Containers/BidListButton/BidListButton.test.jsx @@ -7,7 +7,7 @@ describe('BidListButton', () => { const props = { toggleBid: () => {}, id: 1, - isLoading: false, + isLoading: new Set(), }; it('is defined', () => { diff --git a/src/Containers/BidTracker/BidTracker.jsx b/src/Containers/BidTracker/BidTracker.jsx index c05929a84a..98b53a534e 100644 --- a/src/Containers/BidTracker/BidTracker.jsx +++ b/src/Containers/BidTracker/BidTracker.jsx @@ -27,7 +27,7 @@ class BidTrackerContainer extends Component { render() { const { bidList, deleteBid, bidListHasErrored, bidListIsLoading, bidListToggleHasErrored, - bidListToggleIsLoading, bidListToggleSuccess, submitBidPosition, + bidListToggleSuccess, submitBidPosition, submitBidHasErrored, submitBidIsLoading, submitBidSuccess, acceptBidPosition, acceptBidHasErrored, acceptBidIsLoading, acceptBidSuccess, declineBidPosition, declineBidHasErrored, declineBidIsLoading, @@ -40,7 +40,6 @@ class BidTrackerContainer extends Component { bidListHasErrored={bidListHasErrored} bidListIsLoading={bidListIsLoading} bidListToggleHasErrored={bidListToggleHasErrored} - bidListToggleIsLoading={bidListToggleIsLoading} bidListToggleSuccess={bidListToggleSuccess} deleteBid={deleteBid} submitBid={submitBidPosition} @@ -76,7 +75,6 @@ BidTrackerContainer.propTypes = { bidListIsLoading: PropTypes.bool, bidList: BID_LIST, bidListToggleHasErrored: BID_LIST_TOGGLE_HAS_ERRORED, - bidListToggleIsLoading: PropTypes.bool, bidListToggleSuccess: BID_LIST_TOGGLE_SUCCESS, submitBidPosition: PropTypes.func.isRequired, submitBidHasErrored: SUBMIT_BID_HAS_ERRORED.isRequired, @@ -108,7 +106,6 @@ BidTrackerContainer.defaultProps = { bidListHasErrored: false, bidListIsLoading: false, bidListToggleHasErrored: false, - bidListToggleIsLoading: false, bidListToggleSuccess: false, submitBidPosition: EMPTY_FUNCTION, submitBidHasErrored: false, @@ -142,7 +139,6 @@ const mapStateToProps = state => ({ bidListIsLoading: state.bidListIsLoading, bidList: state.bidListFetchDataSuccess, bidListToggleHasErrored: state.bidListToggleHasErrored, - bidListToggleIsLoading: state.bidListToggleIsLoading, bidListToggleSuccess: state.bidListToggleSuccess, submitBidHasErrored: state.submitBidHasErrored, submitBidIsLoading: state.submitBidIsLoading, diff --git a/src/Containers/Compare/Compare.jsx b/src/Containers/Compare/Compare.jsx index 5ba06f8fdd..6a46ceb08d 100644 --- a/src/Containers/Compare/Compare.jsx +++ b/src/Containers/Compare/Compare.jsx @@ -3,14 +3,19 @@ import PropTypes from 'prop-types'; import { connect } from 'react-redux'; import { withRouter } from 'react-router'; import { push } from 'react-router-redux'; +import { get } from 'lodash'; import { comparisonsFetchData } from '../../actions/comparisons'; +import { favoritePositionsFetchData } from '../../actions/favoritePositions'; +import { bidListFetchData } from '../../actions/bidList'; import CompareList from '../../Components/CompareList/CompareList'; -import { COMPARE_LIST } from '../../Constants/PropTypes'; +import { COMPARE_LIST, POSITION_SEARCH_RESULTS, BID_LIST, SetType } from '../../Constants/PropTypes'; +import { POSITION_RESULTS_OBJECT } from '../../Constants/DefaultProps'; import { LOGIN_REDIRECT } from '../../login/routes'; -class Results extends Component { +class Compare extends Component { constructor(props) { super(props); + this.onToggle = this.onToggle.bind(this); this.state = { key: 0, }; @@ -20,27 +25,43 @@ class Results extends Component { if (!this.props.isAuthorized()) { this.props.onNavigateTo(LOGIN_REDIRECT); } else { - this.getComparisons(this.props.match.params.ids); + const ids = get(this, 'props.match.params.ids'); + if (ids) { this.getComparisons(ids); } + this.props.fetchFavorites(); + this.props.fetchBidList(); } } + onToggle(id) { + let compareArray = this.props.match.params.ids.split(','); + compareArray = compareArray.filter(f => f !== id); + const compareString = compareArray.toString(); + this.props.onNavigateTo(`/compare/${compareString}`); + this.getComparisons(`${compareString}`); + } + getComparisons(ids) { this.props.fetchData(ids); } render() { - const { comparisons, hasErrored, isLoading } = this.props; + const { comparisons, hasErrored, isLoading, favoritePositions, bidList, + bidListToggleIsLoading } = this.props; return ( ); } } -Results.propTypes = { +Compare.propTypes = { onNavigateTo: PropTypes.func.isRequired, match: PropTypes.shape({ params: PropTypes.shape({ @@ -52,15 +73,23 @@ Results.propTypes = { isLoading: PropTypes.bool.isRequired, comparisons: COMPARE_LIST, isAuthorized: PropTypes.func.isRequired, + favoritePositions: POSITION_SEARCH_RESULTS, + fetchFavorites: PropTypes.func.isRequired, + bidList: BID_LIST, + bidListToggleIsLoading: SetType, + fetchBidList: PropTypes.func.isRequired, }; -Results.defaultProps = { +Compare.defaultProps = { comparisons: [], hasErrored: false, isLoading: true, + favoritePositions: POSITION_RESULTS_OBJECT, + bidList: { results: [] }, + bidListToggleIsLoading: new Set(), }; -Results.contextTypes = { +Compare.contextTypes = { router: PropTypes.object, }; @@ -68,11 +97,16 @@ const mapStateToProps = state => ({ comparisons: state.comparisons, hasErrored: state.comparisonsHasErrored, isLoading: state.comparisonsIsLoading, + favoritePositions: state.favoritePositions, + bidList: state.bidListFetchDataSuccess, + bidListToggleIsLoading: state.bidListToggleIsLoading, }); export const mapDispatchToProps = dispatch => ({ fetchData: url => dispatch(comparisonsFetchData(url)), + fetchBidList: () => dispatch(bidListFetchData()), onNavigateTo: dest => dispatch(push(dest)), + fetchFavorites: () => dispatch(favoritePositionsFetchData()), }); -export default connect(mapStateToProps, mapDispatchToProps)(withRouter(Results)); +export default connect(mapStateToProps, mapDispatchToProps)(withRouter(Compare)); diff --git a/src/Containers/Position/Position.jsx b/src/Containers/Position/Position.jsx index 5537869db3..f45ff8455d 100644 --- a/src/Containers/Position/Position.jsx +++ b/src/Containers/Position/Position.jsx @@ -70,11 +70,11 @@ class Position extends Component { hasErrored, routerLocations, userProfile, + userProfileIsLoading, bidList, bidListHasErrored, bidListIsLoading, bidListToggleHasErrored, - bidListToggleIsLoading, bidListToggleSuccess, descriptionEditHasErrored, descriptionEditIsLoading, @@ -91,11 +91,11 @@ class Position extends Component { hasErrored={hasErrored} goBackLink={getLastRouteLink(routerLocations)} userProfile={userProfile} + userProfileIsLoading={userProfileIsLoading} bidList={bidList} bidListHasErrored={bidListHasErrored} bidListIsLoading={bidListIsLoading} bidListToggleHasErrored={bidListToggleHasErrored} - bidListToggleIsLoading={bidListToggleIsLoading} bidListToggleSuccess={bidListToggleSuccess} editDescriptionContent={this.editDescriptionContent} editPocContent={this.editPocContent} @@ -129,12 +129,12 @@ Position.propTypes = { isAuthorized: PropTypes.func.isRequired, routerLocations: ROUTER_LOCATIONS, userProfile: USER_PROFILE, + userProfileIsLoading: PropTypes.bool, fetchBidList: PropTypes.func, bidListHasErrored: PropTypes.bool, bidListIsLoading: PropTypes.bool, bidList: BID_LIST, bidListToggleHasErrored: BID_LIST_TOGGLE_HAS_ERRORED, - bidListToggleIsLoading: PropTypes.bool, bidListToggleSuccess: BID_LIST_TOGGLE_SUCCESS, editDescriptionContent: PropTypes.func.isRequired, editPocContent: PropTypes.func.isRequired, @@ -154,12 +154,12 @@ Position.defaultProps = { isLoading: true, routerLocations: [], userProfile: {}, + userProfileIsLoading: false, fetchBidList: EMPTY_FUNCTION, bidList: { results: [] }, bidListHasErrored: false, bidListIsLoading: false, bidListToggleHasErrored: false, - bidListToggleIsLoading: false, bidListToggleSuccess: false, editDescriptionContent: EMPTY_FUNCTION, editPocContent: EMPTY_FUNCTION, @@ -178,11 +178,11 @@ const mapStateToProps = (state, ownProps) => ({ routerLocations: state.routerLocations, id: ownProps, userProfile: state.userProfile, + userProfileIsLoading: state.userProfileIsLoading, bidListHasErrored: state.bidListHasErrored, bidListIsLoading: state.bidListIsLoading, bidList: state.bidListFetchDataSuccess, bidListToggleHasErrored: state.bidListToggleHasErrored, - bidListToggleIsLoading: state.bidListToggleIsLoading, bidListToggleSuccess: state.bidListToggleSuccess, descriptionEditHasErrored: state.descriptionEditHasErrored, descriptionEditIsLoading: state.descriptionEditIsLoading, diff --git a/src/Containers/ProfilePublic/ProfilePublic.jsx b/src/Containers/ProfilePublic/ProfilePublic.jsx new file mode 100644 index 0000000000..7f6b60dbdc --- /dev/null +++ b/src/Containers/ProfilePublic/ProfilePublic.jsx @@ -0,0 +1,72 @@ +import React, { Component } from 'react'; +import PropTypes from 'prop-types'; +import { connect } from 'react-redux'; +import { withRouter } from 'react-router'; +import { push } from 'react-router-redux'; +import { get } from 'lodash'; +import { USER_PROFILE, EMPTY_FUNCTION } from '../../Constants/PropTypes'; +import { DEFAULT_USER_PROFILE } from '../../Constants/DefaultProps'; +import ProfileDashboard from '../../Components/ProfileDashboard'; +import { userProfilePublicFetchData } from '../../actions/userProfilePublic'; +import Alert from '../../Components/Alert'; + +class ProfilePublic extends Component { + + componentWillMount() { + this.props.fetchData(get(this.props, 'match.params.id')); + } + + getDetails(id) { + this.props.fetchData(id); + } + + render() { + const { isLoading, hasErrored, userProfile } = this.props; + const { assignments, bidList } = userProfile; + const assignment = get(assignments, '[0]'); + return ( + hasErrored ? + + : + + ); + } +} + +ProfilePublic.propTypes = { + userProfile: USER_PROFILE, + fetchData: PropTypes.func, + isLoading: PropTypes.bool, + hasErrored: PropTypes.bool, +}; + +ProfilePublic.defaultProps = { + isLoading: true, + userProfile: DEFAULT_USER_PROFILE, + fetchData: EMPTY_FUNCTION, + hasErrored: false, +}; + +ProfilePublic.contextTypes = { + router: PropTypes.object, +}; + +const mapStateToProps = (state, ownProps) => ({ + userProfile: state.userProfilePublic, + isLoading: state.userProfilePublicIsLoading, + hasErrored: state.userProfilePublicHasErrored, + id: ownProps, +}); + +export const mapDispatchToProps = dispatch => ({ + fetchData: id => dispatch(userProfilePublicFetchData(id)), + onNavigateTo: dest => dispatch(push(dest)), +}); + +export default connect(mapStateToProps, mapDispatchToProps)(withRouter(ProfilePublic)); diff --git a/src/Containers/ProfilePublic/ProfilePublic.test.jsx b/src/Containers/ProfilePublic/ProfilePublic.test.jsx new file mode 100644 index 0000000000..b25f045130 --- /dev/null +++ b/src/Containers/ProfilePublic/ProfilePublic.test.jsx @@ -0,0 +1,47 @@ +import React from 'react'; +import TestUtils from 'react-dom/test-utils'; +import { Provider } from 'react-redux'; +import { MemoryRouter } from 'react-router-dom'; +import configureStore from 'redux-mock-store'; +import thunk from 'redux-thunk'; +import { testDispatchFunctions } from '../../testUtilities/testUtilities'; +import ProfilePublic, { mapDispatchToProps } from './ProfilePublic'; + +const middlewares = [thunk]; +const mockStore = configureStore(middlewares); + +describe('ProfilePublic', () => { + const userProfile = { + assignments: [], + bidList: [], + }; + it('is defined', () => { + const profile = TestUtils.renderIntoDocument( + true} + onNavigateTo={() => {}} + userProfile={userProfile} + /> + ); + expect(profile).toBeDefined(); + }); + + it('can handle authentication redirects', () => { + const profile = TestUtils.renderIntoDocument( + false} + onNavigateTo={() => {}} + userProfile={userProfile} + /> + ); + expect(profile).toBeDefined(); + }); +}); + +describe('mapDispatchToProps', () => { + const config = { + fetchData: [1], + onNavigateTo: ['/results'], + }; + testDispatchFunctions(mapDispatchToProps, config); +}); diff --git a/src/Containers/ProfilePublic/index.js b/src/Containers/ProfilePublic/index.js new file mode 100644 index 0000000000..64d680672a --- /dev/null +++ b/src/Containers/ProfilePublic/index.js @@ -0,0 +1 @@ +export { default } from './ProfilePublic'; diff --git a/src/Containers/Results/Results.jsx b/src/Containers/Results/Results.jsx index 6c184c4752..6dfc6783dd 100644 --- a/src/Containers/Results/Results.jsx +++ b/src/Containers/Results/Results.jsx @@ -16,6 +16,7 @@ import { postSearchFetchData } from '../../actions/autocomplete/postAutocomplete import { setSelectedAccordion } from '../../actions/selectedAccordion'; import { toggleSearchBar } from '../../actions/showSearchBar'; import ResultsPage from '../../Components/ResultsPage/ResultsPage'; +import CompareDrawer from '../../Components/CompareDrawer'; import { POSITION_SEARCH_RESULTS, FILTERS_PARENT, ACCORDION_SELECTION_OBJECT, USER_PROFILE, SAVED_SEARCH_MESSAGE, NEW_SAVED_SEARCH_SUCCESS_OBJECT, SAVED_SEARCH_OBJECT, MISSION_DETAILS_ARRAY, POST_DETAILS_ARRAY, @@ -210,42 +211,45 @@ class Results extends Component { fetchPostAutocomplete, postSearchResults, postSearchIsLoading, postSearchHasErrored, shouldShowSearchBar, bidList } = this.props; return ( - +
+ + +
); } } diff --git a/src/Containers/Routes/Routes.jsx b/src/Containers/Routes/Routes.jsx index 86de345ebb..d710ba867c 100644 --- a/src/Containers/Routes/Routes.jsx +++ b/src/Containers/Routes/Routes.jsx @@ -7,7 +7,7 @@ const Routes = props => ( { mappedRoutesArray.map(route => ( route.component(props)} diff --git a/src/actions/bidList.js b/src/actions/bidList.js index 933e184628..fc3f8ba253 100644 --- a/src/actions/bidList.js +++ b/src/actions/bidList.js @@ -30,10 +30,10 @@ export function bidListToggleHasErrored(bool) { }; } -export function bidListToggleIsLoading(bool) { +export function bidListToggleIsLoading(bool, id) { return { type: 'BID_LIST_TOGGLE_IS_LOADING', - isLoading: bool, + isLoading: { bool, id }, }; } @@ -214,7 +214,7 @@ export function toggleBidPosition(id, remove) { return (dispatch) => { // reset the states to ensure only one message can be shown dispatch(routeChangeResetState()); - dispatch(bidListToggleIsLoading(true)); + dispatch(bidListToggleIsLoading(true, id)); dispatch(bidListToggleSuccess(false)); dispatch(bidListToggleHasErrored(false)); @@ -229,7 +229,7 @@ export function toggleBidPosition(id, remove) { SystemMessages.DELETE_BID_ITEM_SUCCESS : SystemMessages.ADD_BID_ITEM_SUCCESS; dispatch(bidListToggleSuccess(message)); dispatch(toastSuccess(message)); - dispatch(bidListToggleIsLoading(false)); + dispatch(bidListToggleIsLoading(false, id)); dispatch(bidListToggleHasErrored(false)); dispatch(bidListFetchData()); }) @@ -238,7 +238,8 @@ export function toggleBidPosition(id, remove) { SystemMessages.DELETE_BID_ITEM_ERROR : SystemMessages.ADD_BID_ITEM_ERROR; dispatch(bidListToggleHasErrored(message)); dispatch(toastError(message)); - dispatch(bidListToggleIsLoading(false)); + dispatch(bidListToggleIsLoading(false, id)); + dispatch(bidListFetchData()); }); }; } diff --git a/src/actions/comparisons.js b/src/actions/comparisons.js index e92dfded99..a34e36ac51 100644 --- a/src/actions/comparisons.js +++ b/src/actions/comparisons.js @@ -1,5 +1,8 @@ +import { CancelToken } from 'axios'; import api from '../api'; +let cancel; + export function comparisonsHasErrored(bool) { return { type: 'COMPARISONS_HAS_ERRORED', @@ -23,13 +26,21 @@ export function comparisonsFetchDataSuccess(comparisons) { export function comparisonsFetchData(query) { return (dispatch) => { + if (cancel) { cancel(); } dispatch(comparisonsIsLoading(true)); - api.get(`/position/?position_number__in=${query}`) - .then((response) => { - dispatch(comparisonsIsLoading(false)); - return response.data.results; + if (!query) { + dispatch(comparisonsFetchDataSuccess([])); + dispatch(comparisonsIsLoading(false)); + } else { + api.get(`/position/?position_number__in=${query}`, { + cancelToken: new CancelToken((c) => { cancel = c; }), }) - .then(comparisons => dispatch(comparisonsFetchDataSuccess(comparisons))) - .catch(() => dispatch(comparisonsHasErrored(true))); + .then((response) => { + dispatch(comparisonsIsLoading(false)); + return response.data.results; + }) + .then(comparisons => dispatch(comparisonsFetchDataSuccess(comparisons))) + .catch(() => dispatch(comparisonsHasErrored(true))); + } }; } diff --git a/src/actions/results.js b/src/actions/results.js index c870333336..34030f1947 100644 --- a/src/actions/results.js +++ b/src/actions/results.js @@ -60,15 +60,19 @@ export function resultsFetchSimilarPositions(id) { }; } +export function fetchResultData(query) { + return api + .get(`/position/?${query}`, { + cancelToken: new CancelToken((c) => { cancel = c; }), + }) + .then(response => response.data); +} + export function resultsFetchData(query) { return (dispatch) => { if (cancel) { cancel(); dispatch(resultsIsLoading(true)); } dispatch(resultsIsLoading(true)); - api - .get(`/position/?${query}`, { - cancelToken: new CancelToken((c) => { cancel = c; }), - }) - .then(response => response.data) + fetchResultData(query) .then((results) => { dispatch(resultsFetchDataSuccess(results)); dispatch(resultsIsLoading(false)); diff --git a/src/actions/userProfilePublic.js b/src/actions/userProfilePublic.js new file mode 100644 index 0000000000..776dc080d7 --- /dev/null +++ b/src/actions/userProfilePublic.js @@ -0,0 +1,80 @@ +import axios from 'axios'; +import { get } from 'lodash'; +import api from '../api'; + +export function userProfilePublicHasErrored(bool) { + return { + type: 'USER_PROFILE_PUBLIC_HAS_ERRORED', + hasErrored: bool, + }; +} + +export function userProfilePublicIsLoading(bool) { + return { + type: 'USER_PROFILE_PUBLIC_IS_LOADING', + isLoading: bool, + }; +} + +export function userProfilePublicFetchDataSuccess(userProfile) { + return { + type: 'USER_PROFILE_PUBLIC_FETCH_DATA_SUCCESS', + userProfile, + }; +} + +export function unsetUserProfilePublic() { + return (dispatch) => { + dispatch(userProfilePublicFetchDataSuccess({})); + }; +} + +// include an optional bypass for when we want to silently update the profile +export function userProfilePublicFetchData(id, bypass) { + return (dispatch) => { + if (!bypass) { + dispatch(userProfilePublicIsLoading(true)); + dispatch(userProfilePublicHasErrored(false)); + } + + /** + * create functions to fetch user's profile and other data + */ + // profile + const getUserAccount = () => api.get(`/client/${id}/`); + + // assignments + const getUserAssignments = () => api.get(`/client/${id}/assignments/`); + + // bids + const getUserBids = () => api.get(`/client/${id}/bids/`); + + // use api' Promise.all to fetch the profile, assignments and any other requests we + // might add in the future + axios.all([getUserAccount(), getUserAssignments(), getUserBids()]) + .then(axios.spread((acct, assignments, bids) => { + // form the userProfile object + if (!get(acct, 'data.id')) { + dispatch(userProfilePublicHasErrored(true)); + dispatch(userProfilePublicIsLoading(false)); + } else { + const account = acct.data; + const newProfileObject = { + ...account, + assignments: get(assignments, 'data.results', []), + bidList: get(bids, 'data.results', []), + // any other profile info we want to add in the future + }; + + // then perform dispatches + dispatch(userProfilePublicFetchDataSuccess(newProfileObject)); + dispatch(userProfilePublicIsLoading(false)); + dispatch(userProfilePublicHasErrored(false)); + } + })) + .catch(() => { + dispatch(userProfilePublicHasErrored(true)); + dispatch(userProfilePublicIsLoading(false)); + }); + }; +} diff --git a/src/actions/userProfilePublic.test.js b/src/actions/userProfilePublic.test.js new file mode 100644 index 0000000000..5b8fddd455 --- /dev/null +++ b/src/actions/userProfilePublic.test.js @@ -0,0 +1,80 @@ +import { setupAsyncMocks } from '../testUtilities/testUtilities'; +import * as actions from './userProfilePublic'; +import assignmentObject from '../__mocks__/assignmentObject'; +import bidListObject from '../__mocks__/bidListObject'; + +const { mockStore, mockAdapter } = setupAsyncMocks(); + +describe('async actions', () => { + const profile = { + id: 1, + user: { + username: 'townpostj', + email: 'townpostj@state.gov', + first_name: 'Jenny', + last_name: 'Townpost', + }, + languages: [], + favorite_positions: [{ id: 1 }], + received_shares: [], + }; + + const assignments = [assignmentObject]; + + const bids = bidListObject; + + // reset the mockAdapter since we repeat specific requests + beforeEach(() => { + mockAdapter.reset(); + }); + + it('can fetch a client', (done) => { + const store = mockStore({ profile: {} }); + + mockAdapter.onGet('http://localhost:8000/api/v1/client/1/').reply(200, + profile, + ); + + mockAdapter.onGet('http://localhost:8000/api/v1/client/1/assignments/').reply(200, + { results: assignments }, + ); + + mockAdapter.onGet('http://localhost:8000/api/v1/client/1/bids/').reply(200, + { results: bids }, + ); + + const f = () => { + setTimeout(() => { + store.dispatch(actions.userProfilePublicFetchData()); + done(); + }, 0); + }; + f(); + }); + + it('can handle errors', (done) => { + const store = mockStore({ profile: {} }); + + mockAdapter.reset(); + + mockAdapter.onGet('http://localhost:8000/api/v1/client/1/').reply(404, + {}, + ); + + mockAdapter.onGet('http://localhost:8000/api/v1/client/1/assignments/').reply(404, + {}, + ); + + mockAdapter.onGet('http://localhost:8000/api/v1/client/1/bids/').reply(404, + {}, + ); + + const f = () => { + setTimeout(() => { + store.dispatch(actions.userProfilePublicFetchData()); + done(); + }, 0); + }; + f(); + }); +}); diff --git a/src/reducers/bidList/bidList.js b/src/reducers/bidList/bidList.js index 7ca9b1c587..3b1c0c96e1 100644 --- a/src/reducers/bidList/bidList.js +++ b/src/reducers/bidList/bidList.js @@ -30,14 +30,21 @@ export function bidListToggleHasErrored(state = false, action) { return state; } } -export function bidListToggleIsLoading(state = false, action) { +export function bidListToggleIsLoading(state = new Set(), action) { + const newSet = new Set(state); switch (action.type) { case 'BID_LIST_TOGGLE_IS_LOADING': - return action.isLoading; + if (action.isLoading.bool) { + newSet.add(action.isLoading.id); + return newSet; + } + newSet.delete(action.isLoading.id); + return newSet; default: return state; } } + export function bidListToggleSuccess(state = false, action) { switch (action.type) { case 'BID_LIST_TOGGLE_SUCCESS': diff --git a/src/reducers/bidList/bidList.test.js b/src/reducers/bidList/bidList.test.js index 0f15ad4fee..3fd73630be 100644 --- a/src/reducers/bidList/bidList.test.js +++ b/src/reducers/bidList/bidList.test.js @@ -17,8 +17,12 @@ describe('reducers', () => { expect(reducers.bidListToggleHasErrored(false, { type: 'BID_LIST_TOGGLE_HAS_ERRORED', hasErrored: true })).toBe(true); }); - it('can set reducer BID_LIST_TOGGLE_IS_LOADING', () => { - expect(reducers.bidListToggleIsLoading(false, { type: 'BID_LIST_TOGGLE_IS_LOADING', isLoading: true })).toBe(true); + it('can set reducer BID_LIST_TOGGLE_IS_LOADING to add an id', () => { + expect(reducers.bidListToggleIsLoading(new Set(), { type: 'BID_LIST_TOGGLE_IS_LOADING', isLoading: { bool: true, id: 1 } })).toEqual(new Set([1])); + }); + + it('can set reducer BID_LIST_TOGGLE_IS_LOADING to remove an id', () => { + expect(reducers.bidListToggleIsLoading(new Set(), { type: 'BID_LIST_TOGGLE_IS_LOADING', isLoading: { bool: false, id: 1 } })).toEqual(new Set()); }); it('can set reducer BID_LIST_TOGGLE_SUCCESS', () => { diff --git a/src/reducers/filters/filters.js b/src/reducers/filters/filters.js index e606f4816d..d71d11be5b 100644 --- a/src/reducers/filters/filters.js +++ b/src/reducers/filters/filters.js @@ -9,7 +9,7 @@ const items = title: 'Bid Cycle', sort: 100, description: 'bidCycle', - endpoint: 'bidcycle/', + endpoint: 'bidcycle/?ordering=name', selectionRef: ENDPOINT_PARAMS.bidCycle, text: 'Choose Bid Cycles', }, @@ -187,6 +187,26 @@ const items = { code: 'true', short_description: 'Yes' }, ], }, + + /* Currently we don't display this as a filter, but will appear + as a pill if the query param exists (e.g., the user clicked on Featured positions + positions from the home page). */ + { + item: { + title: 'Featured', + sort: 1000, + bool: true, + description: 'service_needs', + selectionRef: ENDPOINT_PARAMS.highlighted, + text: 'Include only featured positions', + choices: [ + ], + }, + data: [ + { code: 'true', short_description: 'Featured' }, + ], + }, + { item: { title: 'Post', diff --git a/src/reducers/index.js b/src/reducers/index.js index bfab6cb2f6..2a14a82d8f 100644 --- a/src/reducers/index.js +++ b/src/reducers/index.js @@ -14,6 +14,7 @@ import positionDetails from './positionDetails'; import comparisons from './comparisons'; import homePagePositions from './homePagePositions'; import userProfile from './userProfile'; +import userProfilePublic from './userProfilePublic'; import favoritePositions from './favoritePositions'; import bidList from './bidList'; import descriptionEdit from './descriptionEdit'; @@ -46,6 +47,7 @@ export default combineReducers({ ...share, ...savedSearch, ...userProfile, + ...userProfilePublic, ...selectedAccordion, ...routerLocations, ...favoritePositions, diff --git a/src/reducers/userProfilePublic/index.js b/src/reducers/userProfilePublic/index.js new file mode 100644 index 0000000000..fd0488da60 --- /dev/null +++ b/src/reducers/userProfilePublic/index.js @@ -0,0 +1,5 @@ +import { userProfilePublic, userProfilePublicHasErrored, userProfilePublicIsLoading } from './userProfilePublic'; + +export default { userProfilePublic, + userProfilePublicHasErrored, + userProfilePublicIsLoading }; diff --git a/src/reducers/userProfilePublic/userProfilePublic.js b/src/reducers/userProfilePublic/userProfilePublic.js new file mode 100644 index 0000000000..39358e10a1 --- /dev/null +++ b/src/reducers/userProfilePublic/userProfilePublic.js @@ -0,0 +1,26 @@ +import { DEFAULT_USER_PROFILE } from '../../Constants/DefaultProps'; + +export function userProfilePublicHasErrored(state = false, action) { + switch (action.type) { + case 'USER_PROFILE_PUBLIC_HAS_ERRORED': + return action.hasErrored; + default: + return state; + } +} +export function userProfilePublicIsLoading(state = false, action) { + switch (action.type) { + case 'USER_PROFILE_PUBLIC_IS_LOADING': + return action.isLoading; + default: + return state; + } +} +export function userProfilePublic(state = DEFAULT_USER_PROFILE, action) { + switch (action.type) { + case 'USER_PROFILE_PUBLIC_FETCH_DATA_SUCCESS': + return action.userProfile; + default: + return state; + } +} diff --git a/src/reducers/userProfilePublic/userProfilePublic.test.js b/src/reducers/userProfilePublic/userProfilePublic.test.js new file mode 100644 index 0000000000..10482c6317 --- /dev/null +++ b/src/reducers/userProfilePublic/userProfilePublic.test.js @@ -0,0 +1,15 @@ +import * as reducers from './userProfilePublic'; + +describe('reducers', () => { + it('can set reducer USER_PROFILE_PUBLIC_HAS_ERRORED', () => { + expect(reducers.userProfilePublicHasErrored(false, { type: 'USER_PROFILE_PUBLIC_HAS_ERRORED', hasErrored: true })).toBe(true); + }); + + it('can set reducer USER_PROFILE_PUBLIC_IS_LOADING', () => { + expect(reducers.userProfilePublicIsLoading(false, { type: 'USER_PROFILE_PUBLIC_IS_LOADING', isLoading: true })).toBe(true); + }); + + it('can set reducer USER_PROFILE_PUBLIC_FETCH_DATA_SUCCESS', () => { + expect(reducers.userProfilePublic({}, { type: 'USER_PROFILE_PUBLIC_FETCH_DATA_SUCCESS', userProfile: true })).toBe(true); + }); +}); diff --git a/src/routes.js b/src/routes.js index 831a82080e..bb1fd1108f 100644 --- a/src/routes.js +++ b/src/routes.js @@ -4,7 +4,8 @@ const routesArray = [ { path: '/results', componentName: 'Results', pageTitle: 'Search Results' }, { path: '/profile', componentName: 'Profile', pageTitle: 'Profile' }, { path: '/details/:id', componentName: 'Position', pageTitle: 'Position details' }, - { path: '/compare/:ids', componentName: 'Compare', pageTitle: 'Compare Positions' }, + { path: '/compare/:ids', key: 'compareID', componentName: 'Compare', pageTitle: 'Compare Positions' }, + { path: '/compare', key: 'compareNoID', componentName: 'Compare', pageTitle: 'Compare Positions' }, { path: '/about', exact: true, componentName: 'About', pageTitle: 'About' }, { path: '/tokenValidation', componentName: 'Login', pageTitle: 'Token Validation' }, { path: '/loginRedirect', componentName: 'LoginRedirect', pageTitle: 'Login Redirect' }, diff --git a/src/sass/_bidCount.scss b/src/sass/_bidCount.scss index 0014518063..879b485d14 100644 --- a/src/sass/_bidCount.scss +++ b/src/sass/_bidCount.scss @@ -81,25 +81,4 @@ .bid-count-number { padding: .2em 1em; } - - @media screen and (max-width: $screen-sm-max) { - // Set our borders - li { - width: 50%; - } - - // The last element gets a border-right, unlike our general
  • s. - li:nth-last-child(3) { - border-right: solid; - border-right-width: $border-width; - } - - .bid-count-list-item:nth-child(odd) { - border-radius: 5px 0 0 5px; - } - - .bid-count-list-item:nth-child(even) { - border-radius: 0 5px 5px 0; - } - } } diff --git a/src/sass/_bidTracker.scss b/src/sass/_bidTracker.scss index 2e5899b41c..4c98d85f19 100644 --- a/src/sass/_bidTracker.scss +++ b/src/sass/_bidTracker.scss @@ -595,10 +595,18 @@ $draft-icon-offset: 120px; .bid-tracker-standby-container { display: flex; + .bid-tracker-standby-inner-container { + display: flex; + flex-direction: column; + } + .bid-tracker-standby-title { background-color: $tm-navy-dark; color: $color-white; + display: flex; + flex-direction: column; float: left; + justify-content: space-around; padding: 13px 59px; text-align: center; width: 270px; diff --git a/src/sass/_compare.scss b/src/sass/_compare.scss index 0f9af8734c..8fbf3322a3 100644 --- a/src/sass/_compare.scss +++ b/src/sass/_compare.scss @@ -1,6 +1,21 @@ .comparison-container { position: relative; + h1 { + font-family: Roboto; + font-size: 2.4rem; + font-weight: 400; + } + + .bid-list-button { + min-width: 100%; + padding: .75em 1em; + + .button-icon { + display: none; + } + } + .post-data-button { color: $color-white; font-size: .8em; @@ -13,13 +28,11 @@ text-align: center; } } +} - .border-bottom-extension { - background: $primary-blue-darker; - height: 1px; - left: -300px; - position: relative; - top: -1px; - width: 2000px; +.comparison-outer-container { + .button-back-link { + margin-bottom: 0; + margin-top: 20px; } } diff --git a/src/sass/_compareDrawer.scss b/src/sass/_compareDrawer.scss new file mode 100644 index 0000000000..7f21327c2f --- /dev/null +++ b/src/sass/_compareDrawer.scss @@ -0,0 +1,78 @@ +.drawer-hidden { + bottom: -30rem !important; +} + +.compare-drawer { + $background: rgba(0, 0, 0, .8); + $item-background: rgba(255, 255, 255, .9); + $item-empty-background: rgba(0, 0, 0, .4); + + background-color: $background; + bottom: 0; + left: 0; + min-height: 100px; + position: fixed; + transition: bottom 1s; + width: 100%; + z-index: 9999; + + .compare-drawer-inner-container { + align-items: center; + display: flex; + justify-content: center; + margin: auto; + max-width: 1200px; + padding: 25px; + } + + .compare-item { + background-color: $item-background; + display: flex; + flex: 1 1 0; + flex-direction: row; + flex-wrap: wrap; + float: left; + font-size: .8em; + height: 90px; + margin-right: 1.5%; + padding: 15px; + position: relative; + width: 0; + } + + .compare-item-empty { + background-color: $item-empty-background; + } + + .button-container { + display: flex; + flex: 1.5; + justify-content: space-around; + } + + .reset-link { + align-items: center; + display: flex; + } + + .check-container { + position: absolute; + right: 5px; + top: 5px; + } + + .data-point { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } + + .title { + width: 90%; + } + + a { + color: $color-white; + cursor: pointer; + } +} diff --git a/src/sass/_condensedCard.scss b/src/sass/_condensedCard.scss index 98e3d75e7c..dc22327777 100644 --- a/src/sass/_condensedCard.scss +++ b/src/sass/_condensedCard.scss @@ -10,6 +10,7 @@ $condensed-card-data-padding: 15px 20px 9px; font-size: 16px; line-height: 19px; margin-bottom: $condensed-card-margin-bottom; + width: 350px; .bid-count-container { display: flex; @@ -212,9 +213,25 @@ $condensed-card-data-padding: 15px 20px 9px; @media screen and (max-width: $screen-md-min) { .usa-width-one-third.condensed-card { display: block; - float: left; - margin-right: initial; - width: 100%; + + &:last-child { + margin-right: 4.82916%; + } + } +} + +@media screen and (min-width: $screen-lg-min) { + .usa-width-one-third.condensed-card { + margin-right: 2.35765%; + } +} + +@media screen and (max-width: $screen-sm-min) { + .usa-width-one-third.condensed-card, + .usa-width-one-third.condensed-card:last-child { + display: block; + margin-left: auto; + margin-right: auto; } } @@ -244,14 +261,6 @@ $condensed-card-data-padding: 15px 20px 9px; } } -.condensed-card-second { - float: left; -} - -.condensed-card-third { - float: left; -} - .condensed-card:last-child { margin-bottom: $condensed-card-margin-bottom; } @@ -294,15 +303,3 @@ $condensed-card-data-padding: 15px 20px 9px; flex-wrap: wrap; } -@media screen and (max-width: $screen-lg-min) and (min-width: $screen-sm-max) { - .condensed-card.usa-width-one-fourth { - display: block; - float: left; - margin-right: 2.35765%; - width: 30.97857%; - } - - .condensed-card.usa-width-one-fourth:nth-child(3n) { - margin-right: 0; - } -} diff --git a/src/sass/_details.scss b/src/sass/_details.scss index 20ce82a87d..0ed78f9452 100644 --- a/src/sass/_details.scss +++ b/src/sass/_details.scss @@ -103,6 +103,23 @@ font-weight: bold; } + .position-error { + display: flex; + flex-direction: column; + margin-top: 50px; + + h2 { + flex: 1; + margin: auto; + margin-bottom: 1em; + } + + p { + flex: 1; + margin: auto; + } + } + .data-point-section { .condensed-card-data-title { font-weight: bold; diff --git a/src/sass/_dropdown.scss b/src/sass/_dropdown.scss index 9430ebb3e5..aa9442cd47 100644 --- a/src/sass/_dropdown.scss +++ b/src/sass/_dropdown.scss @@ -31,6 +31,10 @@ display: block; } +.dropdown-content-outer-container { + padding-top: 5px; +} + .account-dropdown--avatar { font-size: $avatar-dropdown-size * $avatar-text-ratio; height: $avatar-dropdown-size; @@ -59,7 +63,6 @@ background: $avatar-dropdown-background; border-top: $avatar-dropdown-border-top-color 1px solid; box-shadow: 0 5px 8px $avatar-dropdown-box-shadow-color; - margin-top: 5px; } } diff --git a/src/sass/_profile.scss b/src/sass/_profile.scss index 4dc326d5b5..c576e1e4cf 100644 --- a/src/sass/_profile.scss +++ b/src/sass/_profile.scss @@ -166,6 +166,21 @@ float: left; } + .assignments-section { + .favorites-list-container { + max-height: 410px; + overflow-y: scroll; + } + + .start-end { + display: inline; + + .fa { + color: $color-green; + } + } + } + .user-dashboard-section { background-color: $color-white; border: solid 1px $color-gray-light; diff --git a/src/sass/_results.scss b/src/sass/_results.scss index 5d4bf9c2d3..de9bcc538a 100644 --- a/src/sass/_results.scss +++ b/src/sass/_results.scss @@ -17,7 +17,15 @@ $results-container-width: 100% - $filter-container-width; .results-dropdown-sort { float: left; + } + + .results-download { + float: left; margin-left: 10px; + + button { + margin: 0; + } } .no-results { @@ -41,7 +49,7 @@ $results-container-width: 100% - $filter-container-width; dl { dt, dd { - vertical-align: bottom; + vertical-align: top; } } @@ -53,23 +61,47 @@ $results-container-width: 100% - $filter-container-width; margin-bottom: 1rem; margin-top: .2rem; + .results-card-title-link { + display: flex; + justify-content: flex-start; + } + + .bid-count-secondary { + display: inline-block; + float: right; + } + + a { + font-size: 1.5rem; + margin-top: 2px; + } + h3, dt, dd { font-family: 'Roboto'; - font-size: 1.6rem; + font-size: 1.5rem; margin-bottom: 0; + margin-left: 0; margin-top: 0; } + dd { + padding: 0 0 0 .5rem; + } + h3, dt { font-weight: 500; } + + h3 { + font-size: 1.7rem; + margin-right: .5em; + } } .footer { - margin-bottom: rem(14px); margin-top: rem(14px); padding-top: rem(4px); @@ -438,6 +470,7 @@ $results-container-width: 100% - $filter-container-width; } .results-section-container { + margin-top: 21px; max-width: 1140px; } diff --git a/src/sass/_resultsSearchBarContainer.scss b/src/sass/_resultsSearchBarContainer.scss index aeb5d51592..022e3ed08b 100644 --- a/src/sass/_resultsSearchBarContainer.scss +++ b/src/sass/_resultsSearchBarContainer.scss @@ -14,6 +14,21 @@ $input-height: 40px; padding: 0 0 15px; + .homepage-search-legend { + font-size: 16px; + font-weight: normal; + margin: 0 0 5px; + text-transform: capitalize; + } + + .search-sub-text { + font-size: .7em; + font-style: italic; + margin: 5px 0 0 10px; + position: absolute; + white-space: nowrap; + } + label { font-size: .8em; } @@ -53,7 +68,7 @@ $input-height: 40px; } .search-submit-button { - margin-top: 35px; + margin-top: 37px; button { font-size: .9em; @@ -179,3 +194,13 @@ $input-height: 40px; margin-top: 3px; } } + +.results { + .homepage-offset.results-single-search { + padding-top: 2px; + + .search-submit-button { + margin-top: 33px; + } + } +} diff --git a/src/sass/_table.scss b/src/sass/_table.scss index b2d3a7e310..7177e2c47b 100644 --- a/src/sass/_table.scss +++ b/src/sass/_table.scss @@ -1,51 +1,75 @@ $tm-table-blue: $primary-blue-darkest; .tm-table { - $border-visible-height: 500%; - $border-radius: 9px; + + border-collapse: separate; + border-spacing: 15px 0; color: $primary-base; - margin-bottom: 0; + margin-top: 2em; table-layout: fixed; width: 100%; + .bid-count-container { + float: left; + font-size: .8em; + } + + .column-title-main { + color: $color-black; + float: left; + font-size: 1.1em; + font-weight: bold; + width: 90%; + } + + .close-button-container { + float: left; + width: 10%; + + .fa { + float: right; + font-size: .8em; + position: relative; + right: -7px; + top: -6px; + } + } + .column-title-sub, .column-title-link { - font-weight: 300; + margin-top: .2em; } .column-title-link { font-size: 1.6rem; a { - color: $color-white; + color: $color-black; + } + } + + .empty { + color: $color-gray; + } + + .favorite-container { + float: left; + width: 100%; + + .fa { + margin-right: 5px; } } th, td { border: 0; + vertical-align: top; word-wrap: break-word; } thead { - background: $tm-table-blue; - color: $color-white; - - .border-extension { - height: 1px; - } - - .border-visible { - background: $tm-table-blue; - height: $border-visible-height; - left: -300px; - position: absolute; - top: 0; - width: 2000px; - z-index: -100; - } - th { background: inherit; font-size: 1.9rem; @@ -65,37 +89,52 @@ $tm-table-blue: $primary-blue-darkest; tbody { position: relative; - .border-extension-layer-2 { - height: 1px; + tr:nth-of-type(1), + tr:nth-of-type(2), + tr:last-of-type { + th:first-of-type { + visibility: hidden; + } } - .border-visible-layer-2 { - background: $color-white; - height: $border-visible-height + 500%; - left: -300px; - position: absolute; - top: 0; - width: 2000px; - z-index: -50; + tr:nth-of-type(2) { + th, + td { + padding-bottom: 5px; + padding-top: 0; + } } - .border-extension { - height: 1px; + tr:nth-of-type(1) { + th, + td { + padding-bottom: 0; + } } - .border-visible { - background: $tm-table-blue; - height: 1px; - left: -300px; - position: relative; - top: 16px; - width: 2000px; + tr:nth-last-child(2) { + th, + td { + padding-bottom: 0; + } } - tr:first-of-type { + tr:nth-last-child(1) { th, td { - padding-top: 1.5em; + padding-top: 0; + } + } + + $empty-border: 1px dashed $color-gray-light; + + tr:first-of-type { + td { + border-top: 10px solid $primary-blue; + } + + .empty { + border-top: $empty-border; } } @@ -110,8 +149,26 @@ $tm-table-blue: $primary-blue-darkest; width: 18%; } + $td-border: 1px solid $color-gray-light; + td { - border-left: 1px solid $tm-table-blue; + border-left: $td-border; + border-right: $td-border; + } + + .empty { + border-left: $empty-border; + border-right: $empty-border; + } + + tr:last-of-type { + td { + border-bottom: $td-border; + } + + .empty { + border-bottom: $empty-border; + } } } } diff --git a/src/sass/styles.scss b/src/sass/styles.scss index ac086cde56..f45bd109a2 100644 --- a/src/sass/styles.scss +++ b/src/sass/styles.scss @@ -79,6 +79,7 @@ @import 'betaHeader'; @import 'savedSearches'; @import 'emptyListAlert'; +@import 'compareDrawer'; @import 'inputs'; @import 'toast'; @import 'overrides'; diff --git a/src/utilities.js b/src/utilities.js index 0592222e6e..ae637d835b 100644 --- a/src/utilities.js +++ b/src/utilities.js @@ -28,6 +28,19 @@ export function localStorageFetchValue(key, value) { return saved; } +const dispatchLs = (key) => { + // create, initialize, and dispatch event + const event = document.createEvent('Event'); + event.initEvent(`${key}-ls`, true, true); + document.dispatchEvent(event); +}; + +export function localStorageSetKey(key, value) { + localStorage.setItem(key, value); + dispatchLs(key); +} + +// toggling a specific value in an array export function localStorageToggleValue(key, value) { const existingArray = JSON.parse(localStorage.getItem(key)) || []; const indexOfId = existingArray.indexOf(value); @@ -35,10 +48,12 @@ export function localStorageToggleValue(key, value) { existingArray.splice(indexOfId, 1); localStorage.setItem(key, JSON.stringify(existingArray)); + dispatchLs(key); } else { existingArray.push(value); localStorage.setItem(key, JSON.stringify(existingArray)); + dispatchLs(key); } } @@ -516,3 +531,10 @@ export const isUrl = (url) => { const regex = new RegExp(expression); return url.match(regex); }; + +export const getScrollDistanceFromBottom = () => { + const scrollPosition = window.pageYOffset; + const windowSize = window.innerHeight; + const bodyHeight = document.body.offsetHeight; + return (Math.max(bodyHeight - (scrollPosition + windowSize), 0)); +}; diff --git a/yarn.lock b/yarn.lock index b875aa224a..5c61eef9e2 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7585,6 +7585,11 @@ react-autowhatever@^10.1.0: react-themeable "^1.1.0" section-iterator "^2.0.0" +react-csv@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/react-csv/-/react-csv-1.1.1.tgz#718dadd2607a1795bfc8dbd32ca282a043739634" + integrity sha512-eQSrkEfzPN+1IeXT58kJaDNmpFr3RhdjfE09jTrw+jdTEp0g0Ig9NOI+TpvMBbCukE1HQhOf4dyrLDRFKgZzbA== + react-dev-utils@^3.1.0, react-dev-utils@^3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/react-dev-utils/-/react-dev-utils-3.1.1.tgz#09ae7209a81384248db56547e718e65bd3b20eb5"