feat: implement flexible feeding intervals and fix layout alignment
This commit is contained in:
@@ -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 } from "lucide-react"
|
||||
import { CalendarIcon, User, Home, X, Info, Utensils, Trash2 } from "lucide-react"
|
||||
import { toast } from "sonner"
|
||||
|
||||
import { Button } from "@/components/ui/button"
|
||||
@@ -37,6 +37,9 @@ type Plan = {
|
||||
webhookUrl: string | null
|
||||
notifyAll: boolean
|
||||
bookings: Booking[]
|
||||
feedingPerDay: number
|
||||
feedingInterval: number
|
||||
litterInterval: number
|
||||
}
|
||||
|
||||
interface PlanDashboardProps {
|
||||
@@ -255,6 +258,21 @@ export function PlanDashboard({ plan, dict, settingsDict, lang }: PlanDashboardP
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
)}
|
||||
|
||||
<div className="mt-3 flex flex-wrap gap-2 pt-2 border-t border-border/50">
|
||||
{Math.floor((day.getTime() - new Date(plan.startDate).setHours(0, 0, 0, 0)) / (1000 * 60 * 60 * 24)) % plan.feedingInterval === 0 && (
|
||||
<div className="flex gap-1 items-center" title={`${plan.feedingPerDay}x ${dict.feeding}`}>
|
||||
{Array.from({ length: plan.feedingPerDay }).map((_, i) => (
|
||||
<Utensils key={i} className="w-3.5 h-3.5 text-orange-500/70" />
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
{Math.floor((day.getTime() - new Date(plan.startDate).setHours(0, 0, 0, 0)) / (1000 * 60 * 60 * 24)) % plan.litterInterval === 0 && (
|
||||
<div className="flex items-center gap-1 text-xs text-muted-foreground/70" title={dict.litter}>
|
||||
<Trash2 className="w-3.5 h-3.5 text-blue-500/70" />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
|
||||
@@ -4,7 +4,7 @@ import prisma from '@/lib/prisma';
|
||||
export async function POST(req: Request) {
|
||||
try {
|
||||
const body = await req.json();
|
||||
const { title, startDate, endDate, password, instructions } = body;
|
||||
const { title, startDate, endDate, password, instructions, feedingPerDay, feedingInterval, litterInterval } = body;
|
||||
|
||||
if (!title || !startDate || !endDate || !password) {
|
||||
return NextResponse.json({ error: 'Missing required fields' }, { status: 400 });
|
||||
@@ -17,6 +17,9 @@ export async function POST(req: Request) {
|
||||
endDate: new Date(endDate),
|
||||
password,
|
||||
instructions,
|
||||
feedingPerDay: feedingPerDay ? parseInt(feedingPerDay) : undefined,
|
||||
feedingInterval: feedingInterval ? parseInt(feedingInterval) : undefined,
|
||||
litterInterval: litterInterval ? parseInt(litterInterval) : undefined,
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -43,21 +43,33 @@ export function CreatePlanForm({ dict, lang }: CreatePlanFormProps) {
|
||||
password: z.string().min(4, {
|
||||
message: dict.passwordError,
|
||||
}),
|
||||
feedingPerDay: z.coerce.number().min(1).max(10),
|
||||
feedingInterval: z.coerce.number().min(1).max(30),
|
||||
litterInterval: z.coerce.number().min(1).max(30),
|
||||
instructions: z.string().optional(),
|
||||
})
|
||||
|
||||
const form = useForm<z.infer<typeof formSchema>>({
|
||||
resolver: zodResolver(formSchema),
|
||||
type FormValues = z.infer<typeof formSchema>
|
||||
|
||||
const form = useForm<FormValues>({
|
||||
resolver: zodResolver(formSchema) as any,
|
||||
defaultValues: {
|
||||
title: "",
|
||||
password: "",
|
||||
instructions: "",
|
||||
feedingPerDay: 2,
|
||||
feedingInterval: 1,
|
||||
litterInterval: 2,
|
||||
dateRange: {
|
||||
from: undefined,
|
||||
to: undefined,
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
const dateLocale = lang === "de" ? de : enUS
|
||||
|
||||
async function onSubmit(values: z.infer<typeof formSchema>) {
|
||||
async function onSubmit(values: FormValues) {
|
||||
try {
|
||||
const response = await fetch("/api/plan", {
|
||||
method: "POST",
|
||||
@@ -68,6 +80,9 @@ export function CreatePlanForm({ dict, lang }: CreatePlanFormProps) {
|
||||
endDate: values.dateRange.to,
|
||||
password: values.password,
|
||||
instructions: values.instructions,
|
||||
feedingPerDay: values.feedingPerDay,
|
||||
feedingInterval: values.feedingInterval,
|
||||
litterInterval: values.litterInterval,
|
||||
}),
|
||||
})
|
||||
|
||||
@@ -153,6 +168,52 @@ export function CreatePlanForm({ dict, lang }: CreatePlanFormProps) {
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="feedingInterval"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel className="min-h-[3.5rem] flex items-end pb-2">{dict.feedingInterval}</FormLabel>
|
||||
<FormControl>
|
||||
<Input type="number" {...field} />
|
||||
</FormControl>
|
||||
<FormDescription className="min-h-[4rem]">{dict.feedingIntervalDesc}</FormDescription>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="feedingPerDay"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel className="min-h-[3.5rem] flex items-end pb-2">{dict.feedingPerDay}</FormLabel>
|
||||
<FormControl>
|
||||
<Input type="number" {...field} />
|
||||
</FormControl>
|
||||
<FormDescription className="min-h-[4rem]">{dict.feedingDesc}</FormDescription>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="litterInterval"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel className="min-h-[3.5rem] flex items-end pb-2">{dict.litterInterval}</FormLabel>
|
||||
<FormControl>
|
||||
<Input type="number" {...field} />
|
||||
</FormControl>
|
||||
<FormDescription className="min-h-[4rem]">{dict.litterDesc}</FormDescription>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="password"
|
||||
|
||||
@@ -15,6 +15,12 @@
|
||||
"groupPassword": "Gruppen-Passwort",
|
||||
"passwordPlaceholder": "geheim-miau",
|
||||
"passwordDesc": "Teile dieses Passwort mit deinen Katzen-Sittern.",
|
||||
"feedingPerDay": "Wie oft am Fütterungstag füttern?",
|
||||
"feedingDesc": "Anzahl der Mahlzeiten pro Tag.",
|
||||
"feedingInterval": "Alle wieviel Tage füttern?",
|
||||
"feedingIntervalDesc": "Intervall in Tagen (z.B. 1 für täglich, 3 für Futterautomat).",
|
||||
"litterInterval": "Alle wieviel Tage das Klo reinigen?",
|
||||
"litterDesc": "Intervall in Tagen.",
|
||||
"submit": "Plan erstellen",
|
||||
"success": "Plan erfolgreich erstellt!",
|
||||
"error": "Etwas ist schiefgelaufen. Bitte versuche es erneut.",
|
||||
@@ -27,6 +33,8 @@
|
||||
"instructionsTitle": "Katzenpflege-Anleitungen",
|
||||
"export": "Exportieren",
|
||||
"settings": "Einstellungen",
|
||||
"feeding": "Füttern",
|
||||
"litter": "Klo reinigen",
|
||||
"ownerHome": "Besitzer zu Hause",
|
||||
"illDoIt": "Ich mache das!",
|
||||
"bookTitle": "Buchung für den {date}",
|
||||
|
||||
@@ -15,6 +15,12 @@
|
||||
"groupPassword": "Group Password",
|
||||
"passwordPlaceholder": "secret-meow",
|
||||
"passwordDesc": "Share this password with your cat sitters.",
|
||||
"feedingPerDay": "Feeding times per feeding day",
|
||||
"feedingDesc": "Number of meals per day.",
|
||||
"feedingInterval": "Feeding interval (days)",
|
||||
"feedingIntervalDesc": "Every X days (e.g., 1 for daily, 3 for auto-feeder).",
|
||||
"litterInterval": "Litter cleaning interval (days)",
|
||||
"litterDesc": "Every X days.",
|
||||
"submit": "Create Plan",
|
||||
"success": "Plan created successfully!",
|
||||
"error": "Something went wrong. Please try again.",
|
||||
@@ -27,6 +33,8 @@
|
||||
"instructionsTitle": "Cat Care Instructions",
|
||||
"export": "Export",
|
||||
"settings": "Settings",
|
||||
"feeding": "Feeding",
|
||||
"litter": "Clean litter",
|
||||
"ownerHome": "Owner Home",
|
||||
"illDoIt": "I'll do it!",
|
||||
"bookTitle": "Book {date}",
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
-- RedefineTables
|
||||
PRAGMA defer_foreign_keys=ON;
|
||||
PRAGMA foreign_keys=OFF;
|
||||
CREATE TABLE "new_Plan" (
|
||||
"id" TEXT NOT NULL PRIMARY KEY,
|
||||
"title" TEXT NOT NULL,
|
||||
"password" TEXT NOT NULL,
|
||||
"startDate" DATETIME NOT NULL,
|
||||
"endDate" DATETIME NOT NULL,
|
||||
"instructions" TEXT,
|
||||
"webhookUrl" TEXT,
|
||||
"notifyAll" BOOLEAN NOT NULL DEFAULT true,
|
||||
"feedingPerDay" INTEGER NOT NULL DEFAULT 2,
|
||||
"litterInterval" INTEGER NOT NULL DEFAULT 2,
|
||||
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
INSERT INTO "new_Plan" ("createdAt", "endDate", "id", "instructions", "notifyAll", "password", "startDate", "title", "webhookUrl") SELECT "createdAt", "endDate", "id", "instructions", "notifyAll", "password", "startDate", "title", "webhookUrl" FROM "Plan";
|
||||
DROP TABLE "Plan";
|
||||
ALTER TABLE "new_Plan" RENAME TO "Plan";
|
||||
PRAGMA foreign_keys=ON;
|
||||
PRAGMA defer_foreign_keys=OFF;
|
||||
@@ -0,0 +1,22 @@
|
||||
-- RedefineTables
|
||||
PRAGMA defer_foreign_keys=ON;
|
||||
PRAGMA foreign_keys=OFF;
|
||||
CREATE TABLE "new_Plan" (
|
||||
"id" TEXT NOT NULL PRIMARY KEY,
|
||||
"title" TEXT NOT NULL,
|
||||
"password" TEXT NOT NULL,
|
||||
"startDate" DATETIME NOT NULL,
|
||||
"endDate" DATETIME NOT NULL,
|
||||
"instructions" TEXT,
|
||||
"webhookUrl" TEXT,
|
||||
"notifyAll" BOOLEAN NOT NULL DEFAULT true,
|
||||
"feedingPerDay" INTEGER NOT NULL DEFAULT 2,
|
||||
"feedingInterval" INTEGER NOT NULL DEFAULT 1,
|
||||
"litterInterval" INTEGER NOT NULL DEFAULT 2,
|
||||
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
INSERT INTO "new_Plan" ("createdAt", "endDate", "feedingPerDay", "id", "instructions", "litterInterval", "notifyAll", "password", "startDate", "title", "webhookUrl") SELECT "createdAt", "endDate", "feedingPerDay", "id", "instructions", "litterInterval", "notifyAll", "password", "startDate", "title", "webhookUrl" FROM "Plan";
|
||||
DROP TABLE "Plan";
|
||||
ALTER TABLE "new_Plan" RENAME TO "Plan";
|
||||
PRAGMA foreign_keys=ON;
|
||||
PRAGMA defer_foreign_keys=OFF;
|
||||
@@ -16,6 +16,9 @@ model Plan {
|
||||
instructions String?
|
||||
webhookUrl String?
|
||||
notifyAll Boolean @default(true)
|
||||
feedingPerDay Int @default(2)
|
||||
feedingInterval Int @default(1)
|
||||
litterInterval Int @default(2)
|
||||
createdAt DateTime @default(now())
|
||||
bookings Booking[]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user