diff --git a/src/App.jsx b/src/App.jsx index c50708b..43c9253 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -88,9 +88,12 @@ const App = () => { if (location.pathname === "/earn/my-gigs" && isLoggedIn) { if (!location.search) { store.dispatch(actions.filter.updateGigFilter(initialGigFilter)); - + const cachedGigs = store.getState().myGigs[initialGigFilter.status]; + if (cachedGigs.myGigs && cachedGigs.myGigs.length !== 0) { + return; + } store.dispatch( - actions.myGigs.getMyGigs( + actions.myGigs.getMyOpenGigs( constants.GIGS_FILTER_STATUSES_PARAM[initialGigFilter.status] ) ); @@ -98,23 +101,76 @@ const App = () => { } const params = utils.url.parseUrlQuery(location.search); if (_.keys(params).length == 1 && params.externalId) { + store.dispatch(actions.myGigs.startCheckingGigs(params.externalId)); return; } + const s = + _.values(constants.GIGS_FILTER_STATUSES).indexOf(params.status) >= 0 + ? params.status + : null; const updatedGigFilter = { - status: params.status || "Open Applications", + status: s || "Open Applications", }; const currentGig = store.getState().filter.gig; const diff = !_.isEqual(updatedGigFilter, currentGig); if (diff) { store.dispatch(actions.filter.updateGigFilter(updatedGigFilter)); } - getDataDebounced.current(() => - store.dispatch( - actions.myGigs.getMyGigs( - constants.GIGS_FILTER_STATUSES_PARAM[updatedGigFilter.status] - ) - ) - ); + if (updatedGigFilter.status !== initialGigFilter.status) { + // preload the open application first page data. + const cachedOpenGigs = store.getState().myGigs[initialGigFilter.status]; + if (!cachedOpenGigs.myGigs) { + store.dispatch( + actions.myGigs.getMyOpenGigs( + constants.GIGS_FILTER_STATUSES_PARAM[initialGigFilter.status] + ) + ); + } + } + const cachedGigs = store.getState().myGigs[updatedGigFilter.status]; + if (cachedGigs.myGigs) { + return; + } + getDataDebounced.current(() => { + if ( + updatedGigFilter.status == constants.GIGS_FILTER_STATUSES.ACTIVE_JOBS + ) { + store.dispatch( + actions.myGigs.getMyActiveGigs( + constants.GIGS_FILTER_STATUSES_PARAM[updatedGigFilter.status] + ) + ); + } + if ( + updatedGigFilter.status == constants.GIGS_FILTER_STATUSES.OPEN_JOBS + ) { + store.dispatch( + actions.myGigs.getMyOpenGigs( + constants.GIGS_FILTER_STATUSES_PARAM[updatedGigFilter.status] + ) + ); + } + if ( + updatedGigFilter.status == + constants.GIGS_FILTER_STATUSES.COMPLETED_JOBS + ) { + store.dispatch( + actions.myGigs.getMyCompletedGigs( + constants.GIGS_FILTER_STATUSES_PARAM[updatedGigFilter.status] + ) + ); + } + if ( + updatedGigFilter.status == + constants.GIGS_FILTER_STATUSES.ARCHIVED_JOBS + ) { + store.dispatch( + actions.myGigs.getMyArchivedGigs( + constants.GIGS_FILTER_STATUSES_PARAM[updatedGigFilter.status] + ) + ); + } + }); } }, [location, isLoggedIn]); diff --git a/src/actions/myGigs.js b/src/actions/myGigs.js index 0c9c8c8..2e14a3c 100644 --- a/src/actions/myGigs.js +++ b/src/actions/myGigs.js @@ -6,24 +6,36 @@ import { } from "../constants"; import service from "../services/myGigs"; -/** - * Action to get my gigs. - * @param {number} page page to fetch - * @param {number} perPage items per page. by default is 10. - * @returns - */ -async function getMyGigs(status = "open_jobs", page = 1, perPage = PER_PAGE) { +async function getMyActiveGigs( + status = "active_jobs", + page = 1, + perPage = PER_PAGE +) { return service.getMyGigs(status, page, perPage); } -/** - * Action to load more pages of my gigs - * @param {number} nextPage page to fetch - * @param {*} perPage items per page. by default is 10 - * @returns - */ -async function loadMoreMyGigs(status, nextPage, perPage = PER_PAGE) { - return service.getMyGigs(status, nextPage, perPage); +async function getMyOpenGigs( + status = "open_jobs", + page = 1, + perPage = PER_PAGE +) { + return service.getMyGigs(status, page, perPage); +} + +async function getMyCompletedGigs( + status = "completed_jobs", + page = 1, + perPage = PER_PAGE +) { + return service.getMyGigs(status, page, perPage); +} + +async function getMyArchivedGigsDone( + status = "archived_jobs", + page = 1, + perPage = PER_PAGE +) { + return service.getMyGigs(status, page, perPage); } async function getProfile() { @@ -54,8 +66,10 @@ async function startCheckingGigs(externalId) { } export default createActions({ - GET_MY_GIGS: getMyGigs, - LOAD_MORE_MY_GIGS: loadMoreMyGigs, + GET_MY_ACTIVE_GIGS: getMyActiveGigs, + GET_MY_OPEN_GIGS: getMyOpenGigs, + GET_MY_COMPLETED_GIGS: getMyCompletedGigs, + GET_MY_ARCHIVED_GIGS: getMyArchivedGigsDone, GET_PROFILE: getProfile, UPDATE_PROFILE: updateProfile, START_CHECKING_GIGS: startCheckingGigs, diff --git a/src/api/common/helper.js b/src/api/common/helper.js index 85729a4..3f36bbb 100644 --- a/src/api/common/helper.js +++ b/src/api/common/helper.js @@ -299,7 +299,7 @@ async function getJobCandidates(criteria) { * @param {*} userId * @returns */ -async function handlePlacedJobCandidates(jobCandidates, userId) { +async function handlePlacedJobCandidates(jobCandidates, userId, userHandle) { if (!jobCandidates || jobCandidates.length == 0 || !userId) { return; } @@ -345,13 +345,71 @@ async function handlePlacedJobCandidates(jobCandidates, userId) { new Date(rb.endDate).toDateString() != new Date().toDateString() ) { jc.status = "completed"; + jc.rbStartDate = rb.startDate; + jc.rbEndDate = rb.endDate; + jc.rbId = rb.id; + jc.userHandle = userHandle; } } } }); + await getWorkingPeriods(jobCandidates, userHandle, rbRes); return; } +/** + * Get payment Total for working period + * + * @param {*} jobCandidates job candidates we will process + * @param {*} userHandle the user's handle + * @param {*} resourceBookings the resource booking belongs to this user + * @returns + */ +async function getWorkingPeriods(jobCandidates, userHandle, resourceBookings) { + if ( + !userHandle || + !resourceBookings || + resourceBookings.length == 0 || + !jobCandidates || + jobCandidates.length == 0 + ) { + return; + } + const rbIds = resourceBookings.map((item) => item.id); + const token = await getM2MToken(); + const url = `${config.API.V5}/work-periods`; + const criteria = { + userHandle: userHandle, + resourceBookingIds: rbIds.join(","), + }; + const res = await request + .get(url) + .query(criteria) + .set("Authorization", `Bearer ${token}`) + .set("Accept", "application/json"); + localLogger.debug({ + context: "getWorkingPeriods", + message: `response body: ${JSON.stringify(res.body)}`, + }); + if (res.body && res.body.length == 0) { + return; + } + // All the working periods for the rbs. + const wpRes = res.body; + _.each(rbIds, (rbId) => { + const wps = wpRes.filter( + (wp) => wp.userHandle == userHandle && wp.resourceBookingId == rbId + ); + const paymentTotal = wps.reduce((total, wp) => total + wp.paymentTotal, 0); + const jc = jobCandidates.find( + (item) => item.rbId == rbId && item.userHandle == userHandle + ); + if (jc) { + jc.paymentTotal = paymentTotal; + } + }); +} + /** * Return jobs by given criteria * @param {string} criteria the search criteria diff --git a/src/api/docs/swagger.yaml b/src/api/docs/swagger.yaml index 24ab579..3fd5238 100644 --- a/src/api/docs/swagger.yaml +++ b/src/api/docs/swagger.yaml @@ -273,6 +273,23 @@ components: example: "Dummy title" description: "The title." maxLength: 64 + paymentTotal: + type: integer + example: 20 + description: "the amount of payment that a member has received for a completed job" + rbStartDate: + type: string + example: "2021-06-05" + description: "the official start time for a job" + rbEndDate: + type: string + example: "2021-07-05" + description: "the official end time for a job" + updatedAt: + type: string + format: date-time + example: "2021-06-21T03:57:17.774Z" + description: "The latest updated date time for a jobCandidate." payment: $ref: "#/components/schemas/Payment" hoursPerWeek: diff --git a/src/api/services/JobApplicationService.js b/src/api/services/JobApplicationService.js index c44ef24..7d0d8db 100644 --- a/src/api/services/JobApplicationService.js +++ b/src/api/services/JobApplicationService.js @@ -30,12 +30,12 @@ async function getMyJobApplications(currentUser, criteria) { return emptyResult; } // get user id by calling taas-api with current user's token - const { id: userId } = await helper.getCurrentUserDetails( + const { id: userId, handle: userHandle } = await helper.getCurrentUserDetails( currentUser.jwtToken ); - if (!userId) { + if (!userId || !userHandle) { throw new errors.NotFoundError( - `Id for user: ${currentUser.userId} not found` + `Id for user: ${currentUser.userId} or handle for user: ${currentUser.handle} not found` ); } // get jobCandidates of current user by calling taas-api @@ -54,7 +54,11 @@ async function getMyJobApplications(currentUser, criteria) { let jcResult = jobCandidates.result; // handle placed status for completed_jobs, archived_jobs query if (status && (status == "active_jobs" || status == "completed_jobs")) { - await helper.handlePlacedJobCandidates(jobCandidates.result, userId); + await helper.handlePlacedJobCandidates( + jobCandidates.result, + userId, + userHandle + ); if (status == "completed_jobs") { jcResult = jobCandidates.result.filter( (item) => item.status == "completed" @@ -76,6 +80,10 @@ async function getMyJobApplications(currentUser, criteria) { const job = _.find(jobs, ["id", jobCandidate.jobId]); return { title: job.title, + paymentTotal: jobCandidate.paymentTotal, + rbStartDate: jobCandidate.rbStartDate, + rbEndDate: jobCandidate.rbEndDate, + updatedAt: jobCandidate.updatedAt, payment: { min: job.minSalary, max: job.maxSalary, diff --git a/src/assets/images/completed.svg b/src/assets/images/completed.svg new file mode 100644 index 0000000..8b63802 --- /dev/null +++ b/src/assets/images/completed.svg @@ -0,0 +1,11 @@ + + + C23ADC25-9150-4E3C-9043-97A704EBE690 + + + + + + + + \ No newline at end of file diff --git a/src/components/Empty/styles.scss b/src/components/Empty/styles.scss index c2c49b6..84f4f2d 100644 --- a/src/components/Empty/styles.scss +++ b/src/components/Empty/styles.scss @@ -8,6 +8,7 @@ background-color: #ffffff; padding: 81px 0px 330px 0px; min-width: 650px; + min-height: 551px; .empty-inner { display: flex; flex-direction: column; diff --git a/src/constants/index.js b/src/constants/index.js index 152179d..31c4635 100644 --- a/src/constants/index.js +++ b/src/constants/index.js @@ -353,10 +353,10 @@ export const SORT_STATUS_ORDER = [ MY_GIG_PHASE.PHONE_SCREEN, MY_GIG_PHASE.SKILLS_TEST, MY_GIG_PHASE.APPLIED, + MY_GIG_PHASE.WITHDRAWN, MY_GIG_PHASE.JOB_CLOSED, MY_GIG_PHASE.NOT_SELECTED, MY_GIG_PHASE.COMPLETED, - MY_GIG_PHASE.WITHDRAWN, ]; export const PER_PAGE = 10; @@ -373,6 +373,8 @@ export const AVAILABLE_REMARK_BY_JOB_STATUS = [ MY_GIGS_JOB_STATUS.REJECTED_OTHER, MY_GIGS_JOB_STATUS.REJECTED_PRE_SCREEN, MY_GIGS_JOB_STATUS.JOB_CLOSED, + MY_GIGS_JOB_STATUS.WITHDRAWN, + MY_GIGS_JOB_STATUS.WITHDRAWN_PRESCREEN, ]; export const MY_GIG_STATUS_PLACED = "PLACED"; @@ -387,7 +389,8 @@ export const GIG_STATUS_TOOLTIP = { }; export const MY_GIGS_STATUS_EMPTY_TEXT = { - [GIGS_FILTER_STATUSES.ACTIVE_JOBS]: "YOU DON'T HAVE ANY ACTIVE GIGS YET.", + [GIGS_FILTER_STATUSES.ACTIVE_JOBS]: + "YOU ARE NOT ENGAGED IN ANY GIGS AT THE MOMENT.", [GIGS_FILTER_STATUSES.OPEN_JOBS]: "LOOKS LIKE YOU HAVEN'T APPLIED TO ANY GIG OPPORTUNITIES YET.", [GIGS_FILTER_STATUSES.COMPLETED_JOBS]: @@ -395,6 +398,14 @@ export const MY_GIGS_STATUS_EMPTY_TEXT = { [GIGS_FILTER_STATUSES.ARCHIVED_JOBS]: "YOU DON'T HAVE ANY ARCHIVED GIGS YET.", }; +export const MY_GIGS_STATUS_REMARK_TEXT = { + [MY_GIGS_JOB_STATUS.WITHDRAWN]: + "You withdrew your application for this gig or you have been placed in another gig.", + [MY_GIGS_JOB_STATUS.WITHDRAWN_PRESCREEN]: + "You withdrew your application for this gig or you have been placed in another gig.", + [MY_GIGS_JOB_STATUS.COMPLETED]: "Congrats on completing the gig!", +}; + export const CHECKING_GIG_TIMES = 3; export const DELAY_CHECK_GIG_TIME = 2000; diff --git a/src/containers/MyGigs/JobListing/JobCard/index.jsx b/src/containers/MyGigs/JobListing/JobCard/index.jsx index 2828366..2da84b4 100644 --- a/src/containers/MyGigs/JobListing/JobCard/index.jsx +++ b/src/containers/MyGigs/JobListing/JobCard/index.jsx @@ -1,4 +1,4 @@ -import React, { useEffect, useRef, useState } from "react"; +import React, { useEffect, useRef, useState, useMemo } from "react"; import PT from "prop-types"; import ProgressBar from "./ProgressBar"; import Ribbon from "../../../../components/Ribbon"; @@ -6,14 +6,18 @@ import Button from "../../../../components/Button"; import IconChevronDown from "assets/icons/button-chevron-down.svg"; import ProgressTooltip from "./tooltips/ProgressTooltip"; import NoteTooltip from "./tooltips/NoteTooltip"; +import EarnTooltip from "./tooltips/EarnTooltip"; import { MY_GIG_PHASE_LABEL, MY_GIG_PHASE_ACTION, MY_GIGS_JOB_STATUS, PHASES_FOR_JOB_STATUS, + MY_GIGS_STATUS_REMARK_TEXT, } from "../../../../constants"; import { formatMoneyValue } from "../../../../utils"; +import { getDateRange } from "../../../../utils/myGig"; import IconNote from "../../../../assets/icons/note.svg"; +import IconInfo from "../../../../assets/icons/ribbon-icon.svg"; import "./styles.scss"; @@ -30,108 +34,158 @@ const JobCard = ({ job }) => { setFooterHeight(footerRef.current.offsetHeight); }, [expanded]); + const paymentInfo = useMemo(() => { + if (job.paymentRangeFrom && job.paymentRangeTo && job.currency) { + return `${job.currency} + ${formatMoneyValue(job.paymentRangeFrom, "")} + ${" - "} + ${formatMoneyValue(job.paymentRangeTo, "")} + ${" (USD)"} + ${" / "} + ${job.paymentRangeRateType}`; + } + return ""; + }, [ + job.paymentRangeFrom, + job.paymentRangeTo, + job.currency, + job.paymentRangeRateType, + ]); + return (
-
-
- ( - {children} - )} - /> + +
+
+ ( + {children} + )} + /> +
-
-
-
-
-

- - {job.title} - -

-
    -
  • -
    -
    Payment Range
    -
    - {job.paymentRangeFrom && - job.paymentRangeTo && - job.currency && ( - <> - {job.currency} - {formatMoneyValue(job.paymentRangeFrom, "")} - {" - "} - {formatMoneyValue(job.paymentRangeTo, "")} - {" (USD)"} - {" / "} - {job.paymentRangeRateType} - - )} +
    +
    +
    +

    {job.title}

    +
      +
    • +
      + {MY_GIGS_JOB_STATUS.COMPLETED === job.status && ( + <> +
      Duration
      +
      + {getDateRange(job.rbStartDate, job.rbEndDate)} +
      + + )} + {MY_GIGS_JOB_STATUS.COMPLETED !== job.status && ( + <> +
      Payment Range
      +
      {paymentInfo}
      + + )}
      -
    -
  • -
  • -
    -
    Location
    -
    {job.location}
    -
    -
  • -
  • -
    -
    Duration
    -
    - {job.duration && `${job.duration} Weeks`} +
  • +
  • +
    + {MY_GIGS_JOB_STATUS.COMPLETED === job.status && ( + <> +
    + Total Earnings + + + + + +
    +
    {`${job.currency}${job.paymentTotal}`}
    + + )} + {MY_GIGS_JOB_STATUS.COMPLETED !== job.status && ( + <> +
    Location
    +
    {job.location}
    + + )}
    -
- -
  • -
    -
    Hours
    -
    - {job.hours && `${job.hours} hours / week`} +
  • + {MY_GIGS_JOB_STATUS.COMPLETED !== job.status && ( +
  • +
    +
    Duration
    +
    + {job.duration && `${job.duration} Weeks`} +
    +
    +
  • + )} +
  • +
    +
    Hours
    +
    + {job.hours && `${job.hours} hours / week`} +
    -
  • - -
  • -
    -
    Working Hours
    -
    - {job.workingHours && `${job.workingHours} hours`} +
  • +
  • +
    +
    Working Hours
    +
    + {job.workingHours && `${job.workingHours} hours`} +
    -
  • - - -
    -
    - {job.phaseAction && } + + +
    +
    + {job.phaseAction && } +
    - +
    - {job.remark && ( + {(job.remark || + [ + MY_GIGS_JOB_STATUS.WITHDRAWN, + MY_GIGS_JOB_STATUS.WITHDRAWN_PRESCREEN, + MY_GIGS_JOB_STATUS.COMPLETED, + ].includes(job.status)) && ( )} - {job.remark} + + {[ + MY_GIGS_JOB_STATUS.WITHDRAWN, + MY_GIGS_JOB_STATUS.WITHDRAWN_PRESCREEN, + MY_GIGS_JOB_STATUS.COMPLETED, + ].includes(job.status) + ? MY_GIGS_STATUS_REMARK_TEXT[job.status] + : job.remark} + {![ MY_GIGS_JOB_STATUS.JOB_CLOSED, MY_GIGS_JOB_STATUS.REJECTED_OTHER, diff --git a/src/containers/MyGigs/JobListing/JobCard/styles.scss b/src/containers/MyGigs/JobListing/JobCard/styles.scss index c0f1523..b1a69f8 100644 --- a/src/containers/MyGigs/JobListing/JobCard/styles.scss +++ b/src/containers/MyGigs/JobListing/JobCard/styles.scss @@ -10,6 +10,7 @@ border-radius: $border-radius-lg; box-shadow: $shadow; z-index:0; + min-width: 780px; } .card-header { @@ -53,7 +54,8 @@ .job-card { &.label-selected, &.label-offered, - &.label-placed { + &.label-placed, + &.label-completed { .job-card-content { color: $white; } @@ -89,6 +91,23 @@ } } + &.label-withdrawn { + :global(.ribbon) { + color: $white; + background: linear-gradient(221.5deg, #2A2A2A 0%, #555555 100%); + } + } + + &.label-completed { + :global(.ribbon) { + color: $tc-black; + background: $tc-white; + } + .card-image { + background: url('https://github.com/assets/images/completed.svg') no-repeat right 24px center / auto, linear-gradient(52.91deg, #2984BD 0%, #50ADE8 100%); + } + } + &.label-not-selected { :global(.ribbon) { color: $white; @@ -132,6 +151,19 @@ font-size: $font-size-xs; color: $tc-gray-70; line-height: $line-height-xs; + + .earn-tip { + position: absolute; + line-height: 22px; + + svg { + transform: scale(0.71); + + path { + fill: currentColor + } + } + } } .text { @@ -158,6 +190,7 @@ color: $tc-gray-70; background-color: $gray1; border-color: $gray2; + cursor: default; } } diff --git a/src/containers/MyGigs/JobListing/JobCard/tooltips/EarnTooltip/index.jsx b/src/containers/MyGigs/JobListing/JobCard/tooltips/EarnTooltip/index.jsx new file mode 100644 index 0000000..9fb1b06 --- /dev/null +++ b/src/containers/MyGigs/JobListing/JobCard/tooltips/EarnTooltip/index.jsx @@ -0,0 +1,25 @@ +import React from "react"; +import PT from "prop-types"; +import Tooltip from "../../../../../../components/Tooltip"; + +import "./styles.scss"; + +const EarnTooltip = ({ children }) => { + const Content = () => ( +
    + Amount may not reflect any pending payments +
    + ); + + return ( + } placement="bottom"> + {children} + + ); +}; + +EarnTooltip.propTypes = { + children: PT.node, +}; + +export default EarnTooltip; diff --git a/src/containers/MyGigs/JobListing/JobCard/tooltips/EarnTooltip/styles.scss b/src/containers/MyGigs/JobListing/JobCard/tooltips/EarnTooltip/styles.scss new file mode 100644 index 0000000..1593d64 --- /dev/null +++ b/src/containers/MyGigs/JobListing/JobCard/tooltips/EarnTooltip/styles.scss @@ -0,0 +1,9 @@ +@import "https://github.com/styles/variables"; + +.earn-tooltip { + width: 280px; + padding: 7px 10px; + line-height: $line-height-base; + font-size: 12px; + text-align: center; +} diff --git a/src/containers/MyGigs/JobListing/index.jsx b/src/containers/MyGigs/JobListing/index.jsx index 8e5caa0..14608b6 100644 --- a/src/containers/MyGigs/JobListing/index.jsx +++ b/src/containers/MyGigs/JobListing/index.jsx @@ -7,9 +7,9 @@ import * as constants from "../../../constants"; import "./styles.scss"; -const JobListing = ({ jobs, loadMore, total, numLoaded, gigStatus }) => { +const JobListing = ({ jobs, loadMore, total, numLoaded, gigStatus, page }) => { const scrollLock = useScrollLock(); - const [page, setPage] = useState(1); + // const [page, setPage] = useState(1); const varsRef = useRef(); varsRef.current = { scrollLock }; @@ -17,7 +17,7 @@ const JobListing = ({ jobs, loadMore, total, numLoaded, gigStatus }) => { const handleLoadMoreClick = () => { const nextPage = page + 1; scrollLock(true); - setPage(nextPage); + // setPage(nextPage); loadMore(constants.GIGS_FILTER_STATUSES_PARAM[gigStatus], nextPage); }; @@ -25,17 +25,40 @@ const JobListing = ({ jobs, loadMore, total, numLoaded, gigStatus }) => { varsRef.current.scrollLock(false); }, [jobs]); - useEffect(() => { - setPage(1); - }, [gigStatus]); - + // useEffect(() => { + // setPage(1); + // }, [gigStatus]); return (
    - {jobs.map((job, index) => ( -
    - -
    - ))} + {![ + constants.GIGS_FILTER_STATUSES.COMPLETED_JOBS, + constants.GIGS_FILTER_STATUSES.ARCHIVED_JOBS, + ].includes(gigStatus) && + jobs.map((job, index) => ( +
    + +
    + ))} + + {[ + constants.GIGS_FILTER_STATUSES.COMPLETED_JOBS, + constants.GIGS_FILTER_STATUSES.ARCHIVED_JOBS, + ].includes(gigStatus) && + jobs + .sort((a, b) => { + if (a.sortPrio == b.sortPrio) { + return ( + new Date(b.updatedAt).getTime() - + new Date(a.updatedAt).getTime() + ); + } + return a.sortPrio - b.sortPrio; + }) + .map((job, index) => ( +
    + +
    + ))} {numLoaded < total && page * constants.PER_PAGE < total && (
    @@ -52,6 +75,7 @@ JobListing.propTypes = { total: PT.number, numLoaded: PT.number, gigStatus: PT.string, + page: PT.number, }; export default JobListing; diff --git a/src/containers/MyGigs/index.jsx b/src/containers/MyGigs/index.jsx index e30da7b..6c755b9 100644 --- a/src/containers/MyGigs/index.jsx +++ b/src/containers/MyGigs/index.jsx @@ -1,5 +1,5 @@ -import React, { useEffect, useRef, useState } from "react"; -import { useLocation } from "@reach/router"; +import React, { useEffect, useRef, useState, useCallback } from "react"; +// import { useLocation } from "@reach/router"; import PT from "prop-types"; import { connect } from "react-redux"; import Modal from "../../components/Modal"; @@ -8,7 +8,7 @@ import Loading from "../../components/Loading"; import Empty from "../../components/Empty"; import JobListing from "./JobListing"; import actions from "../../actions"; -import * as utils from "../../utils"; +import * as constants from "../../constants"; import UpdateGigProfile from "./modals/UpdateGigProfile"; import UpdateSuccess from "./modals/UpdateSuccess"; @@ -16,11 +16,10 @@ import UpdateSuccess from "./modals/UpdateSuccess"; import "./styles.scss"; const MyGigs = ({ - myGigs, - getMyGigs, - loadMore, - total, - numLoaded, + myActiveGigs, + myOpenGigs, + myCompletedGigs, + myArchivedGigs, profile, getProfile, updateProfile, @@ -29,26 +28,23 @@ const MyGigs = ({ checkingGigs, startCheckingGigs, gigStatus, + loadingMyGigs, + getMyActiveGigs, + getMyOpenGigs, + getMyCompletedGigs, + getMyArchivedGigs, }) => { - const location = useLocation(); - const params = utils.url.parseUrlQuery(location.search); const propsRef = useRef(); propsRef.current = { - getMyGigs, + getMyOpenGigs, getProfile, getAllCountries, startCheckingGigs, - params, }; useEffect(() => { propsRef.current.getProfile(); propsRef.current.getAllCountries(); - if (propsRef.current.params.externalId) { - propsRef.current.startCheckingGigs(propsRef.current.params.externalId); - } else { - // propsRef.current.getMyGigs(); - } }, []); const isInitialMount = useRef(true); @@ -58,12 +54,28 @@ const MyGigs = ({ return; } if (!checkingGigs) { - propsRef.current.getMyGigs(); + propsRef.current.getMyOpenGigs(); } }, [checkingGigs]); const [openUpdateProfile, setOpenUpdateProfile] = useState(false); const [openUpdateSuccess, setOpenUpdateSuccess] = useState(false); + const [currentGigs, setCurrentGigs] = useState({}); + + useEffect(() => { + if (gigStatus == constants.GIGS_FILTER_STATUSES.ACTIVE_JOBS) { + setCurrentGigs(myActiveGigs); + } + if (gigStatus == constants.GIGS_FILTER_STATUSES.OPEN_JOBS) { + setCurrentGigs(myOpenGigs); + } + if (gigStatus == constants.GIGS_FILTER_STATUSES.COMPLETED_JOBS) { + setCurrentGigs(myCompletedGigs); + } + if (gigStatus == constants.GIGS_FILTER_STATUSES.ARCHIVED_JOBS) { + setCurrentGigs(myArchivedGigs); + } + }, [gigStatus, myActiveGigs, myOpenGigs, myCompletedGigs, myArchivedGigs]); useEffect(() => { if (updateProfileSuccess) { @@ -73,6 +85,30 @@ const MyGigs = ({ } }, [updateProfileSuccess]); + const currentLoadMore = useCallback( + (status, page) => { + if (gigStatus == constants.GIGS_FILTER_STATUSES.ACTIVE_JOBS) { + getMyActiveGigs(status, page); + } + if (gigStatus == constants.GIGS_FILTER_STATUSES.OPEN_JOBS) { + getMyOpenGigs(status, page); + } + if (gigStatus == constants.GIGS_FILTER_STATUSES.COMPLETED_JOBS) { + getMyCompletedGigs(status, page); + } + if (gigStatus == constants.GIGS_FILTER_STATUSES.ARCHIVED_JOBS) { + getMyArchivedGigs(status, page); + } + }, + [ + gigStatus, + getMyActiveGigs, + getMyOpenGigs, + getMyCompletedGigs, + getMyArchivedGigs, + ] + ); + return ( <>
    @@ -99,19 +135,25 @@ const MyGigs = ({
    - {!checkingGigs && myGigs && myGigs.length == 0 && ( - - )} - {!checkingGigs && myGigs && myGigs.length > 0 && ( - + {!checkingGigs && + !loadingMyGigs && + currentGigs.myGigs && + currentGigs.myGigs.length == 0 && } + {!checkingGigs && + currentGigs.myGigs && + currentGigs.myGigs.length > 0 && ( + + )} + {(checkingGigs || (loadingMyGigs && !currentGigs.myGigs)) && ( + )} - {checkingGigs && }
    ({ gigStatus: state.filter.gig.status, checkingGigs: state.myGigs.checkingGigs, - myGigs: state.myGigs.myGigs, - total: state.myGigs.total, - numLoaded: state.myGigs.numLoaded, + loadingMyGigs: state.myGigs.loadingMyGigs, + myActiveGigs: state.myGigs[constants.GIGS_FILTER_STATUSES.ACTIVE_JOBS], + myOpenGigs: state.myGigs[constants.GIGS_FILTER_STATUSES.OPEN_JOBS], + myCompletedGigs: state.myGigs[constants.GIGS_FILTER_STATUSES.COMPLETED_JOBS], + myArchivedGigs: state.myGigs[constants.GIGS_FILTER_STATUSES.ARCHIVED_JOBS], profile: state.myGigs.profile, updateProfileSuccess: state.myGigs.updatingProfileSucess, }); const mapDispatchToProps = { - getMyGigs: actions.myGigs.getMyGigs, - loadMore: actions.myGigs.loadMoreMyGigs, + getMyActiveGigs: actions.myGigs.getMyActiveGigs, + getMyOpenGigs: actions.myGigs.getMyOpenGigs, + getMyCompletedGigs: actions.myGigs.getMyCompletedGigs, + getMyArchivedGigs: actions.myGigs.getMyArchivedGigs, getProfile: actions.myGigs.getProfile, updateProfile: actions.myGigs.updateProfile, getAllCountries: actions.lookup.getAllCountries, diff --git a/src/containers/MyGigs/modals/UpdateGigProfile/index.jsx b/src/containers/MyGigs/modals/UpdateGigProfile/index.jsx index 851c744..7ee22bb 100644 --- a/src/containers/MyGigs/modals/UpdateGigProfile/index.jsx +++ b/src/containers/MyGigs/modals/UpdateGigProfile/index.jsx @@ -214,9 +214,7 @@ const UpdateGigProfile = ({ />
    - - - +
    diff --git a/src/containers/MyGigsFilter/GigsFilter/index.jsx b/src/containers/MyGigsFilter/GigsFilter/index.jsx index c62f014..e65ed26 100644 --- a/src/containers/MyGigsFilter/GigsFilter/index.jsx +++ b/src/containers/MyGigsFilter/GigsFilter/index.jsx @@ -1,4 +1,4 @@ -import React from "react"; +import React, { useEffect, useRef } from "react"; import PT from "prop-types"; import _ from "lodash"; import RadioButton from "../../../components/RadioButton"; @@ -6,12 +6,35 @@ import * as utils from "../../../utils"; import "./styles.scss"; -const GigsFilter = ({ gigStatus, gigsStatuses, updateGigFilter }) => { +const GigsFilter = ({ + gigStatus, + gigsStatuses, + updateGigFilter, + openJobsCount, +}) => { const bucketOptions = utils.createRadioOptions(gigsStatuses, gigStatus); + const ref = useRef(null); + + useEffect(() => { + if (!ref.current) { + return; + } + + const openJobsElement = ref.current.children[0].children[1]; + const badgeElement = utils.icon.createBadgeElement( + openJobsElement, + `${openJobsCount}` + ); + + return () => { + badgeElement.parentElement.removeChild(badgeElement); + }; + }, [openJobsCount]); + return (
    -
    +
    { @@ -31,6 +54,7 @@ GigsFilter.propTypes = { gigStatus: PT.string, gigsStatuses: PT.arrayOf(PT.string), updateGigFilter: PT.func, + openJobsCount: PT.number, }; export default GigsFilter; diff --git a/src/containers/MyGigsFilter/GigsFilter/styles.scss b/src/containers/MyGigsFilter/GigsFilter/styles.scss index 7e538c4..327a793 100644 --- a/src/containers/MyGigsFilter/GigsFilter/styles.scss +++ b/src/containers/MyGigsFilter/GigsFilter/styles.scss @@ -25,4 +25,19 @@ $filter-padding-y: 3 * $base-unit; margin: $base-unit 0; } } + :global { + .count-badge { + display: inline-block; + margin: -2px 0 0 $base-unit; + padding: 2px 5px 0; + font-weight: bold; + font-size: 11px; + line-height: 14px; + text-align: center; + color: white; + vertical-align: middle; + background: #EF476F; + border-radius: 13px; + } + } } diff --git a/src/containers/MyGigsFilter/index.jsx b/src/containers/MyGigsFilter/index.jsx index 6e3dfcf..fa58450 100644 --- a/src/containers/MyGigsFilter/index.jsx +++ b/src/containers/MyGigsFilter/index.jsx @@ -5,8 +5,14 @@ import { connect } from "react-redux"; import GigsFilter from "./GigsFilter"; import actions from "../../actions"; import { updateQuery } from "../../utils/url"; +import * as constants from "../../constants"; -const MyGigsFilter = ({ gigStatus, gigsStatuses, updateGigFilter }) => { +const MyGigsFilter = ({ + gigStatus, + gigsStatuses, + updateGigFilter, + openJobsCount, +}) => { const location = useLocation(); const propsRef = useRef(null); propsRef.current = { location }; @@ -16,6 +22,7 @@ const MyGigsFilter = ({ gigStatus, gigsStatuses, updateGigFilter }) => { { updateGigFilter(gigFilterChanged); updateQuery(gigFilterChanged); @@ -31,11 +38,13 @@ MyGigsFilter.propTypes = { gigStatus: PT.string, gigsStatuses: PT.arrayOf(PT.string), updateGigFilter: PT.func, + openJobsCount: PT.number, }; const mapStateToProps = (state) => ({ state: state, gigStatus: state.filter.gig.status, + openJobsCount: state.myGigs[constants.GIGS_FILTER_STATUSES.OPEN_JOBS].total, gigsStatuses: state.lookup.gigsStatuses, }); diff --git a/src/reducers/myGigs.js b/src/reducers/myGigs.js index c4c8e76..bb2bdd2 100644 --- a/src/reducers/myGigs.js +++ b/src/reducers/myGigs.js @@ -1,14 +1,34 @@ import { size, sortBy } from "lodash"; import { handleActions } from "redux-actions"; +import * as constants from "../constants"; const defaultState = { loadingMyGigs: false, loadingMyGigsError: null, - myGigs: null, - total: 0, - numLoaded: 0, - loadingMore: false, - loadingMoreError: null, + [constants.GIGS_FILTER_STATUSES.ACTIVE_JOBS]: { + myGigs: null, + page: 1, + numLoaded: 0, + total: 0, + }, + [constants.GIGS_FILTER_STATUSES.OPEN_JOBS]: { + myGigs: null, + page: 1, + numLoaded: 0, + total: 0, + }, + [constants.GIGS_FILTER_STATUSES.COMPLETED_JOBS]: { + myGigs: null, + page: 1, + numLoaded: 0, + total: 0, + }, + [constants.GIGS_FILTER_STATUSES.ARCHIVED_JOBS]: { + myGigs: null, + page: 1, + numLoaded: 0, + total: 0, + }, profile: {}, loadingProfile: false, loadingProfileError: null, @@ -18,48 +38,84 @@ const defaultState = { checkingGigs: false, }; -function onGetMyGigsInit(state) { +function onGetMyActiveGigsInit(state) { return { ...state, loadingMyGigs: true, loadingMyGigsError: null }; } -function onGetMyGigsDone(state, { payload }) { +function onGetMyActiveGigsDone(state, { payload }) { + const currentGigs = + state[constants.GIGS_FILTER_STATUSES.ACTIVE_JOBS].myGigs || []; return { ...state, - myGigs: sortBy(payload.myGigs, ["sortPrio"]), - total: payload.total, - numLoaded: payload.myGigs.length, + [constants.GIGS_FILTER_STATUSES.ACTIVE_JOBS]: { + myGigs: sortBy(currentGigs.concat(payload.myGigs), ["sortPrio"]), + total: payload.total, + numLoaded: currentGigs.length + payload.myGigs.length, + page: payload.page, + }, loadingMyGigs: false, loadingMyGigsError: null, }; } -function onGetMyGigsFailure(state, { payload }) { +function onGetMyOpenGigsInit(state) { + return { ...state, loadingMyGigs: true, loadingMyGigsError: null }; +} + +function onGetMyOpenGigsDone(state, { payload }) { + const currentGigs = + state[constants.GIGS_FILTER_STATUSES.OPEN_JOBS].myGigs || []; return { ...state, + [constants.GIGS_FILTER_STATUSES.OPEN_JOBS]: { + myGigs: sortBy(currentGigs.concat(payload.myGigs), ["sortPrio"]), + total: payload.total, + numLoaded: currentGigs.length + payload.myGigs.length, + page: payload.page, + }, loadingMyGigs: false, - loadingMyGigsError: payload, - myGigs: null, - total: 0, - numLoaded: 0, + loadingMyGigsError: null, }; } -function onLoadMoreMyGigsInit(state) { - return { ...state, loadingMore: true, loadingMoreError: null }; +function onGetMyCompletedGigsInit(state) { + return { ...state, loadingMyGigs: true, loadingMyGigsError: null }; } -function onLoadMoreMyGigsDone(state, { payload: { myGigs } }) { +function onGetMyCompletedGigsDone(state, { payload }) { + const currentGigs = + state[constants.GIGS_FILTER_STATUSES.COMPLETED_JOBS].myGigs || []; return { ...state, - myGigs: sortBy(state.myGigs.concat(myGigs), ["sortPrio"]), - numLoaded: state.numLoaded + size(myGigs), - loadingMore: false, - loadingMoreError: null, + [constants.GIGS_FILTER_STATUSES.COMPLETED_JOBS]: { + myGigs: sortBy(currentGigs.concat(payload.myGigs), ["sortPrio"]), + total: payload.total, + numLoaded: currentGigs.length + payload.myGigs.length, + page: payload.page, + }, + loadingMyGigs: false, + loadingMyGigsError: null, }; } -function onLoadMoreMyGigsFailure(state, { payload }) { - return { ...state, loadingMore: false, loadingMoreError: payload }; +function onGetMyArchivedGigsInit(state) { + return { ...state, loadingMyGigs: true, loadingMyGigsError: null }; +} + +function onGetMyArchivedGigsDone(state, { payload }) { + const currentGigs = + state[constants.GIGS_FILTER_STATUSES.ARCHIVED_JOBS].myGigs || []; + return { + ...state, + [constants.GIGS_FILTER_STATUSES.ARCHIVED_JOBS]: { + myGigs: sortBy(currentGigs.concat(payload.myGigs), ["sortPrio"]), + total: payload.total, + numLoaded: currentGigs.length + payload.myGigs.length, + page: payload.page, + }, + loadingMyGigs: false, + loadingMyGigsError: null, + }; } function onGetProfileInit(state) { @@ -128,12 +184,15 @@ function onCheckingGigsDone(state) { export default handleActions( { - GET_MY_GIGS_INIT: onGetMyGigsInit, - GET_MY_GIGS_DONE: onGetMyGigsDone, - GET_MY_GIGS_FAILURE: onGetMyGigsFailure, - LOAD_MORE_MY_GIGS_INIT: onLoadMoreMyGigsInit, - LOAD_MORE_MY_GIGS_DONE: onLoadMoreMyGigsDone, - LOAD_MORE_MY_GIGS_FAILURE: onLoadMoreMyGigsFailure, + GET_MY_ACTIVE_GIGS_INIT: onGetMyActiveGigsInit, + GET_MY_ACTIVE_GIGS_DONE: onGetMyActiveGigsDone, + GET_MY_OPEN_GIGS_INIT: onGetMyOpenGigsInit, + GET_MY_OPEN_GIGS_DONE: onGetMyOpenGigsDone, + GET_MY_COMPLETED_GIGS_INIT: onGetMyCompletedGigsInit, + GET_MY_COMPLETED_GIGS_DONE: onGetMyCompletedGigsDone, + GET_MY_ARCHIVED_GIGS_INIT: onGetMyArchivedGigsInit, + GET_MY_ARCHIVED_GIGS_DONE: onGetMyArchivedGigsDone, + GET_PROFILE_INIT: onGetProfileInit, GET_PROFILE_DONE: onGetProfileDone, GET_PROFILE_FAILURE: onGetProfileFailure, diff --git a/src/services/myGigs.js b/src/services/myGigs.js index 56e8cb6..ecbb41a 100644 --- a/src/services/myGigs.js +++ b/src/services/myGigs.js @@ -40,6 +40,10 @@ const mapMyGigsData = (serverResponse) => { return { label: (gigPhase || "").toUpperCase(), title: myGig.title, + rbStartDate: myGig.rbStartDate || "", + rbEndDate: myGig.rbEndDate || "", + paymentTotal: myGig.paymentTotal || 0, + updatedAt: myGig.updatedAt || "", jobExternalId: myGig.jobExternalId, paymentRangeFrom: myGig.payment.min, paymentRangeTo: myGig.payment.max, @@ -92,6 +96,7 @@ async function getMyGigs(status, page, perPage) { return { myGigs: mapMyGigsData(response), total: response.meta.total, + page: response.meta.page, }; } diff --git a/src/utils/icon.js b/src/utils/icon.js index 1e00043..3dcced0 100644 --- a/src/utils/icon.js +++ b/src/utils/icon.js @@ -146,3 +146,29 @@ export function createBadgeElement(htmlElement, content) { return badgeElement; } + +// export function createCompanyLogo() { +// return ( +// +// 9825381A-BF03-46A3-AD6D-206A06A2B45D +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// ) +// } \ No newline at end of file diff --git a/src/utils/myGig.js b/src/utils/myGig.js index efaa6d1..c8ef9b0 100644 --- a/src/utils/myGig.js +++ b/src/utils/myGig.js @@ -106,3 +106,18 @@ export function validatePhone(phoneNumber, country) { return error ? error : null; } + +export function getDateRange(startDate, endDate) { + const yearStart = new Date(startDate).getFullYear(); + const yearEnd = new Date(endDate).getFullYear(); + if (yearStart > yearEnd) { + return ""; + } + const options = { month: "long", day: "numeric" }; + const first = new Date(startDate).toLocaleDateString("en-us", options); + const second = new Date(endDate).toLocaleDateString("en-us", options); + if (yearStart == yearEnd) { + return `${first} - ${second}, ${yearStart}`; + } + return `${first}, ${yearStart} - ${second}, ${yearEnd}`; +}