From c05aa56db9fa3663b9bcdc82753563f0f06029bd Mon Sep 17 00:00:00 2001 From: Krishna292002 Date: Wed, 24 Sep 2025 23:58:07 +0530 Subject: [PATCH 01/11] i have --- src/app/api/workers/route.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/api/workers/route.ts b/src/app/api/workers/route.ts index 44a245e..414d0ce 100644 --- a/src/app/api/workers/route.ts +++ b/src/app/api/workers/route.ts @@ -5,7 +5,7 @@ export async function GET() { try { return NextResponse.json({ success: true, - data: workersData + data: workersDat }) } catch (error) { console.error('API Error:', error) From 3aea0129175e2e5a9b2a2ec815b49c04f645efb3 Mon Sep 17 00:00:00 2001 From: Krishna292002 Date: Thu, 25 Sep 2025 12:30:56 +0530 Subject: [PATCH 02/11] fix: improve card layout and responsiveness for Task 1 --- src/app/api/workers/route.ts | 3 +++ src/app/page.tsx | 32 ++++++++++++++++++++++---------- 2 files changed, 25 insertions(+), 10 deletions(-) diff --git a/src/app/api/workers/route.ts b/src/app/api/workers/route.ts index 414d0ce..211617d 100644 --- a/src/app/api/workers/route.ts +++ b/src/app/api/workers/route.ts @@ -16,3 +16,6 @@ export async function GET() { } } +git config --global user.email +"krishna.ks292002@gmail.com" + git config --global user.name "Krishna292002" \ No newline at end of file diff --git a/src/app/page.tsx b/src/app/page.tsx index 23eaf49..c71ae0a 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -9,6 +9,7 @@ export default function WorkersPage() { useEffect(() => { const loadData = async () => { try { + // Changed: Removed duplicate loadData call to prevent loading data twice const response = await import('../../workers.json') setWorkersData(response.default) } catch (error) { @@ -16,23 +17,32 @@ export default function WorkersPage() { } } loadData() - loadData() }, []) return (
-

Our Workers

+ {/* Changed: Added text-white for better contrast on dark background */} +

Our Workers

-
+ {/* Changed: Updated grid classes for better responsiveness + - 1 col on mobile + - 2 cols on small screens + - 3 cols on medium (tablet) + - 4 cols on large (desktop) + */} +
{workersData - .filter((worker) => worker.pricePerDay > 0) - .filter((worker) => worker.id !== null) + .filter(worker => worker.pricePerDay > 0) + .filter(worker => worker.id !== null) .sort((a, b) => a.name.localeCompare(b.name)) .map((worker: WorkerType) => (
+ {/* Changed: Added fixed height and relative positioning for uniform image sizing */}
-
-

{worker.name}

-

{worker.service}

-

+ {/* Changed: Added flex-grow on text container and margin spacing for better vertical alignment */} +

+

{worker.name}

+

{worker.service}

+ {/* Changed: Use mt-auto to push price to bottom of card for balanced layout */} +

₹{Math.round(worker.pricePerDay * 1.18)} / day

From 1031bb6066200515dae2e195b4ca0d1185aa7c81 Mon Sep 17 00:00:00 2001 From: Krishna292002 Date: Thu, 25 Sep 2025 13:31:34 +0530 Subject: [PATCH 03/11] feat: added responsive sticky navbar with mobile toggle (Task 2) --- src/components/Navbar.tsx | 44 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 src/components/Navbar.tsx 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; From 5b4f69031d74f299b628d37676a322474fc611ba Mon Sep 17 00:00:00 2001 From: Krishna292002 Date: Thu, 25 Sep 2025 13:33:21 +0530 Subject: [PATCH 04/11] feat: added responsive sticky navbar with mobile toggle (Task 2) --- frontend_dev_assignment | 1 + src/app/page.tsx | 5 +++++ 2 files changed, 6 insertions(+) create mode 160000 frontend_dev_assignment 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 c71ae0a..db5e981 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -1,4 +1,5 @@ 'use client' +import Navbar from '../components/Navbar'; import { WorkerType } from '@/types/workers' import Image from 'next/image' import { useState, useEffect } from 'react' @@ -20,6 +21,9 @@ export default function WorkersPage() { }, []) return ( + <> + +
{/* Changed: Added text-white for better contrast on dark background */}

Our Workers

@@ -65,5 +69,6 @@ export default function WorkersPage() { ))}
+ ) } From 29f786dcf862bd7369e8b1a198abd4779673e4b7 Mon Sep 17 00:00:00 2001 From: Krishna292002 Date: Thu, 25 Sep 2025 13:55:28 +0530 Subject: [PATCH 05/11] feat: add skeleton loaders for workers page data loading (Task 3) --- src/app/page.tsx | 83 ++++++++++----------------------- src/components/SkeletonCard.tsx | 14 ++++++ 2 files changed, 39 insertions(+), 58 deletions(-) create mode 100644 src/components/SkeletonCard.tsx diff --git a/src/app/page.tsx b/src/app/page.tsx index db5e981..4152a0f 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -1,74 +1,41 @@ 'use client' +import SkeletonCard from '../components/SkeletonCard'; // Import Skeleton import Navbar from '../components/Navbar'; -import { WorkerType } from '@/types/workers' -import Image from 'next/image' -import { useState, useEffect } from 'react' +import WorkerCard from '../components/WorkerCard'; // Import WorkerCard component +import { WorkerType } from '@/types/workers'; +import { useState, useEffect } from 'react'; export default function WorkersPage() { - const [workersData, setWorkersData] = useState([]) + const [workersData, setWorkersData] = useState([]); useEffect(() => { const loadData = async () => { try { - // Changed: Removed duplicate loadData call to prevent loading data twice - const response = await import('../../workers.json') - setWorkersData(response.default) + const response = await import('../../workers.json'); + setWorkersData(response.default); } catch (error) { - console.error('Failed to load workers:', error) + console.error('Failed to load workers:', error); } - } - loadData() - }, []) + }; + loadData(); + }, []); return ( <> - + +
+

Our Workers

-
- {/* Changed: Added text-white for better contrast on dark background */} -

Our Workers

- - {/* Changed: Updated grid classes for better responsiveness - - 1 col on mobile - - 2 cols on small screens - - 3 cols on medium (tablet) - - 4 cols on large (desktop) - */} -
- {workersData - .filter(worker => worker.pricePerDay > 0) - .filter(worker => worker.id !== null) - .sort((a, b) => a.name.localeCompare(b.name)) - .map((worker: WorkerType) => ( -
- {/* Changed: Added fixed height and relative positioning for uniform image sizing */} -
- {worker.name} -
- {/* Changed: Added flex-grow on text container and margin spacing for better vertical alignment */} -
-

{worker.name}

-

{worker.service}

- {/* Changed: Use mt-auto to push price to bottom of card for balanced layout */} -

- ₹{Math.round(worker.pricePerDay * 1.18)} / day -

-
-
- ))} -
-
+
+ {workersData + .filter(worker => worker.pricePerDay > 0) + .filter(worker => worker.id !== null) + .sort((a, b) => a.name.localeCompare(b.name)) + .map((worker: WorkerType) => ( + + ))} +
+
- ) + ); } 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; From a7605b98d1a6a36be5ef4a8de9a152e568a3f21e Mon Sep 17 00:00:00 2001 From: Krishna292002 Date: Thu, 25 Sep 2025 14:52:42 +0530 Subject: [PATCH 06/11] Add WorkerCard component for task separation --- src/components/WorkerCard.tsx | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 src/components/WorkerCard.tsx diff --git a/src/components/WorkerCard.tsx b/src/components/WorkerCard.tsx new file mode 100644 index 0000000..5ccb650 --- /dev/null +++ b/src/components/WorkerCard.tsx @@ -0,0 +1,33 @@ +import React from 'react' +import Image from 'next/image' +import { WorkerType } from '@/types/workers' + +interface WorkerCardProps { + worker: WorkerType +} + +const WorkerCard: React.FC = React.memo(({ worker }) => { + return ( +
+
+ {worker.name} +
+
+

{worker.name}

+

{worker.service}

+

₹{Math.round(worker.pricePerDay * 1.18)} / day

+
+
+ ) +}) + +export default WorkerCard From e8cd58f0a42aac9c33f4246c81f24cd33ce49997 Mon Sep 17 00:00:00 2001 From: Krishna292002 Date: Thu, 25 Sep 2025 14:48:33 +0530 Subject: [PATCH 07/11] feat: implement pagination on workers listing page (Task 4) --- src/app/page.tsx | 59 ++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 49 insertions(+), 10 deletions(-) diff --git a/src/app/page.tsx b/src/app/page.tsx index 4152a0f..4feded4 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -1,12 +1,14 @@ 'use client' -import SkeletonCard from '../components/SkeletonCard'; // Import Skeleton import Navbar from '../components/Navbar'; -import WorkerCard from '../components/WorkerCard'; // Import WorkerCard component +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 [currentPage, setCurrentPage] = useState(1); + const itemsPerPage = 9; useEffect(() => { const loadData = async () => { @@ -20,20 +22,57 @@ export default function WorkersPage() { loadData(); }, []); + const filteredWorkers = workersData + .filter(worker => worker.pricePerDay > 0) + .filter(worker => worker.id !== null) + .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); + }; + 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) => ( - - ))} + {workersData.length === 0 + ? Array.from({ length: itemsPerPage }, (_, idx) => ) + : currentWorkers.map((worker: WorkerType) => ( + + ))} +
+
+ + {Array.from({ length: totalPages }, (_, i) => ( + + ))} +
From 6864db719b9f658ca562c33c5b78fb2e03e951ea Mon Sep 17 00:00:00 2001 From: Krishna292002 Date: Thu, 25 Sep 2025 15:27:57 +0530 Subject: [PATCH 08/11] feat: implement service filters with pagination (Task 5) --- src/app/page.tsx | 46 +++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 45 insertions(+), 1 deletion(-) diff --git a/src/app/page.tsx b/src/app/page.tsx index 4feded4..ed5e8d6 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -8,6 +8,9 @@ import { useState, useEffect } from 'react'; export default function WorkersPage() { const [workersData, setWorkersData] = useState([]); const [currentPage, setCurrentPage] = useState(1); + const [minPrice, setMinPrice] = useState(''); + const [maxPrice, setMaxPrice] = useState(''); + const [selectedService, setSelectedService] = useState(''); const itemsPerPage = 9; useEffect(() => { @@ -22,9 +25,17 @@ export default function WorkersPage() { loadData(); }, []); + // Reset current page on any filter change + useEffect(() => { + setCurrentPage(1); + }, [minPrice, maxPrice, selectedService]); + const filteredWorkers = 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); @@ -50,6 +61,36 @@ export default function WorkersPage() {

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 */}
{workersData.length === 0 ? Array.from({ length: itemsPerPage }, (_, idx) => ) @@ -57,7 +98,9 @@ export default function WorkersPage() { ))}
-
+ + {/* Pagination Controls */} +
@@ -74,6 +117,7 @@ export default function WorkersPage() { Next
+
); From e29cc0f31da17bd73c9ecf62bf128e962a29d1b2 Mon Sep 17 00:00:00 2001 From: Krishna292002 Date: Thu, 25 Sep 2025 15:51:52 +0530 Subject: [PATCH 09/11] chore: clean up code and finalize bug fixes (Task 6) --- src/app/page.tsx | 52 ++++++++++++++++++++--------------- src/components/WorkerCard.tsx | 39 ++++++++++---------------- 2 files changed, 45 insertions(+), 46 deletions(-) diff --git a/src/app/page.tsx b/src/app/page.tsx index ed5e8d6..522327a 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -1,4 +1,4 @@ -'use client' +'use client'; import Navbar from '../components/Navbar'; import WorkerCard from '../components/WorkerCard'; import SkeletonCard from '../components/SkeletonCard'; @@ -25,17 +25,16 @@ export default function WorkersPage() { loadData(); }, []); - // Reset current page on any filter change useEffect(() => { setCurrentPage(1); }, [minPrice, maxPrice, selectedService]); const filteredWorkers = 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)) + .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); @@ -59,8 +58,8 @@ export default function WorkersPage() { return ( <> -
-

Our Workers

+
+

Our Workers

{/* Filters Section */}
@@ -68,56 +67,65 @@ export default function WorkersPage() { type="number" placeholder="Min Price" value={minPrice} - onChange={e => setMinPrice(e.target.value === '' ? '' : Number(e.target.value))} + onChange={(e) => setMinPrice(e.target.value === '' ? '' : Number(e.target.value))} className="border p-2 rounded w-40" /> setMaxPrice(e.target.value === '' ? '' : Number(e.target.value))} + onChange={(e) => setMaxPrice(e.target.value === '' ? '' : Number(e.target.value))} className="border p-2 rounded w-40" />
{/* Workers Grid */} -
+
{workersData.length === 0 ? Array.from({ length: itemsPerPage }, (_, idx) => ) - : currentWorkers.map((worker: WorkerType) => ( - - ))} + : currentWorkers.map((worker: WorkerType) => )}
{/* Pagination Controls */}
- {Array.from({ length: totalPages }, (_, i) => ( ))} -
-
); diff --git a/src/components/WorkerCard.tsx b/src/components/WorkerCard.tsx index 5ccb650..bf5014d 100644 --- a/src/components/WorkerCard.tsx +++ b/src/components/WorkerCard.tsx @@ -1,33 +1,24 @@ -import React from 'react' -import Image from 'next/image' -import { WorkerType } from '@/types/workers' +'use client'; +import Image from 'next/image'; +import { WorkerType } from '@/types/workers'; -interface WorkerCardProps { - worker: WorkerType -} - -const WorkerCard: React.FC = React.memo(({ worker }) => { +export default function WorkerCard({ worker }: { worker: WorkerType }) { return ( -
-
+
+
{worker.name}
-
-

{worker.name}

-

{worker.service}

-

₹{Math.round(worker.pricePerDay * 1.18)} / day

-
+

{worker.name}

+

{worker.service}

+

₹{worker.pricePerDay} / day

- ) -}) - -export default WorkerCard + ); +} From 5b7cd190546614eeaeff0c30df16d99768a5b553 Mon Sep 17 00:00:00 2001 From: Krishna292002 Date: Thu, 25 Sep 2025 16:51:45 +0530 Subject: [PATCH 10/11] feat: final assignment submission by Krishna Sharma --- README.md | 7 ++++ src/app/api/workers/route.ts | 5 +-- src/app/page.tsx | 70 +++++++++++++++++++++++++++--------- 3 files changed, 61 insertions(+), 21 deletions(-) diff --git a/README.md b/README.md index bb6a8c8..e82fbaa 100644 --- a/README.md +++ b/README.md @@ -83,3 +83,10 @@ 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. + diff --git a/src/app/api/workers/route.ts b/src/app/api/workers/route.ts index 211617d..44a245e 100644 --- a/src/app/api/workers/route.ts +++ b/src/app/api/workers/route.ts @@ -5,7 +5,7 @@ export async function GET() { try { return NextResponse.json({ success: true, - data: workersDat + data: workersData }) } catch (error) { console.error('API Error:', error) @@ -16,6 +16,3 @@ export async function GET() { } } -git config --global user.email -"krishna.ks292002@gmail.com" - git config --global user.name "Krishna292002" \ No newline at end of file diff --git a/src/app/page.tsx b/src/app/page.tsx index 522327a..af931c7 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -7,6 +7,8 @@ import { useState, useEffect } from 'react'; export default function WorkersPage() { 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(''); @@ -14,28 +16,35 @@ export default function WorkersPage() { 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'); } + setLoading(false); }; - loadData(); + fetchWorkers(); }, []); useEffect(() => { setCurrentPage(1); }, [minPrice, maxPrice, selectedService]); - const filteredWorkers = 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 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; @@ -55,6 +64,33 @@ export default function WorkersPage() { if (currentPage > 1) setCurrentPage(currentPage - 1); }; + if (loading) { + return ( + <> + +
+

Loading workers...

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

Error: {error}

+
+ + ); + } + return ( <> @@ -93,13 +129,13 @@ export default function WorkersPage() { {/* Workers Grid */}
- {workersData.length === 0 - ? Array.from({ length: itemsPerPage }, (_, idx) => ) - : currentWorkers.map((worker: WorkerType) => )} + {currentWorkers.map((worker: WorkerType) => ( + + ))}
{/* Pagination Controls */} -
+