// components/CoursesPage.jsx /** * Courses Page Component * * Fetches and displays a list of courses with controls for searching, * filtering, sorting, and pagination. Renders clickable course cards. * This is now the main landing page. */ // React, Hooks, PropTypes, utils, and child components are globally available function CoursesPage() { // --- State --- const [courses, setCourses] = useState([]); const [isLoading, setIsLoading] = useState(true); // Loading state for the course list const [error, setError] = useState(null); const [currentPage, setCurrentPage] = useState(1); const [totalPages, setTotalPages] = useState(1); const [totalCourses, setTotalCourses] = useState(0); const [searchTerm, setSearchTerm] = useState(""); const [categoryFilter, setCategoryFilter] = useState(""); const [sortColumn, setSortColumn] = useState("created_at"); // Default sort const [sortOrder, setSortOrder] = useState("desc"); // Default order const [categories, setCategories] = useState([]); // For category filter dropdown const [isCategoriesLoading, setIsCategoriesLoading] = useState(true); // Separate loading for categories // --- Constants --- const ITEMS_PER_PAGE = 6; // Should match config.php or be dynamic if needed const API_BASE_URL = "/api"; // Define base URL // --- API Fetching (Categories - runs once) --- useEffect(() => { const fetchCategories = async () => { console.log("Fetching categories for filter..."); setIsCategoriesLoading(true); try { // Fetch courses just to extract categories (consider dedicated endpoint later) const response = await fetch(`${API_BASE_URL}/get_courses.php?limit=1000`); const data = await response.json(); if (!response.ok || !data.success) { // Don't throw fatal error, just log issue with categories console.error("Failed to fetch categories:", data.error || "Unknown error"); } else if (data.data && data.data.courses) { const uniqueCategories = [ ...new Set(data.data.courses.map((c) => c.category).filter(Boolean)), ]; setCategories(uniqueCategories.sort()); console.log("Categories fetched:", uniqueCategories); } } catch (err) { console.error("Error fetching categories:", err); } finally { setIsCategoriesLoading(false); } }; fetchCategories(); }, []); // Empty dependency array ensures this runs only once // --- API Fetching (Courses - runs on dependency change) --- // Memoized function to fetch the list of courses based on current filters/pagination const fetchCourses = useCallback(async () => { setIsLoading(true); // Set loading true for course list specifically setError(null); console.log( `Fetching courses: Page=${currentPage}, Search='${searchTerm}', Cat='${categoryFilter}', Sort=${sortColumn} ${sortOrder}` ); // Construct API URL with query parameters const params = new URLSearchParams({ page: currentPage, limit: ITEMS_PER_PAGE, sort: sortColumn, order: sortOrder, }); if (searchTerm) params.set("search", searchTerm); if (categoryFilter) params.set("category", categoryFilter); const apiUrl = `${API_BASE_URL}/get_courses.php?${params.toString()}`; try { const response = await fetch(apiUrl); // Check for network errors first if (!response.ok) { throw new Error(`Network error: ${response.status} ${response.statusText}`); } const data = await response.json(); // Check for API-level success flag if (!data.success) { throw new Error(data.error || "API request failed to retrieve courses"); } // Validate the expected data structure if (!data.data || !data.data.courses || !data.data.pagination) { console.error("API Response missing expected structure:", data); throw new Error("Received invalid data structure from API."); } console.log("Courses fetched successfully:", data.data); setCourses(data.data.courses); // Set courses array // Access pagination data correctly setTotalPages(data.data.pagination.total_pages || 1); setTotalCourses(data.data.pagination.total || 0); } catch (err) { console.error("Error fetching courses:", err); setError(err.message); // Set the error message state setCourses([]); // Clear courses on error setTotalPages(1); setTotalCourses(0); } finally { setIsLoading(false); // Finished loading course list } }, [currentPage, searchTerm, categoryFilter, sortColumn, sortOrder]); // Dependencies for re-fetching courses // --- Effects --- // Fetch courses whenever relevant state (filters, page, sort) changes useEffect(() => { fetchCourses(); }, [fetchCourses]); // Dependency array includes the memoized fetch function // Debounced search handler using globally available debounce const debouncedSearch = useCallback( window.debounce((value) => { setSearchTerm(value); setCurrentPage(1); // Reset to page 1 on new search }, window.DEBOUNCE_DELAY || 350), // Use global delay or default [] // No dependencies needed for debounce definition itself ); // --- Event Handlers --- const handleSearchChange = (event) => { debouncedSearch(event.target.value); }; const handleCategoryChange = (event) => { setCategoryFilter(event.target.value); setCurrentPage(1); // Reset page }; const handleSortChange = (event) => { const [column, order] = event.target.value.split("-"); setSortColumn(column); setSortOrder(order); setCurrentPage(1); // Reset page }; const handlePageChange = (newPage) => { // Prevent unnecessary state updates if page hasn't changed if (newPage >= 1 && newPage <= totalPages && newPage !== currentPage) { setCurrentPage(newPage); } }; // --- Render --- return (

Discover Your Next Course

{/* Controls: Search, Filter, Sort */}
{/* Search Input */}
{/* Category Filter */}
{/* Sort Options */}
{/* Status Messages & Course List */} {/* Display error prominently if it occurs */} {error &&
Error: {error}
} {/* Course List Area - Render based on loading state */}
{isLoading ? // Show Skeletons while loading Array(ITEMS_PER_PAGE) .fill(null) .map((_, index) => ) : // Show actual cards when loaded (and no error) !error && courses.map((course) => ( // Wrap the card component/structure in a link {/* Use CourseCard component to render the card's content */} {window.CourseCard ? ( ) : (
Course Card Component Not Loaded
// Fallback )}
))}
{/* Pagination (only show if more than one page and not loading/error) */} {!isLoading && !error && totalPages > 1 && ( window.Pagination ? ( ) : (
Pagination Component Not Loaded
) )} {/* No results message (when not loading, no error, and no courses found) */} {!isLoading && !error && courses.length === 0 && (
No courses found matching your criteria. Try adjusting your search or filters.
)}
); } // Make CoursesPage globally available window.CoursesPage = CoursesPage;