added vehicle creation functionality

master
kikimor 3 weeks ago
parent 6a42099169
commit be91231b05

@ -128,7 +128,8 @@ class VehicleSerializer(serializers.ModelSerializer):
class Meta: class Meta:
model = Vehicle 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'] 'gross', 'gross_date', 'gross_user', 'gross_user_name', 'extra']
read_only_fields = ['id', 'tare_date', 'tare_user', 'tare_user_name', read_only_fields = ['id', 'tare_date', 'tare_user', 'tare_user_name',
'gross_date', 'gross_user', 'gross_user_name'] 'gross_date', 'gross_user', 'gross_user_name']

@ -17,8 +17,6 @@ class Vehicle(models.Model):
gross_date = models.DateTimeField(null=True, blank=True) 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) gross_user = models.ForeignKey(User, null=True, blank=True, related_name='user_vehicle_gross', on_delete=models.SET_NULL)
def __str__(self): def __str__(self):
return f"{self.vehicle_number}" return f"{self.vehicle_number}"

@ -4,4 +4,4 @@ from .models import Vehicle
class VehicleSerializer(serializers.ModelSerializer): class VehicleSerializer(serializers.ModelSerializer):
class Meta: class Meta:
model = Vehicle model = Vehicle
fields = ['id', 'vehicle_number'] fields = ['id', 'vehicle_number', 'trailer1_number', 'trailer2_number', 'driver_pid', 'tare', 'tare_date', 'tare_user', 'gross', 'gross_date', 'gross_user']

