fixing bugs

This commit is contained in:
Rajpreet0 2026-09-01 22:03:38 +02:00
parent 285eeb1536
commit dad50d1c97
11 changed files with 372 additions and 95 deletions

View file

@ -139,7 +139,7 @@ model Course {
isStandingOrder Boolean @default(false)
frequency String? // daily, weekly, biweekly, monthly, custom
weekdays String[] // For custom frequency
weekdayTimings Json? // { "monday": { "timeFrom": "08:00", "timeTo": "09:00" }, ... }
weekdayTimings Json? // { "monday": [{ "timeFrom": "08:00", "timeTo": "09:00" }, ...], ... } — mehrere Zeitfenster pro Tag. Legacy: { "monday": { "timeFrom", "timeTo" } }
endDate DateTime? // Required when isStandingOrder = true
parentCourseId String? // Null on template; set on every child instance
parentCourse Course? @relation("StandingOrderInstances", fields: [parentCourseId], references: [id], onDelete: Cascade)

View file

@ -1,6 +1,6 @@
import Image from "next/image";
import MultiStepProzess from "./MultiStepProzess";
import Logo from "@/src/public/logo.png";
import Logo from "@/src/public/logo-gelb.png";
import { Card, CardHeader, CardTitle, CardContent, CardFooter } from "@/src/components/ui/card";
import Link from "next/link";

View file

@ -125,11 +125,7 @@ export async function createCourse(data: CourseFormData, status: "draft" | "publ
defaultTimeFrom: validatedData.timeFrom || "",
defaultTimeTo: validatedData.timeTo || "",
weekdays: validatedData.weekdays || [],
weekdayTimings:
(validatedData.weekdayTimings as Record<
string,
{ timeFrom: string; timeTo: string }
>) ?? {},
weekdayTimings: validatedData.weekdayTimings ?? {},
});
if (occurrences.length > 0) {
@ -551,11 +547,7 @@ export async function updateCourse(
defaultTimeFrom: validatedData.timeFrom || "",
defaultTimeTo: validatedData.timeTo || "",
weekdays: validatedData.weekdays || [],
weekdayTimings:
(validatedData.weekdayTimings as Record<
string,
{ timeFrom: string; timeTo: string }
>) ?? {},
weekdayTimings: validatedData.weekdayTimings ?? {},
});
// Das Template selbst wird (wie bei createCourse) nicht als sichtbarer

View file

@ -0,0 +1,181 @@
"use client";
import { useMemo, useState } from "react";
export type WeekdaySlot = { timeFrom: string; timeTo: string };
export type WeekdayTimings = Record<string, WeekdaySlot[]>;
/** Rohform, wie sie aus der DB kommen kann (neues Array- oder altes Einzelobjekt-Format). */
type RawWeekdayTimings =
| Record<string, WeekdaySlot | WeekdaySlot[] | null | undefined>
| null
| undefined;
export const emptySlot = (): WeekdaySlot => ({ timeFrom: "", timeTo: "" });
/**
* Bringt gespeicherte weekdayTimings in das Array-Format. Akzeptiert sowohl das
* neue Format ({ monday: [{...}] }) als auch das alte Einzelobjekt-Format
* ({ monday: { timeFrom, timeTo } }) aus bestehenden DB-Datensätzen.
*/
export const normalizeWeekdayTimings = (raw: RawWeekdayTimings): WeekdayTimings => {
if (!raw) return {};
const result: WeekdayTimings = {};
for (const [day, value] of Object.entries(raw)) {
if (Array.isArray(value)) {
const slots = value.filter((s): s is WeekdaySlot => !!s && typeof s === "object");
if (slots.length > 0) result[day] = slots;
} else if (value && typeof value === "object") {
result[day] = [{ timeFrom: value.timeFrom ?? "", timeTo: value.timeTo ?? "" }];
}
}
return result;
};
/** Ermittelt ein Standard-Zeitfenster, falls alle Tage genau ein identisches haben. */
const deriveDefaultSlot = (timings: WeekdayTimings): WeekdaySlot => {
const daySlots = Object.values(timings);
if (daySlots.length === 0) return emptySlot();
const first = daySlots[0][0];
if (!first) return emptySlot();
const allSame = daySlots.every(
(slots) =>
slots.length === 1 && slots[0].timeFrom === first.timeFrom && slots[0].timeTo === first.timeTo
);
return allSame ? { ...first } : emptySlot();
};
interface UseWeekdayTimingsOptions {
/** Vorbelegung (Edit-Modus) — beliebiges gespeichertes Format. */
initialWeekdays?: string[];
initialTimings?: RawWeekdayTimings;
}
/**
* Kapselt die Wochentag-Auswahl und deren Zeitfenster für die "custom"-Häufigkeit
* eines Dauerauftrags. Jeder Tag kann mehrere Zeitfenster haben; ein
* Standard-Zeitfenster kann auf alle Tage übertragen werden.
*/
export function useWeekdayTimings({
initialWeekdays = [],
initialTimings,
}: UseWeekdayTimingsOptions = {}) {
const [selectedWeekdays, setSelectedWeekdays] = useState<string[]>(initialWeekdays);
const [weekdayTimings, setWeekdayTimings] = useState<WeekdayTimings>(() =>
normalizeWeekdayTimings(initialTimings)
);
const [defaultSlot, setDefaultSlot] = useState<WeekdaySlot>(() =>
deriveDefaultSlot(normalizeWeekdayTimings(initialTimings))
);
const isDefaultSlotFilled = !!defaultSlot.timeFrom && !!defaultSlot.timeTo;
const reset = () => {
setSelectedWeekdays([]);
setWeekdayTimings({});
setDefaultSlot(emptySlot());
};
// Hält weekdayTimings mit der Tagesauswahl synchron: jeder ausgewählte Tag
// bekommt mindestens einen (ggf. leeren) Slot, abgewählte Tage fallen raus.
const syncWeekdays = (days: string[]) => {
setSelectedWeekdays(days);
setWeekdayTimings((prev) => {
const next: WeekdayTimings = {};
days.forEach((d) => {
next[d] = prev[d]?.length ? prev[d] : [{ ...defaultSlot }];
});
return next;
});
};
// Standard-Zeitfenster auf alle ausgewählten Tage übertragen (ersetzt deren Slots).
const applyDefaultToAll = () => {
if (!isDefaultSlotFilled || selectedWeekdays.length === 0) return;
setWeekdayTimings(() => {
const next: WeekdayTimings = {};
selectedWeekdays.forEach((d) => {
next[d] = [{ ...defaultSlot }];
});
return next;
});
};
const updateDefaultSlot = (patch: Partial<WeekdaySlot>) => {
const updated = { ...defaultSlot, ...patch };
setDefaultSlot(updated);
// Solange noch kein Tag eine eigene Uhrzeit gesetzt hat, die Änderung direkt
// auf alle ausgewählten Tage spiegeln (initiale Bequemlichkeit).
const noDayCustomized = selectedWeekdays.every((d) =>
(weekdayTimings[d] ?? []).every((s) => !s.timeFrom && !s.timeTo)
);
if (noDayCustomized && selectedWeekdays.length > 0) {
setWeekdayTimings(() => {
const next: WeekdayTimings = {};
selectedWeekdays.forEach((d) => {
next[d] = [{ ...updated }];
});
return next;
});
}
};
const updateSlot = (day: string, index: number, patch: Partial<WeekdaySlot>) => {
setWeekdayTimings((prev) => {
const slots = (prev[day] ?? [emptySlot()]).map((s, i) =>
i === index ? { ...s, ...patch } : s
);
return { ...prev, [day]: slots };
});
};
const addSlot = (day: string) => {
setWeekdayTimings((prev) => ({
...prev,
[day]: [...(prev[day] ?? []), emptySlot()],
}));
};
const removeSlot = (day: string, index: number) => {
setWeekdayTimings((prev) => {
const slots = (prev[day] ?? []).filter((_, i) => i !== index);
return { ...prev, [day]: slots.length ? slots : [emptySlot()] };
});
};
/** Nur befüllte Slots; Tage ohne gültige Uhrzeit fallen weg. `undefined`, wenn leer. */
const cleanedTimings = useMemo((): WeekdayTimings | undefined => {
const cleaned: WeekdayTimings = {};
for (const [day, slots] of Object.entries(weekdayTimings)) {
const filled = slots.filter((s) => s.timeFrom || s.timeTo);
if (filled.length > 0) cleaned[day] = filled;
}
return Object.keys(cleaned).length > 0 ? cleaned : undefined;
}, [weekdayTimings]);
/** True, wenn ein ausgewählter Tag kein oder ein ungültiges Zeitfenster hat. */
const hasInvalidTiming = useMemo(
() =>
selectedWeekdays.some((day) => {
const slots = weekdayTimings[day] ?? [];
if (slots.length === 0) return true;
return slots.some((s) => !s.timeFrom || !s.timeTo || s.timeFrom >= s.timeTo);
}),
[selectedWeekdays, weekdayTimings]
);
return {
selectedWeekdays,
weekdayTimings,
defaultSlot,
isDefaultSlotFilled,
cleanedTimings,
hasInvalidTiming,
reset,
syncWeekdays,
applyDefaultToAll,
updateDefaultSlot,
updateSlot,
addSlot,
removeSlot,
};
}

