|
@@ -11,6 +11,8 @@ import { Button } from "@/components/ui/button";
|
|
|
import { Input } from "@/components/ui/input";
|
|
import { Input } from "@/components/ui/input";
|
|
|
import { Label } from "@/components/ui/label";
|
|
import { Label } from "@/components/ui/label";
|
|
|
import { Textarea } from "@/components/ui/textarea";
|
|
import { Textarea } from "@/components/ui/textarea";
|
|
|
|
|
+import { Checkbox } from "@/components/ui/checkbox";
|
|
|
|
|
+import { CustomerCombobox } from "@/features/quotes/Components/CustomerCombobox";
|
|
|
import { Calendar } from "@/components/ui/calendar";
|
|
import { Calendar } from "@/components/ui/calendar";
|
|
|
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";
|
|
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";
|
|
|
import {
|
|
import {
|
|
@@ -64,7 +66,10 @@ interface Reminder {
|
|
|
dueDate: Date | null;
|
|
dueDate: Date | null;
|
|
|
dueMileage: number | null;
|
|
dueMileage: number | null;
|
|
|
isCompleted: boolean;
|
|
isCompleted: boolean;
|
|
|
|
|
+ notifyInApp: boolean;
|
|
|
|
|
+ notifyEmail: boolean;
|
|
|
createdAt: Date;
|
|
createdAt: Date;
|
|
|
|
|
+ customer: { id: string; name: string } | null;
|
|
|
vehicle: {
|
|
vehicle: {
|
|
|
id: string;
|
|
id: string;
|
|
|
make: string;
|
|
make: string;
|
|
@@ -72,7 +77,7 @@ interface Reminder {
|
|
|
year: number;
|
|
year: number;
|
|
|
licensePlate: string | null;
|
|
licensePlate: string | null;
|
|
|
mileage: number;
|
|
mileage: number;
|
|
|
- };
|
|
|
|
|
|
|
+ } | null;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
interface VehicleOption {
|
|
interface VehicleOption {
|
|
@@ -82,6 +87,7 @@ interface VehicleOption {
|
|
|
year: number;
|
|
year: number;
|
|
|
licensePlate: string | null;
|
|
licensePlate: string | null;
|
|
|
customerName: string | null;
|
|
customerName: string | null;
|
|
|
|
|
+ customerId: string | null;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
interface RemindersPageClientProps {
|
|
interface RemindersPageClientProps {
|
|
@@ -123,6 +129,10 @@ export function RemindersPageClient({ reminders, vehicles, unitSystem }: Reminde
|
|
|
const [formDescription, setFormDescription] = useState("");
|
|
const [formDescription, setFormDescription] = useState("");
|
|
|
const [formDueDate, setFormDueDate] = useState<Date | undefined>();
|
|
const [formDueDate, setFormDueDate] = useState<Date | undefined>();
|
|
|
const [formDueMileage, setFormDueMileage] = useState("");
|
|
const [formDueMileage, setFormDueMileage] = useState("");
|
|
|
|
|
+ const [formNotifyInApp, setFormNotifyInApp] = useState(true);
|
|
|
|
|
+ const [formNotifyEmail, setFormNotifyEmail] = useState(false);
|
|
|
|
|
+ const [formCustomerId, setFormCustomerId] = useState("");
|
|
|
|
|
+ const [formCustomer, setFormCustomer] = useState<{ id: string; name: string; company: string | null } | null>(null);
|
|
|
const [formLoading, setFormLoading] = useState(false);
|
|
const [formLoading, setFormLoading] = useState(false);
|
|
|
const [vehicleOpen, setVehicleOpen] = useState(false);
|
|
const [vehicleOpen, setVehicleOpen] = useState(false);
|
|
|
const [calendarOpen, setCalendarOpen] = useState(false);
|
|
const [calendarOpen, setCalendarOpen] = useState(false);
|
|
@@ -157,30 +167,40 @@ export function RemindersPageClient({ reminders, vehicles, unitSystem }: Reminde
|
|
|
setFormDescription("");
|
|
setFormDescription("");
|
|
|
setFormDueDate(undefined);
|
|
setFormDueDate(undefined);
|
|
|
setFormDueMileage("");
|
|
setFormDueMileage("");
|
|
|
|
|
+ setFormNotifyInApp(true);
|
|
|
|
|
+ setFormNotifyEmail(false);
|
|
|
|
|
+ setFormCustomerId("");
|
|
|
|
|
+ setFormCustomer(null);
|
|
|
setShowForm(true);
|
|
setShowForm(true);
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
const openEditForm = (r: Reminder) => {
|
|
const openEditForm = (r: Reminder) => {
|
|
|
setEditingReminder(r);
|
|
setEditingReminder(r);
|
|
|
- setFormVehicleId(r.vehicle.id);
|
|
|
|
|
|
|
+ setFormVehicleId(r.vehicle?.id ?? "");
|
|
|
setFormTitle(r.title);
|
|
setFormTitle(r.title);
|
|
|
setFormDescription(r.description || "");
|
|
setFormDescription(r.description || "");
|
|
|
setFormDueDate(r.dueDate ? new Date(r.dueDate) : undefined);
|
|
setFormDueDate(r.dueDate ? new Date(r.dueDate) : undefined);
|
|
|
setFormDueMileage(r.dueMileage ? String(r.dueMileage) : "");
|
|
setFormDueMileage(r.dueMileage ? String(r.dueMileage) : "");
|
|
|
|
|
+ setFormNotifyInApp(r.notifyInApp ?? true);
|
|
|
|
|
+ setFormNotifyEmail(r.notifyEmail ?? false);
|
|
|
|
|
+ setFormCustomerId(r.customer?.id ?? "");
|
|
|
|
|
+ setFormCustomer(r.customer ? { ...r.customer, company: null } : null);
|
|
|
setShowForm(true);
|
|
setShowForm(true);
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
const handleFormSubmit = async (e: React.FormEvent) => {
|
|
const handleFormSubmit = async (e: React.FormEvent) => {
|
|
|
e.preventDefault();
|
|
e.preventDefault();
|
|
|
- if (!formVehicleId) return;
|
|
|
|
|
setFormLoading(true);
|
|
setFormLoading(true);
|
|
|
|
|
|
|
|
const payload = {
|
|
const payload = {
|
|
|
- vehicleId: formVehicleId,
|
|
|
|
|
|
|
+ vehicleId: formVehicleId || null,
|
|
|
|
|
+ customerId: formCustomerId || null,
|
|
|
title: formTitle,
|
|
title: formTitle,
|
|
|
description: formDescription || undefined,
|
|
description: formDescription || undefined,
|
|
|
dueDate: formDueDate ? formDueDate.toISOString().split("T")[0] : undefined,
|
|
dueDate: formDueDate ? formDueDate.toISOString().split("T")[0] : undefined,
|
|
|
dueMileage: formDueMileage ? Number(formDueMileage) : undefined,
|
|
dueMileage: formDueMileage ? Number(formDueMileage) : undefined,
|
|
|
|
|
+ notifyInApp: formNotifyInApp,
|
|
|
|
|
+ notifyEmail: formNotifyEmail,
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
const result = editingReminder
|
|
const result = editingReminder
|
|
@@ -287,15 +307,28 @@ export function RemindersPageClient({ reminders, vehicles, unitSystem }: Reminde
|
|
|
{tv("dueSoon")}
|
|
{tv("dueSoon")}
|
|
|
</Badge>
|
|
</Badge>
|
|
|
)}
|
|
)}
|
|
|
- <span className="text-xs text-muted-foreground">
|
|
|
|
|
- <span
|
|
|
|
|
- className="hover:underline cursor-pointer"
|
|
|
|
|
- onClick={() => router.push(`/vehicles/${r.vehicle.id}?tab=reminders`)}
|
|
|
|
|
- >
|
|
|
|
|
- {r.vehicle.year} {r.vehicle.make} {r.vehicle.model}
|
|
|
|
|
- {r.vehicle.licensePlate && ` · ${r.vehicle.licensePlate}`}
|
|
|
|
|
|
|
+ {r.vehicle ? (
|
|
|
|
|
+ <span className="text-xs text-muted-foreground">
|
|
|
|
|
+ <span
|
|
|
|
|
+ className="hover:underline cursor-pointer"
|
|
|
|
|
+ onClick={() => router.push(`/vehicles/${r.vehicle?.id}?tab=reminders`)}
|
|
|
|
|
+ >
|
|
|
|
|
+ {r.vehicle.year} {r.vehicle.make} {r.vehicle.model}
|
|
|
|
|
+ {r.vehicle.licensePlate && ` · ${r.vehicle.licensePlate}`}
|
|
|
|
|
+ </span>
|
|
|
</span>
|
|
</span>
|
|
|
- </span>
|
|
|
|
|
|
|
+ ) : r.customer ? (
|
|
|
|
|
+ <span className="text-xs text-muted-foreground">
|
|
|
|
|
+ <span
|
|
|
|
|
+ className="hover:underline cursor-pointer"
|
|
|
|
|
+ onClick={() => router.push(`/customers/${r.customer?.id}`)}
|
|
|
|
|
+ >
|
|
|
|
|
+ {r.customer.name}
|
|
|
|
|
+ </span>
|
|
|
|
|
+ </span>
|
|
|
|
|
+ ) : (
|
|
|
|
|
+ <span className="text-xs text-muted-foreground">{t("workshopReminder")}</span>
|
|
|
|
|
+ )}
|
|
|
</div>
|
|
</div>
|
|
|
<div className="shrink-0 flex items-center gap-2 text-xs text-muted-foreground">
|
|
<div className="shrink-0 flex items-center gap-2 text-xs text-muted-foreground">
|
|
|
{r.dueDate && (
|
|
{r.dueDate && (
|
|
@@ -340,9 +373,23 @@ export function RemindersPageClient({ reminders, vehicles, unitSystem }: Reminde
|
|
|
<DialogTitle>{editingReminder ? tv("editTitle") : tv("addTitle")}</DialogTitle>
|
|
<DialogTitle>{editingReminder ? tv("editTitle") : tv("addTitle")}</DialogTitle>
|
|
|
</DialogHeader>
|
|
</DialogHeader>
|
|
|
<form onSubmit={handleFormSubmit} className="space-y-4">
|
|
<form onSubmit={handleFormSubmit} className="space-y-4">
|
|
|
- {/* Vehicle selector */}
|
|
|
|
|
|
|
+ {/* Target: workshop (nothing), customer, or vehicle */}
|
|
|
<div className="space-y-2">
|
|
<div className="space-y-2">
|
|
|
- <Label>{t("vehicle")}</Label>
|
|
|
|
|
|
|
+ <Label>{t("relatesTo")}</Label>
|
|
|
|
|
+ <CustomerCombobox
|
|
|
|
|
+ value={formCustomerId}
|
|
|
|
|
+ initialCustomer={formCustomer}
|
|
|
|
|
+ placeholder={t("selectCustomer")}
|
|
|
|
|
+ noneLabel={t("noneOption")}
|
|
|
|
|
+ onChange={(id) => {
|
|
|
|
|
+ setFormCustomerId(id);
|
|
|
|
|
+ // A vehicle belonging to another customer no longer fits
|
|
|
|
|
+ const current = vehicles.find((v) => v.id === formVehicleId);
|
|
|
|
|
+ if (id && current && current.customerId !== id) {
|
|
|
|
|
+ setFormVehicleId("");
|
|
|
|
|
+ }
|
|
|
|
|
+ }}
|
|
|
|
|
+ />
|
|
|
<Popover open={vehicleOpen} onOpenChange={setVehicleOpen}>
|
|
<Popover open={vehicleOpen} onOpenChange={setVehicleOpen}>
|
|
|
<PopoverTrigger asChild>
|
|
<PopoverTrigger asChild>
|
|
|
<Button
|
|
<Button
|
|
@@ -350,7 +397,6 @@ export function RemindersPageClient({ reminders, vehicles, unitSystem }: Reminde
|
|
|
role="combobox"
|
|
role="combobox"
|
|
|
aria-expanded={vehicleOpen}
|
|
aria-expanded={vehicleOpen}
|
|
|
className="w-full justify-between font-normal"
|
|
className="w-full justify-between font-normal"
|
|
|
- disabled={!!editingReminder}
|
|
|
|
|
>
|
|
>
|
|
|
{selectedVehicle
|
|
{selectedVehicle
|
|
|
? `${selectedVehicle.year} ${selectedVehicle.make} ${selectedVehicle.model}${selectedVehicle.licensePlate ? ` · ${selectedVehicle.licensePlate}` : ""}`
|
|
? `${selectedVehicle.year} ${selectedVehicle.make} ${selectedVehicle.model}${selectedVehicle.licensePlate ? ` · ${selectedVehicle.licensePlate}` : ""}`
|
|
@@ -364,12 +410,31 @@ export function RemindersPageClient({ reminders, vehicles, unitSystem }: Reminde
|
|
|
<CommandList>
|
|
<CommandList>
|
|
|
<CommandEmpty>{t("noVehicle")}</CommandEmpty>
|
|
<CommandEmpty>{t("noVehicle")}</CommandEmpty>
|
|
|
<CommandGroup>
|
|
<CommandGroup>
|
|
|
- {vehicles.map((v) => (
|
|
|
|
|
|
|
+ <CommandItem
|
|
|
|
|
+ value="__none__"
|
|
|
|
|
+ onSelect={() => {
|
|
|
|
|
+ setFormVehicleId("");
|
|
|
|
|
+ setVehicleOpen(false);
|
|
|
|
|
+ }}
|
|
|
|
|
+ >
|
|
|
|
|
+ <Check
|
|
|
|
|
+ className={cn("mr-2 h-4 w-4", !formVehicleId ? "opacity-100" : "opacity-0")}
|
|
|
|
|
+ />
|
|
|
|
|
+ {t("noneOption")}
|
|
|
|
|
+ </CommandItem>
|
|
|
|
|
+ {vehicles
|
|
|
|
|
+ .filter((v) => !formCustomerId || v.customerId === formCustomerId)
|
|
|
|
|
+ .map((v) => (
|
|
|
<CommandItem
|
|
<CommandItem
|
|
|
key={v.id}
|
|
key={v.id}
|
|
|
value={`${v.year} ${v.make} ${v.model} ${v.licensePlate || ""} ${v.customerName || ""}`}
|
|
value={`${v.year} ${v.make} ${v.model} ${v.licensePlate || ""} ${v.customerName || ""}`}
|
|
|
onSelect={() => {
|
|
onSelect={() => {
|
|
|
setFormVehicleId(v.id);
|
|
setFormVehicleId(v.id);
|
|
|
|
|
+ // The vehicle's customer follows automatically
|
|
|
|
|
+ if (v.customerId) {
|
|
|
|
|
+ setFormCustomerId(v.customerId);
|
|
|
|
|
+ setFormCustomer({ id: v.customerId, name: v.customerName || "", company: null });
|
|
|
|
|
+ }
|
|
|
setVehicleOpen(false);
|
|
setVehicleOpen(false);
|
|
|
}}
|
|
}}
|
|
|
>
|
|
>
|
|
@@ -395,6 +460,9 @@ export function RemindersPageClient({ reminders, vehicles, unitSystem }: Reminde
|
|
|
</Command>
|
|
</Command>
|
|
|
</PopoverContent>
|
|
</PopoverContent>
|
|
|
</Popover>
|
|
</Popover>
|
|
|
|
|
+ {!formVehicleId && !formCustomerId && (
|
|
|
|
|
+ <p className="text-xs text-muted-foreground">{t("workshopHint")}</p>
|
|
|
|
|
+ )}
|
|
|
</div>
|
|
</div>
|
|
|
|
|
|
|
|
{/* Title */}
|
|
{/* Title */}
|
|
@@ -463,11 +531,31 @@ export function RemindersPageClient({ reminders, vehicles, unitSystem }: Reminde
|
|
|
</div>
|
|
</div>
|
|
|
</div>
|
|
</div>
|
|
|
|
|
|
|
|
|
|
+ <div className="space-y-2">
|
|
|
|
|
+ <Label>{tv("notifyLabel")}</Label>
|
|
|
|
|
+ <div className="flex gap-6">
|
|
|
|
|
+ <label className="flex items-center gap-2 text-sm font-normal">
|
|
|
|
|
+ <Checkbox
|
|
|
|
|
+ checked={formNotifyInApp}
|
|
|
|
|
+ onCheckedChange={(v) => setFormNotifyInApp(v === true)}
|
|
|
|
|
+ />
|
|
|
|
|
+ {tv("notifyInApp")}
|
|
|
|
|
+ </label>
|
|
|
|
|
+ <label className="flex items-center gap-2 text-sm font-normal">
|
|
|
|
|
+ <Checkbox
|
|
|
|
|
+ checked={formNotifyEmail}
|
|
|
|
|
+ onCheckedChange={(v) => setFormNotifyEmail(v === true)}
|
|
|
|
|
+ />
|
|
|
|
|
+ {tv("notifyEmail")}
|
|
|
|
|
+ </label>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
<div className="flex justify-end gap-3 pt-2">
|
|
<div className="flex justify-end gap-3 pt-2">
|
|
|
<Button type="button" variant="outline" onClick={() => setShowForm(false)}>
|
|
<Button type="button" variant="outline" onClick={() => setShowForm(false)}>
|
|
|
{tc("cancel")}
|
|
{tc("cancel")}
|
|
|
</Button>
|
|
</Button>
|
|
|
- <Button type="submit" disabled={formLoading || !formVehicleId}>
|
|
|
|
|
|
|
+ <Button type="submit" disabled={formLoading}>
|
|
|
{formLoading && <Loader2 className="mr-2 h-4 w-4 animate-spin" />}
|
|
{formLoading && <Loader2 className="mr-2 h-4 w-4 animate-spin" />}
|
|
|
{editingReminder ? tc("saveChanges") : tv("addTitle")}
|
|
{editingReminder ? tc("saveChanges") : tv("addTitle")}
|
|
|
</Button>
|
|
</Button>
|