✨ feat(people): add employee details

This commit is contained in:
2026-02-10 16:10:13 +01:00
parent 91f6bbb8f5
commit f0ed429cee
4 changed files with 248 additions and 26 deletions
@@ -0,0 +1,68 @@
import { Dialog, DialogProps, DialogTitle, DialogContent, IconButton, Typography, Stack, Divider } from '@mui/material'
import CloseIcon from '@mui/icons-material/Close'
export type EmployeeDescription = {
title: string
position?: string
image?: string
body?: string[]
captions?: string[]
}
export type EmployeeDescriptionDialogProps = DialogProps & {
description: EmployeeDescription
}
const EmployeeDescriptionDialog: React.FC<EmployeeDescriptionDialogProps> = ({ description, ...props }) => (
<Dialog fullWidth {...props}>
<DialogTitle>
<Typography variant="h4" component="div">
{description.title}
</Typography>
{description.position && (
<Typography variant="h6" color="text.secondary">
{description.position}
</Typography>
)}
</DialogTitle>
<IconButton
aria-label="close"
onClick={props.onClose as React.MouseEventHandler<HTMLButtonElement>}
sx={{
position: 'absolute',
right: 0,
top: 0,
}}>
<CloseIcon />
</IconButton>
<DialogContent sx={{ pt: 0 }}>
<img
srcSet={description.image}
src={description.image}
alt={description.title}
loading="lazy"
style={{ width: '100%', objectFit: 'contain' }}
/>
{description.body &&
description.body.map((paragraph, idx) => (
<Typography key={idx} pb={1}>
{paragraph}
</Typography>
))}
{description.captions && (
<>
<Divider sx={{ my: 2 }} />
<Stack color="text.secondary" mt={1} gap={1}>
{description.captions.map((caption, idx) => (
<Typography key={idx} variant="caption">
{caption}
</Typography>
))}
</Stack>
</>
)}
</DialogContent>
</Dialog>
)
export default EmployeeDescriptionDialog
+26 -8
View File
@@ -1,16 +1,28 @@
import { Card, CardContent, CardMedia, CardProps, Typography } from '@mui/material'
import { FC } from 'react'
import { FC, useMemo } from 'react'
import { Card, CardActionArea, CardContent, CardMedia, CardProps, Typography } from '@mui/material'
export interface EmplyeeCardProps extends CardProps {
import { EmployeeDescription } from 'components/molecules/EmployeeDescription/index.js'
export type EmployeeCardProps = Omit<CardProps, 'onClick'> & {
name: string
image: string
position?: string
description?: Partial<EmployeeDescription>
onClick?: (event: React.MouseEvent<HTMLDivElement, MouseEvent>, description: EmployeeDescription) => void
}
export const EmplyeeCard: FC<EmplyeeCardProps> = ({ name, position, image, sx, ...props }) => {
return (
<Card sx={{ flexShrink: 0, width: 270, height: 360, ...sx }} {...props}>
<CardMedia component="img" sx={{ minHeight: '140' }} image={image} alt={`${name} image`} />
export const EmplyeeCard: FC<EmployeeCardProps> = ({ name, position, image, description = {}, sx, onClick, ...props }) => {
const handleClick = (event: React.MouseEvent<HTMLDivElement, MouseEvent>) => {
onClick(event, {
title: name,
image: image,
position: position,
...description,
})
}
const content = useMemo(
() => [
<CardMedia component="img" sx={{ minHeight: '140' }} image={image} alt={`${name} image`} />,
<CardContent>
<Typography gutterBottom variant="h5" component="div">
{name}
@@ -20,7 +32,13 @@ export const EmplyeeCard: FC<EmplyeeCardProps> = ({ name, position, image, sx, .
{position}
</Typography>
)}
</CardContent>
</CardContent>,
],
[name, position, image]
)
return (
<Card sx={{ flexShrink: 0, width: 270, height: 360, ...sx }} {...props} onClick={handleClick}>
<CardActionArea>{content}</CardActionArea>
</Card>
)
}
@@ -1,10 +1,13 @@
import { FC, useEffect, useState } from 'react'
import { Box, IconButton } from '@mui/material'
import { EmplyeeCard, EmplyeeCardProps } from 'components/molecules/EmplyeeCard/index.js'
import ShuffleIcon from '@mui/icons-material/NextPlan'
import { EmplyeeCard, EmployeeCardProps } from 'components/molecules/EmplyeeCard/index.js'
import { EmployeeDescription } from 'components/molecules/EmployeeDescription/index.js'
interface Props {
items: EmplyeeCardProps[]
items: EmployeeCardProps[]
onClick?: (event: React.MouseEvent<HTMLDivElement, MouseEvent>, description: EmployeeDescription) => void
// Visual offsets
offsetStep?: number // px shift per card
@@ -15,6 +18,7 @@ const EmplyeeStack: FC<Props> = ({
offsetStep = 10, // px shift per card
maxVisible = 6, // how many cards we actually render (performance)
items: initialItems,
onClick,
}) => {
const [items, setItems] = useState(initialItems)
@@ -29,7 +33,7 @@ const EmplyeeStack: FC<Props> = ({
}
if (items.length === 0) return null
if (items.length === 1) return <EmplyeeCard {...items[0]} />
if (items.length === 1) return <EmplyeeCard {...items[0]} onClick={onClick} />
return (
<Box
@@ -41,6 +45,7 @@ const EmplyeeStack: FC<Props> = ({
{items.slice(0, maxVisible).map((props, idx) => (
<EmplyeeCard
{...props}
onClick={onClick}
key={idx}
sx={{
position: 'absolute',