mirror of
https://github.com/Alvin-Zilverstand/challenge-11.git
synced 2026-03-06 11:06:21 +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,
|
||||
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) =>
|
||||
|
||||
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({
|
||||
name: {
|
||||
type: String,
|
||||
required: true
|
||||
required: true,
|
||||
trim: true
|
||||
},
|
||||
email: {
|
||||
type: String,
|
||||
required: true,
|
||||
unique: true
|
||||
unique: true,
|
||||
trim: true,
|
||||
lowercase: true
|
||||
},
|
||||
phone: {
|
||||
type: String
|
||||
type: String,
|
||||
required: true,
|
||||
trim: true
|
||||
},
|
||||
carDetails: {
|
||||
make: String,
|
||||
model: String,
|
||||
year: Number,
|
||||
modifications: [String]
|
||||
address: {
|
||||
type: String,
|
||||
trim: true
|
||||
},
|
||||
carModel: {
|
||||
type: String,
|
||||
required: true,
|
||||
trim: true
|
||||
},
|
||||
carYear: {
|
||||
type: String,
|
||||
required: true,
|
||||
trim: true
|
||||
},
|
||||
createdAt: {
|
||||
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);
|
||||
@@ -1,73 +1,78 @@
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const Customer = require('../models/Customer');
|
||||
const auth = require('../middleware/auth');
|
||||
|
||||
// Get all customers
|
||||
router.get('/', async (req, res) => {
|
||||
router.get('/', auth, async (req, res) => {
|
||||
try {
|
||||
const customers = await Customer.find().sort({ name: 1 });
|
||||
res.json(customers);
|
||||
} catch (error) {
|
||||
console.error('Error fetching customers:', error);
|
||||
res.status(500).json({ message: 'Server error' });
|
||||
} catch (err) {
|
||||
res.status(500).json({ message: err.message });
|
||||
}
|
||||
});
|
||||
|
||||
// Get single customer
|
||||
router.get('/:id', async (req, res) => {
|
||||
router.get('/:id', auth, async (req, res) => {
|
||||
try {
|
||||
const customer = await Customer.findById(req.params.id);
|
||||
if (!customer) {
|
||||
return res.status(404).json({ message: 'Customer not found' });
|
||||
}
|
||||
res.json(customer);
|
||||
} catch (error) {
|
||||
console.error('Error fetching customer:', error);
|
||||
res.status(500).json({ message: 'Server error' });
|
||||
} catch (err) {
|
||||
res.status(500).json({ message: err.message });
|
||||
}
|
||||
});
|
||||
|
||||
// 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 {
|
||||
const customer = new Customer(req.body);
|
||||
await customer.save();
|
||||
res.status(201).json(customer);
|
||||
} catch (error) {
|
||||
console.error('Error creating customer:', error);
|
||||
res.status(500).json({ message: 'Server error' });
|
||||
const newCustomer = await customer.save();
|
||||
res.status(201).json(newCustomer);
|
||||
} catch (err) {
|
||||
res.status(400).json({ message: err.message });
|
||||
}
|
||||
});
|
||||
|
||||
// Update customer
|
||||
router.put('/:id', async (req, res) => {
|
||||
router.put('/:id', auth, async (req, res) => {
|
||||
try {
|
||||
const customer = await Customer.findByIdAndUpdate(
|
||||
req.params.id,
|
||||
{ ...req.body, updatedAt: Date.now() },
|
||||
{ new: true }
|
||||
);
|
||||
const customer = await Customer.findById(req.params.id);
|
||||
if (!customer) {
|
||||
return res.status(404).json({ message: 'Customer not found' });
|
||||
}
|
||||
res.json(customer);
|
||||
} catch (error) {
|
||||
console.error('Error updating customer:', error);
|
||||
res.status(500).json({ message: 'Server error' });
|
||||
|
||||
Object.assign(customer, req.body);
|
||||
const updatedCustomer = await customer.save();
|
||||
res.json(updatedCustomer);
|
||||
} catch (err) {
|
||||
res.status(400).json({ message: err.message });
|
||||
}
|
||||
});
|
||||
|
||||
// Delete customer
|
||||
router.delete('/:id', async (req, res) => {
|
||||
router.delete('/:id', auth, async (req, res) => {
|
||||
try {
|
||||
const customer = await Customer.findByIdAndDelete(req.params.id);
|
||||
const customer = await Customer.findById(req.params.id);
|
||||
if (!customer) {
|
||||
return res.status(404).json({ message: 'Customer not found' });
|
||||
}
|
||||
res.json({ message: 'Customer deleted successfully' });
|
||||
} catch (error) {
|
||||
console.error('Error deleting customer:', error);
|
||||
res.status(500).json({ message: 'Server error' });
|
||||
|
||||
await customer.remove();
|
||||
res.json({ message: 'Customer deleted' });
|
||||
} catch (err) {
|
||||
res.status(500).json({ message: err.message });
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user