From 311736ce067a46582ee38c022fef019eb6b92e67 Mon Sep 17 00:00:00 2001 From: Berkay Date: Sat, 21 Jun 2025 23:44:52 +0300 Subject: [PATCH] create/update actions and redis cahce store completed tested --- ServicesWeb/customer/README.md | 42 +- .../src/app/api/context/cache/route.ts | 90 ++++ .../customer/src/app/api/test/create/route.ts | 41 ++ ServicesWeb/customer/src/app/api/test/data.ts | 45 ++ .../customer/src/app/api/test/list/route.ts | 109 +++++ .../customer/src/app/api/test/update/route.ts | 50 +++ .../custom/content/PageToBeChildrendMulti.tsx | 7 + .../content/TableCardComponentImproved.tsx | 158 +++++-- .../components/custom/content/component.tsx | 4 +- .../custom/content/createFromComponent.tsx | 403 +++++++++++++++++ .../custom/content/updateFromComponent.tsx | 425 ++++++++++++++++++ .../mutual/context/cache/context.tsx | 211 +++++++++ .../mutual/context/cache/withCache.tsx | 21 + .../mutual/providers/client-providers.tsx | 5 +- .../fetchers/custom/context/cache/fetch.tsx | 123 +++++ .../customer/src/layouts/dashboard/client.tsx | 5 +- ServicesWeb/customer/src/pages/multi/index.ts | 8 +- .../src/validations/mutual/dashboard/props.ts | 6 + .../validations/mutual/table/validations.ts | 12 +- 19 files changed, 1717 insertions(+), 48 deletions(-) create mode 100644 ServicesWeb/customer/src/app/api/context/cache/route.ts create mode 100644 ServicesWeb/customer/src/app/api/test/create/route.ts create mode 100644 ServicesWeb/customer/src/app/api/test/data.ts create mode 100644 ServicesWeb/customer/src/app/api/test/list/route.ts create mode 100644 ServicesWeb/customer/src/app/api/test/update/route.ts create mode 100644 ServicesWeb/customer/src/components/custom/content/createFromComponent.tsx create mode 100644 ServicesWeb/customer/src/components/custom/content/updateFromComponent.tsx create mode 100644 ServicesWeb/customer/src/components/mutual/context/cache/context.tsx create mode 100644 ServicesWeb/customer/src/components/mutual/context/cache/withCache.tsx create mode 100644 ServicesWeb/customer/src/fetchers/custom/context/cache/fetch.tsx diff --git a/ServicesWeb/customer/README.md b/ServicesWeb/customer/README.md index 7b78ee1..45dfb4c 100644 --- a/ServicesWeb/customer/README.md +++ b/ServicesWeb/customer/README.md @@ -43,10 +43,44 @@ Network Manager NM Network Manager Application Manager AM Application Manager Super User SUE Super User - Daire Sakini Vekili Daire Sakini Vekili FL-REP Daire FL -URL Type Tested -/building/accounts/managment/accounts : flat_representative No +URL Type Tested +/building/accounts/managment/accounts : flat_representative No -/definitions/identifications/people : flat_tenant No +/definitions/identifications/people : flat_tenant No +## Table Component with Edit/Create Functionality + +### Features Implemented + +1. **Table Actions Column** + + - Added an actions column with pencil icon edit button as the first column in the table + - Implemented proper styling and visibility for the edit button + - Ensured the button appears in each row of the table + +2. **Redis Cache Integration** + + - Implemented data caching between list and edit/create views + - Row data is stored in Redis when edit button is clicked + - Cached data is retrieved and populated in the update form + - Boolean values (checkboxes) are properly handled with type conversion + +3. **Navigation** + + - Added dynamic routing based on activePageUrl + - Edit button navigates to `/panel/${activePageUrl}/update` + - Added "Create New" button that navigates to `/panel/${activePageUrl}/create` + - Added "Back to List" buttons on both create and update forms + +4. **UI Improvements** + - Fixed page dropdown to always show at least page 1 when there's only one page + - Added debugging logs for troubleshooting + - Improved form field handling with proper type conversion + +### Implementation Details + +- Used `@tanstack/react-table` for table rendering +- Leveraged Redis for state management between pages +- Implemented proper error handling and debugging +- Used Next.js routing for navigation diff --git a/ServicesWeb/customer/src/app/api/context/cache/route.ts b/ServicesWeb/customer/src/app/api/context/cache/route.ts new file mode 100644 index 0000000..e4fc53d --- /dev/null +++ b/ServicesWeb/customer/src/app/api/context/cache/route.ts @@ -0,0 +1,90 @@ +import { + getCacheFromRedis, + setCacheToRedis, + clearCacheFromRedis, +} from "@/fetchers/custom/context/cache/fetch"; +import { AuthError } from "@/fetchers/types/context"; +import { NextResponse } from "next/server"; + +export async function GET(request: Request) { + try { + // Get the URL from query parameters + const { searchParams } = new URL(request.url); + const url = searchParams.get("url"); + + if (!url) { + return new NextResponse( + JSON.stringify({ status: 400, error: "URL parameter is required" }), + { status: 400, headers: { "Content-Type": "application/json" } } + ); + } + + const cacheData = await getCacheFromRedis(url); + return NextResponse.json({ status: 200, data: cacheData || null }); + } catch (error) { + if (error instanceof AuthError) { + return new NextResponse( + JSON.stringify({ status: 401, error: error.message }), + { status: 401, headers: { "Content-Type": "application/json" } } + ); + } + return new NextResponse( + JSON.stringify({ status: 500, error: "Internal server error" }), + { status: 500, headers: { "Content-Type": "application/json" } } + ); + } +} + +export async function POST(request: Request) { + try { + const body = await request.json(); + + // Check if required parameters are present + if (!body.url || !body.data) { + return new NextResponse( + JSON.stringify({ + status: 400, + error: "URL and data parameters are required", + }), + { status: 400, headers: { "Content-Type": "application/json" } } + ); + } + + await setCacheToRedis(body.url, body.data); + return NextResponse.json({ status: 200, success: true }); + } catch (error) { + if (error instanceof AuthError) { + return new NextResponse( + JSON.stringify({ status: 401, error: error.message }), + { status: 401, headers: { "Content-Type": "application/json" } } + ); + } + return new NextResponse( + JSON.stringify({ status: 500, error: "Internal server error" }), + { status: 500, headers: { "Content-Type": "application/json" } } + ); + } +} + +export async function DELETE(request: Request) { + try { + // Get the URL from query parameters + const { searchParams } = new URL(request.url); + const url = searchParams.get("url"); + + if (!url) { + return new NextResponse( + JSON.stringify({ status: 400, error: "URL parameter is required" }), + { status: 400, headers: { "Content-Type": "application/json" } } + ); + } + + await clearCacheFromRedis(url); + return NextResponse.json({ status: 200, success: true }); + } catch (error) { + return new NextResponse( + JSON.stringify({ status: 500, error: "Internal server error" }), + { status: 500, headers: { "Content-Type": "application/json" } } + ); + } +} diff --git a/ServicesWeb/customer/src/app/api/test/create/route.ts b/ServicesWeb/customer/src/app/api/test/create/route.ts new file mode 100644 index 0000000..99ea2ee --- /dev/null +++ b/ServicesWeb/customer/src/app/api/test/create/route.ts @@ -0,0 +1,41 @@ +import { NextResponse } from "next/server"; +import { globalData, ensureDataInitialized, TableItem } from "../data"; + +export async function POST(request: Request) { + try { + // Parse the request body + const body = await request.json(); + + // Ensure we have the required fields + if (!body.name || !body.email) { + return NextResponse.json( + { status: 400, message: "Name and email are required" }, + { status: 400 } + ); + } + + // Create a new item with an ID + const newItem: TableItem = { + id: `id-${Date.now()}-${globalData.length}`, + ...body + }; + + // Add to global data + ensureDataInitialized(); + globalData.unshift(newItem); // Add to beginning of array + + console.log(`POST /api/test/create - Added new item with id: ${newItem.id}`); + + return NextResponse.json({ + status: 201, + data: newItem, + message: "Item added successfully" + }, { status: 201 }); + } catch (error) { + console.error('Error in test/create POST API:', error); + return NextResponse.json( + { status: 500, message: "Error adding data" }, + { status: 500 } + ); + } +} diff --git a/ServicesWeb/customer/src/app/api/test/data.ts b/ServicesWeb/customer/src/app/api/test/data.ts new file mode 100644 index 0000000..daea130 --- /dev/null +++ b/ServicesWeb/customer/src/app/api/test/data.ts @@ -0,0 +1,45 @@ +// Define the data type +export type TableItem = { + id?: string; + name: string; + email: string; + description: string; + category: string; + priority: string; + notifications: boolean; + terms: boolean; + attachments: string; +}; + +// Global data store +export let globalData: TableItem[] = []; + +// Initialize with mock data if empty +export function ensureDataInitialized() { + if (globalData.length === 0) { + globalData = generateMockData(20); + } + return globalData; +} + +// Generate mock data for testing +export function generateMockData(count = 20) { + const categories = ["general", "billing", "technical", "other"]; + const priorities = ["low", "medium", "high"]; + const attachmentTypes = ["document", "image", "video", ""]; + + return Array.from({ length: count }, (_, i) => ({ + id: `id-${Date.now()}-${i}`, + name: `User ${i + 1}`, + email: `user${i + 1}@example.com`, + description: `This is a sample description for item ${ + i + 1 + }. It contains enough text to demonstrate how descriptions look in the table.`, + category: categories[Math.floor(Math.random() * categories.length)], + priority: priorities[Math.floor(Math.random() * priorities.length)], + notifications: Math.random() > 0.5, + terms: Math.random() > 0.3, // Most users accept terms + attachments: + attachmentTypes[Math.floor(Math.random() * attachmentTypes.length)], + })); +} diff --git a/ServicesWeb/customer/src/app/api/test/list/route.ts b/ServicesWeb/customer/src/app/api/test/list/route.ts new file mode 100644 index 0000000..342bfc1 --- /dev/null +++ b/ServicesWeb/customer/src/app/api/test/list/route.ts @@ -0,0 +1,109 @@ +import { NextResponse } from "next/server"; +import { ensureDataInitialized } from "../data"; + +// Helper function to handle pagination logic +function getPaginatedData(page: number, size: number) { + // Get data from global store + const allData = ensureDataInitialized(); + + // Calculate pagination + const startIndex = (page - 1) * size; + const endIndex = startIndex + size; + const paginatedData = allData.slice(startIndex, endIndex); + + return { + paginatedData, + allData, + meta: { + page, + size, + totalCount: allData.length, + totalPages: Math.ceil(allData.length / size), + pageCount: paginatedData.length, + }, + }; +} + +// GET endpoint for listing data with pagination +export async function GET(request: Request) { + try { + // Get query parameters for pagination + const url = new URL(request.url); + const page = parseInt(url.searchParams.get("page") || "1"); + const size = parseInt(url.searchParams.get("size") || "10"); + + const { paginatedData, meta } = getPaginatedData(page, size); + + console.log( + `GET /api/test/list - Returning ${paginatedData.length} items (page ${page}/${meta.totalPages})` + ); + + // Return paginated response with the structure that useTableData expects + return NextResponse.json({ + success: true, + data: { + data: paginatedData, + pagination: { + size: meta.size, + page: meta.page, + allCount: meta.totalCount, + totalCount: meta.totalCount, + totalPages: meta.totalPages, + pageCount: meta.pageCount, + orderField: [], + orderType: [], + next: meta.page < meta.totalPages, + back: meta.page > 1, + }, + }, + }); + } catch (error) { + console.error("Error in test/list GET API:", error); + return NextResponse.json( + { success: false, message: "Error fetching data" }, + { status: 500 } + ); + } +} + +// POST endpoint for listing data with pagination (for useTableData compatibility) +export async function POST(request: Request) { + try { + // Parse the request body for pagination parameters + const body = await request.json(); + const page = parseInt(body.page?.toString() || "1"); + const size = parseInt(body.size?.toString() || "10"); + + const { paginatedData, meta } = getPaginatedData(page, size); + + console.log( + `POST /api/test/list - Returning ${paginatedData.length} items (page ${page}/${meta.totalPages})` + ); + + // Return paginated response with the structure that useTableData expects + return NextResponse.json({ + success: true, + data: { + data: paginatedData, + pagination: { + size: meta.size, + page: meta.page, + allCount: meta.totalCount, + totalCount: meta.totalCount, + totalPages: meta.totalPages, + pageCount: meta.pageCount, + orderField: [], + orderType: [], + next: meta.page < meta.totalPages, + back: meta.page > 1, + }, + }, + }); + } catch (error) { + console.error("Error in test/list POST API:", error); + return NextResponse.json( + { success: false, message: "Error fetching data" }, + { status: 500 } + ); + } +} diff --git a/ServicesWeb/customer/src/app/api/test/update/route.ts b/ServicesWeb/customer/src/app/api/test/update/route.ts new file mode 100644 index 0000000..c86d788 --- /dev/null +++ b/ServicesWeb/customer/src/app/api/test/update/route.ts @@ -0,0 +1,50 @@ +import { NextResponse } from "next/server"; +import { globalData, ensureDataInitialized } from "../data"; + +export async function PATCH(request: Request) { + try { + // Parse the request body + const body = await request.json(); + + // Ensure we have an ID to update + if (!body.id) { + return NextResponse.json( + { status: 400, message: "ID is required for updates" }, + { status: 400 } + ); + } + + // Find the item to update + ensureDataInitialized(); + const itemIndex = globalData.findIndex(item => item.id === body.id); + + if (itemIndex === -1) { + return NextResponse.json( + { status: 404, message: "Item not found" }, + { status: 404 } + ); + } + + // Update the item + const updatedItem = { + ...globalData[itemIndex], + ...body + }; + + globalData[itemIndex] = updatedItem; + + console.log(`PATCH /api/test/update - Updated item with id: ${updatedItem.id}`); + + return NextResponse.json({ + status: 200, + data: updatedItem, + message: "Item updated successfully" + }); + } catch (error) { + console.error('Error in test/update PATCH API:', error); + return NextResponse.json( + { status: 500, message: "Error updating data" }, + { status: 500 } + ); + } +} diff --git a/ServicesWeb/customer/src/components/custom/content/PageToBeChildrendMulti.tsx b/ServicesWeb/customer/src/components/custom/content/PageToBeChildrendMulti.tsx index 926d3bb..c187614 100644 --- a/ServicesWeb/customer/src/components/custom/content/PageToBeChildrendMulti.tsx +++ b/ServicesWeb/customer/src/components/custom/content/PageToBeChildrendMulti.tsx @@ -15,6 +15,12 @@ const PageToBeChildrendMulti: React.FC = ({ updateOnline, refreshUser, updateUser, + cacheData, + cacheLoading, + cacheError, + refreshCache, + updateCache, + clearCache }) => { const pageComponents = pageIndexMulti[activePageUrl]; if (!pageComponents) { return } @@ -25,6 +31,7 @@ const PageToBeChildrendMulti: React.FC = ({ activePageUrl={activePageUrl} searchParams={searchParams} userData={userData} userLoading={userLoading} userError={userError} refreshUser={refreshUser} updateUser={updateUser} onlineData={onlineData} onlineLoading={onlineLoading} onlineError={onlineError} refreshOnline={refreshOnline} updateOnline={updateOnline} + cacheData={cacheData} cacheLoading={cacheLoading} cacheError={cacheError} refreshCache={refreshCache} updateCache={updateCache} clearCache={clearCache} />; } diff --git a/ServicesWeb/customer/src/components/custom/content/TableCardComponentImproved.tsx b/ServicesWeb/customer/src/components/custom/content/TableCardComponentImproved.tsx index 5ffce7b..7a80cce 100644 --- a/ServicesWeb/customer/src/components/custom/content/TableCardComponentImproved.tsx +++ b/ServicesWeb/customer/src/components/custom/content/TableCardComponentImproved.tsx @@ -33,6 +33,9 @@ import { TableDataItem, } from "@/validations/mutual/table/validations"; import LoadingContent from "@/components/mutual/loader/component"; +import { Pencil } from "lucide-react"; +import { useRouter } from "next/navigation"; +import { setCacheData } from "@/components/mutual/context/cache/context"; interface DataTableProps { table: ReturnType; @@ -42,6 +45,7 @@ interface DataTableProps { getSortingIcon: (columnId: string) => React.ReactNode; flexRender: typeof flexRender; t: Translations; + handleEditRow?: (row: TableDataItem) => void; } interface TableFormProps { @@ -105,7 +109,8 @@ const DataTable: React.FC = React.memo(({ handleSortingChange, getSortingIcon, flexRender, - t + t, + handleEditRow }) => { return (
@@ -120,20 +125,23 @@ const DataTable: React.FC = React.memo(({ {table.getHeaderGroups().map((headerGroup) => ( - {headerGroup.headers.map((header) => ( - handleSortingChange(header.column.id)} - aria-sort={header.column.getIsSorted() ? (header.column.getIsSorted() === 'desc' ? 'descending' : 'ascending') : undefined} - scope="col" - > -
- {flexRender(header.column.columnDef.header, header.getContext())} - {getSortingIcon(header.column.id)} -
- - ))} + {headerGroup.headers.map((header) => { + console.log('Rendering header:', header.id); + return ( + handleSortingChange(header.column.id)} + aria-sort={header.column.getIsSorted() ? (header.column.getIsSorted() === 'desc' ? 'descending' : 'ascending') : undefined} + scope="col" + > +
+ {flexRender(header.column.columnDef.header, header.getContext())} + {getSortingIcon(header.column.id)} +
+ + ); + })} ))} @@ -147,11 +155,14 @@ const DataTable: React.FC = React.memo(({ ) : ( table.getRowModel().rows.map((row) => ( - {row.getVisibleCells().map((cell) => ( - - {flexRender(cell.column.columnDef.cell, cell.getContext())} - - ))} + {row.getVisibleCells().map((cell) => { + console.log('Rendering cell:', cell.column.id); + return ( + + {flexRender(cell.column.columnDef.cell, cell.getContext())} + + ); + })} )) )} @@ -196,11 +207,17 @@ const TableForm: React.FC = ({ - {renderPageOptions().map((option: { key: string | number; value: string; label: string }) => ( - - {option.label} + {renderPageOptions().length > 0 ? ( + renderPageOptions().map((option: { key: string | number; value: string; label: string }) => ( + + {option.label} + + )) + ) : ( + + 1 - ))} + )} @@ -328,6 +345,7 @@ const TableCardComponentImproved: React.FC = React.memo((pro // Initialize translation with English as default const language = props.onlineData?.lang as LanguageTypes || 'en'; const t = translations[language]; + const router = useRouter(); const { form, @@ -351,30 +369,90 @@ const TableCardComponentImproved: React.FC = React.memo((pro isNextDisabled, pageSizeOptions, renderPageOptions, - } = useTableData({ apiUrl: `${API_BASE_URL}/test` }); + } = useTableData({ apiUrl: `${API_BASE_URL}/test/list` }); + const activePageUrl = props.activePageUrl || ''; + console.log("activePageUrl", activePageUrl); + + // Function to handle editing a row + const handleEditRow = async (row: TableDataItem) => { + try { + // Store the row data in the cache + await setCacheData(`${activePageUrl}/update`, row); + console.log('Row data stored in cache:', row); + + // Navigate to the update form + router.push(`/panel/${activePageUrl}/update`); + } catch (error) { + console.error('Error storing row data in cache:', error); + } + }; const columnHelper = createColumnHelper(); + // Make sure columns are properly defined with the actions column const columns = React.useMemo(() => [ - columnHelper.accessor('uu_id', { - cell: info => info.getValue(), - header: () => UUID, - footer: info => info.column.id + columnHelper.display({ + id: 'actions', + header: () => Actions, + cell: (info) => { + console.log('Rendering action cell for row:', info.row.id); + return ( + + ); + } }), - columnHelper.accessor('process_name', { + columnHelper.accessor('name', { cell: info => info.getValue(), header: () => Name, footer: info => info.column.id }), - columnHelper.accessor('bank_date', { - header: () => Bank Date, + columnHelper.accessor('email', { + cell: info => info.getValue(), + header: () => Email, + footer: info => info.column.id + }), + columnHelper.accessor('description', { + header: () => Description, cell: info => info.getValue(), footer: info => info.column.id }), - columnHelper.accessor('currency_value', { - header: () => Currency Value, + columnHelper.accessor('category', { + header: () => Category, cell: info => String(info.getValue()), footer: info => info.column.id }), + columnHelper.accessor('priority', { + header: () => Priority, + cell: info => String(info.getValue()), + footer: info => info.column.id + }), + columnHelper.accessor('notifications', { + header: () => Notifications, + cell: info => info.getValue() ? 'Yes' : 'No', + footer: info => info.column.id + }), + columnHelper.accessor('terms', { + header: () => Terms Accepted, + cell: info => info.getValue() ? 'Yes' : 'No', + footer: info => info.column.id + }), + columnHelper.accessor('attachments', { + header: () => Attachments, + cell: info => String(info.getValue() || '-'), + footer: info => info.column.id + }), + ], [columnHelper]) as ColumnDef[]; const table = useReactTable({ @@ -387,8 +465,12 @@ const TableCardComponentImproved: React.FC = React.memo((pro getPaginationRowModel: getPaginationRowModel(), manualPagination: true, pageCount: apiPagination.totalPages || 1, + debugTable: true, }); + // Check if actions column is included + const actionColumnExists = table.getVisibleLeafColumns().some(col => col.id === 'actions'); + return ( isLoading ? :
@@ -401,6 +483,13 @@ const TableCardComponentImproved: React.FC = React.memo((pro error={error} t={t} /> +
= React.memo((pro getSortingIcon={getSortingIcon} flexRender={flexRender} t={t} + handleEditRow={handleEditRow} />
diff --git a/ServicesWeb/customer/src/components/custom/content/component.tsx b/ServicesWeb/customer/src/components/custom/content/component.tsx index 721b1a6..0530bd7 100644 --- a/ServicesWeb/customer/src/components/custom/content/component.tsx +++ b/ServicesWeb/customer/src/components/custom/content/component.tsx @@ -40,6 +40,7 @@ const FallbackContent: FC<{ lang: LanguageTypes; activePageUrl: string }> = memo const ContentComponent: FC = ({ searchParams, activePageUrl, mode, userData, userLoading, userError, refreshUser, updateUser, onlineData, onlineLoading, onlineError, refreshOnline, updateOnline, + cacheData, cacheLoading, cacheError, refreshCache, updateCache, clearCache }) => { const loadingContent = ; const classNameDiv = "fixed top-24 left-80 right-0 py-10 px-15 border-emerald-150 border-l-2 overflow-y-auto h-[calc(100vh-64px)]"; @@ -62,7 +63,8 @@ const ContentComponent: FC = ({ + onlineData={onlineData} onlineLoading={onlineLoading} onlineError={onlineError} refreshOnline={refreshOnline} updateOnline={updateOnline} + cacheData={cacheData} cacheLoading={cacheLoading} cacheError={cacheError} refreshCache={refreshCache} updateCache={updateCache} clearCache={clearCache} /> diff --git a/ServicesWeb/customer/src/components/custom/content/createFromComponent.tsx b/ServicesWeb/customer/src/components/custom/content/createFromComponent.tsx new file mode 100644 index 0000000..6ccfd14 --- /dev/null +++ b/ServicesWeb/customer/src/components/custom/content/createFromComponent.tsx @@ -0,0 +1,403 @@ +import React, { useState, useEffect } from "react"; +import { apiPostFetcher } 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; + +function CreateFromComponentBase({ + cacheData, + cacheLoading, + cacheError, + refreshCache, + updateCache, + clearCache, + activePageUrl +}: { + cacheData?: { [url: string]: any } | null; + cacheLoading?: boolean; + cacheError?: string | null; + refreshCache?: (url?: string) => Promise; + updateCache?: (url: string, data: any) => Promise; + clearCache?: (url: string) => Promise; + activePageUrl?: string; +}) { + const [open, setOpen] = useState(false); + const [cacheLoaded, setCacheLoaded] = useState(false); + const router = useRouter(); + const listUrl = `/panel/${activePageUrl?.replace("/create", "")}`; + + const form = useForm({ + 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 { + console.log("Directly fetching cache for URL:", activePageUrl); + + // Directly fetch cache data using the imported function + const cachedData = await getCacheData(activePageUrl); + console.log("Directly fetched cache data:", cachedData); + + if (cachedData) { + const formValues = form.getValues(); + + // Create a merged data object with proper typing + 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: cachedData.notifications ?? formValues.notifications ?? false, + terms: cachedData.terms ?? formValues.terms ?? false, + attachments: cachedData.attachments || formValues.attachments || "" + }; + + console.log("Setting form with direct cache data:", mergedData); + form.reset(mergedData); + } + + setCacheLoaded(true); + + // Also call the context refresh if available (for state consistency) + 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 + const response = await apiPostFetcher({ url: "/tst/create", 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 ( +
+ {/* back to list button */} +
+ +
+ +

Example Form

+ +
+ + {/* Input example */} + ( + + Name + + { field.onBlur(); handleFieldBlur("name", e.target.value) }} + /> + + + + )} + /> + + {/* Email input example */} + ( + + Email + + { field.onBlur(); handleFieldBlur("email", e.target.value) }} + /> + + + + )} + /> + + {/* Textarea example */} + ( + + Description + +