Add Stargil Nails logo and favicon
- Replace emoji icons with Stargil Nails logo in header and loading spinner - Add favicon.png to public directory - Copy logo to public/assets for browser access - Update vite.config.ts to serve public directory - Add favicon link to HTML head section
This commit is contained in:
@@ -8,10 +8,13 @@ export function AdminAvailability() {
|
||||
const [time, setTime] = useState<string>("09:00");
|
||||
const [duration, setDuration] = useState<number>(30);
|
||||
|
||||
const { data: slots } = useQuery(
|
||||
queryClient.availability.live.byDate.experimental_liveOptions(selectedDate)
|
||||
const { data: allSlots } = useQuery(
|
||||
queryClient.availability.live.list.experimental_liveOptions()
|
||||
);
|
||||
|
||||
const [errorMsg, setErrorMsg] = useState<string>("");
|
||||
const [successMsg, setSuccessMsg] = useState<string>("");
|
||||
|
||||
const { mutate: createSlot, isPending: isCreating } = useMutation(
|
||||
queryClient.availability.create.mutationOptions()
|
||||
);
|
||||
@@ -20,8 +23,38 @@ export function AdminAvailability() {
|
||||
);
|
||||
|
||||
const addSlot = () => {
|
||||
if (!selectedDate || !time || !duration) return;
|
||||
createSlot({ sessionId: localStorage.getItem("sessionId") || "", date: selectedDate, time, durationMinutes: duration });
|
||||
setErrorMsg("");
|
||||
setSuccessMsg("");
|
||||
if (!selectedDate || !time || !duration) {
|
||||
setErrorMsg("Bitte Datum, Uhrzeit und Dauer angeben.");
|
||||
return;
|
||||
}
|
||||
const sessionId = localStorage.getItem("sessionId") || "";
|
||||
if (!sessionId) {
|
||||
setErrorMsg("Nicht eingeloggt. Bitte als Inhaber anmelden.");
|
||||
return;
|
||||
}
|
||||
createSlot(
|
||||
{ sessionId, date: selectedDate, time, durationMinutes: duration },
|
||||
{
|
||||
onSuccess: () => {
|
||||
setSuccessMsg("Slot angelegt.");
|
||||
// advance time to next 30-minute step
|
||||
const [hStr, mStr] = time.split(":");
|
||||
let h = parseInt(hStr, 10);
|
||||
let m = parseInt(mStr, 10);
|
||||
m += 30;
|
||||
if (m >= 60) { h += 1; m -= 60; }
|
||||
if (h >= 24) { h = 0; }
|
||||
const next = `${String(h).padStart(2, "0")}:${String(m).padStart(2, "0")}`;
|
||||
setTime(next);
|
||||
},
|
||||
onError: (err: any) => {
|
||||
const msg = (err && (err.message || (err as any).toString())) || "Fehler beim Anlegen.";
|
||||
setErrorMsg(msg);
|
||||
},
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -58,32 +91,43 @@ export function AdminAvailability() {
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{(errorMsg || successMsg) && (
|
||||
<div className="text-sm">
|
||||
{errorMsg && <div className="text-red-600">{errorMsg}</div>}
|
||||
{successMsg && <div className="text-green-700">{successMsg}</div>}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="space-y-2">
|
||||
<h3 className="font-medium">Slots am {selectedDate}</h3>
|
||||
<h3 className="font-medium">Alle freien Slots</h3>
|
||||
<div className="grid grid-cols-1 gap-2">
|
||||
{slots?.sort((a, b) => a.time.localeCompare(b.time)).map((slot) => (
|
||||
<div key={slot.id} className="flex items-center justify-between border rounded px-3 py-2">
|
||||
<div className="flex items-center gap-3">
|
||||
<span className="font-mono">{slot.time}</span>
|
||||
<span className="text-sm text-gray-600">{slot.durationMinutes} Min</span>
|
||||
<span className={`text-xs px-2 py-1 rounded ${slot.status === "free" ? "bg-green-100 text-green-800" : "bg-yellow-100 text-yellow-800"}`}>
|
||||
{slot.status === "free" ? "frei" : "reserviert"}
|
||||
</span>
|
||||
{allSlots
|
||||
?.filter((s) => s.status === "free")
|
||||
.sort((a, b) => (a.date === b.date ? a.time.localeCompare(b.time) : a.date.localeCompare(b.date)))
|
||||
.map((slot) => (
|
||||
<div key={slot.id} className="flex items-center justify-between border rounded px-3 py-2">
|
||||
<div className="flex items-center gap-3">
|
||||
<span className="text-sm text-gray-600">{slot.date}</span>
|
||||
<span className="font-mono">{slot.time}</span>
|
||||
<span className="text-sm text-gray-600">{slot.durationMinutes} Min</span>
|
||||
<span className={`text-xs px-2 py-1 rounded ${slot.status === "free" ? "bg-green-100 text-green-800" : "bg-yellow-100 text-yellow-800"}`}>
|
||||
{slot.status === "free" ? "frei" : "reserviert"}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<button
|
||||
onClick={() => removeSlot({ sessionId: localStorage.getItem("sessionId") || "", id: slot.id })}
|
||||
className="text-red-600 hover:underline"
|
||||
disabled={slot.status === "reserved"}
|
||||
title={slot.status === "reserved" ? "Slot ist reserviert" : "Slot löschen"}
|
||||
>
|
||||
Löschen
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<button
|
||||
onClick={() => removeSlot({ sessionId: localStorage.getItem("sessionId") || "", id: slot.id })}
|
||||
className="text-red-600 hover:underline"
|
||||
disabled={slot.status === "reserved"}
|
||||
title={slot.status === "reserved" ? "Slot ist reserviert" : "Slot löschen"}
|
||||
>
|
||||
Löschen
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
{slots?.length === 0 && (
|
||||
<div className="text-sm text-gray-600">Keine Slots vorhanden.</div>
|
||||
))}
|
||||
{allSlots?.filter((s) => s.status === "free").length === 0 && (
|
||||
<div className="text-sm text-gray-600">Keine freien Slots vorhanden.</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
Reference in New Issue
Block a user