diff --git a/src/components/jobPosting/JobPosting.tsx b/src/components/jobPosting/JobPosting.tsx new file mode 100644 index 000000000..0f9436265 --- /dev/null +++ b/src/components/jobPosting/JobPosting.tsx @@ -0,0 +1,31 @@ +import Badge from "src/components/badge/Badge"; +import Text from "src/components/text/Text"; +import { CompanyLocation } from "studio/lib/interfaces/companyDetails"; +import { IJobPosting } from "studio/lib/interfaces/jobPosting"; + +import styles from "./jobPosting.module.css"; + +interface JobPostingProps { + jobPosting: IJobPosting; +} + +export default function JobPosting({ jobPosting }: JobPostingProps) { + const jobPostingLocations = jobPosting.locations + .map((location: CompanyLocation) => location.companyLocationName) + .join(", "); + + return ( + +
+
+ {jobPosting.role} +
+ {jobPostingLocations} +
+
+ ); +} diff --git a/src/components/jobPosting/jobPosting.module.css b/src/components/jobPosting/jobPosting.module.css new file mode 100644 index 000000000..e633b19dc --- /dev/null +++ b/src/components/jobPosting/jobPosting.module.css @@ -0,0 +1,52 @@ +.jobPosting { + display: flex; + flex-direction: row; + justify-content: space-between; + align-items: center; + border-radius: 1.5rem; + padding: 0.5rem 1.5rem; + gap: 5rem; + width: 100%; + background-color: var(--text-primary-light); + color: var(--text-primary); + + &::after { + justify-self: end; + width: 1.5rem; + height: 1.5rem; + content: ""; + -webkit-mask: url("/_assets/arrow-right.svg") no-repeat center; + -webkit-mask-size: cover; + mask: url("/_assets/arrow-right.svg") no-repeat center; + mask-size: cover; + background-color: var(--background-bg-dark); + } +} + +.details { + display: flex; + flex-direction: row; + justify-content: flex-start; + align-items: center; + gap: 0.5rem; + width: 100%; +} + +.role { + width: 250px; + @media (max-width: 640px) { + width: 100%; + } +} + +.locations { + border: solid; + border-color: var(--text-primary); + border-width: thin; + border-radius: 25rem; + padding: 0.375rem 0.75rem; + text-align: center; + @media (max-width: 640px) { + display: none; + } +} diff --git a/src/components/sections/jobs/JobPostingList.tsx b/src/components/sections/jobs/JobPostingList.tsx new file mode 100644 index 000000000..addaa8e6f --- /dev/null +++ b/src/components/sections/jobs/JobPostingList.tsx @@ -0,0 +1,81 @@ +"use client"; + +import { useEffect, useMemo, useState } from "react"; + +import JobPosting from "src/components/jobPosting/JobPosting"; +import { Tag } from "src/components/tag"; +import Text from "src/components/text/Text"; +import { CompanyLocation } from "studio/lib/interfaces/companyDetails"; +import { IJobPosting } from "studio/lib/interfaces/jobPosting"; + +import styles from "./jobs.module.css"; + +interface JobPostingListProps { + jobPostings: IJobPosting[]; + companyLocations: CompanyLocation[]; +} + +export default function JobPostingList({ + jobPostings, + companyLocations, +}: JobPostingListProps) { + const [locationFilter, setLocationFilter] = useState( + null, + ); + const [filteredJobPostings, setFilteredJobPostings] = + useState(jobPostings); + const jobPostingLocations = useMemo( + () => + Object.fromEntries( + jobPostings.map((jobPosting) => [ + jobPosting._key, + jobPosting.locations.map((location) => location.companyLocationName), + ]), + ), + [jobPostings], + ); + + useEffect(() => { + if (locationFilter === null) { + setFilteredJobPostings(jobPostings); + } else { + setFilteredJobPostings( + jobPostings?.filter((jobPosting) => + jobPostingLocations[jobPosting._key].includes( + locationFilter.companyLocationName, + ), + ), + ); + } + }, [locationFilter, jobPostings, jobPostingLocations]); + + return ( +
+
+ Kontor + setLocationFilter(null)} + text="Alle" + background="violet" + /> + {companyLocations.map((location: CompanyLocation) => ( + setLocationFilter(location)} + text={location.companyLocationName} + key={location._id} + background="violet" + /> + ))} +
+
+ {filteredJobPostings?.map((jobPosting: IJobPosting) => ( + + ))} +
+
+ ); +} diff --git a/src/components/sections/jobs/Jobs.tsx b/src/components/sections/jobs/Jobs.tsx new file mode 100644 index 000000000..b85e08d99 --- /dev/null +++ b/src/components/sections/jobs/Jobs.tsx @@ -0,0 +1,47 @@ +import Text from "src/components/text/Text"; +import { CompanyLocation } from "studio/lib/interfaces/companyDetails"; +import { IJobPostings } from "studio/lib/interfaces/jobPosting"; +import { JobsSection } from "studio/lib/interfaces/pages"; +import { + COMPANY_LOCATIONS_QUERY, + JOB_POSTINGS_QUERY, +} from "studio/lib/queries/admin"; +import { loadStudioQuery } from "studio/lib/store"; + +import JobPostingList from "./JobPostingList"; +import styles from "./jobs.module.css"; + +export interface JobsProps { + language: string; + section: JobsSection; +} + +export default async function Jobs({ language, section }: JobsProps) { + const { data: jobPostings } = await loadStudioQuery( + JOB_POSTINGS_QUERY, + { + language, + }, + ); + + const { data: companyLocations } = await loadStudioQuery( + COMPANY_LOCATIONS_QUERY, + {}, + ); + + return ( + jobPostings && + companyLocations && ( +
+
+ {section.basicTitle} + {section.subtitle} +
+ +
+ ) + ); +} diff --git a/src/components/sections/jobs/jobs.module.css b/src/components/sections/jobs/jobs.module.css new file mode 100644 index 000000000..7dc5cc238 --- /dev/null +++ b/src/components/sections/jobs/jobs.module.css @@ -0,0 +1,39 @@ +.wrapper { + display: flex; + flex-direction: column; + justify-content: center; + padding: 2rem 0.375rem 0.375rem 0.375rem; + max-width: 53rem; + margin: 5rem auto; + border-radius: 0.75px; + gap: 1.5rem; + background-color: var(--Violet-700); + color: var(--text-primary-light); +} + +.titleSection { + padding: 0px 24px; +} + +.jobPostingsContainer { + display: flex; + flex-direction: column; + gap: 1rem; +} + +.filters { + display: flex; + flex-direction: row; + flex-wrap: wrap; + align-items: center; + padding: 0rem 1.5rem; + gap: 0.75rem; +} + +.jobPostings { + display: flex; + flex-direction: column; + width: 100%; + gap: 0.5rem; + text-wrap: wrap; +} diff --git a/src/components/tag/index.tsx b/src/components/tag/index.tsx index 468d3ae93..f17917ff7 100644 --- a/src/components/tag/index.tsx +++ b/src/components/tag/index.tsx @@ -14,9 +14,22 @@ type TagInner = href: string; } & JSX.IntrinsicElements["link"]); +function getBackgroundClass(backgroundColor: string) { + switch (backgroundColor) { + case "light": + return ""; + case "dark": + return styles["tag--bgDark"]; + case "violet": + return styles["tag--bgViolet"]; + default: + return ""; + } +} + type TagProps = { active?: boolean; - background?: "light" | "dark"; + background?: "light" | "dark" | "violet"; text: string; } & TagInner; @@ -27,8 +40,8 @@ export const Tag = ({ ...props }: TagProps) => { const activeClass = active ? styles["tag--active"] : ""; - const bgDarkClass = background === "dark" ? styles["tag--bgDark"] : ""; - const className = `${styles.tag} ${activeClass} ${bgDarkClass}`; + const bgClass = getBackgroundClass(background); + const className = `${styles.tag} ${activeClass} ${bgClass}`; if (props.type === "button") { return (