From be91231b05f9cca443f92975bf9173aea87d6f19 Mon Sep 17 00:00:00 2001 From: kikimor Date: Tue, 10 Feb 2026 17:50:49 +0200 Subject: [PATCH] added vehicle creation functionality --- backend/api/serializers.py | 3 +- backend/vehicles/models.py | 2 - backend/vehicles/serializers.py | 2 +- frontend/src/components/Main.css | 85 +++++++---------- frontend/src/components/Main.jsx | 157 +++++++++++++++++++++++++------ 5 files changed, 164 insertions(+), 85 deletions(-) diff --git a/backend/api/serializers.py b/backend/api/serializers.py index 9810f9b..54929ff 100644 --- a/backend/api/serializers.py +++ b/backend/api/serializers.py @@ -128,7 +128,8 @@ class VehicleSerializer(serializers.ModelSerializer): class Meta: model = Vehicle - fields = ['id', 'vehicle_number', 'tare', 'tare_date', 'tare_user', 'tare_user_name', + fields = ['id', 'vehicle_number', 'trailer1_number', 'trailer2_number', 'driver_pid', + 'tare', 'tare_date', 'tare_user', 'tare_user_name', 'gross', 'gross_date', 'gross_user', 'gross_user_name', 'extra'] read_only_fields = ['id', 'tare_date', 'tare_user', 'tare_user_name', 'gross_date', 'gross_user', 'gross_user_name'] diff --git a/backend/vehicles/models.py b/backend/vehicles/models.py index 0e69e39..b756b76 100644 --- a/backend/vehicles/models.py +++ b/backend/vehicles/models.py @@ -17,8 +17,6 @@ class Vehicle(models.Model): gross_date = models.DateTimeField(null=True, blank=True) gross_user = models.ForeignKey(User, null=True, blank=True, related_name='user_vehicle_gross', on_delete=models.SET_NULL) - - def __str__(self): return f"{self.vehicle_number}" diff --git a/backend/vehicles/serializers.py b/backend/vehicles/serializers.py index 68f5dd3..aacb85e 100644 --- a/backend/vehicles/serializers.py +++ b/backend/vehicles/serializers.py @@ -4,4 +4,4 @@ from .models import Vehicle class VehicleSerializer(serializers.ModelSerializer): class Meta: model = Vehicle - fields = ['id', 'vehicle_number'] \ No newline at end of file + fields = ['id', 'vehicle_number', 'trailer1_number', 'trailer2_number', 'driver_pid', 'tare', 'tare_date', 'tare_user', 'gross', 'gross_date', 'gross_user'] \ No newline at end of file diff --git a/frontend/src/components/Main.css b/frontend/src/components/Main.css index 5a72ede..ad35157 100644 --- a/frontend/src/components/Main.css +++ b/frontend/src/components/Main.css @@ -51,56 +51,6 @@ border-color: #667eea; } -/* New vehicle form */ -.vehicle-new-form { - padding: 10px 12px; - border-bottom: 1px solid #e0e0e0; - background: #f0f0ff; -} - -.vehicle-new-form input { - width: 100%; - padding: 7px 10px; - border: 1px solid #ccc; - border-radius: 4px; - font-size: 13px; - box-sizing: border-box; - margin-bottom: 6px; -} - -.vehicle-new-form input:focus { - outline: none; - border-color: #667eea; -} - -.vehicle-new-actions { - display: flex; - gap: 6px; -} - -.vehicle-new-actions button { - flex: 1; - padding: 5px 10px; - border-radius: 4px; - font-size: 12px; - font-weight: 500; - cursor: pointer; - border: 1px solid #ccc; - background: white; - color: #333; - transition: background 0.2s; -} - -.vehicle-new-actions button:first-child { - background: #667eea; - color: white; - border-color: #667eea; -} - -.vehicle-new-actions button:first-child:hover { - background: #5568d3; -} - /* Vehicle list */ .vehicle-list { flex: 1; @@ -367,6 +317,41 @@ cursor: not-allowed; } +/* Vehicle form sections */ +.vehicle-form-section { + margin-bottom: 20px; +} + +.vehicle-form-actions { + display: flex; + gap: 10px; + margin-top: 20px; + padding-top: 16px; + border-top: 1px solid #e0e0e0; +} + +.vehicle-form-cancel { + padding: 8px 20px; + background: white; + color: #666; + border: 1px solid #ddd; + border-radius: 6px; + font-size: 13px; + font-weight: 600; + cursor: pointer; + transition: background 0.2s, border-color 0.2s; +} + +.vehicle-form-cancel:hover:not(:disabled) { + background: #f5f5f5; + border-color: #ccc; +} + +.vehicle-form-cancel:disabled { + opacity: 0.5; + cursor: not-allowed; +} + /* Responsive */ @media (max-width: 768px) { .main { diff --git a/frontend/src/components/Main.jsx b/frontend/src/components/Main.jsx index 2d17300..5266200 100644 --- a/frontend/src/components/Main.jsx +++ b/frontend/src/components/Main.jsx @@ -13,8 +13,9 @@ export default function Main() { const [selectedVehicleId, setSelectedVehicleId] = useState(null); const [searchQuery, setSearchQuery] = useState(''); - const [newVehicleNumber, setNewVehicleNumber] = useState(''); const [showNewForm, setShowNewForm] = useState(false); + const [newForm, setNewForm] = useState({ vehicle_number: '', trailer1_number: '', trailer2_number: '', driver_pid: '' }); + const [newExtraData, setNewExtraData] = useState({}); const [error, setError] = useState(''); const [isSaving, setIsSaving] = useState(false); @@ -64,18 +65,48 @@ export default function Main() { } }, [selectedVehicle]); + // Open new vehicle form + const handleNewVehicle = () => { + setSelectedVehicleId(null); + setShowNewForm(true); + setNewForm({ vehicle_number: '', trailer1_number: '', trailer2_number: '', driver_pid: '' }); + setNewExtraData({}); + setError(''); + }; + + // Cancel new vehicle form + const handleCancelNew = () => { + setShowNewForm(false); + setNewForm({ vehicle_number: '', trailer1_number: '', trailer2_number: '', driver_pid: '' }); + setNewExtraData({}); + setError(''); + }; + // Create new vehicle const handleCreateVehicle = async () => { - if (!newVehicleNumber.trim()) return; + if (!newForm.vehicle_number.trim()) return; setIsSaving(true); setError(''); try { - const res = await api.post('/api/vehicles/', { vehicle_number: newVehicleNumber.trim() }); + const payload = { + vehicle_number: newForm.vehicle_number.trim(), + trailer1_number: newForm.trailer1_number.trim() || null, + trailer2_number: newForm.trailer2_number.trim() || null, + driver_pid: newForm.driver_pid.trim() || null, + }; + // Include extra data if any values set + const hasExtra = Object.values(newExtraData).some(v => v !== undefined && v !== null && v !== ''); + if (hasExtra) { + payload.extra = { data: newExtraData }; + } + const res = await api.post('/api/vehicles/', payload); setSelectedVehicleId(res.data.id); - setNewVehicleNumber(''); setShowNewForm(false); + setNewForm({ vehicle_number: '', trailer1_number: '', trailer2_number: '', driver_pid: '' }); + setNewExtraData({}); } catch (err) { - const msg = err.response?.data?.vehicle_number?.[0] || err.response?.data?.detail || 'Failed to create vehicle'; + const data = err.response?.data; + const msg = data?.vehicle_number?.[0] || data?.detail || (typeof data === 'string' ? data : JSON.stringify(data)) || 'Failed to create vehicle'; setError(msg); } finally { setIsSaving(false); @@ -142,7 +173,7 @@ export default function Main() {
@@ -155,34 +186,12 @@ export default function Main() { />
- {showNewForm && ( -
- setNewVehicleNumber(e.target.value)} - onKeyDown={e => e.key === 'Enter' && handleCreateVehicle()} - disabled={isSaving} - autoFocus - /> -
- - -
-
- )} -
{filteredVehicles.map(v => (
{ setSelectedVehicleId(v.id); setError(''); }} + onClick={() => { setSelectedVehicleId(v.id); setShowNewForm(false); setError(''); }} > {v.vehicle_number} {v.tare != null && ( @@ -196,9 +205,95 @@ export default function Main() {
- {/* Right panel - Vehicle detail */} + {/* Right panel - Vehicle detail or New form */}
- {!selectedVehicle ? ( + {showNewForm ? ( +
+ {error &&
{error}
} + +

New Vehicle

+ +
+
+
+ + setNewForm(prev => ({ ...prev, vehicle_number: e.target.value }))} + disabled={isSaving} + autoFocus + /> +
+
+ + setNewForm(prev => ({ ...prev, trailer1_number: e.target.value }))} + disabled={isSaving} + /> +
+
+ + setNewForm(prev => ({ ...prev, trailer2_number: e.target.value }))} + disabled={isSaving} + /> +
+
+ + setNewForm(prev => ({ ...prev, driver_pid: e.target.value }))} + disabled={isSaving} + /> +
+
+
+ + {vehicleNomenclatures.length > 0 && ( +
+

Additional Data

+
+ {vehicleNomenclatures.map(nom => ( +
+ setNewExtraData(prev => ({ + ...prev, + [nom.code]: val, + }))} + disabled={isSaving} + /> +
+ ))} +
+
+ )} + +
+ + +
+
+ ) : !selectedVehicle ? (
Select a vehicle or create a new one