diff --git a/src/app/(admin)/admin/customers/components/EditCustomer.tsx b/src/app/(admin)/admin/customers/components/EditCustomer.tsx index c8d9a8b..90bf468 100644 --- a/src/app/(admin)/admin/customers/components/EditCustomer.tsx +++ b/src/app/(admin)/admin/customers/components/EditCustomer.tsx @@ -9,6 +9,7 @@ interface Customer { name: string; url: string; email: string; + city?: string; createdAt: string; modifiedAt: string; } @@ -23,6 +24,7 @@ export default function EditCustomer({ id }: EditCustomerProps) { name: '', url: '', email: '', + city: '', }); const [isLoading, setIsLoading] = useState(!!id); // Only loading if editing const [isSubmitting, setIsSubmitting] = useState(false); @@ -49,6 +51,7 @@ export default function EditCustomer({ id }: EditCustomerProps) { name: customer.name, url: customer.url || '', email: customer.email, + city: customer.city || '', }); setIsLoading(false); @@ -169,7 +172,7 @@ export default function EditCustomer({ id }: EditCustomerProps) { /> -
+
@@ -185,6 +188,21 @@ export default function EditCustomer({ id }: EditCustomerProps) { />
+
+ + +
+
+
+
City
+
+ {customer.city || -} +
+
Created
diff --git a/src/app/(admin)/admin/customers/page.tsx b/src/app/(admin)/admin/customers/page.tsx index e49650a..b5a5750 100644 --- a/src/app/(admin)/admin/customers/page.tsx +++ b/src/app/(admin)/admin/customers/page.tsx @@ -11,6 +11,7 @@ interface Customer { name: string; url: string; email: string; + city?: string; createdAt: string; modifiedAt: string; } @@ -44,6 +45,7 @@ export default function AdminCustomers() { name: '', email: '', url: '', + city: '', hasEmail: false }); const [debouncedFilters, setDebouncedFilters] = useState(filters); @@ -74,6 +76,7 @@ export default function AdminCustomers() { if (debouncedFilters.name) params.append('name', debouncedFilters.name); if (debouncedFilters.email) params.append('email', debouncedFilters.email); if (debouncedFilters.url) params.append('url', debouncedFilters.url); + if (debouncedFilters.city) params.append('city', debouncedFilters.city); if (debouncedFilters.hasEmail) params.append('hasEmail', 'true'); const response = await fetch(`/api/customers?${params.toString()}`); @@ -116,6 +119,7 @@ export default function AdminCustomers() { name: '', email: '', url: '', + city: '', hasEmail: false }); }; @@ -238,6 +242,31 @@ export default function AdminCustomers() { )}
+
+ +
+ handleFilterChange('city', e.target.value)} + className="block w-full rounded-md border-gray-300 pr-10 focus:border-indigo-500 focus:ring-indigo-500 dark:bg-gray-700 dark:border-gray-600 dark:text-white sm:text-sm" + placeholder="Enter city..." + /> + {filters.city && ( + + )} +
+
- {(filters.name || filters.email || filters.url || filters.hasEmail) && ( + {(filters.name || filters.email || filters.url || filters.city || filters.hasEmail) && (
Active filters: {[ filters.name && 'Name', filters.email && 'Email', filters.url && 'URL', + filters.city && 'City', filters.hasEmail && 'Has Email' ].filter(Boolean).join(', ')}
@@ -321,6 +351,12 @@ export default function AdminCustomers() { > Email + + City + + + {customer.city || -} + {new Date(customer.createdAt).toLocaleDateString()} @@ -399,7 +438,7 @@ export default function AdminCustomers() { )) ) : ( - + No customers found. Create your first customer! diff --git a/src/app/api/customers/[id]/route.ts b/src/app/api/customers/[id]/route.ts index bb6e1d7..9496603 100644 --- a/src/app/api/customers/[id]/route.ts +++ b/src/app/api/customers/[id]/route.ts @@ -55,7 +55,7 @@ export async function PUT( } const data = await request.json(); - const { name, url, email } = data; + const { name, url, email, city } = data; // Validate required fields if (!name || !email) { @@ -69,6 +69,7 @@ export async function PUT( customer.name = name; customer.url = url || ''; customer.email = email; + customer.city = city || null; // Save the updated customer const updatedCustomer = await customerRepository.save(customer); diff --git a/src/app/api/customers/route.ts b/src/app/api/customers/route.ts index c33eae3..f377752 100644 --- a/src/app/api/customers/route.ts +++ b/src/app/api/customers/route.ts @@ -19,6 +19,7 @@ export async function GET(request: NextRequest) { const nameFilter = url.searchParams.get('name'); const emailFilter = url.searchParams.get('email'); const urlFilter = url.searchParams.get('url'); + const cityFilter = url.searchParams.get('city'); const hasEmailFilter = url.searchParams.get('hasEmail'); // Build query @@ -34,6 +35,9 @@ export async function GET(request: NextRequest) { if (urlFilter) { queryBuilder = queryBuilder.andWhere('LOWER(customer.url) LIKE LOWER(:url)', { url: `%${urlFilter}%` }); } + if (cityFilter) { + queryBuilder = queryBuilder.andWhere('LOWER(customer.city) LIKE LOWER(:city)', { city: `%${cityFilter}%` }); + } if (hasEmailFilter === 'true') { queryBuilder = queryBuilder.andWhere('customer.email IS NOT NULL AND customer.email != :emptyString', { emptyString: '' }); } @@ -80,7 +84,7 @@ export async function POST(request: NextRequest) { const customerRepository = dataSource.getRepository(Customer); const data = await request.json(); - const { name, url, email } = data; + const { name, url, email, city } = data; // Validate required fields if (!name || !email) { @@ -95,6 +99,7 @@ export async function POST(request: NextRequest) { customer.name = name; customer.url = url || ''; customer.email = email; + customer.city = city || null; const savedCustomer = await customerRepository.save(customer); diff --git a/src/lib/database/entities/Customer.ts b/src/lib/database/entities/Customer.ts index 3ef2154..48013fb 100644 --- a/src/lib/database/entities/Customer.ts +++ b/src/lib/database/entities/Customer.ts @@ -15,6 +15,9 @@ export class Customer { @Column() email: string; + @Column({ nullable: true }) + city: string; + @CreateDateColumn() createdAt: Date; diff --git a/src/scripts/import-customers.ts b/src/scripts/import-customers.ts index 9c9c4b5..55b27eb 100644 --- a/src/scripts/import-customers.ts +++ b/src/scripts/import-customers.ts @@ -28,65 +28,78 @@ async function importCustomers(csvFilePath: string): Promise { console.log(`Found ${records.length} records in CSV file`); - // Track processed emails to skip duplicates - const processedEmails = new Set(); - // Track existing names to ensure uniqueness - const existingNames = new Set( - (await customerRepository.find()).map(customer => customer.name) - ); + // Get existing customers for update + const existingCustomers = await customerRepository.find(); + const customersByEmail = new Map(); + const customersByName = new Map(); + + // Create lookup maps for faster access + existingCustomers.forEach(customer => { + if (customer.email) { + customersByEmail.set(customer.email.toLowerCase(), customer); + } + customersByName.set(customer.name.toLowerCase(), customer); + }); let importedCount = 0; - let skippedDuplicateEmail = 0; - let skippedDuplicateName = 0; + let updatedCount = 0; for (const record of records) { const email = record.Email === 'null' ? '' : record.Email; const name = record.Name; - // Skip if email is already processed (not empty and already seen) - if (email && processedEmails.has(email)) { - console.log(`Skipping record with duplicate email: ${email}`); - skippedDuplicateEmail++; - continue; + let customer: Customer; + let isUpdate = false; + + // Check if customer exists by email or name + if (email && customersByEmail.has(email.toLowerCase())) { + // Update existing customer by email + customer = customersByEmail.get(email.toLowerCase())!; + isUpdate = true; + console.log(`Updating customer with email: ${email}`); + } else if (customersByName.has(name.toLowerCase())) { + // Update existing customer by name + customer = customersByName.get(name.toLowerCase())!; + isUpdate = true; + console.log(`Updating customer with name: ${name}`); + } else { + // Create new customer + customer = new Customer(); + console.log(`Creating new customer: ${name}`); } - // Skip if name already exists in database - if (existingNames.has(name)) { - console.log(`Skipping record with duplicate name: ${name}`); - skippedDuplicateName++; - continue; - } - - // Add to processed sets - if (email) { - processedEmails.add(email); - } - existingNames.add(name); - - // Create new customer - const customer = new Customer(); + // Update customer fields customer.name = name; - customer.url = record['URL'] === 'null' ? '' : record['URL']; + customer.url = record.URL === 'null' ? '' : record.URL; customer.email = email; + customer.city = record.City === 'null' ? '' : record.City; try { - // Save to database await customerRepository.save(customer); - importedCount++; - console.log(`Imported customer: ${name}`); + if (isUpdate) { + updatedCount++; + console.log(`Updated customer: ${name}`); + } else { + importedCount++; + console.log(`Imported customer: ${name}`); + + // Add to lookup maps for future reference + if (email) { + customersByEmail.set(email.toLowerCase(), customer); + } + customersByName.set(name.toLowerCase(), customer); + } } catch (e) { - console.log(`Skipped: ${name}`); + console.log(`Error saving customer: ${name}`, e); } - } console.log('Import summary:'); console.log(`- Total records in CSV: ${records.length}`); - console.log(`- Successfully imported: ${importedCount}`); - console.log(`- Skipped (duplicate email): ${skippedDuplicateEmail}`); - console.log(`- Skipped (duplicate name): ${skippedDuplicateName}`); + console.log(`- Successfully imported (new): ${importedCount}`); + console.log(`- Successfully updated: ${updatedCount}`); } catch (error) { console.error('Error importing customers:', error);