diff --git a/messages/en.json b/messages/en.json index 0f562f822..4583d4a0e 100644 --- a/messages/en.json +++ b/messages/en.json @@ -35,6 +35,7 @@ "Design": "Design", "Utvikling": "Development", "Administasjon": "Administration", - "field": "Field" + "field": "Field", + "showMore": "Show all" } } diff --git a/messages/no.json b/messages/no.json index 37abbeeef..9c3bdf35a 100644 --- a/messages/no.json +++ b/messages/no.json @@ -35,6 +35,7 @@ "Design": "Design", "Utvikling": "Utvikling", "Administasjon": "Administrasjon", - "field": "Fag" + "field": "Fag", + "showMore": "Vis alle" } } diff --git a/messages/se.json b/messages/se.json index 0436b4dea..8a549f44a 100644 --- a/messages/se.json +++ b/messages/se.json @@ -35,6 +35,7 @@ "Design": "Design", "Utvikling": "Utveckling", "Administasjon": "Administration", - "field": "Fält" + "field": "Fält", + "showMore": "Vis alla" } } diff --git a/public/_assets/arrow-down.svg b/public/_assets/arrow-down.svg new file mode 100644 index 000000000..4d5727fd9 --- /dev/null +++ b/public/_assets/arrow-down.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/components/employeeCard/EmployeeCard.tsx b/src/components/employeeCard/EmployeeCard.tsx index 056c5b765..9d69236db 100644 --- a/src/components/employeeCard/EmployeeCard.tsx +++ b/src/components/employeeCard/EmployeeCard.tsx @@ -24,53 +24,52 @@ export default function EmployeeCard({ employee.name && employee.email && (
-
- -
- {employee.name} -
- -
- - - {employee.name} - - - -
- {employee.competences.map((competence) => ( - - {competence} - - ))} -
+ +
+ {employee.name} +
+ +
+ + + {employee.name} + + - - {employee.email} - - {employee.telephone && ( - - - {formatPhoneNumber(employee.telephone)} - +
+ {employee.competences.map((competence) => ( + + {competence} - )} + ))}
+ + + {employee.email} + + {employee.telephone && ( + + + {formatPhoneNumber(employee.telephone)} + + + )}
) diff --git a/src/components/sections/employees/EmployeeList.tsx b/src/components/sections/employees/EmployeeList.tsx index 4e8a73a76..19b2217f6 100644 --- a/src/components/sections/employees/EmployeeList.tsx +++ b/src/components/sections/employees/EmployeeList.tsx @@ -1,7 +1,10 @@ "use client"; +import Image from "next/image"; +import Link from "next/link"; +import { usePathname, useRouter, useSearchParams } from "next/navigation"; import { useTranslations } from "next-intl"; -import { use, useState } from "react"; +import { use, useState, useTransition } from "react"; import EmployeeCard from "src/components/employeeCard/EmployeeCard"; import { Tag } from "src/components/tag"; @@ -10,6 +13,7 @@ import { ChewbaccaEmployee, Competence } from "src/types/employees"; import { Result } from "studio/utils/result"; import styles from "./employees.module.css"; +import { EmployeeListSkeleton } from "./EmployeeSkeleton"; const competences: Competence[] = [ "Utvikling", @@ -29,6 +33,8 @@ interface EmployeeFilters { locationFilter: string | null; } +const DEFAULT_LIMIT = 4 * 2; + export default function EmployeeList({ employees: employeesPromise, language, @@ -39,6 +45,14 @@ export default function EmployeeList({ const [filteredEmployees, setFilteredEmployees] = useState(employees); + const { + showShowMoreButton, + isShowMorePending, + limitedEmployees, + showMoreHandler, + showMoreHref, + } = useShowAll(filteredEmployees); + const locations = Array.from(new Set(employees.map((e) => e.officeName))); const t = useTranslations("employee_card"); @@ -95,7 +109,7 @@ export default function EmployeeList({ text={t("all")} /> - {competences.map((competence) => { + {sortCompetenceAlphabetically(competences).map((competence) => { const active = employeeFilters.competenceFilter == competence; return ( - {locations.map((location) => { + {sortAlphabetically(locations).map((location) => { if (!location) return null; const active = employeeFilters.locationFilter == location; return ( @@ -147,7 +161,7 @@ export default function EmployeeList({

- {filteredEmployees.map((employee) => ( + {limitedEmployees.map((employee) => ( ))}
+ {isShowMorePending && } + + {showShowMoreButton && ( + + )}
); } + +function ShowMoreButton({ + showMoreHandler, + showMoreHref, +}: { + showMoreHandler: () => void; + showMoreHref: string; +}) { + const t = useTranslations("employee_card"); + + // @TODO Replace with Button component when actually implemented + return ( +
+ + {t("showMore")}{" "} + + +
+ ); +} + +function useShowAll(filteredEmployees: ChewbaccaEmployee[]) { + const [isPending, startTransition] = useTransition(); + + const currentPath = usePathname(); + const searchParams = useSearchParams(); + const { replace } = useRouter(); + const limitEmployees = !searchParams.has("showAll"); + const limitedEmployees = limitEmployees + ? filteredEmployees.slice(0, DEFAULT_LIMIT) + : filteredEmployees; + + const showMoreHandler = () => + startTransition(() => { + replace(`${currentPath}?showAll`); + }); + + return { + limitedEmployees, + showMoreHandler, + isShowMorePending: isPending, + showShowMoreButton: + limitEmployees && !isPending && filteredEmployees.length > DEFAULT_LIMIT, + showMoreHref: `${currentPath}?showAll`, + }; +} + +function sortAlphabetically(filter: (string | null | undefined)[]) { + return filter.sort((a, b) => a?.localeCompare(b ?? "") ?? 0); +} + +function sortCompetenceAlphabetically(competences: Competence[]) { + return competences.sort((a, b) => a?.localeCompare(b ?? "") ?? 0); +} diff --git a/src/components/sections/employees/EmployeeSkeleton.tsx b/src/components/sections/employees/EmployeeSkeleton.tsx new file mode 100644 index 000000000..dbed3219b --- /dev/null +++ b/src/components/sections/employees/EmployeeSkeleton.tsx @@ -0,0 +1,13 @@ +import { EmployeeCardSkeleton } from "src/components/employeeCard/EmployeeCard"; + +import styles from "./employees.module.css"; + +export function EmployeeListSkeleton() { + return ( +
+ {[...Array(4)].map((_, index) => ( + + ))} +
+ ); +} diff --git a/src/components/sections/employees/Employees.tsx b/src/components/sections/employees/Employees.tsx index 6bf7d83f7..6559b3cc1 100644 --- a/src/components/sections/employees/Employees.tsx +++ b/src/components/sections/employees/Employees.tsx @@ -1,7 +1,6 @@ import { headers } from "next/headers"; import { Suspense } from "react"; -import { EmployeeCardSkeleton } from "src/components/employeeCard/EmployeeCard"; import Text from "src/components/text/Text"; import { fetchAllChewbaccaEmployees } from "src/utils/employees"; import { domainFromHostname } from "src/utils/url"; @@ -11,6 +10,7 @@ import { loadStudioQuery } from "studio/lib/store"; import EmployeeList from "./EmployeeList"; import styles from "./employees.module.css"; +import { EmployeeListSkeleton } from "./EmployeeSkeleton"; export interface EmployeesProps { language: string; @@ -47,13 +47,3 @@ export default async function Employees({ language, section }: EmployeesProps) {
); } - -function EmployeeListSkeleton() { - return ( -
- {[...Array(4)].map((_, index) => ( - - ))} -
- ); -} diff --git a/src/components/sections/employees/employees.module.css b/src/components/sections/employees/employees.module.css index 01a0e1ebf..30de6b4bb 100644 --- a/src/components/sections/employees/employees.module.css +++ b/src/components/sections/employees/employees.module.css @@ -78,3 +78,45 @@ grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); gap: 1.5rem; } + +.showMore { + display: flex; + align-items: center; + justify-content: center; + position: relative; + margin-top: 2rem; +} + +.showMore::before { + content: ""; + display: block; + height: 1px; + background: var(--background-button-outline-dark); + width: 100%; + position: absolute; + top: 50%; + left: 0; + z-index: 1; +} + +.showMore__button { + line-height: 1; + display: flex; + align-items: center; + justify-content: center; + + padding: 0.25rem 0.5rem; + gap: 0.25rem; + + border-radius: var(--radius-small); + border: 1px solid var(--background-button-outline-dark); + background: var(--background-bg-light-primary); + text-decoration: none; + z-index: 2; + position: relative; +} +.showMore__button img { + display: block; + height: 1.5rem; + width: 1.5rem; +}