Enhance App component by adding Register and BackToHome routes, and update CustomerList to CustomerManagement for improved customer management functionality. Refactor CustomerManagement to utilize a centralized axios instance for API calls and implement a dropdown for car model selection.

This commit is contained in:
Alvin
2025-06-10 10:24:25 +02:00
parent cefd020db6
commit ec07b89dab
5 changed files with 294 additions and 40 deletions

View File

@@ -3,13 +3,16 @@ import { BrowserRouter as Router, Routes, Route, Navigate } from 'react-router-d
import { ThemeProvider, createTheme } from '@mui/material/styles';
import CssBaseline from '@mui/material/CssBaseline';
import Login from './components/Login';
import Register from './components/Register';
import Dashboard from './components/Dashboard';
import CustomerList from './components/CustomerList';
import CustomerDetail from './components/CustomerDetail';
import CarModifications from './components/CarModifications';
import ContactHistory from './components/ContactHistory';
import CustomerManagement from './components/CustomerManagement';
import PrivateRoute from './components/PrivateRoute';
import UserManagement from './components/UserManagement';
import BackToHome from './components/BackToHome';
// Create a theme that matches the "stoer en snel" (tough and fast) requirement
const theme = createTheme({
@@ -55,6 +58,7 @@ function App() {
<Router>
<Routes>
<Route path="/login" element={<Login />} />
<Route path="/register" element={<Register />} />
<Route
path="/"
element={
@@ -67,7 +71,7 @@ function App() {
path="/customers"
element={
<PrivateRoute>
<CustomerList />
<CustomerManagement />
</PrivateRoute>
}
/>
@@ -105,6 +109,7 @@ function App() {
/>
<Route path="*" element={<Navigate to="/" replace />} />
</Routes>
<BackToHome />
</Router>
</ThemeProvider>
);

View File

@@ -0,0 +1,39 @@
import React from 'react';
import { useNavigate } from 'react-router-dom';
import { Button, Box } from '@mui/material';
import HomeIcon from '@mui/icons-material/Home';
const BackToHome = () => {
const navigate = useNavigate();
return (
<Box
sx={{
position: 'fixed',
bottom: 20,
right: 20,
zIndex: 1000,
}}
>
<Button
variant="contained"
color="primary"
startIcon={<HomeIcon />}
onClick={() => navigate('/')}
sx={{
borderRadius: '50px',
padding: '10px 20px',
boxShadow: 3,
'&:hover': {
transform: 'scale(1.05)',
transition: 'transform 0.2s ease-in-out',
},
}}
>
Home
</Button>
</Box>
);
};
export default BackToHome;

View File

@@ -18,12 +18,57 @@ import {
Paper,
IconButton,
Alert,
MenuItem,
Select,
FormControl,
InputLabel,
} 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 carModels = [
{ value: 'bmw_m3', label: 'BMW M3' },
{ value: 'bmw_m4', label: 'BMW M4' },
{ value: 'audi_rs3', label: 'Audi RS3' },
{ value: 'audi_rs4', label: 'Audi RS4' },
{ value: 'mercedes_c63', label: 'Mercedes-AMG C63' },
{ value: 'mercedes_e63', label: 'Mercedes-AMG E63' },
{ value: 'volkswagen_golf_r', label: 'Volkswagen Golf R' },
{ value: 'volkswagen_arteon_r', label: 'Volkswagen Arteon R' },
{ value: 'toyota_supra', label: 'Toyota Supra' },
{ value: 'nissan_gtr', label: 'Nissan GT-R' },
{ value: 'porsche_911', label: 'Porsche 911' },
{ value: 'porsche_cayman', label: 'Porsche Cayman' },
{ value: 'honda_civic_type_r', label: 'Honda Civic Type R' },
{ value: 'subaru_wrx_sti', label: 'Subaru WRX STI' },
{ value: 'mitsubishi_evo', label: 'Mitsubishi Lancer Evolution' },
{ value: 'other', label: 'Other' },
];
// Create axios instance with default config
const api = axios.create({
baseURL: 'http://localhost:5000/api',
headers: {
'Content-Type': 'application/json',
},
});
// Add request interceptor to add token to all requests
api.interceptors.request.use(
(config) => {
const token = localStorage.getItem('token');
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
},
(error) => {
return Promise.reject(error);
}
);
const CustomerManagement = () => {
const [customers, setCustomers] = useState([]);
const [open, setOpen] = useState(false);
@@ -45,7 +90,7 @@ const CustomerManagement = () => {
const fetchCustomers = async () => {
try {
const response = await axios.get('http://localhost:5000/api/customers');
const response = await api.get('/customers');
setCustomers(response.data);
} catch (err) {
setError('Failed to fetch customers');
@@ -87,10 +132,10 @@ const CustomerManagement = () => {
e.preventDefault();
try {
if (selectedCustomer) {
await axios.put(`http://localhost:5000/api/customers/${selectedCustomer._id}`, formData);
await api.put(`/customers/${selectedCustomer._id}`, formData);
setSuccess('Customer updated successfully');
} else {
await axios.post('http://localhost:5000/api/customers', formData);
await api.post('/customers', formData);
setSuccess('Customer added successfully');
}
fetchCustomers();
@@ -103,7 +148,7 @@ const CustomerManagement = () => {
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}`);
await api.delete(`/customers/${id}`);
setSuccess('Customer deleted successfully');
fetchCustomers();
} catch (err) {
@@ -218,15 +263,21 @@ const CustomerManagement = () => {
multiline
rows={2}
/>
<TextField
fullWidth
label="Car Model"
name="carModel"
value={formData.carModel}
onChange={handleChange}
margin="normal"
required
/>
<FormControl fullWidth margin="normal" required>
<InputLabel>Car Model</InputLabel>
<Select
name="carModel"
value={formData.carModel}
onChange={handleChange}
label="Car Model"
>
{carModels.map((model) => (
<MenuItem key={model.value} value={model.value}>
{model.label}
</MenuItem>
))}
</Select>
</FormControl>
<TextField
fullWidth
label="Car Year"

View File

@@ -7,9 +7,9 @@ import {
Typography,
Box,
} from '@mui/material';
import DirectionsCarIcon from '@mui/icons-material/DirectionsCar';
import PeopleIcon from '@mui/icons-material/People';
import ChatIcon from '@mui/icons-material/Chat';
import BuildIcon from '@mui/icons-material/Build';
import HistoryIcon from '@mui/icons-material/History';
import AdminPanelSettingsIcon from '@mui/icons-material/AdminPanelSettings';
import axios from 'axios';
@@ -20,43 +20,38 @@ const Dashboard = () => {
useEffect(() => {
const checkAdminStatus = async () => {
try {
const response = await axios.get('http://localhost:5000/api/users/me', {
headers: {
Authorization: `Bearer ${localStorage.getItem('token')}`,
},
});
const response = await axios.get('http://localhost:5000/api/users/me');
setIsAdmin(response.data.role === 'admin');
} catch (error) {
console.error('Error checking admin status:', error);
} catch (err) {
console.error('Error checking admin status:', err);
}
};
checkAdminStatus();
}, []);
const menuItems = [
{
title: 'Customers',
description: 'Manage customer information and car details',
icon: <PeopleIcon sx={{ fontSize: 40 }} />,
description: 'View and manage customer information',
path: '/customers',
},
{
title: 'Car Modifications',
icon: <DirectionsCarIcon sx={{ fontSize: 40 }} />,
description: 'Browse available car modifications',
description: 'Browse available car modifications and upgrades',
icon: <BuildIcon sx={{ fontSize: 40 }} />,
path: '/modifications',
},
{
title: 'Contact History',
icon: <ChatIcon sx={{ fontSize: 40 }} />,
description: 'View customer interaction history',
description: 'View and manage customer interactions',
icon: <HistoryIcon sx={{ fontSize: 40 }} />,
path: '/contacts',
},
...(isAdmin ? [{
title: 'User Management',
icon: <AdminPanelSettingsIcon sx={{ fontSize: 40 }} />,
description: 'Manage system users and permissions',
icon: <AdminPanelSettingsIcon sx={{ fontSize: 40 }} />,
path: '/users',
}] : []),
];
@@ -68,7 +63,7 @@ const Dashboard = () => {
</Typography>
<Grid container spacing={3}>
{menuItems.map((item) => (
<Grid item xs={12} md={4} key={item.title}>
<Grid item xs={12} sm={6} md={4} key={item.title}>
<Paper
sx={{
p: 3,
@@ -86,20 +81,20 @@ const Dashboard = () => {
<Box
sx={{
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'center',
flexGrow: 1,
alignItems: 'center',
mb: 2,
color: 'primary.main',
}}
>
{item.icon}
<Typography variant="h6" component="h2" sx={{ mt: 2, fontWeight: 'bold' }}>
{item.title}
</Typography>
<Typography variant="body2" color="text.secondary" sx={{ mt: 1, textAlign: 'center' }}>
{item.description}
</Typography>
</Box>
<Typography variant="h5" component="h2" gutterBottom>
{item.title}
</Typography>
<Typography variant="body1" color="text.secondary">
{item.description}
</Typography>
</Paper>
</Grid>
))}

View File

@@ -0,0 +1,164 @@
import React, { useState } from 'react';
import { useNavigate } from 'react-router-dom';
import {
Container,
Paper,
Typography,
TextField,
Button,
Box,
Alert,
} from '@mui/material';
import axios from 'axios';
const Register = () => {
const navigate = useNavigate();
const [formData, setFormData] = useState({
name: '',
email: '',
password: '',
confirmPassword: '',
});
const [error, setError] = useState('');
const [success, setSuccess] = useState('');
const handleChange = (e) => {
setFormData({
...formData,
[e.target.name]: e.target.value,
});
};
const handleSubmit = async (e) => {
e.preventDefault();
setError('');
setSuccess('');
if (formData.password !== formData.confirmPassword) {
setError('Passwords do not match');
return;
}
try {
const response = await axios.post('http://localhost:5000/api/users/register', {
name: formData.name,
email: formData.email,
password: formData.password,
});
setSuccess('Registration successful! Redirecting to login...');
setTimeout(() => {
navigate('/login');
}, 2000);
} catch (err) {
setError(err.response?.data?.message || 'Registration failed');
}
};
return (
<Container component="main" maxWidth="xs">
<Box
sx={{
marginTop: 8,
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
}}
>
<Paper
elevation={3}
sx={{
padding: 4,
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
width: '100%',
}}
>
<Typography component="h1" variant="h5" sx={{ mb: 3, fontWeight: 'bold' }}>
Register
</Typography>
{error && (
<Alert severity="error" sx={{ width: '100%', mb: 2 }}>
{error}
</Alert>
)}
{success && (
<Alert severity="success" sx={{ width: '100%', mb: 2 }}>
{success}
</Alert>
)}
<Box component="form" onSubmit={handleSubmit} sx={{ width: '100%' }}>
<TextField
margin="normal"
required
fullWidth
id="name"
label="Full Name"
name="name"
autoComplete="name"
autoFocus
value={formData.name}
onChange={handleChange}
/>
<TextField
margin="normal"
required
fullWidth
id="email"
label="Email Address"
name="email"
autoComplete="email"
value={formData.email}
onChange={handleChange}
/>
<TextField
margin="normal"
required
fullWidth
name="password"
label="Password"
type="password"
id="password"
autoComplete="new-password"
value={formData.password}
onChange={handleChange}
/>
<TextField
margin="normal"
required
fullWidth
name="confirmPassword"
label="Confirm Password"
type="password"
id="confirmPassword"
value={formData.confirmPassword}
onChange={handleChange}
/>
<Button
type="submit"
fullWidth
variant="contained"
sx={{ mt: 3, mb: 2 }}
>
Register
</Button>
<Button
fullWidth
variant="text"
onClick={() => navigate('/login')}
sx={{ mt: 1 }}
>
Already have an account? Login
</Button>
</Box>
</Paper>
</Box>
</Container>
);
};
export default Register;