mirror of
https://github.com/Since-3/Kuma.git
synced 2026-10-12 21:41:17 +00:00
Merge pull request #96 from Since-3/bug/fix-latest-bugs
Bug/fix latest bugs
This commit is contained in:
commit
35c2cf8832
4 changed files with 63 additions and 40 deletions
|
|
@ -41,7 +41,10 @@ export const publishedCourseSchema = z
|
|||
date: z.string().min(1, "Datum ist erforderlich"),
|
||||
timeFrom: z.string().min(1, "Anfangszeit ist erforderlich"),
|
||||
timeTo: z.string().min(1, "Endzeit ist erforderlich"),
|
||||
trainers: z.array(z.string()).min(1, "Mindestens ein Trainer muss ausgewählt werden"),
|
||||
trainers: z
|
||||
.array(z.string())
|
||||
.min(1, "Mindestens ein Trainer muss ausgewählt werden")
|
||||
.optional(),
|
||||
room: z.string().min(1, "Raum ist erforderlich"),
|
||||
description: z.string().optional(),
|
||||
maxParticipants: z
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
import { useState, useEffect } from "react";
|
||||
import Image from "next/image";
|
||||
import { Clock, MapPin, Users, Mail, Phone } from "lucide-react";
|
||||
import { Clock, MapPin, Users, Mail, Phone, DoorOpen, Info } from "lucide-react";
|
||||
import { Button } from "@/src/components/ui/button";
|
||||
import { Dialog, DialogContent, DialogTitle } from "@/src/components/ui/dialog";
|
||||
import { VisuallyHidden } from "@radix-ui/react-visually-hidden";
|
||||
|
|
@ -204,25 +204,36 @@ const BookingDialog = ({
|
|||
</span>
|
||||
|
||||
{/* Info rows */}
|
||||
<div className="flex flex-col gap-2.5 text-sm text-gray-700">
|
||||
<div className="flex flex-col gap-4 text-sm text-gray-700">
|
||||
<div className="flex items-center gap-2.5">
|
||||
<Clock size={15} className="text-gray-400 shrink-0" />
|
||||
<span>
|
||||
{course.timeFrom} – {course.timeTo} Uhr
|
||||
<b>Uhrzeit:</b> {course.timeFrom} – {course.timeTo}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{(course.roomName || business?.address) && (
|
||||
{course.roomName && (
|
||||
<div className="flex items-center gap-2.5">
|
||||
<DoorOpen size={15} className="text-gray-400 shrink-0" />
|
||||
<span>
|
||||
<b>Raum:</b> {course.roomName}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{business?.address && (
|
||||
<div className="flex items-center gap-2.5">
|
||||
<MapPin size={15} className="text-gray-400 shrink-0" />
|
||||
<span>{course.roomName ?? business?.address}</span>
|
||||
<span>
|
||||
<b>Ort:</b> {business?.address}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="flex items-center gap-2.5">
|
||||
<Users size={15} className="text-gray-400 shrink-0" />
|
||||
<span>
|
||||
Teilnehmer: {safeCurrent}/{safeMax}
|
||||
<b>Teilnehmer:</b> {safeCurrent}/{safeMax}
|
||||
{isFull && <span className="ml-2 text-red-600 font-medium">· Ausgebucht</span>}
|
||||
</span>
|
||||
</div>
|
||||
|
|
@ -231,7 +242,7 @@ const BookingDialog = ({
|
|||
<div className="flex items-center gap-2.5">
|
||||
<Mail size={15} className="text-gray-400 shrink-0" />
|
||||
<a href={`mailto:${business.email}`} className="hover:underline text-gray-700">
|
||||
{business.email}
|
||||
<b>Email:</b> {business.email}
|
||||
</a>
|
||||
</div>
|
||||
)}
|
||||
|
|
@ -246,10 +257,16 @@ const BookingDialog = ({
|
|||
)}
|
||||
|
||||
{course.description && (
|
||||
<div
|
||||
className="prose prose-sm max-w-none text-gray-600"
|
||||
dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(course.description) }}
|
||||
/>
|
||||
<div className="flex items-start gap-2.5">
|
||||
<Info size={15} className="text-gray-400 shrink-0 mt-0.5" />
|
||||
<div>
|
||||
<b>Was Sie beachten sollten:</b>
|
||||
<div
|
||||
className="prose prose-sm max-w-none text-gray-600 mt-1"
|
||||
dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(course.description) }}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
|
|
@ -288,7 +305,7 @@ const PublicCourseCard = ({ course, business }: PublicCourseCardProps) => {
|
|||
const formattedDate = new Date(course.date).toLocaleDateString("de-DE", {
|
||||
weekday: "long",
|
||||
year: "numeric",
|
||||
month: "long",
|
||||
month: "numeric",
|
||||
day: "numeric",
|
||||
});
|
||||
|
||||
|
|
@ -301,7 +318,10 @@ const PublicCourseCard = ({ course, business }: PublicCourseCardProps) => {
|
|||
|
||||
return (
|
||||
<>
|
||||
<div className="bg-white border border-gray-200 rounded-xl p-5 shadow-sm hover:shadow-md transition flex flex-col gap-4">
|
||||
<div
|
||||
onClick={() => setDialogOpen(true)}
|
||||
className="bg-white border border-gray-200 rounded-xl p-5 shadow-sm hover:shadow-md transition flex flex-col gap-4"
|
||||
>
|
||||
<div className="flex items-center justify-between">
|
||||
<p className="text-gray-500 text-sm">{formattedDate}</p>
|
||||
<p className="text-gray-500 text-sm">
|
||||
|
|
|
|||
|
|
@ -123,6 +123,7 @@ const BusinessPublicView = ({
|
|||
weekday: "long",
|
||||
day: "numeric",
|
||||
month: "long",
|
||||
year: "numeric",
|
||||
});
|
||||
|
||||
return (
|
||||
|
|
@ -148,31 +149,10 @@ const BusinessPublicView = ({
|
|||
|
||||
{/* Main content */}
|
||||
<div className="max-w-6xl mx-auto px-6 py-10">
|
||||
{/* Datum + Sport-Tabs — über beiden Spalten */}
|
||||
<div className="mb-4">
|
||||
<h2 className="text-2xl font-semibold text-gray-900 mb-1">{formattedSelectedDate}</h2>
|
||||
{allSports.length > 1 && (
|
||||
<div className="flex flex-wrap gap-2 mt-3">
|
||||
{allSports.map((sport) => (
|
||||
<button
|
||||
key={sport}
|
||||
onClick={() => setActiveSport(sport)}
|
||||
className={`px-4 py-1.5 rounded-full text-sm font-medium transition-colors ${
|
||||
resolvedSport === sport
|
||||
? "bg-gray-900 text-white"
|
||||
: "bg-white border border-gray-200 text-gray-600 hover:border-gray-400"
|
||||
}`}
|
||||
>
|
||||
{sport === "Alle" ? "Alle" : capitalize(sport)}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col lg:flex-row gap-8 lg:items-start">
|
||||
{/* Standing calendar */}
|
||||
{/* Left: sticky calendar */}
|
||||
<div className="lg:sticky lg:top-6 shrink-0 w-full lg:w-auto">
|
||||
<h2 className="text-2xl font-semibold text-gray-900 mb-3">{formattedSelectedDate}</h2>
|
||||
<div className="bg-white border border-gray-200 rounded-xl shadow-sm p-4 flex justify-center">
|
||||
<Calendar
|
||||
mode="single"
|
||||
|
|
@ -185,8 +165,30 @@ const BusinessPublicView = ({
|
|||
</div>
|
||||
</div>
|
||||
|
||||
{/* Courses panel */}
|
||||
{/* Right: filter + cards */}
|
||||
<div className="flex-1 min-w-0">
|
||||
{/* Filter bündig mit Kalender-Oberkante (gleiche mb wie h2 links) */}
|
||||
<div className="mb-4 mt-11">
|
||||
{allSports.length > 1 && (
|
||||
<div className="flex flex-wrap gap-2 mt-3">
|
||||
{allSports.map((sport) => (
|
||||
<button
|
||||
key={sport}
|
||||
onClick={() => setActiveSport(sport)}
|
||||
className={`px-4 py-1.5 rounded-full text-sm font-medium transition-colors ${
|
||||
resolvedSport === sport
|
||||
? "bg-gray-900 text-white"
|
||||
: "bg-white border border-gray-200 text-gray-600 hover:border-gray-400"
|
||||
}`}
|
||||
>
|
||||
{sport === "Alle" ? "Alle" : capitalize(sport)}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Courses */}
|
||||
{isPending ? (
|
||||
<div className="flex justify-center py-16">
|
||||
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-blue-500" />
|
||||
|
|
|
|||
|
|
@ -195,8 +195,6 @@ const CourseCreateView = ({
|
|||
newErrors.timeTo = "Endzeit muss nach der Anfangszeit liegen";
|
||||
}
|
||||
|
||||
if (trainers.length > 0 && selectedTrainers.length === 0)
|
||||
newErrors.trainers = "Mindestens ein Trainer muss ausgewählt werden";
|
||||
if (!selectedRoom) newErrors.room = "Raum ist erforderlich";
|
||||
|
||||
const maxPart = parseInt(maxParticipants);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue