From 63a402b3ad3dda26b8e3e0013be677bcff94881f Mon Sep 17 00:00:00 2001 From: Quests Agent Date: Mon, 29 Sep 2025 18:01:00 +0200 Subject: [PATCH] =?UTF-8?q?I=C2=B4d=20like=20to=20create=20a=20booking=20p?= =?UTF-8?q?latform=20for=20a=20beauty=20shop=20(nail=20design).=20the=20cu?= =?UTF-8?q?stomer=20shall=20be=20able=20to=20book=20a=20treatment.=20an=20?= =?UTF-8?q?admin=20backend=20is=20needed=20to=20manage=20articles=20and=20?= =?UTF-8?q?their=20durations.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- quests.json | 16 +- src/client/app.tsx | 129 ++++++++- src/client/components/admin-bookings.tsx | 213 ++++++++++++++ src/client/components/admin-treatments.tsx | 270 ++++++++++++++++++ src/client/components/booking-form.tsx | 225 +++++++++++++++ src/client/components/initial-data-loader.tsx | 69 +++++ src/server/rpc/bookings.ts | 96 +++++++ src/server/rpc/index.ts | 14 +- src/server/rpc/treatments.ts | 63 ++++ 9 files changed, 1068 insertions(+), 27 deletions(-) create mode 100644 src/client/components/admin-bookings.tsx create mode 100644 src/client/components/admin-treatments.tsx create mode 100644 src/client/components/booking-form.tsx create mode 100644 src/client/components/initial-data-loader.tsx create mode 100644 src/server/rpc/bookings.ts create mode 100644 src/server/rpc/treatments.ts diff --git a/quests.json b/quests.json index d5fef4a..8a8c964 100644 --- a/quests.json +++ b/quests.json @@ -1,8 +1,8 @@ -{ - "name": "New Project", - "description": "", - "icon": { - "lucide": "square-dashed", - "background": "conic-gradient(from 42deg at 50% 50%, #18181b, #27272a)" - } -} +{ + "description": "", + "icon": { + "background": "conic-gradient(from 42deg at 50% 50%, #ffd60a, #26cd41)", + "lucide": "calendar" + }, + "name": "Beauty Shop Booking Platform" +} \ No newline at end of file diff --git a/src/client/app.tsx b/src/client/app.tsx index de11294..29abc67 100644 --- a/src/client/app.tsx +++ b/src/client/app.tsx @@ -1,14 +1,115 @@ -function App() { - return ( -
- {/* Replace this placeholder content with your app components */} -
-

- Building your new project... -

-
-
- ); -} - -export default App; +import { useState } from "react"; +import { BookingForm } from "@/client/components/booking-form"; +import { AdminTreatments } from "@/client/components/admin-treatments"; +import { AdminBookings } from "@/client/components/admin-bookings"; +import { InitialDataLoader } from "@/client/components/initial-data-loader"; + +function App() { + const [activeTab, setActiveTab] = useState<"booking" | "admin-treatments" | "admin-bookings">("booking"); + + const tabs = [ + { id: "booking", label: "Book Appointment", icon: "📅" }, + { id: "admin-treatments", label: "Manage Treatments", icon: "💅" }, + { id: "admin-bookings", label: "Manage Bookings", icon: "📋" }, + ] as const; + + return ( +
+ + + {/* Header */} +
+
+
+
+
💅
+
+

Bella Nails Studio

+

Professional Nail Design & Care

+
+
+
+
+
+ + {/* Navigation */} + + + {/* Main Content */} +
+ {activeTab === "booking" && ( +
+
+

+ Book Your Perfect Nail Treatment +

+

+ Experience professional nail care with our expert technicians. + Choose from our wide range of treatments and book your appointment today. +

+
+ +
+ )} + + {activeTab === "admin-treatments" && ( +
+
+

+ Treatment Management +

+

+ Add, edit, and manage your nail treatment services. +

+
+ +
+ )} + + {activeTab === "admin-bookings" && ( +
+
+

+ Booking Management +

+

+ View and manage customer appointments and bookings. +

+
+ +
+ )} +
+ + {/* Footer */} +
+
+
+

© 2024 Bella Nails Studio. Professional nail care services.

+
+
+
+
+ ); +} + +export default App; diff --git a/src/client/components/admin-bookings.tsx b/src/client/components/admin-bookings.tsx new file mode 100644 index 0000000..7f39d1c --- /dev/null +++ b/src/client/components/admin-bookings.tsx @@ -0,0 +1,213 @@ +import { useState } from "react"; +import { useMutation, useQuery } from "@tanstack/react-query"; +import { queryClient } from "@/client/rpc-client"; + +export function AdminBookings() { + const [selectedDate, setSelectedDate] = useState(new Date().toISOString().split('T')[0]); + + const { data: bookings } = useQuery( + queryClient.bookings.live.list.experimental_liveOptions() + ); + + const { data: treatments } = useQuery( + queryClient.treatments.live.list.experimental_liveOptions() + ); + + const { mutate: updateBookingStatus } = useMutation( + queryClient.bookings.updateStatus.mutationOptions() + ); + + const getTreatmentName = (treatmentId: string) => { + return treatments?.find(t => t.id === treatmentId)?.name || "Unknown Treatment"; + }; + + const getStatusColor = (status: string) => { + switch (status) { + case "pending": return "bg-yellow-100 text-yellow-800"; + case "confirmed": return "bg-green-100 text-green-800"; + case "cancelled": return "bg-red-100 text-red-800"; + case "completed": return "bg-blue-100 text-blue-800"; + default: return "bg-gray-100 text-gray-800"; + } + }; + + const filteredBookings = bookings?.filter(booking => + selectedDate ? booking.appointmentDate === selectedDate : true + ).sort((a, b) => { + if (a.appointmentDate === b.appointmentDate) { + return a.appointmentTime.localeCompare(b.appointmentTime); + } + return a.appointmentDate.localeCompare(b.appointmentDate); + }); + + const upcomingBookings = bookings?.filter(booking => { + const bookingDate = new Date(booking.appointmentDate); + const today = new Date(); + today.setHours(0, 0, 0, 0); + return bookingDate >= today && booking.status !== "cancelled"; + }).sort((a, b) => { + if (a.appointmentDate === b.appointmentDate) { + return a.appointmentTime.localeCompare(b.appointmentTime); + } + return a.appointmentDate.localeCompare(b.appointmentDate); + }); + + return ( +
+

Manage Bookings

+ + {/* Quick Stats */} +
+
+
+ {bookings?.filter(b => b.status === "pending").length || 0} +
+
Pending Approval
+
+
+
+ {bookings?.filter(b => b.status === "confirmed").length || 0} +
+
Confirmed
+
+
+
+ {upcomingBookings?.length || 0} +
+
Upcoming
+
+
+
+ {bookings?.length || 0} +
+
Total Bookings
+
+
+ + {/* Date Filter */} +
+
+ + setSelectedDate(e.target.value)} + className="p-2 border border-gray-300 rounded-md focus:ring-2 focus:ring-pink-500 focus:border-pink-500" + /> + +
+
+ + {/* Bookings Table */} +
+ + + + + + + + + + + + {filteredBookings?.map((booking) => ( + + + + + + + + ))} + +
+ Customer + + Treatment + + Date & Time + + Status + + Actions +
+
+
{booking.customerName}
+
{booking.customerEmail}
+
{booking.customerPhone}
+
+
+
{getTreatmentName(booking.treatmentId)}
+ {booking.notes && ( +
Notes: {booking.notes}
+ )} +
+
{new Date(booking.appointmentDate).toLocaleDateString()}
+
{booking.appointmentTime}
+
+ + {booking.status} + + +
+ {booking.status === "pending" && ( + <> + + + + )} + {booking.status === "confirmed" && ( + <> + + + + )} + {(booking.status === "cancelled" || booking.status === "completed") && ( + + )} +
+
+ + {!filteredBookings?.length && ( +
+ {selectedDate + ? `No bookings found for ${new Date(selectedDate).toLocaleDateString()}` + : "No bookings available." + } +
+ )} +
+
+ ); +} \ No newline at end of file diff --git a/src/client/components/admin-treatments.tsx b/src/client/components/admin-treatments.tsx new file mode 100644 index 0000000..8c03b65 --- /dev/null +++ b/src/client/components/admin-treatments.tsx @@ -0,0 +1,270 @@ +import { useState } from "react"; +import { useMutation, useQuery } from "@tanstack/react-query"; +import { queryClient } from "@/client/rpc-client"; + +export function AdminTreatments() { + const [showForm, setShowForm] = useState(false); + const [editingTreatment, setEditingTreatment] = useState(null); + const [formData, setFormData] = useState({ + name: "", + description: "", + duration: 60, + price: 5000, // $50.00 in cents + category: "Manicure", + }); + + const { data: treatments } = useQuery( + queryClient.treatments.live.list.experimental_liveOptions() + ); + + const { mutate: createTreatment, isPending: isCreating } = useMutation( + queryClient.treatments.create.mutationOptions() + ); + + const { mutate: updateTreatment, isPending: isUpdating } = useMutation( + queryClient.treatments.update.mutationOptions() + ); + + const { mutate: deleteTreatment } = useMutation( + queryClient.treatments.remove.mutationOptions() + ); + + const categories = ["Manicure", "Pedicure", "Nail Art", "Extensions", "Other"]; + + const handleSubmit = (e: React.FormEvent) => { + e.preventDefault(); + + if (editingTreatment) { + updateTreatment({ + id: editingTreatment.id, + ...formData, + }, { + onSuccess: () => { + setEditingTreatment(null); + setShowForm(false); + resetForm(); + } + }); + } else { + createTreatment(formData, { + onSuccess: () => { + setShowForm(false); + resetForm(); + } + }); + } + }; + + const resetForm = () => { + setFormData({ + name: "", + description: "", + duration: 60, + price: 5000, + category: "Manicure", + }); + }; + + const handleEdit = (treatment: any) => { + setEditingTreatment(treatment); + setFormData({ + name: treatment.name, + description: treatment.description, + duration: treatment.duration, + price: treatment.price, + category: treatment.category, + }); + setShowForm(true); + }; + + const handleCancel = () => { + setShowForm(false); + setEditingTreatment(null); + resetForm(); + }; + + return ( +
+
+

Manage Treatments

+ +
+ + {showForm && ( +
+

+ {editingTreatment ? "Edit Treatment" : "Add New Treatment"} +

+ +
+
+
+ + setFormData({...formData, name: e.target.value})} + className="w-full p-3 border border-gray-300 rounded-md focus:ring-2 focus:ring-pink-500 focus:border-pink-500" + required + /> +
+
+ + +
+
+ +
+ +