@ -51,56 +51,6 @@
border-color: #667eea; 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 */
.vehicle-list { .vehicle-list {
flex: 1; flex: 1;
@ -367,6 +317,41 @@
cursor: not-allowed; 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 */ /* Responsive */
@media (max-width: 768px) { @media (max-width: 768px) {
.main { .main {

@ -13,8 +13,9 @@ export default function Main() {
const [selectedVehicleId, setSelectedVehicleId] = useState(null); const [selectedVehicleId, setSelectedVehicleId] = useState(null);
const [searchQuery, setSearchQuery] = useState(''); const [searchQuery, setSearchQuery] = useState('');
const [newVehicleNumber, setNewVehicleNumber] = useState('');
const [showNewForm, setShowNewForm] = useState(false); 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 [error, setError] = useState('');
const [isSaving, setIsSaving] = useState(false); const [isSaving, setIsSaving] = useState(false);
@ -64,18 +65,48 @@ export default function Main() {
} }
}, [selectedVehicle]); }, [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 // Create new vehicle
const handleCreateVehicle = async () => { const handleCreateVehicle = async () => {
if (!newVehicleNumber.trim()) return; if (!newForm.vehicle_number.trim()) return;
setIsSaving(true); setIsSaving(true);
setError(''); setError('');
try { 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); setSelectedVehicleId(res.data.id);
setNewVehicleNumber('');
setShowNewForm(false); setShowNewForm(false);
setNewForm({ vehicle_number: '', trailer1_number: '', trailer2_number: '', driver_pid: '' });
setNewExtraData({});
} catch (err) { } 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); setError(msg);
} finally { } finally {
setIsSaving(false); setIsSaving(false);
@ -142,7 +173,7 @@ export default function Main() {
<div className="vehicle-list-header"> <div className="vehicle-list-header">
<button <button
className="vehicle-add-btn" className="vehicle-add-btn"
onClick={() => { setShowNewForm(true); setError(''); }} onClick={handleNewVehicle}
> >
+ New Vehicle + New Vehicle
</button> </button>
@ -155,34 +186,12 @@ export default function Main() {
/> />
</div> </div>
{showNewForm && (
<div className="vehicle-new-form">
<input
type="text"
placeholder="Vehicle number"
value={newVehicleNumber}
onChange={e => setNewVehicleNumber(e.target.value)}
onKeyDown={e => e.key === 'Enter' && handleCreateVehicle()}
disabled={isSaving}
autoFocus
/>
<div className="vehicle-new-actions">
<button onClick={handleCreateVehicle} disabled={isSaving}>
{isSaving ? '...' : 'Create'}
</button>
<button onClick={() => { setShowNewForm(false); setNewVehicleNumber(''); setError(''); }}>
Cancel
</button>
</div>
</div>
)}
<div className="vehicle-list"> <div className="vehicle-list">
{filteredVehicles.map(v => ( {filteredVehicles.map(v => (
<div <div
key={v.id} key={v.id}
className={`vehicle-list-item ${selectedVehicleId === v.id ? 'vehicle-list-item--active' : ''}`} className={`vehicle-list-item ${selectedVehicleId === v.id ? 'vehicle-list-item--active' : ''}`}
onClick={() => { setSelectedVehicleId(v.id); setError(''); }} onClick={() => { setSelectedVehicleId(v.id); setShowNewForm(false); setError(''); }}
> >
<span className="vehicle-list-number">{v.vehicle_number}</span> <span className="vehicle-list-number">{v.vehicle_number}</span>
{v.tare != null && ( {v.tare != null && (
@ -196,9 +205,95 @@ export default function Main() {
</div> </div>
</div> </div>
{/* Right panel - Vehicle detail */} {/* Right panel - Vehicle detail or New form */}
<div className="main-right"> <div className="main-right">
{!selectedVehicle ? ( {showNewForm ? (
<div className="vehicle-detail">
{error && <div className="vehicle-error">{error}</div>}
<h2 className="vehicle-detail-title">New Vehicle</h2>
<div className="vehicle-form-section">
<div className="extra-fields">
<div className="extra-field">
<label>Vehicle Number *</label>
<input
type="text"
value={newForm.vehicle_number}
onChange={e => setNewForm(prev => ({ ...prev, vehicle_number: e.target.value }))}
disabled={isSaving}
autoFocus
/>
</div>
<div className="extra-field">
<label>Trailer 1</label>
<input
type="text"
value={newForm.trailer1_number}
onChange={e => setNewForm(prev => ({ ...prev, trailer1_number: e.target.value }))}
disabled={isSaving}
/>
</div>
<div className="extra-field">
<label>Trailer 2</label>
<input
type="text"
value={newForm.trailer2_number}
onChange={e => setNewForm(prev => ({ ...prev, trailer2_number: e.target.value }))}
disabled={isSaving}
/>
</div>
<div className="extra-field">
<label>Driver PID</label>
<input
type="text"
value={newForm.driver_pid}
onChange={e => setNewForm(prev => ({ ...prev, driver_pid: e.target.value }))}
disabled={isSaving}
/>
</div>
</div>
</div>
{vehicleNomenclatures.length > 0 && (
<div className="extra-section">
<h3>Additional Data</h3>
<div className="extra-fields">
{vehicleNomenclatures.map(nom => (
<div key={nom.code} className="extra-field">
<NomenclatureDropdown
nomenclatureCode={nom.code}
value={newExtraData[nom.code]}
onChange={val => setNewExtraData(prev => ({
...prev,
[nom.code]: val,
}))}
disabled={isSaving}
/>
</div>
))}
</div>
</div>
)}
<div className="vehicle-form-actions">
<button
className="vehicle-form-cancel"
onClick={handleCancelNew}
disabled={isSaving}
>
Cancel
</button>
<button
className="extra-save-btn"
onClick={handleCreateVehicle}
disabled={isSaving || !newForm.vehicle_number.trim()}
>
{isSaving ? 'Creating...' : 'Create Vehicle'}
</button>
</div>
</div>
) : !selectedVehicle ? (
<div className="main-placeholder"> <div className="main-placeholder">
Select a vehicle or create a new one Select a vehicle or create a new one
</div> </div>

Loading…
Cancel
Save