Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions src/client/src/App.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import { BrowserRouter as Router, Routes, Route } from "react-router-dom";
import Main from "./components/layout/Main";
import ProblemPage from "./components/problems/ProblemPage";
import Header from "./components/layout/Header";
import Footer from "./components/layout/Footer";

function App() {
return (
Expand All @@ -14,7 +13,6 @@ function App() {
<Route path="/" element={<Main />} />
<Route path="/problems/:id" element={<ProblemPage />} />
</Routes>
<Footer />
</Router>
</div>
);
Expand Down
34 changes: 23 additions & 11 deletions src/client/src/components/layout/Body.js
Original file line number Diff line number Diff line change
@@ -1,21 +1,28 @@
import React from "react";
import ProblemGrid from "../problems/ProblemGrid";
import Pagination from "./Pagination"; // Add this import
import Pagination from "./Pagination";
import "../../styles/layout/Body.css";

const Body = ({
problems,
loading,
error,
filters,
currentPage,
totalPages,
onPageChange
const Body = ({
problems,
loading,
error,
filters,
currentPage,
totalPages,
onPageChange,
hasActiveFilters,
currentView,
onViewChange
}) => {
if (loading) {
return (
<div className="body loading">
<div className="loading-spinner">Loading problems...</div>
<div className="loading-spinner">
{hasActiveFilters
? "Loading filtered problems..."
: "Loading problems..."}
</div>
</div>
);
}
Expand All @@ -34,7 +41,12 @@ const Body = ({
return (
<main className="body">
<div className="body-content">
<ProblemGrid problems={problems} filters={filters} />
<ProblemGrid
problems={problems}
hasActiveFilters={hasActiveFilters}
currentView={currentView}
onViewChange={onViewChange}
/>
<Pagination
currentPage={currentPage}
totalPages={totalPages}
Expand Down
14 changes: 0 additions & 14 deletions src/client/src/components/layout/Footer.js

This file was deleted.

19 changes: 14 additions & 5 deletions src/client/src/components/layout/Main.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,22 +15,29 @@ const Main = () => {
});
const [currentPage, setCurrentPage] = useState(1);
const [totalPages, setTotalPages] = useState(1);
const [currentView, setCurrentView] = useState('list'); // s states 'list' or 'grid'

const PROBLEMS_PER_PAGE = 50;

// Check if any filters are active
const hasActiveFilters = useMemo(() => {
return !!(filters.company || filters.timePeriod || filters.difficulty);
}, [filters]);

useEffect(() => {
const loadProblems = async () => {
setLoading(true);
try {
// ALWAYS pass filters to API - backend will ignore empty ones
const data = await fetchProblems(currentPage, PROBLEMS_PER_PAGE, filters);
setProblems(data);
setError(null);

// Estimate total pages based on response
if (data.length < PROBLEMS_PER_PAGE) {
setTotalPages(currentPage);
setTotalPages(currentPage); // This is the last page
} else {
setTotalPages(currentPage + 1);
setTotalPages(currentPage + 1); // There might be more pages
}
} catch (err) {
setError("Failed to fetch problems. Please try again later.");
Expand All @@ -41,9 +48,9 @@ const Main = () => {
};

loadProblems();
}, [currentPage, filters]);
}, [currentPage, filters]); // Re-fetch when page OR filters change

// Extract unique company names from problems
// Extract unique company names from ALL problems (initial load for dropdown)
const companies = useMemo(() => {
const companySet = new Set();
problems.forEach((problem) => {
Expand All @@ -63,7 +70,6 @@ const Main = () => {

const handlePageChange = (page) => {
setCurrentPage(page);
// Scroll to top when page changes
window.scrollTo({ top: 0, behavior: 'smooth' });
};

Expand All @@ -82,6 +88,9 @@ const Main = () => {
currentPage={currentPage}
totalPages={totalPages}
onPageChange={handlePageChange}
hasActiveFilters={hasActiveFilters}
currentView={currentView}
onViewChange={setCurrentView}
/>
</div>
);
Expand Down
25 changes: 25 additions & 0 deletions src/client/src/components/layout/ViewToggle.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import React from "react";
import "../../styles/layout/ViewToggle.css";

const ViewToggle = ({ currentView, onViewChange }) => {
return (
<div className="view-toggle">
<button
className={`toggle-btn ${currentView === 'grid' ? 'active' : ''}`}
onClick={() => onViewChange('grid')}
title="Grid View"
>
⏹️ Grid
</button>
<button
className={`toggle-btn ${currentView === 'list' ? 'active' : ''}`}
onClick={() => onViewChange('list')}
title="List View"
>
📋 List
</button>
</div>
);
};

export default ViewToggle;
66 changes: 23 additions & 43 deletions src/client/src/components/problems/ProblemGrid.js
Original file line number Diff line number Diff line change
@@ -1,58 +1,38 @@
import React, { useMemo } from "react";
import React from "react";
import ProblemCard from "./ProblemCard";
import ProblemList from "./ProblemList";
import ViewToggle from "../layout/ViewToggle";
import "../../styles/components/ProblemGrid.css";

const ProblemGrid = ({ problems, filters }) => {
const filteredProblems = useMemo(() => {
if (!filters.company && !filters.timePeriod && !filters.difficulty) {
return problems;
}

return problems.filter((problem) => {
// Company filter
if (
filters.company &&
(!problem.companies || !problem.companies[filters.company])
) {
return false;
}

// Time period filter - only apply if both company and time period are selected
if (filters.company && filters.timePeriod) {
if (
!problem.companies?.[filters.company]?.includes(filters.timePeriod)
) {
return false;
}
}

// Difficulty filter
if (
filters.difficulty &&
problem.difficulty !== Number(filters.difficulty)
) {
return false;
}

return true;
});
}, [problems, filters]);

if (filteredProblems.length === 0) {
const ProblemGrid = ({ problems, hasActiveFilters, currentView, onViewChange }) => {
if (problems.length === 0) {
return (
<div className="no-problems">
<p>No problems found matching your criteria.</p>
{hasActiveFilters && (
<p>Try adjusting your filters or clearing them to see all problems.</p>
)}
</div>
);
}

return (
<div className="problem-grid">
{filteredProblems.map((problem) => (
<ProblemCard key={problem.id} problem={problem} />
))}
<div className="problem-container">
<ViewToggle currentView={currentView} onViewChange={onViewChange} />

{currentView === 'grid' ? (
<div className="problem-grid">
{problems.map((problem) => (
<ProblemCard key={problem.id} problem={problem} />
))}
</div>
) : (
<div className="problem-list-container">
<ProblemList problems={problems} />
</div>
)}
</div>
);
};

export default ProblemGrid;
export default ProblemGrid;
53 changes: 53 additions & 0 deletions src/client/src/components/problems/ProblemList.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import React from "react";
import { Link } from "react-router-dom";
import {
getDifficultyText,
getDifficultyClass,
} from "../../constants/difficulty";
import "../../styles/components/ProblemList.css";

const ProblemList = ({ problems }) => {
if (problems.length === 0) {
return (
<div className="no-problems">
<p>No problems found matching your criteria.</p>
</div>
);
}

return (
<div className="problem-list">
<div className="list-header">
<span className="header-id">ID</span>
<span className="header-title">Problem Name</span>
<span className="header-difficulty">Difficulty</span>
<span className="header-acceptance">Acceptance</span>
</div>
<div className="list-content">
{problems.map((problem, index) => (
<ProblemListItem key={problem.id} problem={problem} index={index} />
))}
</div>
</div>
);
};

const ProblemListItem = ({ problem, index }) => {
const difficultyText = getDifficultyText(problem.difficulty);
const difficultyClass = getDifficultyClass(problem.difficulty);

return (
<div className="problem-list-item" data-index={index}>
<div className="problem-id">{problem.id}</div>
<div className="problem-title">
<Link to={`/problems/${problem.id}`}>{problem.title}</Link>
</div>
<div className={`problem-difficulty ${difficultyClass}`}>
{difficultyText}
</div>
<div className="problem-acceptance">{problem.acceptance}%</div>
</div>
);
};

export default ProblemList;
6 changes: 3 additions & 3 deletions src/client/src/services/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,13 @@ export const fetchProblems = async (page = 1, limit = 50, filters = {}) => {

// Add filter parameters if they exist
if (filters.company) {
params.append('companies', filters.company);
params.append('company', filters.company);
}
if (filters.difficulty) {
params.append('difficulties', filters.difficulty);
params.append('difficulty', filters.difficulty);
}
if (filters.timePeriod) {
params.append('tags', filters.timePeriod);
params.append('tag', filters.timePeriod);
}

const response = await fetch(`${API_BASE_URL}/problems?${params}`);
Expand Down
Loading