From c9751b058fd510e5eb6e93a5969af075f4ac7b75 Mon Sep 17 00:00:00 2001 From: Ken Yasue Date: Tue, 25 Mar 2025 07:08:43 +0100 Subject: [PATCH] contact recoed list --- doc/prompts/10. Contact Record list | 5 + .../(admin)/admin/contact-records/page.tsx | 292 ++++++++++++++++++ src/app/(admin)/admin/layout.tsx | 6 + src/app/api/contact-records/route.ts | 28 +- 4 files changed, 330 insertions(+), 1 deletion(-) create mode 100644 doc/prompts/10. Contact Record list create mode 100644 src/app/(admin)/admin/contact-records/page.tsx diff --git a/doc/prompts/10. Contact Record list b/doc/prompts/10. Contact Record list new file mode 100644 index 0000000..e8d8779 --- /dev/null +++ b/doc/prompts/10. Contact Record list @@ -0,0 +1,5 @@ +I want add list page for Contact Recored. +- Add link to contact recored list page to admin console headder +- Users can see customer name, contact type, and notes +- Users can click on customer name then opens the customer detail +- Users can filter by customer, date ( from - to ) diff --git a/src/app/(admin)/admin/contact-records/page.tsx b/src/app/(admin)/admin/contact-records/page.tsx new file mode 100644 index 0000000..328df10 --- /dev/null +++ b/src/app/(admin)/admin/contact-records/page.tsx @@ -0,0 +1,292 @@ +'use client'; + +import { useState, useEffect, FormEvent } from 'react'; +import Link from 'next/link'; +import { useRouter, useSearchParams } from 'next/navigation'; + +interface Customer { + id: string; + name: string; +} + +interface ContactRecord { + id: string; + customerId: string; + contactType: string; + notes: string; + createdAt: string; + customer: Customer; +} + +export default function ContactRecordsList() { + const router = useRouter(); + const searchParams = useSearchParams(); + + // Get filter values from URL params + const initialCustomerId = searchParams.get('customerId') || ''; + const initialDateFrom = searchParams.get('dateFrom') || ''; + const initialDateTo = searchParams.get('dateTo') || ''; + + // State for filters + const [customerId, setCustomerId] = useState(initialCustomerId); + const [dateFrom, setDateFrom] = useState(initialDateFrom); + const [dateTo, setDateTo] = useState(initialDateTo); + + // State for data + const [contactRecords, setContactRecords] = useState([]); + const [customers, setCustomers] = useState([]); + const [isLoading, setIsLoading] = useState(true); + const [error, setError] = useState(null); + + // Fetch customers for the filter dropdown + useEffect(() => { + const fetchCustomers = async () => { + try { + const response = await fetch('/api/customers'); + if (!response.ok) { + throw new Error('Failed to fetch customers'); + } + const data = await response.json(); + setCustomers(data); + } catch (err) { + console.error('Error fetching customers:', err); + setError(err instanceof Error ? err.message : 'An error occurred'); + } + }; + + fetchCustomers(); + }, []); + + // Fetch contact records with filters + useEffect(() => { + const fetchContactRecords = async () => { + setIsLoading(true); + setError(null); + + try { + // Build query string with filters + const params = new URLSearchParams(); + if (customerId) params.append('customerId', customerId); + if (dateFrom) params.append('dateFrom', dateFrom); + if (dateTo) params.append('dateTo', dateTo); + + const response = await fetch(`/api/contact-records?${params.toString()}`); + + if (!response.ok) { + throw new Error('Failed to fetch contact records'); + } + + const data = await response.json(); + setContactRecords(data); + } catch (err) { + console.error('Error fetching contact records:', err); + setError(err instanceof Error ? err.message : 'An error occurred'); + } finally { + setIsLoading(false); + } + }; + + // Only fetch if we have URL parameters or if this is the initial load + if (initialCustomerId || initialDateFrom || initialDateTo || isLoading) { + fetchContactRecords(); + } + }, [initialCustomerId, initialDateFrom, initialDateTo]); + + // Handle filter form submission + const handleFilterSubmit = (e: FormEvent) => { + e.preventDefault(); + + // Build query string with filters + const params = new URLSearchParams(); + if (customerId) params.append('customerId', customerId); + if (dateFrom) params.append('dateFrom', dateFrom); + if (dateTo) params.append('dateTo', dateTo); + + // Update URL with filters + router.push(`/admin/contact-records?${params.toString()}`); + }; + + // Handle filter reset + const handleReset = () => { + setCustomerId(''); + setDateFrom(''); + setDateTo(''); + router.push('/admin/contact-records'); + }; + + return ( +
+
+

Contact Records

+
+ + {/* Filter Form */} +
+
+

Filter Contact Records

+
+
+
+
+
+ + +
+
+ + setDateFrom(e.target.value)} + className="mt-1 block w-full pl-3 pr-10 py-2 text-base border-gray-300 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm rounded-md dark:bg-gray-800 dark:border-gray-700 dark:text-gray-200" + /> +
+
+ + setDateTo(e.target.value)} + className="mt-1 block w-full pl-3 pr-10 py-2 text-base border-gray-300 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm rounded-md dark:bg-gray-800 dark:border-gray-700 dark:text-gray-200" + /> +
+
+
+ + +
+
+
+
+ + {/* Error Message */} + {error && ( +
+
+
+ + + +
+
+

{error}

+
+
+
+ )} + + {/* Loading State */} + {isLoading ? ( +
+

Loading contact records...

+
+ ) : ( + <> + {/* Contact Records Table */} + {contactRecords.length === 0 ? ( +
+
+

No contact records found.

+
+
+ ) : ( +
+
+ + + + + + + + + + + + {contactRecords.map((record) => ( + + + + + + + + ))} + +
+ Customer + + Contact Type + + Notes + + Date + + Actions +
+ + {record.customer?.name || 'Unknown Customer'} + + + + {record.contactType} + + +
+ {record.notes || No notes} +
+
+ {new Date(record.createdAt).toLocaleString()} + + + Edit + +
+
+
+ )} + + )} +
+ ); +} diff --git a/src/app/(admin)/admin/layout.tsx b/src/app/(admin)/admin/layout.tsx index e4c385c..ac85add 100644 --- a/src/app/(admin)/admin/layout.tsx +++ b/src/app/(admin)/admin/layout.tsx @@ -106,6 +106,12 @@ export default function RootLayout({ > Customers + + Contact Records +
diff --git a/src/app/api/contact-records/route.ts b/src/app/api/contact-records/route.ts index c6b2f28..735c3a6 100644 --- a/src/app/api/contact-records/route.ts +++ b/src/app/api/contact-records/route.ts @@ -10,6 +10,8 @@ export async function GET(request: NextRequest) { // Get query parameters const url = new URL(request.url); const customerId = url.searchParams.get('customerId'); + const dateFrom = url.searchParams.get('dateFrom'); + const dateTo = url.searchParams.get('dateTo'); // Build query const queryOptions: any = { @@ -17,9 +19,33 @@ export async function GET(request: NextRequest) { relations: ['customer'] }; + // Build where clause + let whereClause: any = {}; + // Filter by customer if customerId is provided if (customerId) { - queryOptions.where = { customerId }; + whereClause.customerId = customerId; + } + + // Filter by date range if provided + if (dateFrom || dateTo) { + whereClause.createdAt = {}; + + if (dateFrom) { + whereClause.createdAt.gte = new Date(dateFrom); + } + + if (dateTo) { + // Set the date to the end of the day for inclusive filtering + const endDate = new Date(dateTo); + endDate.setHours(23, 59, 59, 999); + whereClause.createdAt.lte = endDate; + } + } + + // Add where clause to query options if not empty + if (Object.keys(whereClause).length > 0) { + queryOptions.where = whereClause; } const contactRecords = await contactRecordRepository.find(queryOptions);