@abcagency/hc-ui-components 1.3.23 → 1.3.25
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +4 -5
- package/src/.editorconfig +12 -0
- package/src/apis/hcApi.ts +109 -0
- package/src/bundleIndex.js +14 -0
- package/src/clientToken.js +9 -0
- package/src/components/HireControlMap.js +135 -0
- package/src/components/containers/accordions/filter-container.js +48 -0
- package/src/components/containers/accordions/filter-item-container.js +66 -0
- package/src/components/containers/accordions/map-accordion-item-container.js +70 -0
- package/src/components/containers/filter/commute-container.js +89 -0
- package/src/components/containers/filter/filter-container.js +76 -0
- package/src/components/containers/filter/filter-item-container.js +71 -0
- package/src/components/containers/filter/location-container.js +45 -0
- package/src/components/containers/filter/points-of-interest-container.js +33 -0
- package/src/components/containers/filter/points-of-interest-radio-item-container.js +35 -0
- package/src/components/containers/filter/search-container.js +50 -0
- package/src/components/containers/jobListing/listing-details-container.js +40 -0
- package/src/components/containers/list/item-list-container.tsx +81 -0
- package/src/components/containers/list/list-item/list-item-container.js +43 -0
- package/src/components/containers/maps/info-window-content-container.js +51 -0
- package/src/components/containers/maps/map-container.js +204 -0
- package/src/components/containers/maps/map-list-container.js +48 -0
- package/src/components/containers/maps/map-marker-container.js +78 -0
- package/src/components/modules/accordions/MapAccordionItem.js +30 -0
- package/src/components/modules/accordions/default.js +171 -0
- package/src/components/modules/accordions/filterItem.js +27 -0
- package/src/components/modules/accordions/filters.js +32 -0
- package/src/components/modules/buttons/button-group-apply.js +123 -0
- package/src/components/modules/buttons/commute-pill.js +22 -0
- package/src/components/modules/buttons/default.js +194 -0
- package/src/components/modules/buttons/items-pill.js +35 -0
- package/src/components/modules/buttons/pill-wrapper.js +27 -0
- package/src/components/modules/buttons/show-all-button.js +20 -0
- package/src/components/modules/cards/default.js +167 -0
- package/src/components/modules/cards/filter.js +56 -0
- package/src/components/modules/dialogs/apply-dialog.js +48 -0
- package/src/components/modules/filter/commute.js +108 -0
- package/src/components/modules/filter/index.js +55 -0
- package/src/components/modules/filter/item.js +48 -0
- package/src/components/modules/filter/location.js +48 -0
- package/src/components/modules/filter/radio-item.js +42 -0
- package/src/components/modules/filter/search.js +65 -0
- package/src/components/modules/filter/sort.js +83 -0
- package/src/components/modules/grid.js +54 -0
- package/src/components/modules/icon.js +33 -0
- package/src/components/modules/jobListing/listing-details.js +109 -0
- package/src/components/modules/list/field-mapper.js +114 -0
- package/src/components/modules/list/header-item.js +91 -0
- package/src/components/modules/list/header.js +49 -0
- package/src/components/modules/list/item-expand-card/index.js +22 -0
- package/src/components/modules/list/item-expand-card/recruiter-contact-nav.js +50 -0
- package/src/components/modules/list/item-expand-card/recruiter-details.js +68 -0
- package/src/components/modules/list/item-expand-card/recruiter-headshot.js +22 -0
- package/src/components/modules/list/item-list.tsx +84 -0
- package/src/components/modules/list/list-item/list-item.js +130 -0
- package/src/components/modules/maps/info-window-card.js +17 -0
- package/src/components/modules/maps/info-window-content.js +35 -0
- package/src/components/modules/maps/map-list.js +28 -0
- package/src/components/modules/maps/map-marker.js +29 -0
- package/src/components/modules/maps/map.js +76 -0
- package/src/components/modules/maps/place-marker.js +41 -0
- package/src/components/modules/maps/tabs.js +81 -0
- package/src/constants/eventTypes.js +13 -0
- package/src/constants/placeTypes.js +8 -0
- package/src/contexts/mapContext.tsx +129 -0
- package/src/contexts/mapListContext.tsx +311 -0
- package/src/contexts/placesContext.js +102 -0
- package/src/contexts/trackEventContext.js +14 -0
- package/src/enums/SectionType.ts +9 -0
- package/src/hooks/useList.js +89 -0
- package/src/index.js +3 -0
- package/src/services/configService.ts +16 -0
- package/src/services/googlePlacesNearbyService.ts +42 -0
- package/src/services/listingAggregatorService.ts +76 -0
- package/src/services/listingEntityService.ts +16 -0
- package/src/services/listingService.ts +40 -0
- package/src/services/recruiterService.ts +18 -0
- package/src/styles/bundle.css +268 -0
- package/src/styles/index.css +24 -0
- package/src/types/Address.ts +7 -0
- package/src/types/ContentSection.ts +9 -0
- package/src/types/GetListingParams.ts +8 -0
- package/src/types/LatLng.ts +4 -0
- package/src/types/ListingEntity.ts +11 -0
- package/src/types/ListingFields.ts +25 -0
- package/src/types/Listings.ts +32 -0
- package/src/types/Recruiter.ts +9 -0
- package/src/types/SimilarListing.ts +24 -0
- package/src/types/config/Colors.ts +8 -0
- package/src/types/config/MapConfig.ts +31 -0
- package/src/types/config/PointsOfInterestConfig.ts +13 -0
- package/src/types/config/SearchConfig.ts +4 -0
- package/src/util/arrayUtil.js +3 -0
- package/src/util/fieldMapper.js +22 -0
- package/src/util/filterUtil.js +239 -0
- package/src/util/loading.js +17 -0
- package/src/util/localStorageUtil.ts +34 -0
- package/src/util/mapIconUtil.js +180 -0
- package/src/util/mapUtil.js +91 -0
- package/src/util/sortUtil.js +33 -0
- package/src/util/stringUtils.js +6 -0
- package/src/util/urlFilterUtil.js +85 -0
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { twMerge } from 'tailwind-merge';
|
|
3
|
+
|
|
4
|
+
import Grid from '~/components/modules/grid';
|
|
5
|
+
import HeaderItem from '~/components/modules/list/header-item';
|
|
6
|
+
|
|
7
|
+
const ListHeader = ({
|
|
8
|
+
className,
|
|
9
|
+
fieldsShown,
|
|
10
|
+
fieldNames,
|
|
11
|
+
fieldIsSortable = true,
|
|
12
|
+
setSortSetting,
|
|
13
|
+
sortSetting,
|
|
14
|
+
includeFavorite = false
|
|
15
|
+
}) => {
|
|
16
|
+
return (
|
|
17
|
+
<Grid
|
|
18
|
+
columns="hc-grid-flow-col hc-auto-cols-fr"
|
|
19
|
+
gap="hc-gap-0"
|
|
20
|
+
isAnimated={false}
|
|
21
|
+
className={twMerge`
|
|
22
|
+
hc-hidden md:hc-grid hc-bg-uiAccent/10 hc-border-b hc-border-uiAccent/10 hc-sticky hc-top-0 hc-z-10
|
|
23
|
+
${className ?? ""}
|
|
24
|
+
`}
|
|
25
|
+
>
|
|
26
|
+
{fieldsShown.map(field => (
|
|
27
|
+
<HeaderItem
|
|
28
|
+
key={field}
|
|
29
|
+
isSortable={fieldIsSortable}
|
|
30
|
+
sortSetting={sortSetting}
|
|
31
|
+
field={field}
|
|
32
|
+
setSortSetting={setSortSetting}
|
|
33
|
+
>
|
|
34
|
+
{fieldNames[field]}
|
|
35
|
+
</HeaderItem>
|
|
36
|
+
))}
|
|
37
|
+
{ includeFavorite == true &&
|
|
38
|
+
<HeaderItem key={"favorite"}
|
|
39
|
+
isSortable={false}
|
|
40
|
+
field={"favorite"}
|
|
41
|
+
>
|
|
42
|
+
Favorite
|
|
43
|
+
</HeaderItem>
|
|
44
|
+
}
|
|
45
|
+
</Grid>
|
|
46
|
+
);
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
export default ListHeader;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { twMerge } from 'tailwind-merge';
|
|
3
|
+
|
|
4
|
+
const CardItemExpand = ({
|
|
5
|
+
className,
|
|
6
|
+
content,
|
|
7
|
+
...props
|
|
8
|
+
}) => {
|
|
9
|
+
return (
|
|
10
|
+
<div
|
|
11
|
+
className={twMerge(
|
|
12
|
+
'hc-flex hc-flex-col lg:hc-flex-row hc-flex-wrap hc-items-stretch hc-gap-x-4 hc-w-full lg:hc-pt-2 hc-border-t hc-border-uiAccent/20',
|
|
13
|
+
className ?? ''
|
|
14
|
+
)}
|
|
15
|
+
{...props}
|
|
16
|
+
>
|
|
17
|
+
{content}
|
|
18
|
+
</div>
|
|
19
|
+
);
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export default CardItemExpand;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
|
|
3
|
+
import Button from '~/components/modules/buttons/default';
|
|
4
|
+
|
|
5
|
+
const RecruiterContactNav = ({
|
|
6
|
+
children,
|
|
7
|
+
className
|
|
8
|
+
}) => {
|
|
9
|
+
return (
|
|
10
|
+
<nav
|
|
11
|
+
className={`
|
|
12
|
+
hc-inline-flex hc-items-center hc-justify-between hc-gap-2 hc-mt-2
|
|
13
|
+
${className ?? ''}
|
|
14
|
+
`}
|
|
15
|
+
>
|
|
16
|
+
{children}
|
|
17
|
+
</nav>
|
|
18
|
+
);
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export const NavButton = ({
|
|
22
|
+
className,
|
|
23
|
+
href,
|
|
24
|
+
target = '_self',
|
|
25
|
+
title,
|
|
26
|
+
icon,
|
|
27
|
+
...props
|
|
28
|
+
}) => {
|
|
29
|
+
return (
|
|
30
|
+
<Button.Anchor
|
|
31
|
+
href={href}
|
|
32
|
+
variant="icon"
|
|
33
|
+
size="sq"
|
|
34
|
+
target={target}
|
|
35
|
+
title={title}
|
|
36
|
+
className={className ?? ''}
|
|
37
|
+
{...props}
|
|
38
|
+
>
|
|
39
|
+
<span className="hc-sr-only">{title}</span>
|
|
40
|
+
<Button.Icon
|
|
41
|
+
icon={icon}
|
|
42
|
+
size="hc-size-8"
|
|
43
|
+
/>
|
|
44
|
+
</Button.Anchor>
|
|
45
|
+
);
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
RecruiterContactNav.Button = NavButton;
|
|
49
|
+
|
|
50
|
+
export default RecruiterContactNav;
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { twMerge } from 'tailwind-merge';
|
|
3
|
+
|
|
4
|
+
const RecruiterDetails = ({
|
|
5
|
+
contactNav,
|
|
6
|
+
className,
|
|
7
|
+
children
|
|
8
|
+
}) => {
|
|
9
|
+
return (
|
|
10
|
+
<div
|
|
11
|
+
className={twMerge`
|
|
12
|
+
hc-grow
|
|
13
|
+
${className ?? ''}
|
|
14
|
+
`}
|
|
15
|
+
>
|
|
16
|
+
{children}
|
|
17
|
+
|
|
18
|
+
{contactNav &&
|
|
19
|
+
<div className="hc-inline-flex">
|
|
20
|
+
{contactNav}
|
|
21
|
+
</div>
|
|
22
|
+
}
|
|
23
|
+
</div>
|
|
24
|
+
);
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export const Title = ({
|
|
28
|
+
as = 'h4',
|
|
29
|
+
className,
|
|
30
|
+
children
|
|
31
|
+
}) => {
|
|
32
|
+
const Container = as;
|
|
33
|
+
|
|
34
|
+
return (
|
|
35
|
+
<Container
|
|
36
|
+
className={twMerge`
|
|
37
|
+
hc-text-base hc-font-medium
|
|
38
|
+
${className ?? ''}
|
|
39
|
+
`}
|
|
40
|
+
>
|
|
41
|
+
{children}
|
|
42
|
+
</Container>
|
|
43
|
+
);
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
export const Text = ({
|
|
47
|
+
as = 'p',
|
|
48
|
+
className,
|
|
49
|
+
children
|
|
50
|
+
}) => {
|
|
51
|
+
const Container = as;
|
|
52
|
+
|
|
53
|
+
return (
|
|
54
|
+
<Container
|
|
55
|
+
className={twMerge`
|
|
56
|
+
hc-text-sm hc-text-uiText/60
|
|
57
|
+
${className ?? ''}
|
|
58
|
+
`}
|
|
59
|
+
>
|
|
60
|
+
{children}
|
|
61
|
+
</Container>
|
|
62
|
+
);
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
RecruiterDetails.Title = Title;
|
|
66
|
+
RecruiterDetails.Text = Text;
|
|
67
|
+
|
|
68
|
+
export default RecruiterDetails;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
|
|
3
|
+
const RecruiterHeadshot = ({
|
|
4
|
+
image,
|
|
5
|
+
alt,
|
|
6
|
+
className,
|
|
7
|
+
imageClassName
|
|
8
|
+
}) => {
|
|
9
|
+
return (
|
|
10
|
+
<div className={className ?? ''}>
|
|
11
|
+
<img
|
|
12
|
+
src={image}
|
|
13
|
+
width="96"
|
|
14
|
+
height="96"
|
|
15
|
+
alt={alt}
|
|
16
|
+
className={imageClassName ?? ''}
|
|
17
|
+
/>
|
|
18
|
+
</div>
|
|
19
|
+
);
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export default RecruiterHeadshot;
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import React, { RefObject, ReactNode } from 'react';
|
|
2
|
+
import Header from '~/components/modules/list/header';
|
|
3
|
+
import Sort from '~/components/modules/filter/sort';
|
|
4
|
+
import Loading from "~/util/loading";
|
|
5
|
+
import { Listing } from '~/types/Listings';
|
|
6
|
+
|
|
7
|
+
interface ItemsListProps {
|
|
8
|
+
fieldNames: Record<string, string>;
|
|
9
|
+
showMap: boolean;
|
|
10
|
+
fieldsShown: string[];
|
|
11
|
+
filteredListings: Listing[];
|
|
12
|
+
loading: boolean;
|
|
13
|
+
sortSetting: any;
|
|
14
|
+
setSortSetting: (setting: any) => void;
|
|
15
|
+
itemLimit: number;
|
|
16
|
+
loader: RefObject<HTMLDivElement>;
|
|
17
|
+
scrollContainerRef: RefObject<HTMLDivElement>;
|
|
18
|
+
itemRefs: any;
|
|
19
|
+
selectedListItem: Listing | null;
|
|
20
|
+
children: ReactNode;
|
|
21
|
+
includeFavorite: boolean;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const ItemsList: React.FC<ItemsListProps> = ({
|
|
25
|
+
fieldNames,
|
|
26
|
+
showMap,
|
|
27
|
+
fieldsShown,
|
|
28
|
+
filteredListings,
|
|
29
|
+
loading,
|
|
30
|
+
sortSetting,
|
|
31
|
+
setSortSetting,
|
|
32
|
+
itemLimit,
|
|
33
|
+
loader,
|
|
34
|
+
scrollContainerRef,
|
|
35
|
+
includeFavorite = false,
|
|
36
|
+
children
|
|
37
|
+
}) => (
|
|
38
|
+
<div className="hc-relative hc-bg-white md:hc-px-4 hc-flex hc-flex-col">
|
|
39
|
+
<div className="hc-flex hc-flex-wrap hc-items-center hc-justify-between hc-gap-4 md:hc-mb-2 hc-p-3 md:hc-p-0 hc-bg-uiAccent/10 md:hc-bg-transparent hc-border-b md:hc-border-none hc-border-uiAccent/20">
|
|
40
|
+
<h2 className="hc-text-gray-500 hc-font-semibold hc-text-xs md:hc-text-sm">
|
|
41
|
+
{loading && <span>Loading...</span>}
|
|
42
|
+
{!loading && <span>{filteredListings.length} results</span>}
|
|
43
|
+
</h2>
|
|
44
|
+
<div className="hc-block md:hc-hidden">
|
|
45
|
+
<Sort
|
|
46
|
+
className={''}
|
|
47
|
+
fields={fieldsShown}
|
|
48
|
+
setSortSetting={setSortSetting}
|
|
49
|
+
fieldNames={fieldNames}
|
|
50
|
+
/>
|
|
51
|
+
</div>
|
|
52
|
+
</div>
|
|
53
|
+
<div>
|
|
54
|
+
<Header
|
|
55
|
+
className={''}
|
|
56
|
+
setSortSetting={setSortSetting}
|
|
57
|
+
sortSetting={sortSetting}
|
|
58
|
+
fieldsShown={fieldsShown}
|
|
59
|
+
fieldNames={fieldNames}
|
|
60
|
+
includeFavorite={includeFavorite}
|
|
61
|
+
/>
|
|
62
|
+
</div>
|
|
63
|
+
<div
|
|
64
|
+
ref={scrollContainerRef}
|
|
65
|
+
className={`
|
|
66
|
+
hc-flex-grow hc-overflow-y-auto
|
|
67
|
+
${showMap ? "md:hc-max-h-45vh hc-max-h-[100vh]" : "md:hc-max-h-95vh hc-max-h-[95vh]"}
|
|
68
|
+
`}
|
|
69
|
+
>
|
|
70
|
+
{loading ? (
|
|
71
|
+
<div className="hc-flex hc-justify-center hc-items-center hc-pt-20">
|
|
72
|
+
<Loading />
|
|
73
|
+
</div>
|
|
74
|
+
) : (
|
|
75
|
+
children
|
|
76
|
+
)}
|
|
77
|
+
<div ref={loader} style={{ height: "100px", textAlign: "center" }}>
|
|
78
|
+
{filteredListings.length >= itemLimit && <Loading />}
|
|
79
|
+
</div>
|
|
80
|
+
</div>
|
|
81
|
+
</div>
|
|
82
|
+
);
|
|
83
|
+
|
|
84
|
+
export default ItemsList;
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import React, { forwardRef, useState } from 'react';
|
|
2
|
+
import Grid from '~/components/modules/grid';
|
|
3
|
+
import Icon from '~/components/modules/icon';
|
|
4
|
+
import FieldMapper from '~/components/modules/list/field-mapper';
|
|
5
|
+
|
|
6
|
+
const ListItem = forwardRef(
|
|
7
|
+
(
|
|
8
|
+
{
|
|
9
|
+
isActive,
|
|
10
|
+
bodyClassName,
|
|
11
|
+
className,
|
|
12
|
+
item,
|
|
13
|
+
fieldsShown,
|
|
14
|
+
specialFeatures,
|
|
15
|
+
onItemSelected,
|
|
16
|
+
showMap,
|
|
17
|
+
setMobileTab,
|
|
18
|
+
handleSettingFavorites,
|
|
19
|
+
favorites,
|
|
20
|
+
includeFavorite = true,
|
|
21
|
+
siteConfig,
|
|
22
|
+
trackEvent,
|
|
23
|
+
eventTypes,
|
|
24
|
+
...props
|
|
25
|
+
},
|
|
26
|
+
ref
|
|
27
|
+
) => {
|
|
28
|
+
const mapPinColor = !showMap ? null : siteConfig.colors.primary.replace("#", "");
|
|
29
|
+
|
|
30
|
+
const handleClick = () => {
|
|
31
|
+
if (onItemSelected) {
|
|
32
|
+
onItemSelected(item);
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
let isFavorite = favorites.includes(item.id);
|
|
36
|
+
|
|
37
|
+
const handleFavouriteClick = (event, item) => {
|
|
38
|
+
if(!includeFavorite)return;
|
|
39
|
+
event.stopPropagation();
|
|
40
|
+
let updatedFavorites;
|
|
41
|
+
if (isFavorite) {
|
|
42
|
+
updatedFavorites = favorites.filter(fav => fav !== item.id);
|
|
43
|
+
} else {
|
|
44
|
+
updatedFavorites = [...favorites, item.id];
|
|
45
|
+
}
|
|
46
|
+
isFavorite = !isFavorite;
|
|
47
|
+
handleSettingFavorites(updatedFavorites);
|
|
48
|
+
};
|
|
49
|
+
return (
|
|
50
|
+
<button
|
|
51
|
+
ref={ref}
|
|
52
|
+
onClick={() => { handleClick(); }}
|
|
53
|
+
className={`
|
|
54
|
+
hc-group hc-relative hc-flex md:hc-flex-col hc-w-full md:hc-pl-4 hc-text-left hc-bg-clip-border hc-border hc-border-transparent hc-break-words hc-overflow-hidden hc-cursor-pointer hc-transition-colors hover:hc-bg-uiAccent/5 focus:hover:hc-bg-uiAccent/5
|
|
55
|
+
${isActive ? "hc-bg-uiAccent/5 hc-border-secondary hc-border" : "hc-text-uiText hc-bg-white"}
|
|
56
|
+
${className ?? ""}
|
|
57
|
+
`}
|
|
58
|
+
{...props}
|
|
59
|
+
>
|
|
60
|
+
<Grid
|
|
61
|
+
columns="hc-grid-flow-col hc-auto-cols-fr"
|
|
62
|
+
gap="hc-gap-2"
|
|
63
|
+
isAnimated={false}
|
|
64
|
+
className={`
|
|
65
|
+
hc-block md:hc-grid hc-p-2 hc-ps-4 hc-w-full hc-grow hc-leading-tight hc-text-sm md:hc-text-xs lg:hc-text-sm
|
|
66
|
+
${bodyClassName ?? ""}
|
|
67
|
+
`}
|
|
68
|
+
>
|
|
69
|
+
<Grid.Item className="hc-hidden md:hc-block md:hc-absolute md:hc-left-1.5 hc-top-1.5">
|
|
70
|
+
<span className="hc-sr-only">Expand row</span>
|
|
71
|
+
<Icon
|
|
72
|
+
icon="fluent-emoji-high-contrast:plus"
|
|
73
|
+
size="hc-size-2.5"
|
|
74
|
+
className={`
|
|
75
|
+
hc-opacity-0 hc-text-uiText/60 hc-transition group-hover:hc-opacity-100 group-active:hc-opacity-100
|
|
76
|
+
${isActive ? "hc-opacity-100 hc-rotate-45" : ""}
|
|
77
|
+
`}
|
|
78
|
+
/>
|
|
79
|
+
</Grid.Item>
|
|
80
|
+
<FieldMapper
|
|
81
|
+
item={item}
|
|
82
|
+
fieldsShown={fieldsShown}
|
|
83
|
+
specialFeatures={specialFeatures}
|
|
84
|
+
isFavorite={isFavorite}
|
|
85
|
+
includeFavorite={includeFavorite}
|
|
86
|
+
handleFavouriteClick={handleFavouriteClick}
|
|
87
|
+
/>
|
|
88
|
+
{includeFavorite &&
|
|
89
|
+
<Grid.Item
|
|
90
|
+
key={"favorites"}
|
|
91
|
+
className="hc-hidden md:hc-block hc-col-span-1"
|
|
92
|
+
>
|
|
93
|
+
<Icon
|
|
94
|
+
icon={isFavorite ? "mdi:heart" : "mdi:heart-outline"}
|
|
95
|
+
size="hc-size-3.5"
|
|
96
|
+
iconClasses={isFavorite ? "hc-text-primary" : ""}
|
|
97
|
+
title={!isFavorite ? 'Add job to favorites' : 'Remove job from favorites'}
|
|
98
|
+
className="hc-pr-2 hc-transition-opacity hc-duration-300 hc-cursor-pointer hc-opacity-100"
|
|
99
|
+
onClick={e => {handleFavouriteClick(e, item);}}
|
|
100
|
+
/>
|
|
101
|
+
</Grid.Item>
|
|
102
|
+
}
|
|
103
|
+
</Grid>
|
|
104
|
+
{showMap && (
|
|
105
|
+
<div onClick={() => { setMobileTab("mapTab"); handleClick(); }} className="md:hc-hidden hc-w-2/5 sm:hc-w-1/3 hc-p-1.5 hc-my-1 hc-bg-uiAccent/5 hc-border hc-border-uiAccent/10 hc-rounded-sm">
|
|
106
|
+
<img
|
|
107
|
+
src={`https://maps.googleapis.com/maps/api/staticmap?scale=2¢er=${item.mapDetails?.latitude},${item.mapDetails?.longitude}&zoom=10&size=240x180&maptype=roadmap&markers=color:0x${mapPinColor}%7Clabel:•%7C${item.mapDetails?.latitude},${item.mapDetails?.longitude}&key=${process.env.GOOGLE_MAPS_API_KEY}`}
|
|
108
|
+
alt={`Map of location for ${item.fields.position}`}
|
|
109
|
+
className="hc-w-full hc-h-full hc-object-cover"
|
|
110
|
+
/>
|
|
111
|
+
</div>
|
|
112
|
+
)}
|
|
113
|
+
</button>
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
);
|
|
117
|
+
|
|
118
|
+
ListItem.displayName = "ListItem";
|
|
119
|
+
|
|
120
|
+
export default React.memo(ListItem, (prevProps, nextProps) => {
|
|
121
|
+
return (
|
|
122
|
+
prevProps.isActive === nextProps.isActive &&
|
|
123
|
+
prevProps.favorites === nextProps.favorites &&
|
|
124
|
+
prevProps.item.id === nextProps.item.id &&
|
|
125
|
+
prevProps.item.fields.travelTime === nextProps.item.fields.travelTime &&
|
|
126
|
+
prevProps.fieldsShown === nextProps.fieldsShown &&
|
|
127
|
+
prevProps.bodyClassName === nextProps.bodyClassName &&
|
|
128
|
+
prevProps.className === nextProps.className
|
|
129
|
+
);
|
|
130
|
+
});
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
|
|
3
|
+
const CardInfoWindow = ({ className, content, id }) => {
|
|
4
|
+
return (
|
|
5
|
+
<div
|
|
6
|
+
id={id}
|
|
7
|
+
className={`
|
|
8
|
+
hc-relative hc-flex hc-flex-col hc-break-words hc-bg-clip-border
|
|
9
|
+
${className ?? ""}
|
|
10
|
+
`}
|
|
11
|
+
>
|
|
12
|
+
{content}
|
|
13
|
+
</div>
|
|
14
|
+
);
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export default CardInfoWindow;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
|
|
3
|
+
const InfoWindowContent = ({
|
|
4
|
+
items,
|
|
5
|
+
fullAddress,
|
|
6
|
+
applyFilters
|
|
7
|
+
}) => {
|
|
8
|
+
if (!items || items.length < 0) {
|
|
9
|
+
return null;
|
|
10
|
+
}
|
|
11
|
+
return (
|
|
12
|
+
<div className="hc-flex-auto hc-p-2">
|
|
13
|
+
<div>
|
|
14
|
+
<div>
|
|
15
|
+
<h4 className="hc-text-lg hc-font-bold hc-leading-tight hc-mb-3">
|
|
16
|
+
{items[0].mapDetails?.entityDisplayName}
|
|
17
|
+
</h4>
|
|
18
|
+
<p>{fullAddress}</p>
|
|
19
|
+
<div className="hc-pt-2">
|
|
20
|
+
<button
|
|
21
|
+
className="hc-cursor-pointer hover:hc-opacity-70 hc-text-primary"
|
|
22
|
+
onClick={applyFilters}
|
|
23
|
+
>
|
|
24
|
+
{items.length > 1
|
|
25
|
+
? `View All ${items.length} jobs at this location`
|
|
26
|
+
: `View job at this location`}
|
|
27
|
+
</button>
|
|
28
|
+
</div>
|
|
29
|
+
</div>
|
|
30
|
+
</div>
|
|
31
|
+
</div>
|
|
32
|
+
);
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
export default InfoWindowContent;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import Tabs from '~/components/modules/maps/tabs';
|
|
3
|
+
|
|
4
|
+
const MapList = ({ showMap, loading, list, map, filter }) => {
|
|
5
|
+
return (
|
|
6
|
+
<>
|
|
7
|
+
<div
|
|
8
|
+
className={`
|
|
9
|
+
${showMap === false ? "md:hc-grid-rows-[100vh]" : "md:hc-grid-rows-[50vh_50vh]"}
|
|
10
|
+
md:hc-grid md:hc-pt-4 hc-overflow-hidden hc-relative bg-gray-100
|
|
11
|
+
`}
|
|
12
|
+
>
|
|
13
|
+
<div>{list}</div>
|
|
14
|
+
<div>{showMap && map}</div>
|
|
15
|
+
</div>
|
|
16
|
+
<div className="md:hc-hidden">
|
|
17
|
+
<Tabs
|
|
18
|
+
showMap={showMap}
|
|
19
|
+
list={list}
|
|
20
|
+
map={!loading && showMap && map}
|
|
21
|
+
filter={filter}
|
|
22
|
+
/>
|
|
23
|
+
</div>
|
|
24
|
+
</>
|
|
25
|
+
);
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export default MapList;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { MarkerF } from '@react-google-maps/api';
|
|
3
|
+
|
|
4
|
+
const MapMarker = ({
|
|
5
|
+
position,
|
|
6
|
+
title,
|
|
7
|
+
icon,
|
|
8
|
+
zIndex,
|
|
9
|
+
onLoad,
|
|
10
|
+
onClick,
|
|
11
|
+
clusterer,
|
|
12
|
+
children
|
|
13
|
+
}) => {
|
|
14
|
+
return (
|
|
15
|
+
<MarkerF
|
|
16
|
+
position={position}
|
|
17
|
+
title={title}
|
|
18
|
+
options={{ icon: icon }}
|
|
19
|
+
zIndex={zIndex}
|
|
20
|
+
onLoad={onLoad}
|
|
21
|
+
onClick={onClick}
|
|
22
|
+
clusterer={clusterer}
|
|
23
|
+
>
|
|
24
|
+
{children}
|
|
25
|
+
</MarkerF>
|
|
26
|
+
);
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
export default MapMarker;
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { GoogleMap, MarkerClustererF } from "@react-google-maps/api";
|
|
3
|
+
import ShowAllButton from "~/components/modules/buttons/show-all-button";
|
|
4
|
+
import { clusterOptions } from "~/util/mapUtil";
|
|
5
|
+
|
|
6
|
+
const Map = ({
|
|
7
|
+
zoom,
|
|
8
|
+
center,
|
|
9
|
+
mapContainerRef,
|
|
10
|
+
onLoad,
|
|
11
|
+
onIdle,
|
|
12
|
+
clusterGridSize,
|
|
13
|
+
markerConfigs,
|
|
14
|
+
mapMarkers,
|
|
15
|
+
placeMarkers,
|
|
16
|
+
mapInteracted,
|
|
17
|
+
pinIconUrl,
|
|
18
|
+
setMapInteracted,
|
|
19
|
+
fitBounds,
|
|
20
|
+
mapRef,
|
|
21
|
+
setQuery,
|
|
22
|
+
filteredListingsLength,
|
|
23
|
+
setSelectedFilters
|
|
24
|
+
}) => {
|
|
25
|
+
return (
|
|
26
|
+
<>
|
|
27
|
+
{zoom && center && (
|
|
28
|
+
<div ref={mapContainerRef} className="hc-h-full hc-relative">
|
|
29
|
+
<GoogleMap
|
|
30
|
+
zoom={zoom}
|
|
31
|
+
onLoad={onLoad}
|
|
32
|
+
onIdle={onIdle}
|
|
33
|
+
center={center}
|
|
34
|
+
mapContainerStyle={{
|
|
35
|
+
width: "100%",
|
|
36
|
+
height: "100%"
|
|
37
|
+
}}
|
|
38
|
+
options={{
|
|
39
|
+
styles: [
|
|
40
|
+
{
|
|
41
|
+
featureType: "poi",
|
|
42
|
+
elementType: "labels",
|
|
43
|
+
stylers: [{ visibility: "off" }]
|
|
44
|
+
}
|
|
45
|
+
]
|
|
46
|
+
}}
|
|
47
|
+
>
|
|
48
|
+
<MarkerClustererF options={clusterOptions(clusterGridSize, markerConfigs.fillColor)}>
|
|
49
|
+
{clusterer => (
|
|
50
|
+
<>
|
|
51
|
+
{mapMarkers}
|
|
52
|
+
{placeMarkers}
|
|
53
|
+
</>
|
|
54
|
+
)}
|
|
55
|
+
</MarkerClustererF>
|
|
56
|
+
</GoogleMap>
|
|
57
|
+
{mapInteracted && markerConfigs && (
|
|
58
|
+
<ShowAllButton
|
|
59
|
+
mapInteracted={mapInteracted}
|
|
60
|
+
markerConfigs={markerConfigs}
|
|
61
|
+
setMapInteracted={setMapInteracted}
|
|
62
|
+
fitBounds={fitBounds}
|
|
63
|
+
mapRef={mapRef}
|
|
64
|
+
pinIconUrl={pinIconUrl}
|
|
65
|
+
setQuery={setQuery}
|
|
66
|
+
listingCount={filteredListingsLength}
|
|
67
|
+
setSelectedFilters={setSelectedFilters}
|
|
68
|
+
/>
|
|
69
|
+
)}
|
|
70
|
+
</div>
|
|
71
|
+
)}
|
|
72
|
+
</>
|
|
73
|
+
);
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
export default Map;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Marker, InfoWindowF } from '@react-google-maps/api';
|
|
3
|
+
|
|
4
|
+
const PlaceMarker = ({
|
|
5
|
+
marker,
|
|
6
|
+
index,
|
|
7
|
+
selectedPlaceMarker,
|
|
8
|
+
setSelectedPlaceMarker,
|
|
9
|
+
placesWindow,
|
|
10
|
+
setPlacesWindow
|
|
11
|
+
}) => {
|
|
12
|
+
const markerKey = `marker-${marker.title}-${index}`;
|
|
13
|
+
|
|
14
|
+
const placeMarkerClickHandler = () => {
|
|
15
|
+
setSelectedPlaceMarker(markerKey);
|
|
16
|
+
setPlacesWindow(true);
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
return (
|
|
20
|
+
<Marker
|
|
21
|
+
key={markerKey}
|
|
22
|
+
position={marker.position}
|
|
23
|
+
title={marker.title}
|
|
24
|
+
icon={marker.icon}
|
|
25
|
+
options={{ optimized: false }}
|
|
26
|
+
onClick={placeMarkerClickHandler}
|
|
27
|
+
>
|
|
28
|
+
{selectedPlaceMarker === markerKey && placesWindow && marker && (
|
|
29
|
+
<InfoWindowF
|
|
30
|
+
position={marker.position}
|
|
31
|
+
onCloseClick={() => setPlacesWindow(false)}
|
|
32
|
+
options={{ maxWidth: 400 }}
|
|
33
|
+
>
|
|
34
|
+
<div>{marker.title}</div>
|
|
35
|
+
</InfoWindowF>
|
|
36
|
+
)}
|
|
37
|
+
</Marker>
|
|
38
|
+
);
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
export default PlaceMarker;
|