diff --git a/apps/nestjs-api/src/tp-job-listings/args/find-all-visible-tp-jobseeker-profiles.args.ts b/apps/nestjs-api/src/tp-job-listings/args/find-all-visible-tp-jobseeker-profiles.args.ts index ed7d91720..4dbe7a403 100644 --- a/apps/nestjs-api/src/tp-job-listings/args/find-all-visible-tp-jobseeker-profiles.args.ts +++ b/apps/nestjs-api/src/tp-job-listings/args/find-all-visible-tp-jobseeker-profiles.args.ts @@ -27,6 +27,8 @@ class FindAllVisibleTpJobListingsArgsFilter { isRemotePossible?: boolean + datePosted?: string + /** * Job Fair Boolean Field(s) * Uncomment & Rename (joins{Location}{Year}{Season}JobFair) the next field when there's an upcoming Job Fair diff --git a/apps/nestjs-api/src/tp-job-listings/tp-job-listings.service.ts b/apps/nestjs-api/src/tp-job-listings/tp-job-listings.service.ts index fff18ff11..c8b933fb5 100644 --- a/apps/nestjs-api/src/tp-job-listings/tp-job-listings.service.ts +++ b/apps/nestjs-api/src/tp-job-listings/tp-job-listings.service.ts @@ -11,6 +11,8 @@ import { TpJobListingStatus, } from '@talent-connect/common-types' import { deleteUndefinedProperties } from '@talent-connect/shared-utils' +import { jobListingCreatedDateInAge } from '@talent-connect/talent-pool/config' +import * as jsforce from 'jsforce' import { CurrentUserInfo } from '../auth/current-user.interface' import { SfApiTpJobListingsService } from '../salesforce-api/sf-api-tp-job-listings.service' import { TpCompanyRepresentativeRelationshipsService } from '../tp-company-profiles/tp-company-representative-relationships.service' @@ -67,6 +69,18 @@ export class TpJobListingsService { if (_filter.filter.isRemotePossible) { filter.Remote_Possible__c = true } + if (_filter.filter.datePosted) { + const createdDate = new Date() + + createdDate.setDate( + createdDate.getDate() - + jobListingCreatedDateInAge[_filter.filter.datePosted] + ) + + filter.CreatedDate = { + $gte: jsforce.SfDate.toDateTimeLiteral(createdDate), + } + } /** * Job Fair Boolean Field(s) * Uncomment & Rename (joins{Location}{Year}{Season}JobFair) the next block when there's an upcoming Job Fair diff --git a/apps/redi-talent-pool/src/assets/locales/en/translation.json b/apps/redi-talent-pool/src/assets/locales/en/translation.json index 17ca4f9fa..b7bbd9865 100644 --- a/apps/redi-talent-pool/src/assets/locales/en/translation.json +++ b/apps/redi-talent-pool/src/assets/locales/en/translation.json @@ -103,6 +103,10 @@ "question": "How can I browse Companies?", "answer": "Select the 'Browse' option located in the left navigation bar, then begin exploring the job postings. Feel free to use filters to refine your search for more specific results." }, + { + "question": "How do ReDI Career Partners' job listings benefit me as a job seeker?", + "answer": "Job listings from ReDI Career Partners are given higher visibility on our platform because these trusted companies actively source ReDI talent and support newcomers. By applying to their positions, you benefit from opportunities with employers who are committed to your success and closely collaborate with us to create more career opportunities in Germany." + }, { "question": "Will there be additional companies joining the Talent Pool?", "answer": "Yes, we are actively engaging with numerous companies and expanding our network of partner companies featured in the Talent Pool." diff --git a/apps/redi-talent-pool/src/components/organisms/JobListingCard.tsx b/apps/redi-talent-pool/src/components/organisms/JobListingCard.tsx index a20ace4f7..33c373052 100644 --- a/apps/redi-talent-pool/src/components/organisms/JobListingCard.tsx +++ b/apps/redi-talent-pool/src/components/organisms/JobListingCard.tsx @@ -1,9 +1,9 @@ -import { useMediaQuery, useTheme } from '@mui/material' +import { Tooltip, useMediaQuery, useTheme } from '@mui/material' import { CardTags } from '@talent-connect/shared-atomic-design-components' import { topSkillsIdToLabelMap } from '@talent-connect/talent-pool/config' import React from 'react' import { Card } from 'react-bulma-components' -import { NavLink } from 'react-router-dom' +import { Link, NavLink } from 'react-router-dom' import CardLocation from '../../../../../libs/shared-atomic-design-components/src/lib/atoms/CardLocation' import placeholderImage from '../../assets/images/company-placeholder-img.svg' import './JobListingCard.scss' @@ -15,6 +15,7 @@ interface JobListingCardProps { toggleFavorite?: (id: string) => void linkTo?: string timestamp?: string + showPromotedLabel?: boolean renderCTA?: () => React.ReactNode onClick?: (e: React.MouseEvent) => void @@ -25,6 +26,7 @@ export function JobListingCard({ linkTo, onClick, timestamp, + showPromotedLabel = false, renderCTA, }: JobListingCardProps) { const { @@ -71,8 +73,28 @@ export function JobListingCard({
{isTabletOrDesktop && renderCTA && renderCTA()} - {timestamp && ( -
{timestamp}
+ {timestamp && + (!showPromotedLabel || !jobListing.isFromCareerPartner) && ( +
{timestamp}
+ )} + {showPromotedLabel && jobListing.isFromCareerPartner && ( +
+ + This job listing is promoted because it is posted by a + ReDI Career Partner. You can learn more about why we do + this by visiting the{' '} + + FAQ section + + + } + placement="top" + > + Promoted + +
)}
diff --git a/apps/redi-talent-pool/src/components/organisms/jobseeker-profile-editables/JobListingCard.generated.ts b/apps/redi-talent-pool/src/components/organisms/jobseeker-profile-editables/JobListingCard.generated.ts index 22b5686ea..66092ed1f 100644 --- a/apps/redi-talent-pool/src/components/organisms/jobseeker-profile-editables/JobListingCard.generated.ts +++ b/apps/redi-talent-pool/src/components/organisms/jobseeker-profile-editables/JobListingCard.generated.ts @@ -1,7 +1,7 @@ // THIS FILE IS GENERATED, DO NOT EDIT! import * as Types from '@talent-connect/data-access'; -export type JobListingCardJobListingPropFragment = { __typename?: 'TpJobListing', id: string, title?: string | null, idealTechnicalSkills?: Array | null, companyName: string, profileAvatarImageS3Key?: string | null, createdAt: any, federalState?: Types.FederalState | null, location?: string | null, isRemotePossible?: boolean | null }; +export type JobListingCardJobListingPropFragment = { __typename?: 'TpJobListing', id: string, title?: string | null, idealTechnicalSkills?: Array | null, companyName: string, profileAvatarImageS3Key?: string | null, createdAt: any, federalState?: Types.FederalState | null, location?: string | null, isRemotePossible?: boolean | null, isFromCareerPartner: boolean }; export const JobListingCardJobListingPropFragmentDoc = ` fragment JobListingCardJobListingProp on TpJobListing { @@ -14,5 +14,6 @@ export const JobListingCardJobListingPropFragmentDoc = ` federalState location isRemotePossible + isFromCareerPartner } `; \ No newline at end of file diff --git a/apps/redi-talent-pool/src/components/organisms/jobseeker-profile-editables/JobListingCard.graphql b/apps/redi-talent-pool/src/components/organisms/jobseeker-profile-editables/JobListingCard.graphql index 5e8c31433..bf1d71cb0 100644 --- a/apps/redi-talent-pool/src/components/organisms/jobseeker-profile-editables/JobListingCard.graphql +++ b/apps/redi-talent-pool/src/components/organisms/jobseeker-profile-editables/JobListingCard.graphql @@ -8,4 +8,5 @@ fragment JobListingCardJobListingProp on TpJobListing { federalState location isRemotePossible + isFromCareerPartner } diff --git a/apps/redi-talent-pool/src/pages/app/browse/BrowseJobseeker.tsx b/apps/redi-talent-pool/src/pages/app/browse/BrowseJobseeker.tsx index b63373fc1..76a47f790 100644 --- a/apps/redi-talent-pool/src/pages/app/browse/BrowseJobseeker.tsx +++ b/apps/redi-talent-pool/src/pages/app/browse/BrowseJobseeker.tsx @@ -19,6 +19,7 @@ import { employmentTypes, employmentTypesIdToLabelMap, germanFederalStates, + jobListingCreatedDate, topSkills, topSkillsIdToLabelMap, } from '@talent-connect/talent-pool/config' @@ -31,6 +32,7 @@ import { Redirect } from 'react-router-dom' import { ArrayParam, BooleanParam, + StringParam, useQueryParams, withDefault, } from 'use-query-params' @@ -61,6 +63,7 @@ export function BrowseJobseeker() { federalStates: withDefault(ArrayParam, []), onlyFavorites: withDefault(BooleanParam, undefined), isRemotePossible: withDefault(BooleanParam, undefined), + datePosted: withDefault(StringParam, undefined), /** * Job Fair Boolean Field(s) * Uncomment & Rename (joins{Location}{Year}{Season}JobFair) the next field when there's an upcoming Job Fair @@ -74,6 +77,7 @@ export function BrowseJobseeker() { const federalStates = query.federalStates as FederalState[] const onlyFavorites = query.onlyFavorites const isRemotePossible = query.isRemotePossible + const datePosted = query.datePosted /** * Job Fair Boolean Field(s) * Uncomment & Rename (joins{Location}{Year}{Season}JobFair) the next field when there's an upcoming Job Fair @@ -88,6 +92,7 @@ export function BrowseJobseeker() { employmentTypes: employmentType, federalStates, isRemotePossible, + datePosted, /** * Job Fair Boolean Field(s) * Uncomment & Rename (joins{Location}{Year}{Season}JobFair) the next field when there's an upcoming Job Fair @@ -102,8 +107,9 @@ export function BrowseJobseeker() { * - Backend currently supports only sorting by one field, which is used for sorting by date * - All fetch job listing queries are using one findAll query, which means this sort would have unexpected side effects */ - const jobListings = - jobListingsQuery.data?.tpJobListings.sort(careerPartnerSortFn) + const jobListings = !datePosted + ? jobListingsQuery.data?.tpJobListings.sort(careerPartnerSortFn) + : jobListingsQuery.data?.tpJobListings const isFavorite = (jobListingId) => favouritedTpJobListingsQuery.data?.tpJobseekerFavoritedJobListings?.some( @@ -155,6 +161,13 @@ export function BrowseJobseeker() { })) } + const onSelectDatePosted = (item) => { + setQuery((latestQuery) => ({ + ...latestQuery, + datePosted: item ?? undefined, + })) + } + const toggleFilters = (filtersArr, filterName, item) => { const newFilters = toggleValueInArray(filtersArr, item) setQuery((latestQuery) => ({ ...latestQuery, [filterName]: newFilters })) @@ -180,6 +193,7 @@ export function BrowseJobseeker() { employmentType: [], federalStates: [], isRemotePossible: undefined, + datePosted: undefined, /** * Job Fair Boolean Field(s) * Uncomment & Rename (joins{Location}{Year}{Season}JobFair) the next field when there's an upcoming Job Fair @@ -194,7 +208,8 @@ export function BrowseJobseeker() { idealTechnicalSkills.length !== 0 || employmentType.length !== 0 || federalStates.length !== 0 || - isRemotePossible + isRemotePossible || + datePosted /** * Job Fair Boolean Field(s) * Uncomment & Rename (joins{Location}{Year}{Season}JobFair) the next field when there's an upcoming Job Fair @@ -301,7 +316,15 @@ export function BrowseJobseeker() { } /> -
+
+ +
@@ -400,6 +423,17 @@ export function BrowseJobseeker() { onClickHandler={toggleRemoteAvailableFilter} /> )} + {datePosted && ( + item.value === datePosted) + ?.label + } + onClickHandler={() => onSelectDatePosted('')} + /> + )} {/* * Job Fair Boolean Field(s) * Uncomment & Rename (joins{Location}{Year}{Season}JobFair) the next FilterTag when there's an upcoming Job Fair @@ -443,6 +477,7 @@ export function BrowseJobseeker() { addSuffix: true, } )}`} + showPromotedLabel /> ))} @@ -498,3 +533,12 @@ const germanFederalStatesOptions = objectEntries(germanFederalStates).map( label, }) ) + +// Adding the 'Any time' option to the date posted filter as undefined cannot be used as a key +const datePostedOptions = [ + { value: null, label: 'Any time' }, + ...objectEntries(jobListingCreatedDate).map(([value, label]) => ({ + value, + label, + })), +] diff --git a/apps/redi-talent-pool/src/services/api/api.tsx b/apps/redi-talent-pool/src/services/api/api.tsx index 8771899a3..4c6a58229 100644 --- a/apps/redi-talent-pool/src/services/api/api.tsx +++ b/apps/redi-talent-pool/src/services/api/api.tsx @@ -324,6 +324,7 @@ export interface TpJobListingFilters { employmentType: string[] federalStates: string[] isRemotePossible: boolean + datePosted?: string } export async function fetchAllTpJobListingsUsingFilters({ @@ -332,6 +333,7 @@ export async function fetchAllTpJobListingsUsingFilters({ employmentType, federalStates, isRemotePossible, + datePosted, }: TpJobListingFilters): Promise> { const filterRelatedPositions = relatedPositions && relatedPositions.length !== 0 @@ -365,6 +367,7 @@ export async function fetchAllTpJobListingsUsingFilters({ employmentType: filterEmploymentTypeOptions, federalState: filterFederalStates, isRemotePossible, + createdAt: datePosted, }, ], }, diff --git a/apps/redi-talent-pool/src/utils/sort-job-listings.ts b/apps/redi-talent-pool/src/utils/sort-job-listings.ts index f8914653e..c74f7f6d0 100644 --- a/apps/redi-talent-pool/src/utils/sort-job-listings.ts +++ b/apps/redi-talent-pool/src/utils/sort-job-listings.ts @@ -1,6 +1,8 @@ import { TpJobListing } from '@talent-connect/data-access' export const careerPartnerSortFn = (a: TpJobListing, b: TpJobListing) => { + console.log('sorting') + if (a.isFromCareerPartner && !b.isFromCareerPartner) { return -1 } else if (!a.isFromCareerPartner && b.isFromCareerPartner) { diff --git a/libs/data-access/src/lib/types/types.ts b/libs/data-access/src/lib/types/types.ts index 1d8eba5f3..c9767b9f2 100644 --- a/libs/data-access/src/lib/types/types.ts +++ b/libs/data-access/src/lib/types/types.ts @@ -239,6 +239,7 @@ export enum FederalState { } export type FindAllVisibleTpJobListingsArgsFilter = { + datePosted?: InputMaybe; employmentTypes?: InputMaybe>; federalStates?: InputMaybe>; isRemotePossible?: InputMaybe; diff --git a/libs/shared-atomic-design-components/src/lib/molecules/FilterDropdown.scss b/libs/shared-atomic-design-components/src/lib/molecules/FilterDropdown.scss index 0505a8c71..80ada0438 100644 --- a/libs/shared-atomic-design-components/src/lib/molecules/FilterDropdown.scss +++ b/libs/shared-atomic-design-components/src/lib/molecules/FilterDropdown.scss @@ -44,7 +44,7 @@ box-shadow: 0 3px 15px 0 rgba(0, 0, 0, 0.12), 0 2px 4px -1px rgba(0, 0, 0, 0.2); max-height: 210px; - overflow: scroll; + overflow: auto; &--show { display: block; @@ -57,6 +57,11 @@ + li { margin-top: 0.5rem; } + + .listItem--singleSelect { + display: flex; + cursor: pointer; + } } } } diff --git a/libs/shared-atomic-design-components/src/lib/molecules/FilterDropdown.tsx b/libs/shared-atomic-design-components/src/lib/molecules/FilterDropdown.tsx index e04b76c90..59c3b75bf 100644 --- a/libs/shared-atomic-design-components/src/lib/molecules/FilterDropdown.tsx +++ b/libs/shared-atomic-design-components/src/lib/molecules/FilterDropdown.tsx @@ -8,9 +8,10 @@ type Item = { label: string; value: string } interface Props { label: string className: string - selected: string[] + selected?: string[] items: Item[] onChange: (item: string) => void + singleSelect?: boolean } const baseClass = 'filter-dropdown' @@ -21,6 +22,7 @@ const FilterDropdown = ({ selected, items, onChange, + singleSelect = false, }: Props) => { const filterDropdown = useRef(null) const [showDropdown, setShowDropdown] = useState(false) @@ -31,6 +33,11 @@ const FilterDropdown = ({ } }, []) + const handleSingleSelectOptionClick = (item: string) => { + onChange(item) + setShowDropdown(false) + } + useEffect(() => { document.addEventListener('click', handleClick) return () => { @@ -66,12 +73,22 @@ const FilterDropdown = ({ > {items.map((item) => (
  • - onChange(item.value)} - checked={selected.includes(item.value)} - > - {item.label} - + {singleSelect ? ( + handleSingleSelectOptionClick(item.value)} + > + {item.label} + + ) : ( + onChange(item.value)} + checked={selected.includes(item.value)} + > + {item.label} + + )}
  • ))} diff --git a/libs/talent-pool/config/src/lib/talent-pool-config.ts b/libs/talent-pool/config/src/lib/talent-pool-config.ts index 964024413..4f9b90161 100644 --- a/libs/talent-pool/config/src/lib/talent-pool-config.ts +++ b/libs/talent-pool/config/src/lib/talent-pool-config.ts @@ -749,3 +749,16 @@ export const germanFederalStates = { THUERINGEN: 'Thüringen', OUTSIDE_GERMANY: 'Outside Germany', } as const + +export const jobListingCreatedDate = { + PAST_24_HOURS: 'Past 24 hours', + PAST_WEEK: 'Past Week', + PAST_MONTH: 'Past Month', +} as const + +export const jobListingCreatedDateInAge = { + ANY_TIME: 0, + PAST_24_HOURS: 1, + PAST_WEEK: 7, + PAST_MONTH: 30, +} as const diff --git a/schema.graphql b/schema.graphql index e609bb336..4897873ce 100644 --- a/schema.graphql +++ b/schema.graphql @@ -217,6 +217,7 @@ enum FederalState { } input FindAllVisibleTpJobListingsArgsFilter { + datePosted: String employmentTypes: [TpEmploymentType!] federalStates: [FederalState!] isRemotePossible: Boolean