homePage projects component updated for Clean Code
This commit is contained in:
parent
6fe97203ab
commit
2e03cfcbc8
@ -1,24 +1,10 @@
|
||||
"use client";
|
||||
|
||||
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { Project, ProjectCategory } from "@/app/types/types";
|
||||
import React, { useEffect, useState, useMemo } from "react";
|
||||
import Skeleton from "react-loading-skeleton";
|
||||
import "react-loading-skeleton/dist/skeleton.css";
|
||||
|
||||
type Category = {
|
||||
label: string;
|
||||
active: boolean;
|
||||
};
|
||||
|
||||
type Project = {
|
||||
id: number;
|
||||
title: string;
|
||||
category: string;
|
||||
images: { src: string; position: string; zIndex: number }[];
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
};
|
||||
|
||||
interface MyProjectsProps {
|
||||
dict: {
|
||||
homePage: {
|
||||
@ -30,8 +16,11 @@ interface MyProjectsProps {
|
||||
};
|
||||
}
|
||||
|
||||
export default function MyProjects({ dict }: MyProjectsProps) {
|
||||
const [categories, setCategories] = useState<Category[]>([]);
|
||||
const MAX_PROJECTS = 6;
|
||||
const MAX_CATEGORIES = 5;
|
||||
|
||||
export default function Projects({ dict }: MyProjectsProps) {
|
||||
const [categories, setCategories] = useState<ProjectCategory[]>([]);
|
||||
const [projects, setProjects] = useState<Project[]>([]);
|
||||
const [selectedCategory, setSelectedCategory] = useState<string>("Newest");
|
||||
|
||||
@ -39,8 +28,8 @@ export default function MyProjects({ dict }: MyProjectsProps) {
|
||||
const fetchProjects = async () => {
|
||||
const res = await fetch("/api/website/projects?sort=desc&per_page=20");
|
||||
const data = await res.json();
|
||||
const resProjects = data.projects.data;
|
||||
const resCategories = data.categories;
|
||||
const resProjects = data.projects.data || []; // Use default value if undefined
|
||||
const resCategories = data.categories || []; // Use default value if undefined
|
||||
|
||||
// Convert JSON string to array
|
||||
const updatedData = resProjects.map((project: { images: string }) => ({
|
||||
@ -51,14 +40,14 @@ export default function MyProjects({ dict }: MyProjectsProps) {
|
||||
setProjects(updatedData);
|
||||
|
||||
// Extract unique categories
|
||||
const uniqueCategories: Category[] = [{ label: "Newest", active: false }];
|
||||
const onlyFiveCategoris: Category[] = [
|
||||
...resCategories.map((category: string) => ({
|
||||
const uniqueCategories: ProjectCategory[] = [
|
||||
{ label: "Newest", active: false },
|
||||
...resCategories.slice(0, MAX_CATEGORIES).map((category: string) => ({
|
||||
label: category,
|
||||
active: false,
|
||||
})),
|
||||
].slice(0, 5);
|
||||
uniqueCategories.push(...onlyFiveCategoris);
|
||||
];
|
||||
|
||||
setCategories(uniqueCategories);
|
||||
};
|
||||
|
||||
@ -68,7 +57,7 @@ export default function MyProjects({ dict }: MyProjectsProps) {
|
||||
// Group projects by category
|
||||
const groupProjectsByCategory = (projects: Project[]) => {
|
||||
return projects.reduce<Record<string, Project[]>>((acc, project) => {
|
||||
const category = project.category;
|
||||
const category = project.category || "Uncategorized"; // Default category
|
||||
if (!acc[category]) {
|
||||
acc[category] = [];
|
||||
}
|
||||
@ -77,95 +66,68 @@ export default function MyProjects({ dict }: MyProjectsProps) {
|
||||
}, {});
|
||||
};
|
||||
|
||||
const groupedProjects = groupProjectsByCategory(projects);
|
||||
const groupedProjects = useMemo(() => groupProjectsByCategory(projects), [projects]);
|
||||
|
||||
// Filter projects by selected category
|
||||
const filteredProjects: Project[] =
|
||||
selectedCategory === "Newest"
|
||||
? projects.slice(0, 6) // Get first 6 projects for "Newest"
|
||||
: groupedProjects[selectedCategory]?.slice(0, 6) || []; // Get first 6 projects from selected category
|
||||
const filteredProjects: Project[] = useMemo(() => {
|
||||
return selectedCategory === "Newest"
|
||||
? projects.slice(0, MAX_PROJECTS)
|
||||
: groupedProjects[selectedCategory]?.slice(0, MAX_PROJECTS) || [];
|
||||
}, [selectedCategory, projects, groupedProjects]);
|
||||
|
||||
const renderSkeletons = (count: number) => (
|
||||
<div className="flex w-full flex-wrap justify-center gap-5">
|
||||
{Array.from({ length: count }, (_, index) => (
|
||||
<div key={index} className="w-[45%] md:w-[30%]">
|
||||
<Skeleton containerClassName={"w-full"} height={150} />
|
||||
<Skeleton containerClassName={"w-full"} height={40} />
|
||||
<Skeleton containerClassName={"w-full"} height={60} />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
|
||||
return (
|
||||
<div
|
||||
className="flex w-full max-w-[90%] flex-wrap items-center justify-center pt-[50px]"
|
||||
id="projects"
|
||||
>
|
||||
<h2 className="my-10 text-[55px] font-[800] text-black capitalize">{dict.homePage.projects.title}</h2>
|
||||
<p className="text-center text-[21px] font-[400] text-black">
|
||||
{dict.homePage.projects.description}
|
||||
</p>
|
||||
<div className="flex w-full max-w-[90%] flex-wrap items-center justify-center pt-[50px]" id="projects">
|
||||
<h2 className="my-10 text-[55px] font-[800] capitalize text-black">{dict.homePage.projects.title}</h2>
|
||||
<p className="text-center text-[21px] font-[400] text-black">{dict.homePage.projects.description}</p>
|
||||
<ul className="mb-10 mt-20 flex w-full flex-wrap items-center justify-center gap-2">
|
||||
{categories.length > 0 ? (
|
||||
categories.map((category, index) => (
|
||||
<li
|
||||
key={index}
|
||||
onClick={() => setSelectedCategory(category.label)}
|
||||
className={`select-none whitespace-nowrap rounded-md border border-[#545454] bg-[#F8F8F8] p-2 text-[14px] md:text-[18px] font-[600] hover:cursor-pointer hover:border-[#FD6F00] capitalize ${
|
||||
selectedCategory === category.label
|
||||
? "bg-[#FD6F00] text-[#FFFFFF]"
|
||||
: "text-[#000]"
|
||||
className={`select-none whitespace-nowrap rounded-md border border-[#545454] bg-[#F8F8F8] p-2 text-[14px] font-[600] capitalize hover:cursor-pointer hover:border-[#FD6F00] md:text-[18px] ${
|
||||
selectedCategory === category.label ? "bg-[#FD6F00] text-[#FFFFFF]" : "text-[#000]"
|
||||
}`}
|
||||
>
|
||||
{category.label}
|
||||
</li>
|
||||
))
|
||||
) : (
|
||||
<div className="flex w-full flex-wrap gap-5">
|
||||
<Skeleton containerClassName={"w-[45%] md:w-[21%]"} height={60} />
|
||||
<Skeleton containerClassName={"w-[45%] md:w-[21%]"} height={60} />
|
||||
<Skeleton containerClassName={"w-[45%] md:w-[21%]"} height={60} />
|
||||
<Skeleton containerClassName={"w-[45%] md:w-[21%]"} height={60} />
|
||||
</div>
|
||||
renderSkeletons(4) // Adjust the count as needed
|
||||
)}
|
||||
</ul>
|
||||
<div className="flex w-full flex-wrap items-start justify-center gap-8">
|
||||
{filteredProjects.length > 0 ? (
|
||||
filteredProjects.map((project, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className="project flex w-[45%] flex-wrap md:w-[30%]"
|
||||
>
|
||||
<div key={index} className="project flex w-[45%] flex-wrap md:w-[30%]">
|
||||
<div className="relative my-5 h-[200px] w-full overflow-hidden bg-[#FFEBDB] md:h-[250px] lg:h-[300px]">
|
||||
{project.images.map((image, idx) => (
|
||||
<img
|
||||
key={idx}
|
||||
src={image.src}
|
||||
alt="UI/UX Vector"
|
||||
className={`absolute ${idx == 0 ? "bottom-0 start-0" : "end-0 top-0"} z-${image.zIndex} project-image w-[85%] max-w-[350px]`}
|
||||
className={`absolute ${idx === 0 ? "bottom-0 start-0" : "end-0 top-0"} z-${image.zIndex} project-image w-[85%] max-w-[350px]`}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
<h5 className="w-full text-[15px] font-[600] text-[#FD6F00] lg:text-[20px]">
|
||||
{project.category}
|
||||
</h5>
|
||||
<p className="text-[18px] font-[800] text-black hover:cursor-pointer lg:text-[22px]">
|
||||
{project.title}
|
||||
</p>
|
||||
<h5 className="w-full text-[15px] font-[600] text-[#FD6F00] lg:text-[20px]">{project.category}</h5>
|
||||
<p className="text-[18px] font-[800] text-black hover:cursor-pointer lg:text-[22px]">{project.title}</p>
|
||||
</div>
|
||||
))
|
||||
) : (
|
||||
<div className="flex w-full flex-wrap justify-center gap-5">
|
||||
<div className="w-[45%] md:w-[30%]">
|
||||
<Skeleton containerClassName={"w-full"} height={150} />
|
||||
<Skeleton containerClassName={"w-full"} height={40} />
|
||||
<Skeleton containerClassName={"w-full"} height={60} />
|
||||
</div>
|
||||
<div className="w-[45%] md:w-[30%]">
|
||||
<Skeleton containerClassName={"w-full"} height={150} />
|
||||
<Skeleton containerClassName={"w-full"} height={40} />
|
||||
<Skeleton containerClassName={"w-full"} height={60} />
|
||||
</div>
|
||||
<div className="w-[45%] md:w-[30%]">
|
||||
<Skeleton containerClassName={"w-full"} height={150} />
|
||||
<Skeleton containerClassName={"w-full"} height={40} />
|
||||
<Skeleton containerClassName={"w-full"} height={60} />
|
||||
</div>
|
||||
<div className="w-[45%] md:w-[30%]">
|
||||
<Skeleton containerClassName={"w-full"} height={150} />
|
||||
<Skeleton containerClassName={"w-full"} height={40} />
|
||||
<Skeleton containerClassName={"w-full"} height={60} />
|
||||
</div>
|
||||
</div>
|
||||
renderSkeletons(3) // Adjust the count as needed
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -200,3 +200,17 @@ export type Service = {
|
||||
imageSrc: string;
|
||||
altText: string;
|
||||
};
|
||||
|
||||
export type ProjectCategory = {
|
||||
label: string;
|
||||
active: boolean;
|
||||
};
|
||||
|
||||
export type Project = {
|
||||
id: number;
|
||||
title: string;
|
||||
category: string;
|
||||
images: { src: string; position: string; zIndex: number }[];
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
};
|
||||
|
||||
Loading…
Reference in New Issue
Block a user