Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature: Improve projects tags #85

Merged
merged 7 commits into from
Apr 5, 2024
8 changes: 8 additions & 0 deletions src/common/types/models/Projects.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,14 @@ export type User = {
role: string;
};

export enum ProjectRoles {
frontEnd = 'front-end',
backEnd = 'back-end',
fullStack = 'full-stack',
designer = 'designer',
any = 'any'
}

export type RequiredRoles = {
'front-end': number;
'back-end': number;
Expand Down
13 changes: 11 additions & 2 deletions src/components/Buttons/ToggleButtonGroup/ToggleButtonGroup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,23 @@ interface ToggleButtonGroupProps {
* Sets the state of the button
*/
setIsActive: Dispatch<boolean>;

/**
* Sets the state of the Filter
*/
setFilter: Dispatch<string[]>;
}

export const ToggleButtonGroup = ({ className, isActive, setIsActive }: ToggleButtonGroupProps) => {
export const ToggleButtonGroup = ({ className, isActive, setIsActive, setFilter }: ToggleButtonGroupProps) => {
const classes = {
container: cn('max-sm:grid max-sm:gap-4 ', className)
};

const handleActive = (isActive) => setIsActive(isActive);
const handleActive = (isActive: boolean) => {
setFilter([]);
setIsActive(isActive);
};

const handleButtonVariant = (isActive: boolean): VARIANT => (isActive ? VARIANT.PRIMARY : VARIANT.SECONDARY);

return (
Expand Down
48 changes: 33 additions & 15 deletions src/components/Cards/ProjectCard/ProjectCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,31 @@ interface ProjectCardProps extends Omit<Project, 'id' | 'createdAt' | 'repositor
* Specify an optional className to be added to the component
*/
className?: string;

/**
* Specify if the project is active
*/
isActive: boolean;
}

export const ProjectCard = ({ className, name, description, administrator, members, requiredRoles, ...restOfProps }: ProjectCardProps) => {
export const ProjectCard = ({
isActive,
className,
name,
description,
administrator,
members,
requiredRoles,
...restOfProps
}: ProjectCardProps) => {
const classes = {
container: cn('grid gap-8 max-w-md w-full max-xl:mx-auto', className),
subTitle: cn('text-4 font-bold'),
list: cn('flex flex-wrap gap-4 mt-4 text-3.5')
list: cn('flex flex-wrap gap-x-4 gap-y-2 mt-2 text-3.5')
};

const handleDescription = () => {
if (description.length > 175) return `${description.slice(0, 130)}...`;
if (description.length > 210) return `${description.slice(0, 210)}...`;
return description;
};
const renderParticipantsTag = () => {
Expand Down Expand Up @@ -46,11 +61,11 @@ export const ProjectCard = ({ className, name, description, administrator, membe
{/* Header */}
<header className="grid gap-4">
<h3 className="font-bold text-8">{name}</h3>
<p className="text-4 h-24 text-balance truncate">{handleDescription()}</p>
<p className="text-4 h-24 text-balance ">{handleDescription()}</p>
</header>

{/* Participants Section */}
<section aria-labelledby="participants-title">
<section aria-labelledby="participants-title" className="py-4">
<h4 id="participants-title" className={classes.subTitle}>
Participantes
</h4>
Expand All @@ -62,17 +77,20 @@ export const ProjectCard = ({ className, name, description, administrator, membe
</ul>
</section>

{/* Roles Section */}
<section aria-labelledby="roles-title">
<h4 id="roles-title" className={classes.subTitle}>
Estamos buscando
</h4>
<ul className={classes.list}>{renderRequiredRolesTag()}</ul>
</section>
{isActive && (
<>
<section aria-labelledby="roles-title">
<h4 id="roles-title" className={classes.subTitle}>
Estamos buscando
</h4>
<ul className={classes.list}>{renderRequiredRolesTag()}</ul>
</section>

<footer className="mx-auto">
<Button>Contactar</Button>
</footer>
<footer className="mx-auto">
<Button>Contactar</Button>
</footer>
</>
)}
</CardWrapper>
);
};
81 changes: 50 additions & 31 deletions src/pages/Projects/Projects.tsx
Original file line number Diff line number Diff line change
@@ -1,26 +1,26 @@
import { useState } from 'react';
import { Project, ProjectStatus, TagVariant, useProjects, VARIANT } from '@common';
import { Button, ProjectCard, Tag, ToggleButtonGroup } from '@components';
import { ButtonSize, cn, Project, ProjectRoles, ProjectStatus, useProjects, VARIANT } from '@common';
import { Button, ProjectCard, ToggleButtonGroup } from '@components';
import { RootLayout } from '@layouts';
import { filterBy } from './utils/filterBy';

export const Projects = () => {
// TODO: Implement a Loading State
const { projects } = useProjects();
const [filter, setFilter] = useState<string[]>([]);
const [isActive, setIsActive] = useState(true);
const activeProjects = projects?.filter((project) => project.status === ProjectStatus.pending);
const closedProjects = projects?.filter((project) => project.status === ProjectStatus.closed);

console.log(activeProjects);

const [filter, setFilter] = useState<string[]>([]);
const classes = {
tag: (role: ProjectRoles) =>
cn('ring-1 ring-neutral-800 px-2.5', {
'bg-gradient-to-rb from-primary-600 to-secondary-500 ': filter.includes(role)
})
};

// TODO: Unificar filters

const filterBy = (projects: Project[], roles: string[]) => {
if (roles?.length === 0) return projects;
return projects.filter((project) => roles?.some((role) => project.requiredRoles[role] > 0));
};

const renderProjects = (projects: Project[] | undefined | null) =>
filterBy(projects ?? [], filter).map((project, index) => {
const animateDelay = index * 0.05;
Expand All @@ -35,12 +35,19 @@ export const Projects = () => {
status={project.status}
className={`animate-fade-up-custom`}
style={{ '--animate-delay': `${animateDelay}s` } as any}
isActive={isActive}
/>
);
});

const updateFilter = (updatedRole: string) => {
setFilter((prev) => (prev.includes(updatedRole) ? prev.filter((role) => role !== updatedRole) : [...prev, updatedRole]));
const resetFilter = () => setFilter([]);
const toggleFilterRole = (roleToToggle: string) => {
setFilter((currentFilters) => {
const isRoleActive = currentFilters.includes(roleToToggle);
if (isRoleActive) {
return currentFilters.filter((role) => role !== roleToToggle);
} else return [...currentFilters, roleToToggle];
});
};

return (
Expand All @@ -50,58 +57,70 @@ export const Projects = () => {
{/* TODO: Change to FIGMA element */}

<div className="grid gap-8 text-white">
<ToggleButtonGroup isActive={isActive} setIsActive={setIsActive} className="mx-auto" />
<ToggleButtonGroup isActive={isActive} setIsActive={setIsActive} setFilter={setFilter} className="mx-auto" />
<p className="mt-4 text-fluid-sm text-center">
Lorem ipsum dolor sit amet consectetur. Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet consectetur. Lorem ipsum dolor
sit amet consectetur. Dolor sit amet.
</p>
</div>

{/* TODO: Implement the filter */}
<section className="flex items-center justify-center gap-x-4 mt-12 flex-wrap">
<span className="i-lucide-filter"></span>
<section className="flex items-center justify-center gap-4 mt-12 flex-wrap">
<Button
className="px-3 ring-1 ring-neutral-800 h-8"
variant={VARIANT.GHOST}
onClick={resetFilter}
disabled={isActive === false}
>
<span className="i-lucide-filter block "></span>
</Button>
<Button
className={classes.tag(ProjectRoles.frontEnd)}
variant={VARIANT.GHOST}
size={ButtonSize.sm}
disabled={isActive === false}
onClick={() => {
updateFilter('front-end');
toggleFilterRole(ProjectRoles.frontEnd);
}}
//TODO: Update strings with constants
className={`p-0 ${filter.includes('front-end') ? 'p-2 bg-red' : ''} `}
>
{/* // TODO: Add hover to tag */}
<Tag variant={TagVariant.neutral}>Front-end</Tag>
Front-end
</Button>
<Button
className={classes.tag(ProjectRoles.backEnd)}
variant={VARIANT.GHOST}
size={ButtonSize.sm}
disabled={isActive === false}
onClick={() => {
updateFilter('back-end');
toggleFilterRole(ProjectRoles.backEnd);
}}
className={`p-0 ${filter.includes('back-end') ? 'p-2 bg-red' : ''} `}
>
<Tag variant={TagVariant.neutral}>Back-end</Tag>
Back-end
</Button>
<Button
className={classes.tag(ProjectRoles.fullStack)}
variant={VARIANT.GHOST}
size={ButtonSize.sm}
disabled={isActive === false}
onClick={() => {
updateFilter('full-stack');
toggleFilterRole(ProjectRoles.fullStack);
}}
className={`p-0 ${filter.includes('full-stack') ? 'p-2 bg-red' : ''} `}
>
<Tag variant={TagVariant.neutral}>Full-Stack</Tag>
Full-Stack
</Button>
<Button
className={classes.tag(ProjectRoles.designer)}
variant={VARIANT.GHOST}
size={ButtonSize.sm}
disabled={isActive === false}
onClick={() => {
updateFilter('designer');
toggleFilterRole(ProjectRoles.designer);
}}
className={`p-0 ${filter.includes('designer') ? 'p-2 bg-red' : ''} `}
>
<Tag variant={TagVariant.neutral}>Diseñador(a)</Tag>
Diseñador(a)
</Button>
</section>

{/* TODO: add the grid-cols arbitrary to UNOCSS config */}
<section className="grid sm:grid-cols-[repeat(auto-fit,_minmax(390px,1fr))] gap-6 pt-12">
<section className="grid sm:grid-cols-[repeat(auto-fit,_minmax(390px,1fr))] gap-6 pt-12 justify-items-center">
{isActive ? renderProjects(activeProjects) : renderProjects(closedProjects)}
</section>
</div>
Expand Down
12 changes: 12 additions & 0 deletions src/pages/Projects/utils/filterBy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { Project } from '@common';

/**
* Filters project by given roles.
* @param {Array} projects - The projects to filter.
* @param {Array} roles - The role to filter bby.
* @param {array} - The filtered projects
*/
export const filterBy = (projects: Project[], roles: string[]) => {
if (roles.length === 0) return projects;
return projects.filter((project) => roles?.some((role) => project.requiredRoles[role] > 0));
};
Loading