This document outlines the frontend system architecture, components, and best practices for the ASB (Automotive Service Business) application.
- React 18 - UI Framework
- React Router DOM - Client-side routing
- Tailwind CSS - Utility-first CSS framework
- Axios - HTTP client for API requests
- React Hot Toast - Toast notifications
- Recharts - Chart library for analytics
- Lucide React - Icon library
- React Tooltip - Tooltip component for UI hints
Use this for all user preference and favorite functionality across the entire project.
import {
getUserPreferences,
saveUserPreferences,
updatePreferences,
getSectionPreferences,
resetPreferences,
PREFERENCE_SECTIONS
} from '../utils/UserPreferences';
// β
Get all preferences
const preferences = getUserPreferences();
// β
Update specific section
updatePreferences(PREFERENCE_SECTIONS.QUOTATIONS, {
favoriteStatuses: ['open', 'win'],
isFilterCollapsed: false
});
// β
Get section-specific preferences
const quotationPrefs = getSectionPreferences(PREFERENCE_SECTIONS.QUOTATIONS);
// β
Reset preferences
resetPreferences(); // Reset all
resetPreferences(PREFERENCE_SECTIONS.ANALYTICS); // Reset specific sectionFeatures:
- Persistent Storage: Automatically saves to localStorage
- Default Fallbacks: Merges with defaults for missing properties
- Section-based: Organized by feature area (analytics, quotations, dashboard)
- Type Safety: Well-defined preference structure
- Error Handling: Graceful fallbacks for corrupted data
Preference Structure:
{
analytics: {
isFilterCollapsed: true,
sectionVisibility: {
followUpStatus: true,
lossAnalysis: true,
closeAnalysis: true,
monthlyTrend: true,
detailedTable: true
}
},
quotations: {
isFilterCollapsed: true,
followUpStatus: {
red: true,
yellow: true,
green: true
},
favoriteStatuses: ['open'] // User's favorite statuses
},
dashboard: {
// Future dashboard preferences
}
}Best Practices:
- Always use
PREFERENCE_SECTIONSconstants for section names - Use
updatePreferences()for partial updates - Use
getSectionPreferences()for component-specific preferences - Handle loading states when preferences are being applied
Use this for all action symbols and interactive elements to provide user guidance.
import { Tooltip } from 'react-tooltip';
// β
Correct usage for action buttons
<button
data-tooltip-id="view-tooltip"
data-tooltip-content="View quotation details"
className="text-blue-600 hover:text-blue-900 p-1"
>
<Eye className="h-4 w-4" />
</button>
<Tooltip id="view-tooltip" />
// β
For multiple tooltips, use unique IDs
<button data-tooltip-id="edit-tooltip" data-tooltip-content="Edit quotation">
<Edit className="h-4 w-4" />
</button>
<Tooltip id="edit-tooltip" />Features:
- Automatic Positioning: Smart positioning based on viewport
- Accessibility: Screen reader friendly
- Customizable: Styling and behavior options
- Performance: Lightweight and efficient
Tooltip Guidelines:
- Action Buttons: Always include tooltips for icon-only buttons
- Status Indicators: Explain what status colors mean
- Form Fields: Provide helpful hints for complex inputs
- Navigation: Guide users through multi-step processes
Always use this for API requests - it automatically includes JWT authentication.
import axiosInstance from '../utils/ApiHelper';
// β
Correct usage
const response = await axiosInstance.get('/api/quotations');
const response = await axiosInstance.post('/api/quotations', data);
const response = await axiosInstance.put(`/api/quotations/${id}`, data);
const response = await axiosInstance.delete(`/api/quotations/${id}`);
// β Don't use axios directly
import axios from 'axios'; // Don't do thisFeatures:
- Automatic JWT token attachment to requests
- Base URL configuration
- Request/response interceptors
- Automatic token refresh handling
Use this for all dropdown/select components instead of native HTML select.
import CustomDropdown from '../components/CustomDropdown';
// β
Correct usage
const options = [
{ value: 'open', label: 'Open' },
{ value: 'close', label: 'Closed' }
];
<CustomDropdown
options={options}
value={selectedValue}
onChange={(value) => setSelectedValue(value)}
placeholder="Select an option"
required={true} // This prop exists but is not currently used - needs fixing
className="mt-1"
/>Features:
- Modern, accessible design
- Smooth animations
- Click-outside-to-close
- Keyboard navigation
- Customizable styling
- Placeholder text support
Known Issue: The required prop is defined but not currently implemented in the component logic.
Use this as the foundation for all modals in the application.
import BaseModal from '../components/BaseModal';
// β
Correct usage
<BaseModal
isOpen={isModalOpen}
onClose={() => setIsModalOpen(false)}
title="Modal Title"
>
{/* Modal content goes here */}
</BaseModal>Features:
- Consistent modal styling
- Backdrop click to close
- ESC key to close
- Responsive design
- Accessibility features
Global state management for user authentication.
import { useContext } from 'react';
import { UserContext } from '../utils/UserContext';
const { user, loginUser, logoutUser } = useContext(UserContext);
// User object structure:
// {
// email: string,
// role: string,
// isLoggedIn: boolean
// }Features:
- Global user state
- Automatic localStorage persistence
- Login/logout functions
- JWT token management
Wraps routes that require authentication.
import ProtectedRoute from '../components/ProtectedRoute';
<Route
path="/dashboard"
element={
<ProtectedRoute>
<Dashboard />
</ProtectedRoute>
}
/>- Primary Red:
#EF4444(red-500) - Hover Red:
#DC2626(red-600) - Background:
#F9FAFB(gray-50) - Card Background:
#FFFFFF(white) - Text Primary:
#111827(gray-900) - Text Secondary:
#6B7280(gray-500)
// Primary button
<button className="bg-red-600 hover:bg-red-700 text-white px-4 py-2 rounded-md transition-colors">
Primary Action
</button>
// Secondary button
<button className="px-4 py-2 text-gray-700 bg-gray-100 hover:bg-gray-200 rounded-md transition-colors">
Secondary Action
</button><input
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-red-500 focus:border-red-500"
// ... other props
/><div className="bg-white rounded-lg shadow-md hover:shadow-lg transition-shadow">
{/* Card content */}
</div>import Navigation from '../components/Navigation';
const MyPage = () => {
return (
<div className="min-h-screen bg-gray-50">
<Navigation title="Page Title" subtitle="Page description" />
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
{/* Page content */}
</div>
</div>
);
};if (loading) {
return (
<div className="min-h-screen flex items-center justify-center">
<div className="animate-spin rounded-full h-32 w-32 border-b-2 border-red-500"></div>
</div>
);
}const [formData, setFormData] = useState({
field1: '',
field2: '',
// ... other fields
});
const handleInputChange = (e) => {
setFormData({
...formData,
[e.target.name]: e.target.value
});
};const [data, setData] = useState([]);
const [loading, setLoading] = useState(true);
const fetchData = async () => {
try {
setLoading(true);
const response = await axiosInstance.get('/api/endpoint');
if (response.data.success) {
setData(response.data.data);
}
} catch (error) {
console.error('Error:', error);
toast.error('Failed to fetch data');
} finally {
setLoading(false);
}
};import toast from 'react-hot-toast';
// Success
toast.success('Operation completed successfully!');
// Error
toast.error('Something went wrong!');
// Loading
const promise = apiCall();
toast.promise(promise, {
loading: 'Saving...',
success: 'Saved!',
error: 'Failed to save'
});try {
const response = await axiosInstance.post('/api/endpoint', data);
if (response.data.success) {
toast.success('Success message');
// Handle success
}
} catch (error) {
console.error('Error:', error);
toast.error(error.response?.data?.message || 'Generic error message');
}const getStatusColor = (status) => {
switch (status) {
case 'open': return 'bg-blue-100 text-blue-800';
case 'close': return 'bg-gray-100 text-gray-800';
case 'loss': return 'bg-red-100 text-red-800';
case 'win': return 'bg-green-100 text-green-800';
default: return 'bg-gray-100 text-gray-800';
}
};
<span className={`px-2 py-1 text-xs font-medium rounded-full ${getStatusColor(status)}`}>
{status}
</span>const formatCurrency = (amount) => {
return new Intl.NumberFormat('id-ID', {
style: 'currency',
currency: 'IDR'
}).format(amount);
};const formatDate = (dateString) => {
return new Date(dateString).toLocaleDateString('en-US', {
year: 'numeric',
month: 'short',
day: 'numeric'
});
};- Login/Register β Updates UserContext
- UserContext β Stores JWT in localStorage
- ApiHelper β Automatically attaches JWT to requests
- ProtectedRoute β Checks authentication status
- Navigation β Shows user info and logout option
src/
βββ components/ # Reusable UI components
β βββ BaseModal.jsx
β βββ CustomDropdown.jsx
β βββ Navigation.jsx
β βββ ProtectedRoute.jsx
β βββ ...
βββ pages/ # Page components
β βββ HomePage.jsx
β βββ QuotationPage.jsx
β βββ AnalyticsPage.jsx
β βββ ...
βββ utils/ # Utility functions and contexts
β βββ ApiHelper.js
β βββ UserContext.jsx
βββ App.jsx # Main app component
- Use TypeScript-style prop documentation
- Include proper error handling
- Add loading states where appropriate
- Follow consistent naming conventions
- Use Tailwind classes for styling
- Include accessibility attributes
- Use functional components with hooks
- Implement proper error boundaries
- Follow React best practices
- Keep components focused and reusable
- Use meaningful variable names
- Add comments for complex logic
- CustomDropdown: The
requiredprop is defined but not implemented - Form Validation: Need to implement comprehensive form validation
- Error Boundaries: Need to add error boundaries for better error handling
- Loading States: Some components could benefit from skeleton loaders
- Responsive Design: Some components need better mobile responsiveness
For questions about the frontend system, refer to:
- This README documentation
- Component source code comments
- React documentation
- Tailwind CSS documentation
Last Updated: December 2024 Version: 1.0.0