1import { useState, useEffect } from 'react';
2import { apiFetch } from '../lib/api';
3import { notifySuccess, notifyError } from '../utils/notifications';
5export default function CustomerMergeModal({ isOpen, onClose, onMergeComplete, customers }) {
6 const [customer1Id, setCustomer1Id] = useState('');
7 const [customer2Id, setCustomer2Id] = useState('');
8 const [comparisonData, setComparisonData] = useState(null);
9 const [primaryCustomerId, setPrimaryCustomerId] = useState(null);
10 const [loading, setLoading] = useState(false);
11 const [error, setError] = useState('');
14 // Reset state when modal opens/closes
18 setComparisonData(null);
19 setPrimaryCustomerId(null);
24 const handleCompare = async () => {
25 if (!customer1Id || !customer2Id) {
26 setError('Please select both customers to compare');
30 if (customer1Id === customer2Id) {
31 setError('Please select two different customers');
39 const token = localStorage.getItem('token');
40 const response = await apiFetch(`/customers/merge/compare/${customer1Id}/${customer2Id}`, {
41 headers: { Authorization: `Bearer ${token}` }
43 const data = await response.json();
44 setComparisonData(data);
45 // Default to customer with more data as primary
46 const c1Total = data.customer1.tickets_count +
47 data.customer1.invoices_count +
48 data.customer1.contracts_count;
49 const c2Total = data.customer2.tickets_count +
50 data.customer2.invoices_count +
51 data.customer2.contracts_count;
52 setPrimaryCustomerId(c1Total >= c2Total ? data.customer1.customer_id : data.customer2.customer_id);
54 const errorData = await err.json?.() || {};
55 setError(errorData.error || 'Failed to compare customers');
56 console.error('Error comparing customers:', err);
62 const handleMerge = async () => {
63 if (!primaryCustomerId || !comparisonData) {
64 setError('Please select which customer to keep');
68 const secondaryCustomerId = primaryCustomerId === comparisonData.customer1.customer_id
69 ? comparisonData.customer2.customer_id
70 : comparisonData.customer1.customer_id;
72 const confirmMessage = `Are you sure you want to merge these customers?\n\n` +
74 `- Keep customer: ${comparisonData[primaryCustomerId === comparisonData.customer1.customer_id ? 'customer1' : 'customer2'].name}\n` +
75 `- Move all data from the other customer\n` +
76 `- Delete the other customer\n\n` +
77 `This action cannot be undone.`;
79 if (!confirm(confirmMessage)) {
87 const token = localStorage.getItem('token');
88 await apiFetch('/customers/merge', {
91 Authorization: `Bearer ${token}`,
92 'Content-Type': 'application/json'
94 body: JSON.stringify({
95 primary_customer_id: primaryCustomerId,
96 secondary_customer_id: secondaryCustomerId
100 await notifySuccess('Merge Successful', 'Customers have been merged successfully');
104 const errorData = await err.json?.() || {};
105 const errorMsg = errorData.error || 'Failed to merge customers';
106 await notifyError('Merge Failed', errorMsg);
108 console.error('Error merging customers:', err);
114 if (!isOpen) return null;
123 backgroundColor: 'rgba(0, 0, 0, 0.5)',
125 alignItems: 'center',
126 justifyContent: 'center',
130 backgroundColor: 'var(--surface)',
137 border: '1px solid var(--border)'
139 <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '20px' }}>
140 <h2 style={{ margin: 0, color: 'var(--text)' }}>Merge Customers</h2>
148 color: 'var(--text-secondary)',
156 {/* Customer Selection */}
157 <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '16px', marginBottom: '20px' }}>
159 <label style={{ display: 'block', marginBottom: '8px', color: 'var(--text)', fontWeight: 500 }}>
164 onChange={(e) => setCustomer1Id(e.target.value)}
170 border: '1px solid var(--border)',
171 backgroundColor: 'var(--background)',
172 color: 'var(--text)',
176 <option value="">Select customer...</option>
177 {customers.map(c => (
178 <option key={c.customer_id} value={c.customer_id}>
186 <label style={{ display: 'block', marginBottom: '8px', color: 'var(--text)', fontWeight: 500 }}>
191 onChange={(e) => setCustomer2Id(e.target.value)}
197 border: '1px solid var(--border)',
198 backgroundColor: 'var(--background)',
199 color: 'var(--text)',
203 <option value="">Select customer...</option>
204 {customers.map(c => (
205 <option key={c.customer_id} value={c.customer_id}>
214 onClick={handleCompare}
215 disabled={loading || !customer1Id || !customer2Id}
217 padding: '10px 20px',
218 backgroundColor: 'var(--primary)',
222 cursor: loading ? 'wait' : 'pointer',
224 marginBottom: '20px',
225 opacity: (!customer1Id || !customer2Id) ? 0.5 : 1
228 {loading ? 'Comparing...' : 'Compare Customers'}
234 backgroundColor: 'var(--error-bg)',
235 color: 'var(--error)',
237 marginBottom: '20px',
238 border: '1px solid var(--error)'
244 {/* Comparison Results */}
249 gridTemplateColumns: '1fr 1fr',
253 {/* Customer 1 Card */}
255 border: primaryCustomerId === comparisonData.customer1.customer_id
256 ? '2px solid var(--primary)'
257 : '1px solid var(--border)',
260 backgroundColor: primaryCustomerId === comparisonData.customer1.customer_id
265 onClick={() => setPrimaryCustomerId(comparisonData.customer1.customer_id)}
267 <div style={{ display: 'flex', alignItems: 'center', marginBottom: '12px' }}>
270 checked={primaryCustomerId === comparisonData.customer1.customer_id}
271 onChange={() => setPrimaryCustomerId(comparisonData.customer1.customer_id)}
272 style={{ marginRight: '8px' }}
274 <h3 style={{ margin: 0, color: 'var(--text)' }}>
275 {comparisonData.customer1.name}
278 <div style={{ fontSize: '13px', color: 'var(--text-secondary)', lineHeight: '1.8' }}>
279 <div><strong>Email:</strong> {comparisonData.customer1.email || 'N/A'}</div>
280 <div><strong>Phone:</strong> {comparisonData.customer1.phone || 'N/A'}</div>
281 <div style={{ marginTop: '12px', paddingTop: '12px', borderTop: '1px solid var(--border)' }}>
282 <strong>Associated Data:</strong>
284 <div>• Tickets: {comparisonData.customer1.tickets_count}</div>
285 <div>• Invoices: {comparisonData.customer1.invoices_count}</div>
286 <div>• Contracts: {comparisonData.customer1.contracts_count}</div>
287 <div>• Agents: {comparisonData.customer1.agents_count}</div>
288 <div>• Hosting Apps: {comparisonData.customer1.hosting_apps_count}</div>
292 {/* Customer 2 Card */}
294 border: primaryCustomerId === comparisonData.customer2.customer_id
295 ? '2px solid var(--primary)'
296 : '1px solid var(--border)',
299 backgroundColor: primaryCustomerId === comparisonData.customer2.customer_id
304 onClick={() => setPrimaryCustomerId(comparisonData.customer2.customer_id)}
306 <div style={{ display: 'flex', alignItems: 'center', marginBottom: '12px' }}>
309 checked={primaryCustomerId === comparisonData.customer2.customer_id}
310 onChange={() => setPrimaryCustomerId(comparisonData.customer2.customer_id)}
311 style={{ marginRight: '8px' }}
313 <h3 style={{ margin: 0, color: 'var(--text)' }}>
314 {comparisonData.customer2.name}
317 <div style={{ fontSize: '13px', color: 'var(--text-secondary)', lineHeight: '1.8' }}>
318 <div><strong>Email:</strong> {comparisonData.customer2.email || 'N/A'}</div>
319 <div><strong>Phone:</strong> {comparisonData.customer2.phone || 'N/A'}</div>
320 <div style={{ marginTop: '12px', paddingTop: '12px', borderTop: '1px solid var(--border)' }}>
321 <strong>Associated Data:</strong>
323 <div>• Tickets: {comparisonData.customer2.tickets_count}</div>
324 <div>• Invoices: {comparisonData.customer2.invoices_count}</div>
325 <div>• Contracts: {comparisonData.customer2.contracts_count}</div>
326 <div>• Agents: {comparisonData.customer2.agents_count}</div>
327 <div>• Hosting Apps: {comparisonData.customer2.hosting_apps_count}</div>
334 backgroundColor: 'var(--warning-bg)',
335 color: 'var(--warning)',
337 marginBottom: '20px',
338 border: '1px solid var(--warning)',
341 <strong>⚠️ Warning:</strong> The selected customer will be kept. All data from the other customer will be moved to it, and the other customer will be permanently deleted.
344 {/* Action Buttons */}
345 <div style={{ display: 'flex', gap: '12px', justifyContent: 'flex-end' }}>
350 padding: '10px 20px',
351 backgroundColor: 'transparent',
352 color: 'var(--text)',
353 border: '1px solid var(--border)',
355 cursor: loading ? 'not-allowed' : 'pointer',
362 onClick={handleMerge}
363 disabled={loading || !primaryCustomerId}
365 padding: '10px 20px',
366 backgroundColor: 'var(--error)',
370 cursor: loading || !primaryCustomerId ? 'not-allowed' : 'pointer',
372 opacity: !primaryCustomerId ? 0.5 : 1
375 {loading ? 'Merging...' : 'Merge Customers'}