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
8 changes: 5 additions & 3 deletions src/client/src/components/layout/Body.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ const Body = ({
currentView,
onViewChange,
solvedProblems,
shuffleState
}) => {
if (loading) {
return (
Expand Down Expand Up @@ -42,12 +43,13 @@ const Body = ({
return (
<main className="body">
<div className="body-content">
<ProblemGrid
problems={problems}
<ProblemGrid
problems={problems}
hasActiveFilters={hasActiveFilters}
currentView={currentView}
onViewChange={onViewChange}
solvedProblems={solvedProblems}
shuffleState={shuffleState}
/>
<Pagination
currentPage={currentPage}
Expand All @@ -59,4 +61,4 @@ const Body = ({
);
};

export default Body;
export default Body;
62 changes: 51 additions & 11 deletions src/client/src/components/layout/Main.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,30 +17,59 @@ const Main = () => {
const [currentPage, setCurrentPage] = useState(1);
const [totalPages, setTotalPages] = useState(1);
const [currentView, setCurrentView] = useState('list');
// Use the solved problems hook
const solvedProblems = useSolvedProblems();
const [shuffle, setShuffle] = useState(-1); // -1 means shuffle is off
const [isShuffleEnabled, setIsShuffleEnabled] = useState(false);

const solvedProblems = useSolvedProblems();
const PROBLEMS_PER_PAGE = 50;

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

const generateShuffleNumber = () => {
return Math.floor(Math.random() * 10000) + 1;
};

// Toggle shuffle on/off
const toggleShuffle = () => {
if (isShuffleEnabled) {
// Turning shuffle off
setIsShuffleEnabled(false);
setShuffle(-1);
} else {
// Turning shuffle on - generate new number if none exists
setIsShuffleEnabled(true);
if (shuffle === -1) {
setShuffle(generateShuffleNumber());
}
}
};

// Regenerate shuffle number
const regenerateShuffle = () => {
const newShuffle = generateShuffleNumber();
setShuffle(newShuffle);
setCurrentPage(1); // Reset to first page when regenerating shuffle
};

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);
const data = await fetchProblems(
currentPage,
PROBLEMS_PER_PAGE,
filters,
isShuffleEnabled ? shuffle : -1 // Only pass shuffle if enabled
);
setProblems(data);
setError(null);

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

loadProblems();
}, [currentPage, filters]);
}, [currentPage, filters, shuffle, isShuffleEnabled]);

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

const handleFilterChange = (newFilters) => {
setFilters(newFilters);
setCurrentPage(1); // Reset to first page when filters change
setCurrentPage(1);
};

const handlePageChange = (page) => {
Expand All @@ -82,6 +110,12 @@ const Main = () => {
filters={filters}
onFilterChange={handleFilterChange}
companies={companies}
shuffleState={{
isShuffleEnabled,
shuffle,
toggleShuffle,
regenerateShuffle
}}
/>
<Body
problems={problems}
Expand All @@ -95,6 +129,12 @@ const Main = () => {
currentView={currentView}
onViewChange={setCurrentView}
solvedProblems={solvedProblems}
shuffleState={{
isShuffleEnabled,
shuffle,
toggleShuffle,
regenerateShuffle
}}
/>
</div>
);
Expand Down
25 changes: 25 additions & 0 deletions src/client/src/components/layout/ShuffleToggle.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import React from "react";
import "../../styles/layout/ShuffleToggle.css";

const ShuffleToggle = ({ shuffleState }) => {
const { isShuffleEnabled, toggleShuffle } = shuffleState;

return (
<div className="shuffle-toggle-container">
<div className="shuffle-toggle">
<label className="shuffle-label">
<input
type="checkbox"
checked={isShuffleEnabled}
onChange={toggleShuffle}
className="shuffle-checkbox"
/>
<span className="shuffle-slider"></span>
<span className="shuffle-text">Shuffle</span>
</label>
</div>
</div>
);
};

export default ShuffleToggle;
66 changes: 41 additions & 25 deletions src/client/src/components/layout/ViewToggle.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
import React from "react";
import ShuffleToggle from "./ShuffleToggle";
import "../../styles/layout/ViewToggle.css";

const ViewToggle = ({ currentView, onViewChange, solvedProblems }) => {
const ViewToggle = ({
currentView,
onViewChange,
solvedProblems,
shuffleState,
}) => {
const handleClearAll = () => {
if (
window.confirm(
Expand All @@ -16,32 +22,42 @@ const ViewToggle = ({ currentView, onViewChange, solvedProblems }) => {

return (
<div className="view-toggle-container">
<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 className="left-controls">
<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>
</div>

{hasSolvedProblems && (
<button
className="clear-all-btn"
onClick={handleClearAll}
title="Clear all solved problems"
>
🗑️ Clear Solved
</button>
)}
<div className="right-controls">
{shuffleState && (
<div className="shuffle-control">
<ShuffleToggle shuffleState={shuffleState} />
</div>
)}

{hasSolvedProblems && (
<button
className="clear-all-btn"
onClick={handleClearAll}
title="Clear all solved problems"
>
🗑️ Clear Solved
</button>
)}
</div>
</div>
);
};
Expand Down
37 changes: 19 additions & 18 deletions src/client/src/components/problems/ProblemGrid.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,52 +4,53 @@ import ProblemList from "./ProblemList";
import ViewToggle from "../layout/ViewToggle";
import "../../styles/components/ProblemGrid.css";

const ProblemGrid = ({
problems,
hasActiveFilters,
currentView,
const ProblemGrid = ({
problems,
hasActiveFilters,
currentView,
onViewChange,
solvedProblems
solvedProblems,
shuffleState,
}) => {
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>
<p>
Try adjusting your filters or clearing them to see all problems.
</p>
)}
</div>
);
}

return (
<div className="problem-container">
<ViewToggle
currentView={currentView}
<ViewToggle
currentView={currentView}
onViewChange={onViewChange}
solvedProblems={solvedProblems}
shuffleState={shuffleState}
/>
{currentView === 'grid' ? (

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

export default ProblemGrid;
export default ProblemGrid;
24 changes: 17 additions & 7 deletions src/client/src/services/api.js
Original file line number Diff line number Diff line change
@@ -1,22 +1,32 @@
// services/api.js
const API_BASE_URL = process.env.REACT_APP_API_BASE_URL || "http://default-fallback.com/api";
const API_BASE_URL =
process.env.REACT_APP_API_BASE_URL || "http://default-fallback.com/api";

export const fetchProblems = async (page = 1, limit = 50, filters = {}) => {
export const fetchProblems = async (
page = 1,
limit = 50,
filters = {},
shuffle = -1
) => {
const skip = (page - 1) * limit;
const params = new URLSearchParams({
skip: skip.toString(),
limit: limit.toString()
limit: limit.toString(),
});

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

if (shuffle !== -1) {
params.append("shuffle", shuffle.toString());
}

const response = await fetch(`${API_BASE_URL}/problems?${params}`);
Expand All @@ -32,4 +42,4 @@ export const getProblemById = async (id) => {
throw new Error(`Failed to fetch problem #${id}`);
}
return await response.json();
};
};
Loading