From 22183a8d599be61a09e1fc86c4b63149c76acaa5 Mon Sep 17 00:00:00 2001 From: elpatron Date: Mon, 12 Jan 2026 23:16:23 +0100 Subject: [PATCH] feat: allow sitters to mark jobs as completed with notification --- .../[planId]/_components/plan-dashboard.tsx | 59 +++++++++++++++---- app/actions/booking.ts | 32 ++++++++++ dictionaries/de.json | 7 ++- dictionaries/en.json | 11 ++-- .../migration.sql | 2 + prisma/schema.prisma | 1 + 6 files changed, 93 insertions(+), 19 deletions(-) create mode 100644 prisma/migrations/20260112221434_add_booking_completed_at/migration.sql diff --git a/app/[lang]/dashboard/[planId]/_components/plan-dashboard.tsx b/app/[lang]/dashboard/[planId]/_components/plan-dashboard.tsx index fd14cbe..a29dadf 100644 --- a/app/[lang]/dashboard/[planId]/_components/plan-dashboard.tsx +++ b/app/[lang]/dashboard/[planId]/_components/plan-dashboard.tsx @@ -3,7 +3,7 @@ import { useState, useEffect } from "react" import { format, eachDayOfInterval, isSameDay } from "date-fns" import { de, enUS } from "date-fns/locale" -import { CalendarIcon, User, Home, X, Info, Utensils, Trash2 } from "lucide-react" +import { CalendarIcon, User, Home, X, Info, Utensils, Trash2, Check } from "lucide-react" import { toast } from "sonner" import { Button } from "@/components/ui/button" @@ -19,7 +19,7 @@ import { import { Input } from "@/components/ui/input" import { Label } from "@/components/ui/label" import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group" -import { createBooking, deleteBooking } from "@/app/actions/booking" +import { createBooking, deleteBooking, completeBooking } from "@/app/actions/booking" import { PlanSettings } from "@/components/plan-settings" type Booking = { @@ -27,6 +27,7 @@ type Booking = { date: Date sitterName: string | null type: string + completedAt?: Date | string | null } type Plan = { @@ -59,6 +60,15 @@ export function PlanDashboard({ plan, dict, settingsDict, lang }: PlanDashboardP const [cancelReason, setCancelReason] = useState("") const [isSubmitting, setIsSubmitting] = useState(false) + const handleComplete = async (bookingId: number) => { + try { + await completeBooking(bookingId, plan.id, lang) + toast.success(dict.bookedSuccess) // reuse for now or add new toast + } catch (error) { + toast.error(dict.bookError) + } + } + const dateLocale = lang === "de" ? de : enUS // Load saved name from localStorage @@ -194,17 +204,40 @@ export function PlanDashboard({ plan, dict, settingsDict, lang }: PlanDashboardP {booking ? ( -
- {isOwnerHome ? ( - <> - - {dict.ownerHome} - - ) : ( - <> - - {booking.sitterName} - +
+
+ {isOwnerHome ? ( + <> + + {dict.ownerHome} + + ) : ( + <> + + {booking.sitterName} + + )} +
+ + {booking.type === "SITTER" && ( +
+ {booking.completedAt ? ( +
+ + {dict.jobDone} +
+ ) : ( + + )} +
)}
) : ( diff --git a/app/actions/booking.ts b/app/actions/booking.ts index 27539d4..fb3ac86 100644 --- a/app/actions/booking.ts +++ b/app/actions/booking.ts @@ -82,3 +82,35 @@ export async function deleteBooking(bookingId: number, planId: string, lang: str revalidatePath(`/${lang}/dashboard/${planId}`) } + +export async function completeBooking(bookingId: number, planId: string, lang: string = "en") { + const dict = await getDictionary(lang as any) + + const booking = await prisma.booking.findUnique({ + where: { id: bookingId }, + include: { plan: true } + }) + + if (!booking) return + + await prisma.booking.update({ + where: { id: bookingId }, + data: { completedAt: new Date() } + }) + + if (booking.plan.webhookUrl) { + const host = (await headers()).get("host") + const protocol = host?.includes("localhost") ? "http" : "https" + const planUrl = `${protocol}://${host}/${lang}/dashboard/${planId}` + + const dateStr = booking.date.toLocaleDateString(lang) + const message = dict.notifications.completed + .replace("{name}", booking.sitterName || "Someone") + .replace("{date}", dateStr) + .replace("{url}", planUrl) + + await sendNotification(booking.plan.webhookUrl, message) + } + + revalidatePath(`/${lang}/dashboard/${planId}`) +} diff --git a/dictionaries/de.json b/dictionaries/de.json index 0225e2f..a5f3e86 100644 --- a/dictionaries/de.json +++ b/dictionaries/de.json @@ -55,7 +55,9 @@ "cancel": "Abbrechen", "cancelSuccess": "Eintrag entfernt", "cancelError": "Entfernen fehlgeschlagen", - "noInstructions": "Noch keine spezifischen Instruktionen hinterlegt." + "noInstructions": "Noch keine spezifischen Instruktionen hinterlegt.", + "markDone": "Job erledigt?", + "jobDone": "Job erledigt!" }, "login": { "title": "Passwort eingeben", @@ -82,6 +84,7 @@ "ownerHome": "🏠 BESITZER ZU HAUSE: Markiert für den {date}.\nPlan: {url}", "newBooking": "✅ NEUE BUCHUNG: {name} sittet am {date}.\nPlan: {url}", "cancellation": "🚨 ABSAGE: {name} hat die Buchung für den {date} gelöscht.{message}\nPlan: {url}", - "instructionsUpdated": "📝 UPDATE: Die Katzen-Instruktionen wurden geändert.\nPlan: {url}" + "instructionsUpdated": "📝 UPDATE: Die Katzen-Instruktionen wurden geändert.\nPlan: {url}", + "completed": "✨ ERLEDIGT: {name} hat den Job für den {date} erledigt!\nPlan: {url}" } } \ No newline at end of file diff --git a/dictionaries/en.json b/dictionaries/en.json index f7bff60..aba9b69 100644 --- a/dictionaries/en.json +++ b/dictionaries/en.json @@ -54,8 +54,10 @@ "cancelSubmit": "Confirm Cancellation", "cancel": "Cancel", "cancelSuccess": "Entry removed", - "cancelError": "Failed to remove entry", - "noInstructions": "No specific instructions provided yet." + "cancelError": "Cancellation failed", + "noInstructions": "No specific instructions added yet.", + "markDone": "Job done?", + "jobDone": "Job done!" }, "login": { "title": "Enter Password", @@ -81,7 +83,8 @@ "notifications": { "ownerHome": "🏠 OWNER HOME: Marked for {date}.\nPlan: {url}", "newBooking": "✅ NEW BOOKING: {name} is sitting on {date}.\nPlan: {url}", - "cancellation": "🚨 CANCELLATION: {name} removed their booking for {date}.{message}\nPlan: {url}", - "instructionsUpdated": "📝 UPDATED: Cat instructions have been modified.\nPlan: {url}" + "cancellation": "🚨 CANCELLATION: {name} deleted the booking for {date}.{message}\nPlan: {url}", + "instructionsUpdated": "📝 UPDATE: Cat instructions were changed.\nPlan: {url}", + "completed": "✨ DONE: {name} finished the job for {date}!\nPlan: {url}" } } \ No newline at end of file diff --git a/prisma/migrations/20260112221434_add_booking_completed_at/migration.sql b/prisma/migrations/20260112221434_add_booking_completed_at/migration.sql new file mode 100644 index 0000000..bf9ce67 --- /dev/null +++ b/prisma/migrations/20260112221434_add_booking_completed_at/migration.sql @@ -0,0 +1,2 @@ +-- AlterTable +ALTER TABLE "Booking" ADD COLUMN "completedAt" DATETIME; diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 48aebbc..a4f676e 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -30,6 +30,7 @@ model Booking { date DateTime sitterName String? type String @default("SITTER") // "SITTER" or "OWNER_HOME" + completedAt DateTime? createdAt DateTime @default(now()) @@unique([planId, date, sitterName])