Skip to content

Commit

Permalink
fix: polishing employee page (#919)
Browse files Browse the repository at this point in the history
* fix: makes loading employees lazy and more performant

* fix: people grid as actual grid

* fixing the most annoying thing I can think of

* feat: adds tag component

* fix: adjustments to minor details of design

* fix: makes employee site more mobile friendly

* woop

* fix: adds max width for employee container
  • Loading branch information
mikaelbr authored Nov 29, 2024
1 parent b65f9c1 commit 474e280
Show file tree
Hide file tree
Showing 9 changed files with 251 additions and 119 deletions.
64 changes: 42 additions & 22 deletions src/components/employeeCard/EmployeeCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,38 +30,58 @@ export default function EmployeeCard({
<div className={styles.employeeImage}>
<Image
src={employee.imageUrl}
alt=""
alt={employee.name}
style={{ objectFit: "cover" }}
fill={true}
/>
</div>
</Link>
<div className={styles.employeeInfoWrapper}>
<div className={styles.employeeInfo}>
<p className={styles.employeeName}>{employee.name}</p>
<div className={styles.employeeRole}>
{employee.competences.map((competence) => (
<>
<Text
className={styles.employeeRoleDot}
type="labelRegular"
key={competence}
>
{competence}
</Text>
</>
))}
</div>
</div>
<Text type="h4" as="h3">
<Link
href={`/${language}/${employeePageSlug}/${aliasFromEmail(employee.email)}`}
className={styles.employeeNameLink}
>
{employee.name}
</Link>
</Text>

<div className={styles.employeeContactInfo}>
<p>{employee.email}</p>
{employee.telephone && (
<p>{formatPhoneNumber(employee.telephone)}</p>
)}
<div className={styles.employeeRole}>
{employee.competences.map((competence) => (
<Text
className={styles.employeeRoleDot}
type="labelRegular"
key={competence}
>
{competence}
</Text>
))}
</div>

<Text type="bodyExtraSmall">
<a href={`mailto:${employee.email}`}>{employee.email}</a>
</Text>
{employee.telephone && (
<Text type="bodyExtraSmall">
<a href={`tel:${employee.telephone}`}>
{formatPhoneNumber(employee.telephone)}
</a>
</Text>
)}
</div>
</div>
)
);
}

export function EmployeeCardSkeleton() {
return (
<div className={`${styles.employeeWrapper} ${styles.skeletonCard}`}>
<div className={styles.employeeImage} />
<div className={`${styles.skeletonText} ${styles.skeletonName}`} />
<div className={`${styles.skeletonText} ${styles.skeletonTitle}`} />
<div className={`${styles.skeletonText} ${styles.skeletonContact}`} />
<div className={`${styles.skeletonText} ${styles.skeletonContact}`} />
</div>
);
}
45 changes: 38 additions & 7 deletions src/components/employeeCard/employeeCard.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
display: flex;
flex-direction: column;
justify-content: space-between;
height: 100%;
gap: 0.375rem;
}

.contactInfo {
Expand All @@ -30,17 +30,14 @@
gap: 0.375rem;
}