View file

@ -5,12 +5,26 @@ const frequencyEnum = z.enum(VALID_FREQUENCIES as [string, ...string[]], {
message: "Ungültige Häufigkeit",
});
const weekdaySlotSchema = z.object({ timeFrom: z.string(), timeTo: z.string() });
// Neues Format: mehrere Zeitfenster pro Tag. Das alte Einzelobjekt-Format
// ({ monday: { timeFrom, timeTo } }) wird für Abwärtskompatibilität weiterhin
// akzeptiert und in ein Array normalisiert.
const weekdayTimingsSchema = z
.record(z.string(), z.object({ timeFrom: z.string(), timeTo: z.string() }))
.record(z.string(), z.union([z.array(weekdaySlotSchema), weekdaySlotSchema]))
.transform((timings) => {
const normalized: Record<string, { timeFrom: string; timeTo: string }[]> = {};
for (const [day, value] of Object.entries(timings)) {
const slots = Array.isArray(value) ? value : [value];
const filled = slots.filter((s) => s.timeFrom || s.timeTo);
if (filled.length > 0) normalized[day] = filled;
}
return normalized;
})
.refine(
(timings) =>
Object.values(timings).every(
({ timeFrom, timeTo }) => !timeFrom || !timeTo || timeFrom < timeTo
Object.values(timings).every((slots) =>
slots.every(({ timeFrom, timeTo }) => !timeFrom || !timeTo || timeFrom < timeTo)
),
{ message: "Endzeit muss nach der Anfangszeit liegen (Wochentag-Uhrzeiten)" }
)

View file

@ -14,7 +14,11 @@ export type Course = {
isStandingOrder: boolean;
frequency: string | null;
weekdays: string[];
weekdayTimings: Record<string, { timeFrom: string; timeTo: string }> | null;
weekdayTimings:
| Record<string, { timeFrom: string; timeTo: string }[]>
// Altes Format, kann noch in bestehenden DB-Datensätzen liegen
| Record<string, { timeFrom: string; timeTo: string }>
| null;
endDate: Date | null;
parentCourseId: string | null;
status: string;

View file

@ -13,7 +13,7 @@ import { getMyRooms } from "@/src/modules/rooms/actions/room-actions";
import { getMyTrainers } from "@/src/modules/employee/actions/employee-actions";
import { toast } from "sonner";
import { Course } from "../../types/course.types";
import { Trash2, ImagePlus, X, Loader2, AlertTriangle } from "lucide-react";
import { Trash2, ImagePlus, X, Loader2, AlertTriangle, Plus, CopyCheck } from "lucide-react";
import Image from "next/image";
import DeleteDialog from "@/src/components/layout/DeleteDialog";
import {
@ -28,6 +28,7 @@ import {
import { Progress } from "@/src/components/ui/progress";
import { downsizeImage } from "@/src/lib/image/downsize";
import { useDeleteCourse } from "../../hooks/useDeleteCourse";
import { useWeekdayTimings, type WeekdaySlot } from "../../hooks/useWeekdayTimings";
import {
generateOccurrences,
TooManyInstancesError,
@ -38,8 +39,6 @@ import {
type StandingOrderScope,
} from "../components/StandingOrderScopeDialog";
type WeekdayTiming = { timeFrom: string; timeTo: string };
const LEVELS = [
{ value: "any", label: "Jedes Niveau" },
{ value: "beginner", label: "Anfänger" },
@ -124,15 +123,14 @@ const CourseCreateView = ({
const [selectedFrequency, setSelectedFrequency] = useState(
isEdit ? initialData!.frequency || "" : ""
);
const [selectedWeekdays, setSelectedWeekdays] = useState<string[]>(
isEdit ? initialData!.weekdays || [] : []
);
const [weekdayTimings, setWeekdayTimings] = useState<Record<string, WeekdayTiming>>(() => {
if (isEdit && initialData!.weekdayTimings) {
return initialData!.weekdayTimings as Record<string, WeekdayTiming>;
}
return {};
const weekdays = useWeekdayTimings({
initialWeekdays: isEdit ? initialData!.weekdays || [] : [],
initialTimings:
isEdit && initialData!.weekdayTimings
? (initialData!.weekdayTimings as Record<string, WeekdaySlot | WeekdaySlot[]>)
: undefined,
});
const { selectedWeekdays, weekdayTimings, defaultSlot, isDefaultSlotFilled } = weekdays;
const [endDate, setEndDate] = useState(() => {
if (isEdit && initialData!.endDate) {
const d = new Date(initialData!.endDate);
@ -300,13 +298,13 @@ const CourseCreateView = ({
if (selectedFrequency === "custom" && selectedWeekdays.length === 0) {
newErrors.weekdays = "Mindestens ein Tag muss ausgewählt werden";
}
if (selectedFrequency === "custom" && selectedWeekdays.length > 0) {
const hasInvalidTiming = selectedWeekdays.some((day) => {
const t = weekdayTimings[day];
return !t?.timeFrom || !t?.timeTo || t.timeFrom >= t.timeTo;
});
if (hasInvalidTiming)
newErrors.weekdayTimings = "Bitte gültige Uhrzeiten für alle Wochentage eingeben";
if (
selectedFrequency === "custom" &&
selectedWeekdays.length > 0 &&
weekdays.hasInvalidTiming
) {
newErrors.weekdayTimings =
"Bitte gültige Uhrzeiten für alle Zeitfenster eingeben (Ende nach Beginn)";
}
if (!newErrors.endDate && selectedFrequency && courseDate && endDate) {
try {
@ -355,7 +353,7 @@ const CourseCreateView = ({
isStandingOrder,
frequency: selectedFrequency || undefined,
weekdays: selectedWeekdays.length > 0 ? selectedWeekdays : undefined,
weekdayTimings: Object.keys(weekdayTimings).length > 0 ? weekdayTimings : undefined,
weekdayTimings: weekdays.cleanedTimings,
endDate: endDate || undefined,
businessId: selectedBusinessId || undefined,
});
@ -699,9 +697,8 @@ const CourseCreateView = ({
setIsStandingOrder(checked === true);
if (!checked) {
setSelectedFrequency("");
setSelectedWeekdays([]);
setWeekdayTimings({});
setEndDate("");
weekdays.reset();
}
}}
/>
@ -718,8 +715,7 @@ const CourseCreateView = ({
onSelect={(value) => {
setSelectedFrequency(value);
if (value !== "custom") {
setSelectedWeekdays([]);
setWeekdayTimings({});
weekdays.reset();
}
}}
options={FREQUENCIES}
@ -743,58 +739,108 @@ const CourseCreateView = ({
<MultiSelectDropdown
label="An welchen Tagen soll der Kurs stattfinden?"
selected={selectedWeekdays}
onSelect={(days) => {
setSelectedWeekdays(days);
// Nicht mehr ausgewählte Tage aus weekdayTimings entfernen
setWeekdayTimings((prev) => {
const next: Record<string, WeekdayTiming> = {};
days.forEach((d) => {
next[d] = prev[d] ?? { timeFrom: "", timeTo: "" };
});
return next;
});
}}
onSelect={weekdays.syncWeekdays}
options={WEEKDAYS}
error={errors.weekdays}
/>
{selectedWeekdays.length > 0 && (
<div className="space-y-3">
<p className="text-sm font-medium">Uhrzeiten pro Tag</p>
{selectedWeekdays.map((day) => {
const dayLabel = WEEKDAYS.find((w) => w.value === day)?.label ?? day;
return (
<div key={day} className="flex items-center gap-3">
<span className="w-24 text-sm shrink-0">{dayLabel}</span>
<InputComponent
type="time"
id={`weekday-time-from-${day}`}
value={weekdayTimings[day]?.timeFrom ?? ""}
onChange={(e) =>
setWeekdayTimings((prev) => ({
...prev,
[day]: { ...prev[day], timeFrom: e.target.value },
}))
}
/>
<span className="text-sm">–</span>
<InputComponent
type="time"
id={`weekday-time-to-${day}`}
value={weekdayTimings[day]?.timeTo ?? ""}
onChange={(e) =>
setWeekdayTimings((prev) => ({
...prev,
[day]: { ...prev[day], timeTo: e.target.value },
}))
}
/>
</div>
);
})}
{errors.weekdayTimings && (
<p className="text-red-500 text-sm">{errors.weekdayTimings}</p>
)}
<div className="space-y-4">
{/* Standard-Zeitfenster mit "für alle übernehmen" */}
<div className="rounded-lg border border-gray-200 bg-gray-50 p-3">
<p className="text-sm font-medium mb-2">Standard-Zeitfenster</p>
<div className="flex flex-wrap items-center gap-3">
<InputComponent
type="time"
id="weekday-default-time-from"
value={defaultSlot.timeFrom}
onChange={(e) => weekdays.updateDefaultSlot({ timeFrom: e.target.value })}
/>
<span className="text-sm">–</span>
<InputComponent
type="time"
id="weekday-default-time-to"
value={defaultSlot.timeTo}
onChange={(e) => weekdays.updateDefaultSlot({ timeTo: e.target.value })}
/>
<button
type="button"
onClick={weekdays.applyDefaultToAll}
disabled={!isDefaultSlotFilled}
className="inline-flex items-center gap-1.5 rounded-md border border-gray-300 bg-white px-2.5 py-1.5 text-sm text-gray-700 hover:bg-gray-100 disabled:opacity-40 disabled:cursor-not-allowed"
>
<CopyCheck size={14} />
Für alle Tage übernehmen
</button>
</div>
<p className="text-xs text-gray-500 mt-2">
Wird automatisch für alle Tage gesetzt, solange du keine Uhrzeit einzeln
anpasst.
</p>
</div>
<div className="space-y-3">
<p className="text-sm font-medium">Uhrzeiten pro Tag</p>
{selectedWeekdays.map((day) => {
const dayLabel = WEEKDAYS.find((w) => w.value === day)?.label ?? day;
const slots: WeekdaySlot[] = weekdayTimings[day]?.length
? weekdayTimings[day]
: [{ timeFrom: "", timeTo: "" }];
return (
<div key={day} className="flex gap-3">
<span className="w-24 text-sm shrink-0 pt-2.5">{dayLabel}</span>
<div className="flex flex-col gap-2">
{slots.map((slot, index) => (
<div key={index} className="flex items-center gap-3">
<InputComponent
type="time"
id={`weekday-time-from-${day}-${index}`}
value={slot.timeFrom}
onChange={(e) =>
weekdays.updateSlot(day, index, {
timeFrom: e.target.value,
})
}
/>
<span className="text-sm">–</span>
<InputComponent
type="time"
id={`weekday-time-to-${day}-${index}`}
value={slot.timeTo}
onChange={(e) =>
weekdays.updateSlot(day, index, {
timeTo: e.target.value,
})
}
/>
{slots.length > 1 && (
<button
type="button"
onClick={() => weekdays.removeSlot(day, index)}
aria-label="Zeitfenster entfernen"
className="text-gray-400 hover:text-red-500"
>
<X size={16} />
</button>
)}
</div>
))}
<button
type="button"
onClick={() => weekdays.addSlot(day)}
className="inline-flex w-fit items-center gap-1 text-sm text-blue hover:underline"
>
<Plus size={14} />
Weiteres Zeitfenster
</button>
</div>
</div>
);
})}
{errors.weekdayTimings && (
<p className="text-red-500 text-sm">{errors.weekdayTimings}</p>
)}
</div>
</div>
)}
</>

View file

@ -1,6 +1,18 @@
export type FrequencyType = "daily" | "weekly" | "biweekly" | "monthly" | "custom";
export type WeekdayTiming = { timeFrom: string; timeTo: string };
export type WeekdaySlot = { timeFrom: string; timeTo: string };
/**
* Uhrzeiten pro Wochentag. Jeder Tag kann mehrere Zeitfenster haben
* (z.B. Montag 10:00–11:00 UND 18:00–19:00).
*/
export type WeekdayTimings = Record<string, WeekdaySlot[]>;
/**
* Altes Format, das noch in der DB liegen kann: genau ein Zeitfenster pro Tag.
* Wird von {@link normalizeWeekdayTimings} in das neue Array-Format überführt.
*/
export type LegacyWeekdayTiming = { timeFrom: string; timeTo: string };
export type CourseOccurrence = { date: Date; timeFrom: string; timeTo: string };
@ -32,6 +44,27 @@ const WEEKDAY_MAP: Record<string, number> = {
const toWeekdayNumber = (weekday: string): number | undefined =>
WEEKDAY_MAP[weekday.trim().toLowerCase()];
/**
* Bringt beliebige gespeicherte weekdayTimings in das Array-Format.
* Akzeptiert das neue Format (`{ monday: [{...}, {...}] }`) und das alte
* Einzelobjekt-Format (`{ monday: { timeFrom, timeTo } }`).
*/
export function normalizeWeekdayTimings(
raw: Record<string, WeekdaySlot[] | LegacyWeekdayTiming | null | undefined> | null | undefined
): WeekdayTimings {
if (!raw) return {};
const result: WeekdayTimings = {};
for (const [day, value] of Object.entries(raw)) {
if (Array.isArray(value)) {
const slots = value.filter((s): s is WeekdaySlot => !!s && typeof s === "object");
if (slots.length > 0) result[day] = slots;
} else if (value && typeof value === "object") {
result[day] = [{ timeFrom: value.timeFrom ?? "", timeTo: value.timeTo ?? "" }];
}
}
return result;
}
export const MAX_INSTANCES = 365;
export class TooManyInstancesError extends Error {
@ -50,7 +83,7 @@ export function generateOccurrences(params: {
defaultTimeFrom: string;
defaultTimeTo: string;
weekdays?: string[];
weekdayTimings?: Record<string, WeekdayTiming>;
weekdayTimings?: Record<string, WeekdaySlot[] | LegacyWeekdayTiming | null | undefined>;
}): CourseOccurrence[] {
const {
startDate,
@ -102,10 +135,11 @@ export function generateOccurrences(params: {
const targetDays = new Set(
weekdays.map(toWeekdayNumber).filter((d): d is number => d !== undefined)
);
const timingsByDay = Object.entries(weekdayTimings).reduce<Record<number, WeekdayTiming>>(
(acc, [weekday, timing]) => {
const normalized = normalizeWeekdayTimings(weekdayTimings);
const slotsByDay = Object.entries(normalized).reduce<Record<number, WeekdaySlot[]>>(
(acc, [weekday, slots]) => {
const day = toWeekdayNumber(weekday);
if (day !== undefined) acc[day] = timing;
if (day !== undefined) acc[day] = slots;
return acc;
},
{}
@ -113,8 +147,14 @@ export function generateOccurrences(params: {
const cursor = new Date(startDate);
while (cursor <= endDate) {
if (targetDays.has(cursor.getDay())) {
const timing = timingsByDay[cursor.getDay()];
addOccurrence(cursor, timing?.timeFrom ?? defaultTimeFrom, timing?.timeTo ?? defaultTimeTo);
const slots = slotsByDay[cursor.getDay()];
if (slots && slots.length > 0) {
for (const slot of slots) {
addOccurrence(cursor, slot.timeFrom || defaultTimeFrom, slot.timeTo || defaultTimeTo);
}
} else {
addOccurrence(cursor, defaultTimeFrom, defaultTimeTo);
}
}
cursor.setDate(cursor.getDate() + 1);
}

View file

@ -117,7 +117,7 @@ const DashboardSidebar = ({ userData, displayName }: DashboardSidebarProps) => {
return (
<Sidebar className="border-r-0 bg-white/55 backdrop-blur-xl supports-backdrop-filter:bg-white/45 shadow-[1px_0_0_0_oklch(0.85_0_0/30%)]">
<SidebarHeader className="text-sidebar-accent-foreground">
<Link href="/" className="flex justify-center items-center gap-2 px-2 pt-2">
<Link href="/" className="flex items-center gap-2 px-2 pt-2">
<Image src={Logo} height={45} alt="Kumasy Logo" />
</Link>
</SidebarHeader>

View file

@ -29,7 +29,7 @@ const settingsMenuTitles: SettingsMenuItem[] = [
},
{
icon: Building2,
name: "Business Einsetllungen",
name: "Business Einstellungen",
href: "/settings/business",
description: "Verwalte deine Business Daten",
},

BIN
src/public/logo-gelb.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB