Files
planka/client/src/components/projects/ProjectCard/ProjectCard.jsx
Maksim Eltyshev 2ee1166747 feat: Version 2
Closes #627, closes #1047
2025-05-10 02:09:06 +02:00

189 lines
5.9 KiB
JavaScript

/*!
* Copyright (c) 2024 PLANKA Software GmbH
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
*/
import upperFirst from 'lodash/upperFirst';
import camelCase from 'lodash/camelCase';
import React, { useCallback, useMemo } from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames';
import { useDispatch, useSelector } from 'react-redux';
import { Link } from 'react-router-dom';
import { Button, Icon } from 'semantic-ui-react';
import selectors from '../../../selectors';
import entryActions from '../../../entry-actions';
import Paths from '../../../constants/Paths';
import { ProjectBackgroundTypes } from '../../../constants/Enums';
import UserAvatar from '../../users/UserAvatar';
import styles from './ProjectCard.module.scss';
import globalStyles from '../../../styles.module.scss';
const Sizes = {
SMALL: 'small',
LARGE: 'large',
};
const ProjectCard = React.memo(
({ id, size, isActive, withDescription, withTypeIndicator, withFavoriteButton, className }) => {
const selectProjectById = useMemo(() => selectors.makeSelectProjectById(), []);
const selectFirstBoardIdByProjectId = useMemo(
() => selectors.makeSelectFirstBoardIdByProjectId(),
[],
);
const selectNotificationsTotalByProjectId = useMemo(
() => selectors.makeSelectNotificationsTotalByProjectId(),
[],
);
const selectProjectManagerById = useMemo(() => selectors.makeSelectProjectManagerById(), []);
const selectBackgroundImageById = useMemo(() => selectors.makeSelectBackgroundImageById(), []);
const project = useSelector((state) => selectProjectById(state, id));
const firstBoardId = useSelector((state) => selectFirstBoardIdByProjectId(state, id));
const notificationsTotal = useSelector((state) =>
selectNotificationsTotalByProjectId(state, id),
);
const ownerProjectManager = useSelector(
(state) =>
project.ownerProjectManagerId &&
selectProjectManagerById(state, project.ownerProjectManagerId),
);
const backgroundImageUrl = useSelector((state) => {
if (!project.backgroundType || project.backgroundType !== ProjectBackgroundTypes.IMAGE) {
return null;
}
const backgroundImage = selectBackgroundImageById(state, project.backgroundImageId);
if (!backgroundImage) {
return null;
}
return backgroundImage.thumbnailUrls.outside360;
});
const dispatch = useDispatch();
const handleToggleFavoriteClick = useCallback(() => {
dispatch(
entryActions.updateProject(project.id, {
isFavorite: !project.isFavorite,
}),
);
}, [project, dispatch]);
const withSidebar = withTypeIndicator || (withFavoriteButton && !project.isHidden);
return (
<div
className={classNames(
className,
styles.wrapper,
styles[`wrapper${upperFirst(size)}`],
project.isHidden && styles.wrapperHidden,
)}
>
<Link
to={
firstBoardId
? Paths.BOARDS.replace(':id', firstBoardId)
: Paths.PROJECTS.replace(':id', id)
}
className={styles.content}
>
<div
className={classNames(
styles.cover,
project.backgroundType === ProjectBackgroundTypes.GRADIENT &&
globalStyles[`background${upperFirst(camelCase(project.backgroundGradient))}`],
)}
style={{
background: backgroundImageUrl && `url("${backgroundImageUrl}") center / cover`,
}}
/>
{notificationsTotal > 0 && (
<span className={styles.notifications}>{notificationsTotal}</span>
)}
<div
className={classNames(styles.information, withSidebar && styles.informationWithSidebar)}
>
<div
className={classNames(
styles.title,
isActive !== undefined && styles.titleActivatable,
isActive && styles.titleActive,
)}
>
{project.name}
</div>
{withDescription && project.description && (
<div className={styles.description}>{project.description}</div>
)}
</div>
{withTypeIndicator && (
<div
className={classNames(
styles.typeIndicator,
ownerProjectManager && styles.typeIndicatorWithUser,
)}
>
{ownerProjectManager ? (
<UserAvatar id={ownerProjectManager.userId} size="small" />
) : (
<Icon
fitted
name="group"
className={classNames(styles.icon, styles.typeIndicatorIcon)}
/>
)}
</div>
)}
</Link>
{withFavoriteButton && !project.isHidden && (
<Button
className={classNames(
styles.favoriteButton,
!project.isFavorite && styles.favoriteButtonAppearable,
)}
onClick={handleToggleFavoriteClick}
>
<Icon
fitted
name={project.isFavorite ? 'star' : 'star outline'}
className={classNames(styles.icon, styles.favoriteButtonIcon)}
/>
</Button>
)}
</div>
);
},
);
ProjectCard.propTypes = {
id: PropTypes.string.isRequired,
size: PropTypes.oneOf(Object.values(Sizes)),
isActive: PropTypes.bool,
withDescription: PropTypes.bool,
withTypeIndicator: PropTypes.bool,
withFavoriteButton: PropTypes.bool,
className: PropTypes.string.isRequired,
};
ProjectCard.defaultProps = {
size: Sizes.LARGE,
isActive: undefined,
withDescription: false,
withTypeIndicator: false,
withFavoriteButton: false,
};
export default ProjectCard;