.employeeName {
font-size: 1rem;
font-weight: 600;
line-height: 120%;
.employeeNameLink {
text-decoration: none;
}

.employeeWrapper {
display: flex;
flex-direction: column;
align-items: flex-start;
margin: 1rem;
min-width: 280px;
max-width: var(--Text-paragraph, 537px);
gap: var(--small, 6px);
Expand Down Expand Up @@ -88,7 +85,7 @@

.employeeRoleDot::after {
content: "·";
margin: 0 0.5rem;
margin: 0 0.25rem;
}

.employeeRoleDot:last-child:after {
Expand All @@ -103,3 +100,37 @@
font-size: 1rem;
color: var(--text-tertiary, #5e5e5e);
}

/* Update the skeleton styles */
.skeletonCard {
animation: pulse 1.5s cubic-bezier(0.4, 0, 0.6, 1) infinite;
}

.skeletonText {
height: 20px;
background-color: var(--background-bg-light-primary);
border-radius: 4px;
align-self: flex-start;
}

.skeletonName {
width: 150px;
}

.skeletonTitle {
width: 100px;
}

.skeletonContact {
width: 130px;
}

@keyframes pulse {
0%,
100% {
opacity: 1;
}
50% {
opacity: 0.5;
}
}
104 changes: 49 additions & 55 deletions src/components/sections/employees/EmployeeList.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
"use client";

import { useTranslations } from "next-intl";
import { useState } from "react";
import { use, useState } from "react";

import Button from "src/components/buttons/Button";
import EmployeeCard from "src/components/employeeCard/EmployeeCard";
import { Tag } from "src/components/tag";
import Text from "src/components/text/Text";
import { ChewbaccaEmployee, Competence } from "src/types/employees";
import { Result } from "studio/utils/result";

import styles from "./employees.module.css";

Expand All @@ -18,7 +19,7 @@ const competences: Competence[] = [
];

export interface EmployeesProps {
employees: ChewbaccaEmployee[];
employees: Promise<Result<ChewbaccaEmployee[], string>>;
language: string;
employeesPageSlug: string;
}
Expand All @@ -29,16 +30,19 @@ interface EmployeeFilters {
}

export default function EmployeeList({
employees,
employees: employeesPromise,
language,
employeesPageSlug,
}: EmployeesProps) {
const employeesRes = use(employeesPromise);
const employees = employeesRes.ok ? employeesRes.value : [];
const [filteredEmployees, setFilteredEmployees] = useState<
ChewbaccaEmployee[]
>(shuffleEmployees(employees));

const locations = Array.from(new Set(employees.map((e) => e.officeName)));
const t = useTranslations("employee_card");

const [filteredEmployees, setFilteredEmployees] =
useState<ChewbaccaEmployee[]>(employees);

const [employeeFilters, setEmployeeFilters] = useState<EmployeeFilters>({
competenceFilter: null,
locationFilter: null,
Expand Down Expand Up @@ -85,89 +89,79 @@ export default function EmployeeList({
<Text type="labelSemibold" className={styles.employeeFilterLabel}>
{t("field")}
</Text>
<Button
size="small"
background={employeeFilters.competenceFilter ? "light" : "dark"}
type={
employeeFilters.competenceFilter == null ? "primary" : "secondary"
}
<Tag
active={!employeeFilters.competenceFilter}
type="button"
onClick={() => filterEmployees({ competenceFilter: null })}
>
<Text type="labelSmall"> {t("all")}</Text>
</Button>
text={t("all")}
/>

{competences.map((competence) => {
const active = employeeFilters.competenceFilter == competence;
return (
<Button
size="small"
<Tag
key={competence}
background={active ? "dark" : "light"}
type={"secondary"}
active={active}
type="button"
onClick={() =>
filterEmployees({ competenceFilter: competence })
}
>
<Text type="labelSmall"> {t(competence)}</Text>
</Button>
text={t(competence)}
/>
);
})}
</div>
<div className={styles.employeeFilterWrapper}>
<Text type="labelSemibold" className={styles.employeeFilterLabel}>
{t("location")}
</Text>
<Button
size="small"
background={employeeFilters.locationFilter ? "light" : "dark"}
type={
employeeFilters.locationFilter == null ? "primary" : "secondary"
}
<Tag
active={!employeeFilters.locationFilter}
type="button"
onClick={() => filterEmployees({ locationFilter: null })}
>
<Text type="labelSmall"> {t("all")}</Text>
</Button>
text={t("all")}
/>

{locations.map((location) => {
if (!location) return null;
const active = employeeFilters.locationFilter == location;
return (
<Button
size="small"
<Tag
key={location}
background={active ? "dark" : "light"}
type={"secondary"}
active={active}
type="button"
onClick={() => filterEmployees({ locationFilter: location })}
>
<Text type="labelSmall"> {location} </Text>
</Button>
text={location}
/>
);
})}
</div>
</div>

<div className={styles.employeeCountWrapper}>
<div style={{ display: "flex" }}></div>

<div className={styles.peopleCountWrapper}>
<p className={styles.employeeCount}>
{t("show")}{" "}
<span className={styles.employeeCountValue}>
{filteredEmployees.length}
</span>{" "}
{t("of")}{" "}
<span className={styles.employeeCountValue}>{employees.length}</span>{" "}
{t("consultants")}
{t("of")} <span>{employees.length}</span> {t("consultants")}
</p>
</div>

<div className={styles.peopleContainer}>
{filteredEmployees.map((employee) => (
<EmployeeCard
employee={employee}
employeePageSlug={employeesPageSlug}
language={language}
key={employee.name}
/>
))}
<div className={styles.peopleContainer}>
{filteredEmployees.map((employee) => (
<EmployeeCard
employee={employee}
employeePageSlug={employeesPageSlug}
language={language}
key={employee.name}
/>
))}
</div>
</div>
</>
);
}

function shuffleEmployees(employees: ChewbaccaEmployee[]) {
return employees.sort(() => Math.random() - 0.5);
}
Loading

0 comments on commit 474e280

Please sign in to comment.