diff --git a/README.md b/README.md index bb6a8c8..345a84e 100644 --- a/README.md +++ b/README.md @@ -83,3 +83,12 @@ This assignment is designed to assess your practical skills in **React, Next.js, - Document any **extra improvements** you make in your submission. Good luck 🚀 + + + + + +During the assignment, I worked on each task by thoroughly testing the implemented features after completion. For each task, I committed the changes to separate branches and pushed them to the remote repository to maintain an independent snapshot of the code at every stage. This organized version control approach ensured both the quality of the project and the traceability of changes throughout development. + +## Branches +- assignment/krishna-sharma: Complete assignment with all implemented features. diff --git a/frontend_dev_assignment b/frontend_dev_assignment new file mode 160000 index 0000000..e8d2c04 --- /dev/null +++ b/frontend_dev_assignment @@ -0,0 +1 @@ +Subproject commit e8d2c0494c22c0411550629ef13b386745562a39 diff --git a/src/app/page.tsx b/src/app/page.tsx index 23eaf49..af931c7 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -1,57 +1,168 @@ -'use client' -import { WorkerType } from '@/types/workers' -import Image from 'next/image' -import { useState, useEffect } from 'react' +'use client'; +import Navbar from '../components/Navbar'; +import WorkerCard from '../components/WorkerCard'; +import SkeletonCard from '../components/SkeletonCard'; +import { WorkerType } from '@/types/workers'; +import { useState, useEffect } from 'react'; export default function WorkersPage() { - const [workersData, setWorkersData] = useState([]) + const [workersData, setWorkersData] = useState([]); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(''); + const [currentPage, setCurrentPage] = useState(1); + const [minPrice, setMinPrice] = useState(''); + const [maxPrice, setMaxPrice] = useState(''); + const [selectedService, setSelectedService] = useState(''); + const itemsPerPage = 9; useEffect(() => { - const loadData = async () => { + const fetchWorkers = async () => { + setLoading(true); + setError(''); try { - const response = await import('../../workers.json') - setWorkersData(response.default) - } catch (error) { - console.error('Failed to load workers:', error) + const response = await fetch('/api/workers'); + if (!response.ok) throw new Error('Failed to fetch workers'); + const json = await response.json(); + setWorkersData(json.data || []); // Use the 'data' property from API response + } catch (err: any) { + setError(err.message || 'Unknown error'); } - } - loadData() - loadData() - }, []) + setLoading(false); + }; + fetchWorkers(); + }, []); + + useEffect(() => { + setCurrentPage(1); + }, [minPrice, maxPrice, selectedService]); + + const filteredWorkers = Array.isArray(workersData) + ? workersData + .filter((worker) => worker.pricePerDay > 0) + .filter((worker) => worker.id !== null) + .filter((worker) => (minPrice === '' ? true : worker.pricePerDay >= minPrice)) + .filter((worker) => (maxPrice === '' ? true : worker.pricePerDay <= maxPrice)) + .filter((worker) => (selectedService === '' ? true : worker.service === selectedService)) + .sort((a, b) => a.name.localeCompare(b.name)) + : []; + + const totalPages = Math.ceil(filteredWorkers.length / itemsPerPage); + const indexOfLastItem = currentPage * itemsPerPage; + const indexOfFirstItem = indexOfLastItem - itemsPerPage; + const currentWorkers = filteredWorkers.slice(indexOfFirstItem, indexOfLastItem); + + const goToPage = (pageNumber: number) => { + if (pageNumber < 1 || pageNumber > totalPages) return; + setCurrentPage(pageNumber); + }; + + const goNext = () => { + if (currentPage < totalPages) setCurrentPage(currentPage + 1); + }; + + const goPrev = () => { + if (currentPage > 1) setCurrentPage(currentPage - 1); + }; + + if (loading) { + return ( + <> + +
+

Loading workers...

+
+ {Array.from({ length: itemsPerPage }, (_, idx) => ( + + ))} +
+
+ + ); + } + + if (error) { + return ( + <> + +
+

Error: {error}

+
+ + ); + } return ( -
-

Our Workers

- -
- {workersData - .filter((worker) => worker.pricePerDay > 0) - .filter((worker) => worker.id !== null) - .sort((a, b) => a.name.localeCompare(b.name)) - .map((worker: WorkerType) => ( -
+ +
+

Our Workers

+ + {/* Filters Section */} +
+ setMinPrice(e.target.value === '' ? '' : Number(e.target.value))} + className="border p-2 rounded w-40" + /> + setMaxPrice(e.target.value === '' ? '' : Number(e.target.value))} + className="border p-2 rounded w-40" + /> + +
+ + {/* Workers Grid */} +
+ {currentWorkers.map((worker: WorkerType) => ( + + ))} +
+ + {/* Pagination Controls */} +
+ + {Array.from({ length: totalPages }, (_, i) => ( +
+ {i + 1} + ))} -
-
- ) + + + + + ); } diff --git a/src/components/Navbar.tsx b/src/components/Navbar.tsx new file mode 100644 index 0000000..dca88d8 --- /dev/null +++ b/src/components/Navbar.tsx @@ -0,0 +1,44 @@ +import { useState } from 'react'; + +const Navbar = () => { + const [isOpen, setIsOpen] = useState(false); + + return ( + + ); +}; + +export default Navbar; diff --git a/src/components/SkeletonCard.tsx b/src/components/SkeletonCard.tsx new file mode 100644 index 0000000..a03c927 --- /dev/null +++ b/src/components/SkeletonCard.tsx @@ -0,0 +1,14 @@ +const SkeletonCard = () => { + return ( +
+
+
+
+
+
+
+
+ ); +}; + +export default SkeletonCard; diff --git a/src/components/WorkerCard.tsx b/src/components/WorkerCard.tsx new file mode 100644 index 0000000..bf5014d --- /dev/null +++ b/src/components/WorkerCard.tsx @@ -0,0 +1,24 @@ +'use client'; +import Image from 'next/image'; +import { WorkerType } from '@/types/workers'; + +export default function WorkerCard({ worker }: { worker: WorkerType }) { + return ( +
+
+ {worker.name} +
+

{worker.name}

+

{worker.service}

+

₹{worker.pricePerDay} / day

+
+ ); +}