diff --git a/backend/app/routes/customers.py b/backend/app/routes/customers.py index 29ca51e..43c5d24 100644 --- a/backend/app/routes/customers.py +++ b/backend/app/routes/customers.py @@ -68,34 +68,136 @@ def get_customer(current_admin, customer_id): except Exception as e: return jsonify({'error': str(e)}), 500 +@customers_bp.route('/', methods=['PUT']) +@token_required +def update_customer(current_admin, customer_id): + """Update customer information""" + try: + data = request.get_json() + + result = call_customer_api( + f'/api/admin/customers/{customer_id}', + method='PUT', + data=data + ) + + if result and result.get('status') == 'success': + return jsonify(result), 200 + else: + return jsonify(result or {'error': 'Failed to update customer'}), 500 + + except Exception as e: + return jsonify({'error': str(e)}), 500 + + +@customers_bp.route('/', methods=['DELETE']) +@token_required +def delete_customer(current_admin, customer_id): + """Delete customer""" + try: + result = call_customer_api( + f'/api/admin/customers/{customer_id}', + method='DELETE' + ) + + if result and result.get('status') == 'success': + return jsonify(result), 200 + else: + return jsonify(result or {'error': 'Failed to delete customer'}), 500 + + except Exception as e: + return jsonify({'error': str(e)}), 500 + + +@customers_bp.route('//suspend', methods=['POST']) +@token_required +def suspend_customer(current_admin, customer_id): + """Suspend customer""" + try: + result = call_customer_api( + f'/api/admin/customers/{customer_id}/suspend', + method='POST' + ) + + if result and result.get('status') == 'success': + return jsonify(result), 200 + else: + return jsonify(result or {'error': 'Failed to suspend customer'}), 500 + + except Exception as e: + return jsonify({'error': str(e)}), 500 + + +@customers_bp.route('//activate', methods=['POST']) +@token_required +def activate_customer(current_admin, customer_id): + """Activate customer""" + try: + result = call_customer_api( + f'/api/admin/customers/{customer_id}/activate', + method='POST' + ) + + if result and result.get('status') == 'success': + return jsonify(result), 200 + else: + return jsonify(result or {'error': 'Failed to activate customer'}), 500 + + except Exception as e: + return jsonify({'error': str(e)}), 500 + + @customers_bp.route('//plan', methods=['PUT']) @token_required def update_customer_plan(current_admin, customer_id): """Update customer's subscription plan""" try: data = request.get_json() - + result = call_customer_api( f'/api/admin/customers/{customer_id}/plan', method='PUT', data=data ) - - if result: + + if result and result.get('status') == 'success': return jsonify(result), 200 else: - return jsonify({'error': 'Failed to update plan'}), 500 - + return jsonify(result or {'error': 'Failed to update plan'}), 500 + except Exception as e: return jsonify({'error': str(e)}), 500 + +@customers_bp.route('//status', methods=['PUT']) +@token_required +def update_customer_status(current_admin, customer_id): + """Update customer status""" + try: + data = request.get_json() + + result = call_customer_api( + f'/api/admin/customers/{customer_id}/status', + method='PUT', + data=data + ) + + if result and result.get('status') == 'success': + return jsonify(result), 200 + else: + return jsonify(result or {'error': 'Failed to update status'}), 500 + + except Exception as e: + return jsonify({'error': str(e)}), 500 + + @customers_bp.route('/stats', methods=['GET']) @token_required def get_customer_stats(current_admin): """Get customer statistics""" try: result = call_customer_api('/api/admin/stats') - + if result: return jsonify(result), 200 else: @@ -109,7 +211,7 @@ def get_customer_stats(current_admin): 'total_containers': 0 } }), 200 - + except Exception as e: return jsonify({'error': str(e)}), 500 diff --git a/frontend/src/pages/Customers.jsx b/frontend/src/pages/Customers.jsx index 6504375..49ad7e6 100644 --- a/frontend/src/pages/Customers.jsx +++ b/frontend/src/pages/Customers.jsx @@ -2,6 +2,7 @@ import { useState, useEffect } from 'react'; import { Link } from 'react-router-dom'; import Layout from '../components/Layout'; import api from '../services/api'; +import customerService from '../services/customerService'; const Customers = () => { const [customers, setCustomers] = useState([]); @@ -11,6 +12,8 @@ const Customers = () => { const [stats, setStats] = useState(null); const [selectedCustomer, setSelectedCustomer] = useState(null); const [showPlanModal, setShowPlanModal] = useState(false); + const [showEditModal, setShowEditModal] = useState(false); + const [showDeleteConfirm, setShowDeleteConfirm] = useState(false); useEffect(() => { fetchCustomers(); @@ -42,13 +45,66 @@ const Customers = () => { const handleUpdatePlan = async (customerId, planData) => { try { - await api.put(`/api/customers/${customerId}/plan`, planData); + await customerService.updateCustomerPlan(customerId, planData); fetchCustomers(); setShowPlanModal(false); setSelectedCustomer(null); + alert('Plan updated successfully!'); } catch (err) { console.error('Failed to update plan:', err); - alert('Failed to update plan'); + alert('Failed to update plan: ' + (err.response?.data?.message || err.message)); + } + }; + + const handleUpdateCustomer = async (customerId, customerData) => { + try { + await customerService.updateCustomer(customerId, customerData); + fetchCustomers(); + setShowEditModal(false); + setSelectedCustomer(null); + alert('Customer updated successfully!'); + } catch (err) { + console.error('Failed to update customer:', err); + alert('Failed to update customer: ' + (err.response?.data?.message || err.message)); + } + }; + + const handleDeleteCustomer = async (customerId) => { + try { + await customerService.deleteCustomer(customerId); + fetchCustomers(); + setShowDeleteConfirm(false); + setSelectedCustomer(null); + alert('Customer deleted successfully!'); + } catch (err) { + console.error('Failed to delete customer:', err); + alert('Failed to delete customer: ' + (err.response?.data?.message || err.message)); + } + }; + + const handleSuspendCustomer = async (customerId) => { + if (!confirm('Are you sure you want to suspend this customer?')) return; + + try { + await customerService.suspendCustomer(customerId); + fetchCustomers(); + alert('Customer suspended successfully!'); + } catch (err) { + console.error('Failed to suspend customer:', err); + alert('Failed to suspend customer: ' + (err.response?.data?.message || err.message)); + } + }; + + const handleActivateCustomer = async (customerId) => { + if (!confirm('Are you sure you want to activate this customer?')) return; + + try { + await customerService.activateCustomer(customerId); + fetchCustomers(); + alert('Customer activated successfully!'); + } catch (err) { + console.error('Failed to activate customer:', err); + alert('Failed to activate customer: ' + (err.response?.data?.message || err.message)); } }; @@ -166,15 +222,55 @@ const Customers = () => { {getStatusBadge(customer.is_active, customer.subscription_status)} {new Date(customer.created_at).toLocaleDateString()} - +
+ + + {customer.subscription_status === 'suspended' || !customer.is_active ? ( + + ) : ( + + )} + +
)) @@ -184,6 +280,18 @@ const Customers = () => { + {/* Edit Customer Modal */} + {showEditModal && selectedCustomer && ( + { + setShowEditModal(false); + setSelectedCustomer(null); + }} + onUpdate={handleUpdateCustomer} + /> + )} + {/* Plan Update Modal */} {showPlanModal && selectedCustomer && ( { onUpdate={handleUpdatePlan} /> )} + + {/* Delete Confirmation Modal */} + {showDeleteConfirm && selectedCustomer && ( + { + setShowDeleteConfirm(false); + setSelectedCustomer(null); + }} + onConfirm={handleDeleteCustomer} + /> + )} ); }; +// Edit Customer Modal Component +const EditCustomerModal = ({ customer, onClose, onUpdate }) => { + const [formData, setFormData] = useState({ + full_name: customer.full_name || '', + email: customer.email || '', + company_name: customer.company_name || '', + phone: customer.phone || '', + billing_address: customer.billing_address || '', + billing_city: customer.billing_city || '', + billing_country: customer.billing_country || '', + billing_postal_code: customer.billing_postal_code || '' + }); + + const handleSubmit = (e) => { + e.preventDefault(); + onUpdate(customer.id, formData); + }; + + return ( +
+
+

Edit Customer - {customer.full_name}

+ +
+
+
+ + setFormData({ ...formData, full_name: e.target.value })} + required + /> +
+ +
+ + setFormData({ ...formData, email: e.target.value })} + required + /> +
+ +
+ + setFormData({ ...formData, company_name: e.target.value })} + /> +
+ +
+ + setFormData({ ...formData, phone: e.target.value })} + /> +
+ +
+ +