mirror of
https://github.com/Alvin-Zilverstand/challenge-11.git
synced 2026-03-06 02:56:27 +01:00
Add new car modification options to CarModifications component, including performance upgrades for engine, exhaust, suspension, and brakes. Update Customer model to include address and car year fields, and implement pre-save hook for updatedAt timestamp. Enhance customer routes with authentication middleware and improved error handling.
This commit is contained in:
@@ -72,6 +72,126 @@ const CarModifications = () => {
|
|||||||
icon: wheelsIcon,
|
icon: wheelsIcon,
|
||||||
category: 'Wheels',
|
category: 'Wheels',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
id: 7,
|
||||||
|
name: 'Turbocharger Kit',
|
||||||
|
description: 'Complete turbo upgrade kit for significant power gains',
|
||||||
|
price: '€2499',
|
||||||
|
icon: engineIcon,
|
||||||
|
category: 'Engine',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 8,
|
||||||
|
name: 'Cat-Back Exhaust',
|
||||||
|
description: 'Performance exhaust system with sport sound',
|
||||||
|
price: '€799',
|
||||||
|
icon: exhaustIcon,
|
||||||
|
category: 'Exhaust',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 9,
|
||||||
|
name: 'Coilover Suspension',
|
||||||
|
description: 'Fully adjustable suspension system for perfect handling',
|
||||||
|
price: '€1299',
|
||||||
|
icon: suspensionIcon,
|
||||||
|
category: 'Suspension',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 10,
|
||||||
|
name: 'Big Brake Kit',
|
||||||
|
description: '6-piston caliper upgrade with larger rotors',
|
||||||
|
price: '€1499',
|
||||||
|
icon: brakesIcon,
|
||||||
|
category: 'Brakes',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 11,
|
||||||
|
name: 'Forged Wheels',
|
||||||
|
description: 'Lightweight forged alloy wheels for better performance',
|
||||||
|
price: '€1999',
|
||||||
|
icon: wheelsIcon,
|
||||||
|
category: 'Wheels',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 12,
|
||||||
|
name: 'Stage 2 Tune',
|
||||||
|
description: 'Advanced ECU remap for maximum power gains',
|
||||||
|
price: '€499',
|
||||||
|
icon: engineIcon,
|
||||||
|
category: 'Engine',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 13,
|
||||||
|
name: 'Downpipe',
|
||||||
|
description: 'High-flow downpipe for improved exhaust flow',
|
||||||
|
price: '€349',
|
||||||
|
icon: exhaustIcon,
|
||||||
|
category: 'Exhaust',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 14,
|
||||||
|
name: 'Sway Bars',
|
||||||
|
description: 'Upgraded sway bars for reduced body roll',
|
||||||
|
price: '€299',
|
||||||
|
icon: suspensionIcon,
|
||||||
|
category: 'Suspension',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 15,
|
||||||
|
name: 'Brake Pads',
|
||||||
|
description: 'High-performance brake pads for better stopping',
|
||||||
|
price: '€199',
|
||||||
|
icon: brakesIcon,
|
||||||
|
category: 'Brakes',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 16,
|
||||||
|
name: 'Wheel Bearings',
|
||||||
|
description: 'Upgraded wheel bearings for smoother rotation',
|
||||||
|
price: '€249',
|
||||||
|
icon: wheelsIcon,
|
||||||
|
category: 'Wheels',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 17,
|
||||||
|
name: 'Intercooler Upgrade',
|
||||||
|
description: 'Larger intercooler for better cooling efficiency',
|
||||||
|
price: '€699',
|
||||||
|
icon: engineIcon,
|
||||||
|
category: 'Engine',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 18,
|
||||||
|
name: 'Exhaust Manifold',
|
||||||
|
description: 'Equal-length exhaust manifold for better flow',
|
||||||
|
price: '€449',
|
||||||
|
icon: exhaustIcon,
|
||||||
|
category: 'Exhaust',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 19,
|
||||||
|
name: 'Strut Brace',
|
||||||
|
description: 'Front strut brace for improved chassis rigidity',
|
||||||
|
price: '€199',
|
||||||
|
icon: suspensionIcon,
|
||||||
|
category: 'Suspension',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 20,
|
||||||
|
name: 'Brake Lines',
|
||||||
|
description: 'Stainless steel braided brake lines',
|
||||||
|
price: '€149',
|
||||||
|
icon: brakesIcon,
|
||||||
|
category: 'Brakes',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 21,
|
||||||
|
name: 'Wheel Locks',
|
||||||
|
description: 'Security wheel locks to prevent theft',
|
||||||
|
price: '€89',
|
||||||
|
icon: wheelsIcon,
|
||||||
|
category: 'Wheels',
|
||||||
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
const filteredModifications = modifications.filter((mod) =>
|
const filteredModifications = modifications.filter((mod) =>
|
||||||
|
|||||||
252
client/src/components/CustomerManagement.js
Normal file
252
client/src/components/CustomerManagement.js
Normal file
@@ -0,0 +1,252 @@
|
|||||||
|
import React, { useState, useEffect } from 'react';
|
||||||
|
import {
|
||||||
|
Container,
|
||||||
|
Typography,
|
||||||
|
Box,
|
||||||
|
Button,
|
||||||
|
Dialog,
|
||||||
|
DialogTitle,
|
||||||
|
DialogContent,
|
||||||
|
DialogActions,
|
||||||
|
TextField,
|
||||||
|
Table,
|
||||||
|
TableBody,
|
||||||
|
TableCell,
|
||||||
|
TableContainer,
|
||||||
|
TableHead,
|
||||||
|
TableRow,
|
||||||
|
Paper,
|
||||||
|
IconButton,
|
||||||
|
Alert,
|
||||||
|
} from '@mui/material';
|
||||||
|
import EditIcon from '@mui/icons-material/Edit';
|
||||||
|
import DeleteIcon from '@mui/icons-material/Delete';
|
||||||
|
import AddIcon from '@mui/icons-material/Add';
|
||||||
|
import axios from 'axios';
|
||||||
|
|
||||||
|
const CustomerManagement = () => {
|
||||||
|
const [customers, setCustomers] = useState([]);
|
||||||
|
const [open, setOpen] = useState(false);
|
||||||
|
const [selectedCustomer, setSelectedCustomer] = useState(null);
|
||||||
|
const [formData, setFormData] = useState({
|
||||||
|
name: '',
|
||||||
|
email: '',
|
||||||
|
phone: '',
|
||||||
|
address: '',
|
||||||
|
carModel: '',
|
||||||
|
carYear: '',
|
||||||
|
});
|
||||||
|
const [error, setError] = useState('');
|
||||||
|
const [success, setSuccess] = useState('');
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
fetchCustomers();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const fetchCustomers = async () => {
|
||||||
|
try {
|
||||||
|
const response = await axios.get('http://localhost:5000/api/customers');
|
||||||
|
setCustomers(response.data);
|
||||||
|
} catch (err) {
|
||||||
|
setError('Failed to fetch customers');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleOpen = (customer = null) => {
|
||||||
|
if (customer) {
|
||||||
|
setSelectedCustomer(customer);
|
||||||
|
setFormData(customer);
|
||||||
|
} else {
|
||||||
|
setSelectedCustomer(null);
|
||||||
|
setFormData({
|
||||||
|
name: '',
|
||||||
|
email: '',
|
||||||
|
phone: '',
|
||||||
|
address: '',
|
||||||
|
carModel: '',
|
||||||
|
carYear: '',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
setOpen(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleClose = () => {
|
||||||
|
setOpen(false);
|
||||||
|
setError('');
|
||||||
|
setSuccess('');
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleChange = (e) => {
|
||||||
|
setFormData({
|
||||||
|
...formData,
|
||||||
|
[e.target.name]: e.target.value,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSubmit = async (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
try {
|
||||||
|
if (selectedCustomer) {
|
||||||
|
await axios.put(`http://localhost:5000/api/customers/${selectedCustomer._id}`, formData);
|
||||||
|
setSuccess('Customer updated successfully');
|
||||||
|
} else {
|
||||||
|
await axios.post('http://localhost:5000/api/customers', formData);
|
||||||
|
setSuccess('Customer added successfully');
|
||||||
|
}
|
||||||
|
fetchCustomers();
|
||||||
|
handleClose();
|
||||||
|
} catch (err) {
|
||||||
|
setError(err.response?.data?.message || 'An error occurred');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDelete = async (id) => {
|
||||||
|
if (window.confirm('Are you sure you want to delete this customer?')) {
|
||||||
|
try {
|
||||||
|
await axios.delete(`http://localhost:5000/api/customers/${id}`);
|
||||||
|
setSuccess('Customer deleted successfully');
|
||||||
|
fetchCustomers();
|
||||||
|
} catch (err) {
|
||||||
|
setError('Failed to delete customer');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Container maxWidth="lg" sx={{ mt: 4, mb: 4 }}>
|
||||||
|
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', mb: 4 }}>
|
||||||
|
<Typography variant="h4" component="h1" gutterBottom sx={{ fontWeight: 'bold' }}>
|
||||||
|
Customer Management
|
||||||
|
</Typography>
|
||||||
|
<Button
|
||||||
|
variant="contained"
|
||||||
|
color="primary"
|
||||||
|
startIcon={<AddIcon />}
|
||||||
|
onClick={() => handleOpen()}
|
||||||
|
>
|
||||||
|
Add Customer
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
{error && (
|
||||||
|
<Alert severity="error" sx={{ mb: 2 }}>
|
||||||
|
{error}
|
||||||
|
</Alert>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{success && (
|
||||||
|
<Alert severity="success" sx={{ mb: 2 }}>
|
||||||
|
{success}
|
||||||
|
</Alert>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<TableContainer component={Paper}>
|
||||||
|
<Table>
|
||||||
|
<TableHead>
|
||||||
|
<TableRow>
|
||||||
|
<TableCell>Name</TableCell>
|
||||||
|
<TableCell>Email</TableCell>
|
||||||
|
<TableCell>Phone</TableCell>
|
||||||
|
<TableCell>Car Model</TableCell>
|
||||||
|
<TableCell>Car Year</TableCell>
|
||||||
|
<TableCell>Actions</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
</TableHead>
|
||||||
|
<TableBody>
|
||||||
|
{customers.map((customer) => (
|
||||||
|
<TableRow key={customer._id}>
|
||||||
|
<TableCell>{customer.name}</TableCell>
|
||||||
|
<TableCell>{customer.email}</TableCell>
|
||||||
|
<TableCell>{customer.phone}</TableCell>
|
||||||
|
<TableCell>{customer.carModel}</TableCell>
|
||||||
|
<TableCell>{customer.carYear}</TableCell>
|
||||||
|
<TableCell>
|
||||||
|
<IconButton onClick={() => handleOpen(customer)} color="primary">
|
||||||
|
<EditIcon />
|
||||||
|
</IconButton>
|
||||||
|
<IconButton onClick={() => handleDelete(customer._id)} color="error">
|
||||||
|
<DeleteIcon />
|
||||||
|
</IconButton>
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
))}
|
||||||
|
</TableBody>
|
||||||
|
</Table>
|
||||||
|
</TableContainer>
|
||||||
|
|
||||||
|
<Dialog open={open} onClose={handleClose} maxWidth="sm" fullWidth>
|
||||||
|
<DialogTitle>
|
||||||
|
{selectedCustomer ? 'Edit Customer' : 'Add New Customer'}
|
||||||
|
</DialogTitle>
|
||||||
|
<form onSubmit={handleSubmit}>
|
||||||
|
<DialogContent>
|
||||||
|
<TextField
|
||||||
|
fullWidth
|
||||||
|
label="Name"
|
||||||
|
name="name"
|
||||||
|
value={formData.name}
|
||||||
|
onChange={handleChange}
|
||||||
|
margin="normal"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
<TextField
|
||||||
|
fullWidth
|
||||||
|
label="Email"
|
||||||
|
name="email"
|
||||||
|
type="email"
|
||||||
|
value={formData.email}
|
||||||
|
onChange={handleChange}
|
||||||
|
margin="normal"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
<TextField
|
||||||
|
fullWidth
|
||||||
|
label="Phone"
|
||||||
|
name="phone"
|
||||||
|
value={formData.phone}
|
||||||
|
onChange={handleChange}
|
||||||
|
margin="normal"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
<TextField
|
||||||
|
fullWidth
|
||||||
|
label="Address"
|
||||||
|
name="address"
|
||||||
|
value={formData.address}
|
||||||
|
onChange={handleChange}
|
||||||
|
margin="normal"
|
||||||
|
multiline
|
||||||
|
rows={2}
|
||||||
|
/>
|
||||||
|
<TextField
|
||||||
|
fullWidth
|
||||||
|
label="Car Model"
|
||||||
|
name="carModel"
|
||||||
|
value={formData.carModel}
|
||||||
|
onChange={handleChange}
|
||||||
|
margin="normal"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
<TextField
|
||||||
|
fullWidth
|
||||||
|
label="Car Year"
|
||||||
|
name="carYear"
|
||||||
|
value={formData.carYear}
|
||||||
|
onChange={handleChange}
|
||||||
|
margin="normal"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</DialogContent>
|
||||||
|
<DialogActions>
|
||||||
|
<Button onClick={handleClose}>Cancel</Button>
|
||||||
|
<Button type="submit" variant="contained" color="primary">
|
||||||
|
{selectedCustomer ? 'Update' : 'Add'}
|
||||||
|
</Button>
|
||||||
|
</DialogActions>
|
||||||
|
</form>
|
||||||
|
</Dialog>
|
||||||
|
</Container>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default CustomerManagement;
|
||||||
@@ -3,21 +3,34 @@ const mongoose = require('mongoose');
|
|||||||
const customerSchema = new mongoose.Schema({
|
const customerSchema = new mongoose.Schema({
|
||||||
name: {
|
name: {
|
||||||
type: String,
|
type: String,
|
||||||
required: true
|
required: true,
|
||||||
|
trim: true
|
||||||
},
|
},
|
||||||
email: {
|
email: {
|
||||||
type: String,
|
type: String,
|
||||||
required: true,
|
required: true,
|
||||||
unique: true
|
unique: true,
|
||||||
|
trim: true,
|
||||||
|
lowercase: true
|
||||||
},
|
},
|
||||||
phone: {
|
phone: {
|
||||||
type: String
|
type: String,
|
||||||
|
required: true,
|
||||||
|
trim: true
|
||||||
},
|
},
|
||||||
carDetails: {
|
address: {
|
||||||
make: String,
|
type: String,
|
||||||
model: String,
|
trim: true
|
||||||
year: Number,
|
},
|
||||||
modifications: [String]
|
carModel: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
trim: true
|
||||||
|
},
|
||||||
|
carYear: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
trim: true
|
||||||
},
|
},
|
||||||
createdAt: {
|
createdAt: {
|
||||||
type: Date,
|
type: Date,
|
||||||
@@ -29,4 +42,10 @@ const customerSchema = new mongoose.Schema({
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Update the updatedAt timestamp before saving
|
||||||
|
customerSchema.pre('save', function(next) {
|
||||||
|
this.updatedAt = Date.now();
|
||||||
|
next();
|
||||||
|
});
|
||||||
|
|
||||||
module.exports = mongoose.model('Customer', customerSchema);
|
module.exports = mongoose.model('Customer', customerSchema);
|
||||||
@@ -1,73 +1,78 @@
|
|||||||
const express = require('express');
|
const express = require('express');
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
const Customer = require('../models/Customer');
|
const Customer = require('../models/Customer');
|
||||||
|
const auth = require('../middleware/auth');
|
||||||
|
|
||||||
// Get all customers
|
// Get all customers
|
||||||
router.get('/', async (req, res) => {
|
router.get('/', auth, async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const customers = await Customer.find().sort({ name: 1 });
|
const customers = await Customer.find().sort({ name: 1 });
|
||||||
res.json(customers);
|
res.json(customers);
|
||||||
} catch (error) {
|
} catch (err) {
|
||||||
console.error('Error fetching customers:', error);
|
res.status(500).json({ message: err.message });
|
||||||
res.status(500).json({ message: 'Server error' });
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Get single customer
|
// Get single customer
|
||||||
router.get('/:id', async (req, res) => {
|
router.get('/:id', auth, async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const customer = await Customer.findById(req.params.id);
|
const customer = await Customer.findById(req.params.id);
|
||||||
if (!customer) {
|
if (!customer) {
|
||||||
return res.status(404).json({ message: 'Customer not found' });
|
return res.status(404).json({ message: 'Customer not found' });
|
||||||
}
|
}
|
||||||
res.json(customer);
|
res.json(customer);
|
||||||
} catch (error) {
|
} catch (err) {
|
||||||
console.error('Error fetching customer:', error);
|
res.status(500).json({ message: err.message });
|
||||||
res.status(500).json({ message: 'Server error' });
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Create customer
|
// Create customer
|
||||||
router.post('/', async (req, res) => {
|
router.post('/', auth, async (req, res) => {
|
||||||
|
const customer = new Customer({
|
||||||
|
name: req.body.name,
|
||||||
|
email: req.body.email,
|
||||||
|
phone: req.body.phone,
|
||||||
|
address: req.body.address,
|
||||||
|
carModel: req.body.carModel,
|
||||||
|
carYear: req.body.carYear,
|
||||||
|
});
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const customer = new Customer(req.body);
|
const newCustomer = await customer.save();
|
||||||
await customer.save();
|
res.status(201).json(newCustomer);
|
||||||
res.status(201).json(customer);
|
} catch (err) {
|
||||||
} catch (error) {
|
res.status(400).json({ message: err.message });
|
||||||
console.error('Error creating customer:', error);
|
|
||||||
res.status(500).json({ message: 'Server error' });
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Update customer
|
// Update customer
|
||||||
router.put('/:id', async (req, res) => {
|
router.put('/:id', auth, async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const customer = await Customer.findByIdAndUpdate(
|
const customer = await Customer.findById(req.params.id);
|
||||||
req.params.id,
|
|
||||||
{ ...req.body, updatedAt: Date.now() },
|
|
||||||
{ new: true }
|
|
||||||
);
|
|
||||||
if (!customer) {
|
if (!customer) {
|
||||||
return res.status(404).json({ message: 'Customer not found' });
|
return res.status(404).json({ message: 'Customer not found' });
|
||||||
}
|
}
|
||||||
res.json(customer);
|
|
||||||
} catch (error) {
|
Object.assign(customer, req.body);
|
||||||
console.error('Error updating customer:', error);
|
const updatedCustomer = await customer.save();
|
||||||
res.status(500).json({ message: 'Server error' });
|
res.json(updatedCustomer);
|
||||||
|
} catch (err) {
|
||||||
|
res.status(400).json({ message: err.message });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Delete customer
|
// Delete customer
|
||||||
router.delete('/:id', async (req, res) => {
|
router.delete('/:id', auth, async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const customer = await Customer.findByIdAndDelete(req.params.id);
|
const customer = await Customer.findById(req.params.id);
|
||||||
if (!customer) {
|
if (!customer) {
|
||||||
return res.status(404).json({ message: 'Customer not found' });
|
return res.status(404).json({ message: 'Customer not found' });
|
||||||
}
|
}
|
||||||
res.json({ message: 'Customer deleted successfully' });
|
|
||||||
} catch (error) {
|
await customer.remove();
|
||||||
console.error('Error deleting customer:', error);
|
res.json({ message: 'Customer deleted' });
|
||||||
res.status(500).json({ message: 'Server error' });
|
} catch (err) {
|
||||||
|
res.status(500).json({ message: err.message });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user