production-evyos-systems-an.../ServicesWeb/customer/src/components/custom/content/updateFromComponent.tsx

414 lines
19 KiB
TypeScript

import React, { useState, useEffect } from "react";
import { apiPatchFetcher } from "@/lib/fetcher";
import { withCache } from "@/components/mutual/context/cache/withCache";
import { Input } from "@/components/mutual/ui/input";
import { Label } from "@/components/mutual/ui/label";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/mutual/ui/select";
import { Textarea } from "@/components/mutual/ui/textarea";
import { Checkbox } from "@/components/mutual/ui/checkbox";
import { RadioGroup, RadioGroupItem } from "@/components/mutual/ui/radio-group";
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "@/components/mutual/ui/dropdown-menu";
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "@/components/mutual/ui/form";
import { Button } from "@/components/mutual/ui/button";
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import * as z from "zod";
import { getCacheData, setCacheData, clearCacheData } from "@/components/mutual/context/cache/context";
import { useRouter } from "next/navigation";
// Define form schema with zod
const zodSchema = z.object({
name: z.string().min(2, { message: "Name must be at least 2 characters." }),
email: z.string().email({ message: "Please enter a valid email address." }),
description: z.string().min(10, { message: "Description must be at least 10 characters." }),
category: z.string({ required_error: "Please select a category." }),
priority: z.string({ required_error: "Please select a priority level." }),
notifications: z.boolean(),
terms: z.boolean().refine(val => val === true, { message: "You must accept the terms." }),
attachments: z.string().optional()
});
// Define the form type
type FormValues = z.infer<typeof zodSchema>;
function UpdateFromBuildComponentBase({
cacheData,
cacheLoading,
cacheError,
refreshCache,
updateCache,
clearCache,
activePageUrl
}: {
cacheData?: { [url: string]: any } | null;
cacheLoading?: boolean;
cacheError?: string | null;
refreshCache?: (url?: string) => Promise<void>;
updateCache?: (url: string, data: any) => Promise<void>;
clearCache?: (url: string) => Promise<void>;
activePageUrl?: string;
}) {
const [open, setOpen] = useState<boolean>(false);
const [cacheLoaded, setCacheLoaded] = useState<boolean>(false);
const router = useRouter();
const listUrl = `/panel/${activePageUrl?.replace("/update", "")}`;
const form = useForm<FormValues>({
resolver: zodResolver(zodSchema),
defaultValues: {
name: "",
email: "",
description: "",
category: "",
priority: "",
notifications: false,
terms: false,
attachments: ""
}
});
// Load cached form values only once when component mounts
useEffect(() => {
const fetchCacheDirectly = async () => {
if (activePageUrl && !cacheLoaded) {
try {
// Directly fetch cache data using the imported function
const cachedData = await getCacheData(activePageUrl);
if (cachedData) {
const formValues = form.getValues();
// Create a merged data object with proper typing
// Convert boolean values explicitly to handle potential string representations
const notificationsValue = typeof cachedData.notifications === 'string'
? cachedData.notifications === 'true'
: Boolean(cachedData.notifications);
const termsValue = typeof cachedData.terms === 'string'
? cachedData.terms === 'true'
: Boolean(cachedData.terms);
const mergedData: FormValues = {
name: cachedData.name || formValues.name || "",
email: cachedData.email || formValues.email || "",
description: cachedData.description || formValues.description || "",
category: cachedData.category || formValues.category || "",
priority: cachedData.priority || formValues.priority || "",
notifications: notificationsValue,
terms: termsValue,
attachments: cachedData.attachments || formValues.attachments || ""
};
form.reset(mergedData);
}
setCacheLoaded(true);
if (refreshCache) {
refreshCache(activePageUrl);
}
} catch (error) {
console.error("Error fetching cache directly:", error);
}
}
};
fetchCacheDirectly();
}, []); // Empty dependency array since we only want to run once on mount
// Function to handle input blur events
const handleFieldBlur = async (fieldName: keyof FormValues, value: any) => {
// Only update if the value is not empty
if (value && activePageUrl) {
try {
// Get current form values
const currentValues = form.getValues();
// Prepare updated data
const updatedData = {
...currentValues,
[fieldName]: value
};
console.log("Directly updating cache with:", updatedData);
// Directly update cache using imported function
await setCacheData(activePageUrl, updatedData);
// Also use the context method if available (for state consistency)
if (updateCache) {
updateCache(activePageUrl, updatedData);
}
} catch (error) {
console.error("Error updating cache:", error);
}
}
};
// Type-safe submit handler
const onSubmit = async (data: FormValues) => {
console.log("Form submitted with data:", data);
try {
// Submit form data to API using PATCH for updates
const response = await apiPatchFetcher<any>({ url: "/api/test/update", isNoCache: true, body: data });
console.log("API response:", response);
// Clear cache on successful submission
if (activePageUrl) {
try {
console.log("Directly clearing cache for URL:", activePageUrl);
// Directly clear cache using imported function
await clearCacheData(activePageUrl);
// Also use the context method if available (for state consistency)
if (clearCache) {
clearCache(activePageUrl);
}
} catch (error) {
console.error("Error clearing cache:", error);
}
}
// Reset form with empty values
const emptyValues: FormValues = {
name: "",
email: "",
description: "",
category: "",
priority: "",
notifications: false,
terms: false,
attachments: ""
};
form.reset(emptyValues);
} catch (error) {
console.error("Error submitting form:", error);
}
}
return (
<div className="w-full max-w-2xl mx-auto p-6 bg-white rounded-lg shadow-md">
{/* back to list button */}
<div className="flex justify-end">
<Button onClick={() => router.push(listUrl)}>Back to List</Button>
</div>
<h2 className="text-2xl font-bold mb-6">Update Form</h2>
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-6">
{/* Input example */}
<FormField
control={form.control}
name="name"
render={({ field }) => (
<FormItem>
<FormLabel>Name</FormLabel>
<FormControl>
<Input
placeholder="Enter your name"
{...field}
onBlur={(e) => { field.onBlur(); handleFieldBlur("name", e.target.value) }}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
{/* Email input example */}
<FormField
control={form.control}
name="email"
render={({ field }) => (
<FormItem>
<FormLabel>Email</FormLabel>
<FormControl>
<Input
type="email"
placeholder="your@email.com"
{...field}
onBlur={(e) => { field.onBlur(); handleFieldBlur("email", e.target.value) }}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
{/* Textarea example */}
<FormField
control={form.control}
name="description"
render={({ field }) => (
<FormItem>
<FormLabel>Description</FormLabel>
<FormControl>
<Textarea
placeholder="Enter a detailed description"
className="min-h-[100px]"
{...field}
onBlur={(e) => { field.onBlur(); handleFieldBlur("description", e.target.value) }}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
{/* Select example */}
<FormField
control={form.control}
name="category"
render={({ field }) => (
<FormItem>
<FormLabel>Category</FormLabel>
<FormControl>
<Select
onValueChange={(value) => { field.onChange(value); handleFieldBlur("category", value) }}
value={field.value}
>
<SelectTrigger>
<SelectValue placeholder="Select a category" />
</SelectTrigger>
<SelectContent>
<SelectItem value="general">General</SelectItem>
<SelectItem value="billing">Billing</SelectItem>
<SelectItem value="technical">Technical</SelectItem>
<SelectItem value="other">Other</SelectItem>
</SelectContent>
</Select>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
{/* RadioGroup example */}
<FormField
control={form.control}
name="priority"
render={({ field }) => (
<FormItem className="space-y-3">
<FormLabel>Priority Level</FormLabel>
<FormControl>
<RadioGroup
onValueChange={(value) => { field.onChange(value); handleFieldBlur("priority", value) }}
value={field.value}
className="flex flex-col space-y-1"
>
<div className="flex items-center space-x-2">
<RadioGroupItem value="low" id="priority-low" />
<Label htmlFor="priority-low">Low</Label>
</div>
<div className="flex items-center space-x-2">
<RadioGroupItem value="medium" id="priority-medium" />
<Label htmlFor="priority-medium">Medium</Label>
</div>
<div className="flex items-center space-x-2">
<RadioGroupItem value="high" id="priority-high" />
<Label htmlFor="priority-high">High</Label>
</div>
</RadioGroup>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
{/* Checkbox example */}
<FormField
control={form.control}
name="notifications"
render={({ field }) => (
<FormItem className="flex flex-row items-start space-x-3 space-y-0 rounded-md p-4 border">
<FormControl>
<Checkbox
checked={Boolean(field.value)}
onCheckedChange={(checked) => {
console.log("Notification checkbox changed to:", checked);
field.onChange(checked);
handleFieldBlur("notifications", checked);
}}
/>
</FormControl>
<div className="space-y-1 leading-none">
<FormLabel>Email notifications</FormLabel>
<p className="text-sm text-gray-500">Receive email notifications when updates occur</p>
</div>
<FormMessage />
</FormItem>
)}
/>
{/* Terms checkbox example */}
<FormField
control={form.control}
name="terms"
render={({ field }) => (
<FormItem className="flex flex-row items-start space-x-3 space-y-0">
<FormControl>
<Checkbox
checked={Boolean(field.value)}
onCheckedChange={(checked) => {
console.log("Terms checkbox changed to:", checked);
field.onChange(checked);
handleFieldBlur("terms", checked);
}}
/>
</FormControl>
<div className="space-y-1 leading-none">
<FormLabel>I accept the terms and conditions</FormLabel>
</div>
<FormMessage />
</FormItem>
)}
/>
{/* DropdownMenu example */}
<FormField
control={form.control}
name="attachments"
render={({ field }) => (
<FormItem>
<FormLabel>Attachments</FormLabel>
<FormControl>
<div>
<DropdownMenu open={open} onOpenChange={(isOpen: boolean) => setOpen(isOpen)}>
<DropdownMenuTrigger asChild>
<Button variant="outline" className="w-full justify-start text-left font-normal">
<span>{field.value || "Select attachment type"}</span>
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent className="w-56">
<DropdownMenuItem onClick={() => { field.onChange("document"); handleFieldBlur("attachments", "document"); setOpen(false) }}>
Document
</DropdownMenuItem>
<DropdownMenuItem onClick={() => { field.onChange("image"); handleFieldBlur("attachments", "image"); setOpen(false) }}>
Image
</DropdownMenuItem>
<DropdownMenuItem onClick={() => { field.onChange("video"); handleFieldBlur("attachments", "video"); setOpen(false) }}>
Video
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</div>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<Button type="submit" className="w-full">Update</Button>
</form>
</Form>
</div>
);
}
export const UpdateFromBuildComponent = withCache(UpdateFromBuildComponentBase);
// Add default export to maintain compatibility with existing imports
export default withCache(UpdateFromBuildComponentBase);