mirror of
https://github.com/Since-3/Kuma.git
synced 2026-10-12 21:41:17 +00:00
Merge pull request #16 from Since-3/feature/course-request-limit
added limit for requesting the courses
This commit is contained in:
commit
8a41262309
2 changed files with 172 additions and 21 deletions
|
|
@ -12,6 +12,7 @@ import { prisma } from "@/src/lib/prisma";
|
|||
import { getUserData, isManager } from "@/src/lib/auth/getUser";
|
||||
import { courseSchema, type CourseFormData } from "../schemas/course-schema";
|
||||
import { revalidatePath } from "next/cache";
|
||||
import { Prisma } from "@prisma/client";
|
||||
|
||||
/**
|
||||
* Erstellt einen neuen Kurs in der Datenbank
|
||||
|
|
@ -132,20 +133,23 @@ export async function getAllCourses() {
|
|||
}
|
||||
|
||||
/**
|
||||
* Ruft alle Kurse des angemeldeten Managers ab
|
||||
* Fetches all courses created by the logged-in manager
|
||||
*
|
||||
* Diese Funktion gibt alle Kurse zurück, die vom aktuell angemeldeten Manager
|
||||
* erstellt wurden, unabhängig vom Status (Entwurf oder veröffentlicht).
|
||||
* Die Kurse werden nach Erstellungsdatum absteigend sortiert (neueste zuerst).
|
||||
* This function returns all courses created by the currently logged-in manager,
|
||||
* regardless of status (draft or published). Supports optional date range filtering
|
||||
* to implement efficient loading for large datasets.
|
||||
*
|
||||
* @returns Ein Objekt mit success-Flag und einem Array von Kursen des Managers
|
||||
* @param options - Optional filtering parameters
|
||||
* @param options.dateFrom - Start date for filtering (inclusive)
|
||||
* @param options.dateTo - End date for filtering (inclusive)
|
||||
* @returns An object with success flag and an array of the manager's courses
|
||||
*/
|
||||
export async function getMyCourses() {
|
||||
export async function getMyCourses(options?: { dateFrom?: Date; dateTo?: Date }) {
|
||||
try {
|
||||
// Aktuellen Benutzer abrufen
|
||||
// Step 1: Get current user
|
||||
const userData = await getUserData();
|
||||
|
||||
// Überprüfen, ob Benutzer angemeldet und ein Manager ist
|
||||
// Step 2: Check if user is logged in and is a manager
|
||||
if (!userData || !isManager(userData)) {
|
||||
return {
|
||||
success: false,
|
||||
|
|
@ -154,13 +158,31 @@ export async function getMyCourses() {
|
|||
};
|
||||
}
|
||||
|
||||
// Alle Kurse abrufen, die von diesem Manager erstellt wurden
|
||||
// Step 3: Build the where clause with optional date filtering
|
||||
const whereClause: Prisma.CourseWhereInput = {
|
||||
createdBy: userData.id, // Filter by manager ID
|
||||
};
|
||||
|
||||
// Add date range filter if provided
|
||||
if (options?.dateFrom || options?.dateTo) {
|
||||
whereClause.date = {};
|
||||
|
||||
if (options.dateFrom) {
|
||||
// Filter courses from this date onwards (inclusive)
|
||||
whereClause.date.gte = options.dateFrom;
|
||||
}
|
||||
|
||||
if (options.dateTo) {
|
||||
// Filter courses up to this date (inclusive)
|
||||
whereClause.date.lte = options.dateTo;
|
||||
}
|
||||
}
|
||||
|
||||
// Step 4: Fetch courses created by this manager with optional date filtering
|
||||
const courses = await prisma.course.findMany({
|
||||
where: {
|
||||
createdBy: userData.id, // Filtert nach Manager-ID
|
||||
},
|
||||
where: whereClause,
|
||||
orderBy: {
|
||||
createdAt: "desc", // Neueste Kurse zuerst
|
||||
date: "asc", // Sort by course date ascending (oldest first)
|
||||
},
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import { Button } from "@/src/components/ui/button";
|
|||
import { toast } from "sonner";
|
||||
import { useRouter } from "next/navigation";
|
||||
import CourseListItem from "../components/CourseListItem";
|
||||
import { Filter } from "lucide-react";
|
||||
import { Filter, ChevronUp, ChevronDown } from "lucide-react";
|
||||
import DeleteDialog from "@/src/components/layout/DeleteDialog";
|
||||
|
||||
type Course = {
|
||||
|
|
@ -42,24 +42,119 @@ const CourseListView = () => {
|
|||
const [courseToDelete, setCourseToDelete] = useState<{ id: string; name: string } | null>(null);
|
||||
const [isDeleting, setIsDeleting] = useState(false);
|
||||
|
||||
const loadCourses = async () => {
|
||||
const result = await getMyCourses();
|
||||
// Date range state for infinite scroll
|
||||
const [loadedDateFrom, setLoadedDateFrom] = useState<Date | null>(null);
|
||||
const [loadedDateTo, setLoadedDateTo] = useState<Date | null>(null);
|
||||
const [isLoadingOlder, setIsLoadingOlder] = useState(false);
|
||||
const [isLoadingNewer, setIsLoadingNewer] = useState(false);
|
||||
const [hasOlderCourses, setHasOlderCourses] = useState(true);
|
||||
const [hasNewerCourses, setHasNewerCourses] = useState(true);
|
||||
|
||||
/**
|
||||
* Load courses with optional date range
|
||||
* @param options - Optional date range parameters
|
||||
* @param mode - Loading mode: 'initial', 'older', or 'newer'
|
||||
*/
|
||||
const loadCourses = async (
|
||||
options?: { dateFrom?: Date; dateTo?: Date },
|
||||
mode: "initial" | "older" | "newer" = "initial"
|
||||
) => {
|
||||
const result = await getMyCourses(options);
|
||||
if (result.success) {
|
||||
setCourses(result.courses);
|
||||
if (mode === "initial") {
|
||||
// Replace all courses on initial load
|
||||
setCourses(result.courses);
|
||||
} else if (mode === "older") {
|
||||
// Prepend older courses to the beginning
|
||||
setCourses((prev) => [...result.courses, ...prev]);
|
||||
} else if (mode === "newer") {
|
||||
// Append newer courses to the end
|
||||
setCourses((prev) => [...prev, ...result.courses]);
|
||||
}
|
||||
|
||||
// Check if there are more courses to load
|
||||
if (mode === "older" && result.courses.length === 0) {
|
||||
setHasOlderCourses(false);
|
||||
}
|
||||
if (mode === "newer" && result.courses.length === 0) {
|
||||
setHasNewerCourses(false);
|
||||
}
|
||||
} else {
|
||||
toast.error(result.error || "Fehler beim Laden der Kurse");
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Initial load: Load courses from -2 weeks to +6 weeks from today
|
||||
*/
|
||||
useEffect(() => {
|
||||
const fetchCourses = async () => {
|
||||
setIsLoading(true);
|
||||
await loadCourses();
|
||||
|
||||
// Calculate default date range: -2 weeks to +6 weeks from today
|
||||
const today = new Date();
|
||||
today.setHours(0, 0, 0, 0);
|
||||
|
||||
const twoWeeksAgo = new Date(today);
|
||||
twoWeeksAgo.setDate(today.getDate() - 14); // -2 weeks
|
||||
|
||||
const sixWeeksLater = new Date(today);
|
||||
sixWeeksLater.setDate(today.getDate() + 42); // +6 weeks
|
||||
|
||||
// Store the loaded date range
|
||||
setLoadedDateFrom(twoWeeksAgo);
|
||||
setLoadedDateTo(sixWeeksLater);
|
||||
|
||||
await loadCourses({ dateFrom: twoWeeksAgo, dateTo: sixWeeksLater }, "initial");
|
||||
setIsLoading(false);
|
||||
};
|
||||
fetchCourses();
|
||||
}, []);
|
||||
|
||||
/**
|
||||
* Load older courses (extend the date range backwards by 4 weeks)
|
||||
*/
|
||||
const loadOlderCourses = async () => {
|
||||
if (!loadedDateFrom || isLoadingOlder) return;
|
||||
|
||||
setIsLoadingOlder(true);
|
||||
|
||||
// Calculate new date range: 4 weeks before the current loadedDateFrom
|
||||
const fourWeeksEarlier = new Date(loadedDateFrom);
|
||||
fourWeeksEarlier.setDate(loadedDateFrom.getDate() - 28); // -4 weeks
|
||||
|
||||
const oneDayBeforeLoadedFrom = new Date(loadedDateFrom);
|
||||
oneDayBeforeLoadedFrom.setDate(loadedDateFrom.getDate() - 1); // -1 day to avoid duplicates
|
||||
|
||||
await loadCourses({ dateFrom: fourWeeksEarlier, dateTo: oneDayBeforeLoadedFrom }, "older");
|
||||
|
||||
// Update the loaded date range
|
||||
setLoadedDateFrom(fourWeeksEarlier);
|
||||
setIsLoadingOlder(false);
|
||||
};
|
||||
|
||||
/**
|
||||
* Load newer courses (extend the date range forwards by 4 weeks)
|
||||
*/
|
||||
const loadNewerCourses = async () => {
|
||||
if (!loadedDateTo || isLoadingNewer) return;
|
||||
|
||||
setIsLoadingNewer(true);
|
||||
|
||||
// Calculate new date range: 4 weeks after the current loadedDateTo
|
||||
const oneDayAfterLoadedTo = new Date(loadedDateTo);
|
||||
oneDayAfterLoadedTo.setDate(loadedDateTo.getDate() + 1); // +1 day to avoid duplicates
|
||||
|
||||
const fourWeeksLater = new Date(loadedDateTo);
|
||||
fourWeeksLater.setDate(loadedDateTo.getDate() + 28); // +4 weeks
|
||||
|
||||
await loadCourses({ dateFrom: oneDayAfterLoadedTo, dateTo: fourWeeksLater }, "newer");
|
||||
|
||||
// Update the loaded date range
|
||||
setLoadedDateTo(fourWeeksLater);
|
||||
setIsLoadingNewer(false);
|
||||
};
|
||||
|
||||
const requestDelete = (courseId: string, courseName: string) => {
|
||||
setCourseToDelete({ id: courseId, name: courseName });
|
||||
setDeleteDialogOpen(true);
|
||||
|
|
@ -78,7 +173,10 @@ const CourseListView = () => {
|
|||
|
||||
if (result.success) {
|
||||
toast.success(result.message);
|
||||
loadCourses();
|
||||
// Reload courses with the current date range
|
||||
if (loadedDateFrom && loadedDateTo) {
|
||||
await loadCourses({ dateFrom: loadedDateFrom, dateTo: loadedDateTo }, "initial");
|
||||
}
|
||||
} else {
|
||||
toast.error(result.error || "Fehler beim Löschen");
|
||||
}
|
||||
|
|
@ -106,7 +204,7 @@ const CourseListView = () => {
|
|||
return course < today;
|
||||
};
|
||||
|
||||
// Filter courses
|
||||
// Filter courses (client-side filtering for status, sport, etc.)
|
||||
const filteredCourses = courses.filter((course) => {
|
||||
if (filterStatus !== "all" && course.status !== filterStatus) return false;
|
||||
if (filterSport !== "all" && course.sport !== filterSport) return false;
|
||||
|
|
@ -114,7 +212,7 @@ const CourseListView = () => {
|
|||
// Hide past courses unless showPastCourses is enabled
|
||||
if (!showPastCourses && isPastCourse(course.date)) return false;
|
||||
|
||||
// Date range filter
|
||||
// Date range filter (additional client-side filter on top of server-side)
|
||||
if (dateFrom) {
|
||||
const fromDate = new Date(dateFrom);
|
||||
fromDate.setHours(0, 0, 0, 0);
|
||||
|
|
@ -295,6 +393,21 @@ const CourseListView = () => {
|
|||
</div>
|
||||
)}
|
||||
|
||||
{/* Load Older Courses Button */}
|
||||
{hasOlderCourses && (
|
||||
<div className="mb-4 flex justify-center">
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={loadOlderCourses}
|
||||
disabled={isLoadingOlder}
|
||||
className="flex items-center gap-2"
|
||||
>
|
||||
<ChevronUp size={18} />
|
||||
{isLoadingOlder ? "Lädt..." : "Ältere Kurse laden"}
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{filteredCourses.length === 0 ? (
|
||||
<div className="text-center py-12 bg-gray-50 rounded-lg">
|
||||
<p className="text-xl text-gray-600">
|
||||
|
|
@ -347,6 +460,22 @@ const CourseListView = () => {
|
|||
})}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Load Newer Courses Button */}
|
||||
{hasNewerCourses && filteredCourses.length > 0 && (
|
||||
<div className="mt-4 flex justify-center">
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={loadNewerCourses}
|
||||
disabled={isLoadingNewer}
|
||||
className="flex items-center gap-2"
|
||||
>
|
||||
<ChevronDown size={18} />
|
||||
{isLoadingNewer ? "Lädt..." : "Neuere Kurse laden"}
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<DeleteDialog
|
||||
open={deleteDialogOpen}
|
||||
onOpenChange={setDeleteDialogOpen}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue