fix
This commit is contained in:
BIN
app/.DS_Store
vendored
BIN
app/.DS_Store
vendored
Binary file not shown.
@@ -48,6 +48,23 @@ exports.getAllVehicleRepairs = (req, res) => {
|
||||
});
|
||||
}
|
||||
|
||||
exports.updateVehicleRepair = (req, res) => {
|
||||
const id = req.params.id;
|
||||
VehicleRepair.findByIdAndUpdate(id, req.body, { new: true })
|
||||
.then(data => {
|
||||
if (!data) {
|
||||
res.status(404).send({ message: `Cannot update Vehicle Repair Record with id=${id}.` });
|
||||
} else {
|
||||
res.send(data);
|
||||
}
|
||||
})
|
||||
.catch(err => {
|
||||
res.status(500).send({
|
||||
message: err.message || "Could not update Vehicle Repair Record."
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
exports.deleteVehicleRepair = (req, res) => {
|
||||
const id = req.params.id;
|
||||
VehicleRepair.findByIdAndDelete(id)
|
||||
|
||||
@@ -11,6 +11,7 @@ module.exports = app => {
|
||||
var router = require("express").Router();
|
||||
router.get("/", [authJwt.verifyToken], vehicleRepairs.getAllVehicleRepairs);
|
||||
router.post("/", [authJwt.verifyToken], vehicleRepairs.createVehicleRepair);
|
||||
router.put("/:id", [authJwt.verifyToken], vehicleRepairs.updateVehicleRepair);
|
||||
router.delete("/:id", [authJwt.verifyToken], vehicleRepairs.deleteVehicleRepair);
|
||||
app.use('/api/vehicle-repairs', router);
|
||||
};
|
||||
@@ -1,16 +1,16 @@
|
||||
{
|
||||
"files": {
|
||||
"main.css": "/static/css/main.46cc12be.css",
|
||||
"main.js": "/static/js/main.671a9950.js",
|
||||
"main.js": "/static/js/main.c98c5007.js",
|
||||
"static/js/787.c4e7f8f9.chunk.js": "/static/js/787.c4e7f8f9.chunk.js",
|
||||
"static/media/landing.png": "/static/media/landing.d4c6072db7a67dff6a78.png",
|
||||
"index.html": "/index.html",
|
||||
"main.46cc12be.css.map": "/static/css/main.46cc12be.css.map",
|
||||
"main.671a9950.js.map": "/static/js/main.671a9950.js.map",
|
||||
"main.c98c5007.js.map": "/static/js/main.c98c5007.js.map",
|
||||
"787.c4e7f8f9.chunk.js.map": "/static/js/787.c4e7f8f9.chunk.js.map"
|
||||
},
|
||||
"entrypoints": [
|
||||
"static/css/main.46cc12be.css",
|
||||
"static/js/main.671a9950.js"
|
||||
"static/js/main.c98c5007.js"
|
||||
]
|
||||
}
|
||||
@@ -1 +1 @@
|
||||
<!doctype html><html lang="en"><head><meta charset="utf-8"/><link rel="icon" href="/favicon.ico"/><meta name="viewport" content="width=device-width,initial-scale=1"/><meta name="theme-color" content="#000000"/><meta name="description" content="Web site created using create-react-app"/><link rel="apple-touch-icon" href="/logo192.png"/><script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.10/lodash.min.js"></script><link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css"><link rel="manifest" href="/manifest.json"/><title>Worldshine Transportation</title><script defer="defer" src="/static/js/main.671a9950.js"></script><link href="/static/css/main.46cc12be.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div></body></html>
|
||||
<!doctype html><html lang="en"><head><meta charset="utf-8"/><link rel="icon" href="/favicon.ico"/><meta name="viewport" content="width=device-width,initial-scale=1"/><meta name="theme-color" content="#000000"/><meta name="description" content="Web site created using create-react-app"/><link rel="apple-touch-icon" href="/logo192.png"/><script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.10/lodash.min.js"></script><link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css"><link rel="manifest" href="/manifest.json"/><title>Worldshine Transportation</title><script defer="defer" src="/static/js/main.c98c5007.js"></script><link href="/static/css/main.46cc12be.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div></body></html>
|
||||
File diff suppressed because one or more lines are too long
3
app/views/static/js/main.c98c5007.js
Normal file
3
app/views/static/js/main.c98c5007.js
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
BIN
client/.DS_Store
vendored
BIN
client/.DS_Store
vendored
Binary file not shown.
@@ -14,6 +14,10 @@ import RouteEdit from "./components/trans-routes/RouteEdit";
|
||||
import RoutesSchedule from "./components/trans-routes/RoutesSchedule";
|
||||
import CreateVehicle from "./components/vehicles/CreateVehicle";
|
||||
import UpdateVehicle from "./components/vehicles/UpdateVehicle";
|
||||
import AddVehicleInspection from "./components/vehicles/AddVehicleInspection";
|
||||
import EditVehicleInspection from "./components/vehicles/EditVehicleInspection";
|
||||
import AddRepairRecord from "./components/vehicles/AddRepairRecord";
|
||||
import EditRepairRecord from "./components/vehicles/EditRepairRecord";
|
||||
import CreateEmployee from "./components/employees/CreateEmployee";
|
||||
import UpdateEmployee from "./components/employees/UpdateEmployee";
|
||||
import EmployeeList from "./components/employees/EmployeeList";
|
||||
@@ -138,6 +142,10 @@ function App() {
|
||||
<Route path="/vehicles/edit/:id" element={<UpdateVehicle />} />
|
||||
<Route path="/vehicles/list" element={<VehicleList/> } />
|
||||
<Route path="/vehicles/:id" element={<ViewVehicle/>}/>
|
||||
<Route path="/vehicles/:id/inspections/:type/add" element={<AddVehicleInspection />} />
|
||||
<Route path="/vehicles/:id/inspections/:type/edit" element={<EditVehicleInspection />} />
|
||||
<Route path="/vehicles/:id/repairs/add" element={<AddRepairRecord />} />
|
||||
<Route path="/vehicles/:id/repairs/edit/:repairId" element={<EditRepairRecord />} />
|
||||
|
||||
<Route path="/messages" element={<CreateMessage /> } />
|
||||
<Route path="/messages/edit/:id" element={<UpdateMessage />} />
|
||||
|
||||
230
client/src/components/vehicles/AddRepairRecord.js
Normal file
230
client/src/components/vehicles/AddRepairRecord.js
Normal file
@@ -0,0 +1,230 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { useNavigate, useParams } from "react-router-dom";
|
||||
import { AuthService, VehicleRepairService, VehicleService } from "../../services";
|
||||
import { Upload, Trash } from "react-bootstrap-icons";
|
||||
import { Breadcrumb } from "react-bootstrap";
|
||||
import DatePicker from "react-datepicker";
|
||||
import moment from "moment";
|
||||
import {
|
||||
REPAIR_PART_NAME,
|
||||
REPAIR_PART_NAME_TEXT,
|
||||
QUANTITY_OPTIONS
|
||||
} from "../../shared";
|
||||
|
||||
const AddRepairRecord = () => {
|
||||
const navigate = useNavigate();
|
||||
const params = useParams();
|
||||
|
||||
const [currentVehicle, setCurrentVehicle] = useState(null);
|
||||
const [existingRepairs, setExistingRepairs] = useState([]);
|
||||
|
||||
const [partName, setPartName] = useState('');
|
||||
const [replacementDate, setReplacementDate] = useState(null);
|
||||
const [mileage, setMileage] = useState('');
|
||||
const [quantity, setQuantity] = useState('');
|
||||
const [cost, setCost] = useState('');
|
||||
const [location, setLocation] = useState('');
|
||||
const [receiptFile, setReceiptFile] = useState(null);
|
||||
const [nextReminder, setNextReminder] = useState('');
|
||||
|
||||
useEffect(() => {
|
||||
if (!AuthService.canAddOrEditVechiles()) {
|
||||
window.alert('You haven\'t login yet OR this user does not have access to this page.');
|
||||
AuthService.logout();
|
||||
navigate('/login');
|
||||
}
|
||||
VehicleService.getVehicle(params.id).then((data) => {
|
||||
setCurrentVehicle(data.data);
|
||||
});
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (currentVehicle) {
|
||||
fetchRepairs();
|
||||
}
|
||||
}, [currentVehicle]);
|
||||
|
||||
const fetchRepairs = () => {
|
||||
VehicleRepairService.getAll(currentVehicle.id)
|
||||
.then(res => setExistingRepairs(res.data || []))
|
||||
.catch(() => setExistingRepairs([]));
|
||||
};
|
||||
|
||||
const formatDateForBackend = (date) => {
|
||||
if (!date) return '';
|
||||
return moment(date).format('MM/DD/YYYY');
|
||||
};
|
||||
|
||||
const handleSave = () => {
|
||||
if (!partName) {
|
||||
window.alert('Please select a part name.');
|
||||
return;
|
||||
}
|
||||
const data = {
|
||||
vehicle: currentVehicle?.id,
|
||||
part_name: partName,
|
||||
repair_date: formatDateForBackend(replacementDate),
|
||||
mileage_at_replacement: mileage,
|
||||
quantity,
|
||||
repair_price: cost,
|
||||
repair_location: location,
|
||||
next_replacement_reminder: nextReminder
|
||||
};
|
||||
VehicleRepairService.createNewVehicleRepair(data).then(result => {
|
||||
const record = result.data;
|
||||
const uploadPromise = receiptFile
|
||||
? VehicleService.uploadVechileFile(
|
||||
(() => { const fd = new FormData(); fd.append('file', receiptFile); return fd; })(),
|
||||
currentVehicle.id, record.id, 'repair', replacementDate || new Date()
|
||||
)
|
||||
: Promise.resolve();
|
||||
uploadPromise.then(() => {
|
||||
setPartName('');
|
||||
setReplacementDate(null);
|
||||
setMileage('');
|
||||
setQuantity('');
|
||||
setCost('');
|
||||
setLocation('');
|
||||
setReceiptFile(null);
|
||||
setNextReminder('');
|
||||
fetchRepairs();
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const deleteRepair = (repairId) => {
|
||||
if (window.confirm('Are you sure you want to delete this repair record?')) {
|
||||
VehicleRepairService.deleteVehicleRepair(repairId).then(() => fetchRepairs());
|
||||
}
|
||||
};
|
||||
|
||||
const goBack = () => {
|
||||
navigate(`/vehicles/${params.id}?tab=documents`);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="list row mb-4">
|
||||
<Breadcrumb>
|
||||
<Breadcrumb.Item href="/trans-routes/dashboard">Transportation</Breadcrumb.Item>
|
||||
<Breadcrumb.Item href="/vehicles/list">Vehicles Information</Breadcrumb.Item>
|
||||
<Breadcrumb.Item href={`/vehicles/${params.id}?tab=documents`}>View Vehicle Information</Breadcrumb.Item>
|
||||
<Breadcrumb.Item active>Add Repair Record</Breadcrumb.Item>
|
||||
</Breadcrumb>
|
||||
<div className="col-md-12 text-primary">
|
||||
<h4>Add Repair Record <button className="btn btn-link btn-sm" onClick={goBack}>Back</button></h4>
|
||||
</div>
|
||||
</div>
|
||||
<div className="app-main-content-list-container form-page">
|
||||
<div className="app-main-content-list-func-container">
|
||||
<h6 className="text-primary">Repair & Maintenance Record</h6>
|
||||
|
||||
{existingRepairs.length > 0 && (
|
||||
<>
|
||||
<div className="field-label mb-2">Existing Records</div>
|
||||
<table className="table table-sm table-bordered mb-4">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Part Name</th>
|
||||
<th>Date</th>
|
||||
<th>Mileage</th>
|
||||
<th>Qty</th>
|
||||
<th>Cost</th>
|
||||
<th>Location</th>
|
||||
<th>Next Reminder</th>
|
||||
<th>Action</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{existingRepairs.map((repair) => (
|
||||
<tr key={repair.id}>
|
||||
<td>{REPAIR_PART_NAME_TEXT[repair.part_name] || repair.repair_description || repair.part_name || '-'}</td>
|
||||
<td>{repair.repair_date || '-'}</td>
|
||||
<td>{repair.mileage_at_replacement || '-'}</td>
|
||||
<td>{repair.quantity || '-'}</td>
|
||||
<td>{repair.repair_price || '-'}</td>
|
||||
<td>{repair.repair_location || '-'}</td>
|
||||
<td>{repair.next_replacement_reminder || '-'}</td>
|
||||
<td>
|
||||
<button className="btn btn-link btn-sm p-0 text-danger" onClick={() => deleteRepair(repair.id)}>
|
||||
<Trash size={14} />
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</>
|
||||
)}
|
||||
|
||||
<div className="field-label mb-2">Add New Record</div>
|
||||
<div className="app-main-content-fields-section">
|
||||
<div className="me-4">
|
||||
<div className="field-label">Part Name</div>
|
||||
<select value={partName} onChange={e => setPartName(e.target.value)}>
|
||||
<option value="">Select...</option>
|
||||
{Object.keys(REPAIR_PART_NAME).map(key => (
|
||||
<option key={key} value={REPAIR_PART_NAME[key]}>{REPAIR_PART_NAME_TEXT[REPAIR_PART_NAME[key]]}</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
<div className="me-4">
|
||||
<div className="field-label">Replacement Date</div>
|
||||
<DatePicker
|
||||
selected={replacementDate}
|
||||
onChange={(date) => setReplacementDate(date)}
|
||||
dateFormat="MM/dd/yyyy"
|
||||
placeholderText="e.g., 03/01/2024"
|
||||
className="form-control"
|
||||
/>
|
||||
</div>
|
||||
<div className="me-4">
|
||||
<div className="field-label">Mileage at Replacement</div>
|
||||
<input type="text" placeholder="e.g., 48,000" value={mileage} onChange={e => setMileage(e.target.value)} />
|
||||
</div>
|
||||
<div className="me-4">
|
||||
<div className="field-label">Quantity</div>
|
||||
<select value={quantity} onChange={e => setQuantity(e.target.value)}>
|
||||
<option value="">Select...</option>
|
||||
{QUANTITY_OPTIONS.map(opt => (
|
||||
<option key={opt.value} value={opt.value}>{opt.label}</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div className="app-main-content-fields-section">
|
||||
<div className="me-4">
|
||||
<div className="field-label">Cost</div>
|
||||
<input type="text" placeholder="e.g., $250.00" value={cost} onChange={e => setCost(e.target.value)} />
|
||||
</div>
|
||||
<div className="me-4">
|
||||
<div className="field-label">Location</div>
|
||||
<input type="text" placeholder="e.g., Rockville Auto Center" value={location} onChange={e => setLocation(e.target.value)} />
|
||||
</div>
|
||||
<div className="me-4">
|
||||
<div className="field-label">Receipt Upload</div>
|
||||
<label className="custom-file-upload">
|
||||
<Upload width={20} color={"#fff"} className="me-2" /> Upload
|
||||
<input type="file" onChange={(e) => setReceiptFile(e.target.files[0])} />
|
||||
</label>
|
||||
<div className="file-name">{receiptFile?.name}</div>
|
||||
</div>
|
||||
<div className="me-4">
|
||||
<div className="field-label">Next Replacement Reminder</div>
|
||||
<input type="text" placeholder="e.g., 78,000" value={nextReminder} onChange={e => setNextReminder(e.target.value)} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="list row mb-5 mt-3">
|
||||
<div className="col-md-12 col-sm-12 col-xs-12">
|
||||
<button className="btn btn-default btn-sm float-right" onClick={goBack}> Cancel </button>
|
||||
<button className="btn btn-primary btn-sm float-right" onClick={handleSave}> Save </button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default AddRepairRecord;
|
||||
143
client/src/components/vehicles/AddVehicleInspection.js
Normal file
143
client/src/components/vehicles/AddVehicleInspection.js
Normal file
@@ -0,0 +1,143 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { useNavigate, useParams } from "react-router-dom";
|
||||
import { AuthService, VehicleService } from "../../services";
|
||||
import { Upload } from "react-bootstrap-icons";
|
||||
import { Breadcrumb } from "react-bootstrap";
|
||||
import DatePicker from "react-datepicker";
|
||||
|
||||
const AddVehicleInspection = () => {
|
||||
const navigate = useNavigate();
|
||||
const params = useParams();
|
||||
const inspectionType = params.type; // 'yearly' or 'monthly'
|
||||
const isYearly = inspectionType === 'yearly';
|
||||
const title = isYearly ? 'Yearly Inspection' : 'Monthly Inspection';
|
||||
|
||||
const [currentVehicle, setCurrentVehicle] = useState(null);
|
||||
const [inspectionDate, setInspectionDate] = useState(null);
|
||||
const [selectedFile, setSelectedFile] = useState(null);
|
||||
const [existingFiles, setExistingFiles] = useState([]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!AuthService.canAddOrEditVechiles()) {
|
||||
window.alert('You haven\'t login yet OR this user does not have access to this page.');
|
||||
AuthService.logout();
|
||||
navigate('/login');
|
||||
}
|
||||
VehicleService.getVehicle(params.id).then((data) => {
|
||||
setCurrentVehicle(data.data);
|
||||
});
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (currentVehicle) {
|
||||
fetchFiles();
|
||||
}
|
||||
}, [currentVehicle]);
|
||||
|
||||
const fetchFiles = () => {
|
||||
const fileType = isYearly ? 'yearlyInspection' : 'monthlyInspection';
|
||||
VehicleService.getAllVechileFiles(currentVehicle.id, currentVehicle.vehicle_number, fileType)
|
||||
.then(res => setExistingFiles(res.data?.data?.files || []))
|
||||
.catch(() => setExistingFiles([]));
|
||||
};
|
||||
|
||||
const getFileDownloadUrl = (fileUrl) => {
|
||||
const baseUrl = require('../../http-common').default?.defaults?.baseURL || '';
|
||||
return baseUrl.replace('/api', '') + fileUrl;
|
||||
};
|
||||
|
||||
const handleSave = () => {
|
||||
if (!selectedFile || !inspectionDate) {
|
||||
window.alert('Please select a date and a file.');
|
||||
return;
|
||||
}
|
||||
const formData = new FormData();
|
||||
formData.append('file', selectedFile);
|
||||
const fileType = isYearly ? 'yearlyInspection' : 'monthlyInspection';
|
||||
VehicleService.uploadVechileFile(formData, currentVehicle.id, currentVehicle.vehicle_number, fileType, inspectionDate).then(() => {
|
||||
setSelectedFile(null);
|
||||
setInspectionDate(null);
|
||||
fetchFiles();
|
||||
});
|
||||
};
|
||||
|
||||
const goBack = () => {
|
||||
navigate(`/vehicles/${params.id}?tab=documents`);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="list row mb-4">
|
||||
<Breadcrumb>
|
||||
<Breadcrumb.Item href="/trans-routes/dashboard">Transportation</Breadcrumb.Item>
|
||||
<Breadcrumb.Item href="/vehicles/list">Vehicles Information</Breadcrumb.Item>
|
||||
<Breadcrumb.Item href={`/vehicles/${params.id}?tab=documents`}>View Vehicle Information</Breadcrumb.Item>
|
||||
<Breadcrumb.Item active>Add {title}</Breadcrumb.Item>
|
||||
</Breadcrumb>
|
||||
<div className="col-md-12 text-primary">
|
||||
<h4>Add {title} <button className="btn btn-link btn-sm" onClick={goBack}>Back</button></h4>
|
||||
</div>
|
||||
</div>
|
||||
<div className="app-main-content-list-container form-page">
|
||||
<div className="app-main-content-list-func-container">
|
||||
<h6 className="text-primary">{title}</h6>
|
||||
|
||||
{existingFiles.length > 0 && (
|
||||
<>
|
||||
<div className="field-label mb-2">Existing Records</div>
|
||||
<table className="table table-sm table-bordered mb-4">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>File Name</th>
|
||||
<th>Action</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{existingFiles.map((file, idx) => (
|
||||
<tr key={idx}>
|
||||
<td>{file.name}</td>
|
||||
<td>
|
||||
<a href={getFileDownloadUrl(file.url)} target="_blank" rel="noopener noreferrer" className="btn btn-link btn-sm p-0">Download</a>
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</>
|
||||
)}
|
||||
|
||||
<div className="field-label mb-2">Add New Record</div>
|
||||
<div className="app-main-content-fields-section">
|
||||
<div className="me-4">
|
||||
<div className="field-label">Inspection Date</div>
|
||||
<DatePicker
|
||||
selected={inspectionDate}
|
||||
onChange={(date) => setInspectionDate(date)}
|
||||
dateFormat="MM/dd/yyyy"
|
||||
placeholderText="e.g., 03/01/2024"
|
||||
className="form-control"
|
||||
/>
|
||||
</div>
|
||||
<div className="me-4">
|
||||
<div className="field-label">Inspection File</div>
|
||||
<label className="custom-file-upload">
|
||||
<Upload width={20} color={"#fff"} className="me-2" /> Upload
|
||||
<input type="file" onChange={(e) => setSelectedFile(e.target.files[0])} />
|
||||
</label>
|
||||
<div className="file-name">{selectedFile?.name}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="list row mb-5 mt-3">
|
||||
<div className="col-md-12 col-sm-12 col-xs-12">
|
||||
<button className="btn btn-default btn-sm float-right" onClick={goBack}> Cancel </button>
|
||||
<button className="btn btn-primary btn-sm float-right" onClick={handleSave}> Save </button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default AddVehicleInspection;
|
||||
175
client/src/components/vehicles/EditRepairRecord.js
Normal file
175
client/src/components/vehicles/EditRepairRecord.js
Normal file
@@ -0,0 +1,175 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { useNavigate, useParams } from "react-router-dom";
|
||||
import { AuthService, VehicleRepairService, VehicleService } from "../../services";
|
||||
import { Upload } from "react-bootstrap-icons";
|
||||
import { Breadcrumb } from "react-bootstrap";
|
||||
import DatePicker from "react-datepicker";
|
||||
import moment from "moment";
|
||||
import {
|
||||
REPAIR_PART_NAME,
|
||||
REPAIR_PART_NAME_TEXT,
|
||||
QUANTITY_OPTIONS
|
||||
} from "../../shared";
|
||||
|
||||
const EditRepairRecord = () => {
|
||||
const navigate = useNavigate();
|
||||
const params = useParams();
|
||||
|
||||
const [currentVehicle, setCurrentVehicle] = useState(null);
|
||||
const [partName, setPartName] = useState('');
|
||||
const [replacementDate, setReplacementDate] = useState(null);
|
||||
const [mileage, setMileage] = useState('');
|
||||
const [quantity, setQuantity] = useState('');
|
||||
const [cost, setCost] = useState('');
|
||||
const [location, setLocation] = useState('');
|
||||
const [receiptFile, setReceiptFile] = useState(null);
|
||||
const [nextReminder, setNextReminder] = useState('');
|
||||
|
||||
useEffect(() => {
|
||||
if (!AuthService.canAddOrEditVechiles()) {
|
||||
window.alert('You haven\'t login yet OR this user does not have access to this page.');
|
||||
AuthService.logout();
|
||||
navigate('/login');
|
||||
}
|
||||
VehicleService.getVehicle(params.id).then((data) => {
|
||||
setCurrentVehicle(data.data);
|
||||
});
|
||||
VehicleRepairService.getAll(params.id).then(res => {
|
||||
const repair = (res.data || []).find(r => r.id === params.repairId);
|
||||
if (repair) {
|
||||
setPartName(repair.part_name || '');
|
||||
if (repair.repair_date) {
|
||||
const parsed = moment(repair.repair_date, 'MM/DD/YYYY');
|
||||
if (parsed.isValid()) setReplacementDate(parsed.toDate());
|
||||
}
|
||||
setMileage(repair.mileage_at_replacement || '');
|
||||
setQuantity(repair.quantity || '');
|
||||
setCost(repair.repair_price || '');
|
||||
setLocation(repair.repair_location || '');
|
||||
setNextReminder(repair.next_replacement_reminder || '');
|
||||
}
|
||||
});
|
||||
}, []);
|
||||
|
||||
const formatDateForBackend = (date) => {
|
||||
if (!date) return '';
|
||||
return moment(date).format('MM/DD/YYYY');
|
||||
};
|
||||
|
||||
const handleSave = () => {
|
||||
if (!partName) {
|
||||
window.alert('Please select a part name.');
|
||||
return;
|
||||
}
|
||||
const data = {
|
||||
part_name: partName,
|
||||
repair_date: formatDateForBackend(replacementDate),
|
||||
mileage_at_replacement: mileage,
|
||||
quantity,
|
||||
repair_price: cost,
|
||||
repair_location: location,
|
||||
next_replacement_reminder: nextReminder
|
||||
};
|
||||
VehicleRepairService.updateVehicleRepair(params.repairId, data).then(() => {
|
||||
if (receiptFile) {
|
||||
const fd = new FormData();
|
||||
fd.append('file', receiptFile);
|
||||
VehicleService.uploadVechileFile(fd, params.id, params.repairId, 'repair', replacementDate || new Date())
|
||||
.then(() => goBack());
|
||||
} else {
|
||||
goBack();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const goBack = () => {
|
||||
navigate(`/vehicles/${params.id}?tab=documents`);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="list row mb-4">
|
||||
<Breadcrumb>
|
||||
<Breadcrumb.Item href="/trans-routes/dashboard">Transportation</Breadcrumb.Item>
|
||||
<Breadcrumb.Item href="/vehicles/list">Vehicles Information</Breadcrumb.Item>
|
||||
<Breadcrumb.Item href={`/vehicles/${params.id}?tab=documents`}>View Vehicle Information</Breadcrumb.Item>
|
||||
<Breadcrumb.Item active>Edit Repair Record</Breadcrumb.Item>
|
||||
</Breadcrumb>
|
||||
<div className="col-md-12 text-primary">
|
||||
<h4>Edit Repair Record <button className="btn btn-link btn-sm" onClick={goBack}>Back</button></h4>
|
||||
</div>
|
||||
</div>
|
||||
<div className="app-main-content-list-container form-page">
|
||||
<div className="app-main-content-list-func-container">
|
||||
<h6 className="text-primary">Repair & Maintenance Record</h6>
|
||||
|
||||
<div className="app-main-content-fields-section">
|
||||
<div className="me-4">
|
||||
<div className="field-label">Part Name</div>
|
||||
<select value={partName} onChange={e => setPartName(e.target.value)}>
|
||||
<option value="">Select...</option>
|
||||
{Object.keys(REPAIR_PART_NAME).map(key => (
|
||||
<option key={key} value={REPAIR_PART_NAME[key]}>{REPAIR_PART_NAME_TEXT[REPAIR_PART_NAME[key]]}</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
<div className="me-4">
|
||||
<div className="field-label">Replacement Date</div>
|
||||
<DatePicker
|
||||
selected={replacementDate}
|
||||
onChange={(date) => setReplacementDate(date)}
|
||||
dateFormat="MM/dd/yyyy"
|
||||
placeholderText="e.g., 03/01/2024"
|
||||
className="form-control"
|
||||
/>
|
||||
</div>
|
||||
<div className="me-4">
|
||||
<div className="field-label">Mileage at Replacement</div>
|
||||
<input type="text" placeholder="e.g., 48,000" value={mileage} onChange={e => setMileage(e.target.value)} />
|
||||
</div>
|
||||
<div className="me-4">
|
||||
<div className="field-label">Quantity</div>
|
||||
<select value={quantity} onChange={e => setQuantity(e.target.value)}>
|
||||
<option value="">Select...</option>
|
||||
{QUANTITY_OPTIONS.map(opt => (
|
||||
<option key={opt.value} value={opt.value}>{opt.label}</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div className="app-main-content-fields-section">
|
||||
<div className="me-4">
|
||||
<div className="field-label">Cost</div>
|
||||
<input type="text" placeholder="e.g., $250.00" value={cost} onChange={e => setCost(e.target.value)} />
|
||||
</div>
|
||||
<div className="me-4">
|
||||
<div className="field-label">Location</div>
|
||||
<input type="text" placeholder="e.g., Rockville Auto Center" value={location} onChange={e => setLocation(e.target.value)} />
|
||||
</div>
|
||||
<div className="me-4">
|
||||
<div className="field-label">Receipt Upload</div>
|
||||
<label className="custom-file-upload">
|
||||
<Upload width={20} color={"#fff"} className="me-2" /> Upload
|
||||
<input type="file" onChange={(e) => setReceiptFile(e.target.files[0])} />
|
||||
</label>
|
||||
<div className="file-name">{receiptFile?.name}</div>
|
||||
</div>
|
||||
<div className="me-4">
|
||||
<div className="field-label">Next Replacement Reminder</div>
|
||||
<input type="text" placeholder="e.g., 78,000" value={nextReminder} onChange={e => setNextReminder(e.target.value)} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="list row mb-5 mt-3">
|
||||
<div className="col-md-12 col-sm-12 col-xs-12">
|
||||
<button className="btn btn-default btn-sm float-right" onClick={goBack}> Cancel </button>
|
||||
<button className="btn btn-primary btn-sm float-right" onClick={handleSave}> Save </button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default EditRepairRecord;
|
||||
121
client/src/components/vehicles/EditVehicleInspection.js
Normal file
121
client/src/components/vehicles/EditVehicleInspection.js
Normal file
@@ -0,0 +1,121 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { useNavigate, useParams, useSearchParams } from "react-router-dom";
|
||||
import { AuthService, VehicleService } from "../../services";
|
||||
import { Upload } from "react-bootstrap-icons";
|
||||
import { Breadcrumb } from "react-bootstrap";
|
||||
import DatePicker from "react-datepicker";
|
||||
import moment from "moment";
|
||||
|
||||
const EditVehicleInspection = () => {
|
||||
const navigate = useNavigate();
|
||||
const params = useParams();
|
||||
const [searchParams] = useSearchParams();
|
||||
const inspectionType = params.type;
|
||||
const isYearly = inspectionType === 'yearly';
|
||||
const title = isYearly ? 'Yearly Inspection' : 'Monthly Inspection';
|
||||
|
||||
const [currentVehicle, setCurrentVehicle] = useState(null);
|
||||
const [inspectionDate, setInspectionDate] = useState(null);
|
||||
const [selectedFile, setSelectedFile] = useState(null);
|
||||
const [currentFileName, setCurrentFileName] = useState('');
|
||||
|
||||
useEffect(() => {
|
||||
if (!AuthService.canAddOrEditVechiles()) {
|
||||
window.alert('You haven\'t login yet OR this user does not have access to this page.');
|
||||
AuthService.logout();
|
||||
navigate('/login');
|
||||
}
|
||||
VehicleService.getVehicle(params.id).then((data) => {
|
||||
setCurrentVehicle(data.data);
|
||||
});
|
||||
|
||||
const fileName = searchParams.get('fileName');
|
||||
const dateStr = searchParams.get('date');
|
||||
if (fileName) setCurrentFileName(decodeURIComponent(fileName));
|
||||
if (dateStr) {
|
||||
const parsed = moment(dateStr, 'MM/DD/YYYY');
|
||||
if (parsed.isValid()) setInspectionDate(parsed.toDate());
|
||||
}
|
||||
}, []);
|
||||
|
||||
const handleSave = () => {
|
||||
if (!selectedFile) {
|
||||
window.alert('Please select a file to upload.');
|
||||
return;
|
||||
}
|
||||
if (!inspectionDate) {
|
||||
window.alert('Please select an inspection date.');
|
||||
return;
|
||||
}
|
||||
const formData = new FormData();
|
||||
formData.append('file', selectedFile);
|
||||
const fileType = isYearly ? 'yearlyInspection' : 'monthlyInspection';
|
||||
VehicleService.uploadVechileFile(formData, currentVehicle.id, currentVehicle.vehicle_number, fileType, inspectionDate).then(() => {
|
||||
goBack();
|
||||
});
|
||||
};
|
||||
|
||||
const goBack = () => {
|
||||
navigate(`/vehicles/${params.id}?tab=documents`);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="list row mb-4">
|
||||
<Breadcrumb>
|
||||
<Breadcrumb.Item href="/trans-routes/dashboard">Transportation</Breadcrumb.Item>
|
||||
<Breadcrumb.Item href="/vehicles/list">Vehicles Information</Breadcrumb.Item>
|
||||
<Breadcrumb.Item href={`/vehicles/${params.id}?tab=documents`}>View Vehicle Information</Breadcrumb.Item>
|
||||
<Breadcrumb.Item active>Edit {title}</Breadcrumb.Item>
|
||||
</Breadcrumb>
|
||||
<div className="col-md-12 text-primary">
|
||||
<h4>Edit {title} <button className="btn btn-link btn-sm" onClick={goBack}>Back</button></h4>
|
||||
</div>
|
||||
</div>
|
||||
<div className="app-main-content-list-container form-page">
|
||||
<div className="app-main-content-list-func-container">
|
||||
<h6 className="text-primary">{title}</h6>
|
||||
|
||||
{currentFileName && (
|
||||
<div className="app-main-content-fields-section mb-3">
|
||||
<div className="field-body">
|
||||
<div className="field-label">Current File</div>
|
||||
<div className="field-value">{currentFileName}</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="app-main-content-fields-section">
|
||||
<div className="me-4">
|
||||
<div className="field-label">Inspection Date</div>
|
||||
<DatePicker
|
||||
selected={inspectionDate}
|
||||
onChange={(date) => setInspectionDate(date)}
|
||||
dateFormat="MM/dd/yyyy"
|
||||
placeholderText="e.g., 03/01/2024"
|
||||
className="form-control"
|
||||
/>
|
||||
</div>
|
||||
<div className="me-4">
|
||||
<div className="field-label">Upload Replacement File</div>
|
||||
<label className="custom-file-upload">
|
||||
<Upload width={20} color={"#fff"} className="me-2" /> Upload
|
||||
<input type="file" onChange={(e) => setSelectedFile(e.target.files[0])} />
|
||||
</label>
|
||||
<div className="file-name">{selectedFile?.name}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="list row mb-5 mt-3">
|
||||
<div className="col-md-12 col-sm-12 col-xs-12">
|
||||
<button className="btn btn-default btn-sm float-right" onClick={goBack}> Cancel </button>
|
||||
<button className="btn btn-primary btn-sm float-right" onClick={handleSave}> Save </button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default EditVehicleInspection;
|
||||
@@ -2,8 +2,8 @@ import React, {useEffect, useState} from "react";
|
||||
import { useSelector, useDispatch } from "react-redux";
|
||||
import { useNavigate, useParams, useSearchParams } from "react-router-dom";
|
||||
import { vehicleSlice, selectVehicleError } from "./../../store";
|
||||
import { AuthService, VehicleRepairService, VehicleService, DriverService } from "../../services";
|
||||
import { Archive, Upload, Trash } from "react-bootstrap-icons";
|
||||
import { AuthService, VehicleService, DriverService } from "../../services";
|
||||
import { Archive, Upload } from "react-bootstrap-icons";
|
||||
import { Spinner, Breadcrumb, BreadcrumbItem, Tabs, Tab, Modal, Button } from "react-bootstrap";
|
||||
import DatePicker from "react-datepicker";
|
||||
import Select from 'react-select';
|
||||
@@ -12,9 +12,7 @@ import {
|
||||
SEATING_CAPACITY_OPTIONS,
|
||||
FUEL_TYPE, FUEL_TYPE_TEXT,
|
||||
VEHICLE_TITLE, VEHICLE_TITLE_TEXT,
|
||||
LIFT_EQUIPPED, LIFT_EQUIPPED_TEXT,
|
||||
REPAIR_PART_NAME, REPAIR_PART_NAME_TEXT,
|
||||
QUANTITY_OPTIONS
|
||||
LIFT_EQUIPPED, LIFT_EQUIPPED_TEXT
|
||||
} from "../../shared";
|
||||
|
||||
const UpdateVehicle = () => {
|
||||
@@ -22,7 +20,9 @@ const UpdateVehicle = () => {
|
||||
const dispatch = useDispatch();
|
||||
const params = useParams();
|
||||
const [searchParams] = useSearchParams();
|
||||
const [activeTab, setActiveTab] = useState(searchParams.get('tab') || 'basicInfo');
|
||||
const validTabs = ['basicInfo', 'complianceDeadlines'];
|
||||
const tabFromUrl = searchParams.get('tab');
|
||||
const [activeTab, setActiveTab] = useState(validTabs.includes(tabFromUrl) ? tabFromUrl : 'basicInfo');
|
||||
const vehicles = useSelector((state) => state.vehicles && state.vehicles.vehicles);
|
||||
const currentVehicle = vehicles.find(item => item.id === params.id ) || undefined;
|
||||
const { updateVehicle, deleteVehicle, fetchAllVehicles } = vehicleSlice.actions;
|
||||
@@ -58,25 +58,6 @@ const UpdateVehicle = () => {
|
||||
// Drivers list
|
||||
const [drivers, setDrivers] = useState([]);
|
||||
|
||||
// Documents
|
||||
const [selectedMonthlyFile, setSelectedMonthlyFile] = useState();
|
||||
const [selectedYearlyFile, setSelectedYearlyFile] = useState();
|
||||
const [monthlyInspectionDate, setMonthlyInspectionDate] = useState(null);
|
||||
const [yearlyInspectionDate, setYearlyInspectionDate] = useState(null);
|
||||
const [existingYearlyFiles, setExistingYearlyFiles] = useState([]);
|
||||
const [existingMonthlyFiles, setExistingMonthlyFiles] = useState([]);
|
||||
|
||||
// Repair & Maintenance Record
|
||||
const [repairPartName, setRepairPartName] = useState('');
|
||||
const [repairReplacementDate, setRepairReplacementDate] = useState(null);
|
||||
const [repairMileage, setRepairMileage] = useState('');
|
||||
const [repairQuantity, setRepairQuantity] = useState('');
|
||||
const [repairCost, setRepairCost] = useState('');
|
||||
const [repairLocation, setRepairLocation] = useState('');
|
||||
const [repairReceiptFile, setRepairReceiptFile] = useState(null);
|
||||
const [repairNextReminder, setRepairNextReminder] = useState('');
|
||||
const [existingRepairs, setExistingRepairs] = useState([]);
|
||||
|
||||
// Modal
|
||||
const [showDeleteModal, setShowDeleteModal] = useState(false);
|
||||
|
||||
@@ -130,29 +111,9 @@ const UpdateVehicle = () => {
|
||||
|
||||
// Additional Information
|
||||
setNote(currentVehicle.note || '');
|
||||
|
||||
// Fetch existing inspection files
|
||||
fetchInspectionFiles(currentVehicle);
|
||||
// Fetch existing repair records
|
||||
fetchRepairRecords(currentVehicle.id);
|
||||
}
|
||||
}, [currentVehicle]);
|
||||
|
||||
const fetchInspectionFiles = (vehicle) => {
|
||||
VehicleService.getAllVechileFiles(vehicle.id, vehicle.vehicle_number, 'yearlyInspection')
|
||||
.then(res => setExistingYearlyFiles(res.data?.data?.files || []))
|
||||
.catch(() => setExistingYearlyFiles([]));
|
||||
VehicleService.getAllVechileFiles(vehicle.id, vehicle.vehicle_number, 'monthlyInspection')
|
||||
.then(res => setExistingMonthlyFiles(res.data?.data?.files || []))
|
||||
.catch(() => setExistingMonthlyFiles([]));
|
||||
};
|
||||
|
||||
const fetchRepairRecords = (vehicleId) => {
|
||||
VehicleRepairService.getAll(vehicleId)
|
||||
.then(res => setExistingRepairs(res.data || []))
|
||||
.catch(() => setExistingRepairs([]));
|
||||
};
|
||||
|
||||
const redirectTo = () => {
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
const redirect = params.get('redirect');
|
||||
@@ -263,83 +224,6 @@ const UpdateVehicle = () => {
|
||||
redirectTo();
|
||||
}
|
||||
|
||||
const saveYearlyInspection = () => {
|
||||
if (!selectedYearlyFile || !yearlyInspectionDate) {
|
||||
window.alert('Please select a date and a file for yearly inspection.');
|
||||
return;
|
||||
}
|
||||
const formData = new FormData();
|
||||
formData.append('file', selectedYearlyFile);
|
||||
VehicleService.uploadVechileFile(formData, currentVehicle.id, currentVehicle.vehicle_number, 'yearlyInspection', yearlyInspectionDate).then(() => {
|
||||
setSelectedYearlyFile(undefined);
|
||||
setYearlyInspectionDate(null);
|
||||
fetchInspectionFiles(currentVehicle);
|
||||
});
|
||||
}
|
||||
|
||||
const saveMonthlyInspection = () => {
|
||||
if (!selectedMonthlyFile || !monthlyInspectionDate) {
|
||||
window.alert('Please select a date and a file for monthly inspection.');
|
||||
return;
|
||||
}
|
||||
const formData = new FormData();
|
||||
formData.append('file', selectedMonthlyFile);
|
||||
VehicleService.uploadVechileFile(formData, currentVehicle.id, currentVehicle.vehicle_number, 'monthlyInspection', monthlyInspectionDate).then(() => {
|
||||
setSelectedMonthlyFile(undefined);
|
||||
setMonthlyInspectionDate(null);
|
||||
fetchInspectionFiles(currentVehicle);
|
||||
});
|
||||
}
|
||||
|
||||
const saveRepair = () => {
|
||||
if (!repairPartName) {
|
||||
window.alert('Please select a part name.');
|
||||
return;
|
||||
}
|
||||
const data = {
|
||||
vehicle: currentVehicle?.id,
|
||||
part_name: repairPartName,
|
||||
repair_date: formatDateForBackend(repairReplacementDate),
|
||||
mileage_at_replacement: repairMileage,
|
||||
quantity: repairQuantity,
|
||||
repair_price: repairCost,
|
||||
repair_location: repairLocation,
|
||||
next_replacement_reminder: repairNextReminder
|
||||
};
|
||||
VehicleRepairService.createNewVehicleRepair(data).then(result => {
|
||||
const record = result.data;
|
||||
const uploadPromise = repairReceiptFile
|
||||
? VehicleService.uploadVechileFile(
|
||||
(() => { const fd = new FormData(); fd.append('file', repairReceiptFile); return fd; })(),
|
||||
currentVehicle.id, record.id, 'repair', repairReplacementDate || new Date()
|
||||
)
|
||||
: Promise.resolve();
|
||||
uploadPromise.then(() => {
|
||||
setRepairPartName('');
|
||||
setRepairReplacementDate(null);
|
||||
setRepairMileage('');
|
||||
setRepairQuantity('');
|
||||
setRepairCost('');
|
||||
setRepairLocation('');
|
||||
setRepairReceiptFile(null);
|
||||
setRepairNextReminder('');
|
||||
fetchRepairRecords(currentVehicle.id);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
const deleteRepairRecord = (repairId) => {
|
||||
if (window.confirm('Are you sure you want to delete this repair record?')) {
|
||||
VehicleRepairService.deleteVehicleRepair(repairId).then(() => {
|
||||
fetchRepairRecords(currentVehicle.id);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const getFileDownloadUrl = (fileUrl) => {
|
||||
const baseUrl = require('../../http-common').default?.defaults?.baseURL || '';
|
||||
return baseUrl.replace('/api', '') + fileUrl;
|
||||
}
|
||||
|
||||
// Custom styles for react-select
|
||||
const selectStyles = {
|
||||
@@ -565,202 +449,6 @@ const UpdateVehicle = () => {
|
||||
</div>
|
||||
</Tab>
|
||||
|
||||
<Tab eventKey="documents" title="Documents & Records">
|
||||
{/* ===== Yearly Inspection ===== */}
|
||||
<h6 className="text-primary">Yearly Inspection</h6>
|
||||
{existingYearlyFiles.length > 0 && (
|
||||
<table className="table table-sm table-bordered mb-3">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>File Name</th>
|
||||
<th>Action</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{existingYearlyFiles.map((file, idx) => (
|
||||
<tr key={idx}>
|
||||
<td>{file.name}</td>
|
||||
<td>
|
||||
<a href={getFileDownloadUrl(file.url)} target="_blank" rel="noopener noreferrer" className="btn btn-link btn-sm p-0">Download</a>
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
)}
|
||||
<div className="app-main-content-fields-section">
|
||||
<div className="me-4">
|
||||
<div className="field-label">Yearly Inspection Date</div>
|
||||
<DatePicker
|
||||
selected={yearlyInspectionDate}
|
||||
onChange={(date) => setYearlyInspectionDate(date)}
|
||||
dateFormat="MM/dd/yyyy"
|
||||
placeholderText="e.g., 03/01/2024"
|
||||
className="form-control"
|
||||
/>
|
||||
</div>
|
||||
<div className="me-4">
|
||||
<div className="field-label">Yearly Inspection File</div>
|
||||
<label className="custom-file-upload">
|
||||
<Upload width={20} color={"#fff"} className="me-2"></Upload> Upload
|
||||
<input type="file" onChange={(e) => setSelectedYearlyFile(e.target.files[0])}/>
|
||||
</label>
|
||||
<div className="file-name">{selectedYearlyFile?.name}</div>
|
||||
</div>
|
||||
<div className="me-4 d-flex align-items-end">
|
||||
<button className="btn btn-primary btn-sm" onClick={() => saveYearlyInspection()}>Add Yearly Inspection</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* ===== Monthly Inspection ===== */}
|
||||
<h6 className="text-primary mt-4">Monthly Inspection</h6>
|
||||
{existingMonthlyFiles.length > 0 && (
|
||||
<table className="table table-sm table-bordered mb-3">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>File Name</th>
|
||||
<th>Action</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{existingMonthlyFiles.map((file, idx) => (
|
||||
<tr key={idx}>
|
||||
<td>{file.name}</td>
|
||||
<td>
|
||||
<a href={getFileDownloadUrl(file.url)} target="_blank" rel="noopener noreferrer" className="btn btn-link btn-sm p-0">Download</a>
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
)}
|
||||
<div className="app-main-content-fields-section">
|
||||
<div className="me-4">
|
||||
<div className="field-label">Monthly Inspection Date</div>
|
||||
<DatePicker
|
||||
selected={monthlyInspectionDate}
|
||||
onChange={(date) => setMonthlyInspectionDate(date)}
|
||||
dateFormat="MM/dd/yyyy"
|
||||
placeholderText="e.g., 03/01/2024"
|
||||
className="form-control"
|
||||
/>
|
||||
</div>
|
||||
<div className="me-4">
|
||||
<div className="field-label">Monthly Inspection File</div>
|
||||
<label className="custom-file-upload">
|
||||
<Upload width={20} color={"#fff"} className="me-2"></Upload> Upload
|
||||
<input type="file" onChange={(e) => setSelectedMonthlyFile(e.target.files[0])}/>
|
||||
</label>
|
||||
<div className="file-name">{selectedMonthlyFile?.name}</div>
|
||||
</div>
|
||||
<div className="me-4 d-flex align-items-end">
|
||||
<button className="btn btn-primary btn-sm" onClick={() => saveMonthlyInspection()}>Add Monthly Inspection</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* ===== Repair & Maintenance Record ===== */}
|
||||
<h6 className="text-primary mt-4">Repair & Maintenance Record</h6>
|
||||
{existingRepairs.length > 0 && (
|
||||
<table className="table table-sm table-bordered mb-3">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Part Name</th>
|
||||
<th>Date</th>
|
||||
<th>Mileage</th>
|
||||
<th>Qty</th>
|
||||
<th>Cost</th>
|
||||
<th>Location</th>
|
||||
<th>Next Reminder</th>
|
||||
<th>Action</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{existingRepairs.map((repair) => (
|
||||
<tr key={repair.id}>
|
||||
<td>{REPAIR_PART_NAME_TEXT[repair.part_name] || repair.repair_description || repair.part_name || '-'}</td>
|
||||
<td>{repair.repair_date || '-'}</td>
|
||||
<td>{repair.mileage_at_replacement || '-'}</td>
|
||||
<td>{repair.quantity || '-'}</td>
|
||||
<td>{repair.repair_price || '-'}</td>
|
||||
<td>{repair.repair_location || '-'}</td>
|
||||
<td>{repair.next_replacement_reminder || '-'}</td>
|
||||
<td>
|
||||
<button className="btn btn-link btn-sm p-0 text-danger" onClick={() => deleteRepairRecord(repair.id)}>
|
||||
<Trash size={14} />
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
)}
|
||||
<div className="app-main-content-fields-section">
|
||||
<div className="me-4">
|
||||
<div className="field-label">Part Name</div>
|
||||
<select value={repairPartName} onChange={e => setRepairPartName(e.target.value)}>
|
||||
<option value="">Select...</option>
|
||||
{Object.keys(REPAIR_PART_NAME).map(key => (
|
||||
<option key={key} value={REPAIR_PART_NAME[key]}>{REPAIR_PART_NAME_TEXT[REPAIR_PART_NAME[key]]}</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
<div className="me-4">
|
||||
<div className="field-label">Replacement Date</div>
|
||||
<DatePicker
|
||||
selected={repairReplacementDate}
|
||||
onChange={(date) => setRepairReplacementDate(date)}
|
||||
dateFormat="MM/dd/yyyy"
|
||||
placeholderText="e.g., 03/01/2024"
|
||||
className="form-control"
|
||||
/>
|
||||
</div>
|
||||
<div className="me-4">
|
||||
<div className="field-label">Mileage at Replacement</div>
|
||||
<input type="text" placeholder="e.g., 48,000" value={repairMileage} onChange={e => setRepairMileage(e.target.value)}/>
|
||||
</div>
|
||||
<div className="me-4">
|
||||
<div className="field-label">Quantity</div>
|
||||
<select value={repairQuantity} onChange={e => setRepairQuantity(e.target.value)}>
|
||||
<option value="">Select...</option>
|
||||
{QUANTITY_OPTIONS.map(opt => (
|
||||
<option key={opt.value} value={opt.value}>{opt.label}</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div className="app-main-content-fields-section">
|
||||
<div className="me-4">
|
||||
<div className="field-label">Cost</div>
|
||||
<input type="text" placeholder="e.g., $250.00" value={repairCost} onChange={e => setRepairCost(e.target.value)}/>
|
||||
</div>
|
||||
<div className="me-4">
|
||||
<div className="field-label">Location</div>
|
||||
<input type="text" placeholder="e.g., Rockville Auto Center" value={repairLocation} onChange={e => setRepairLocation(e.target.value)}/>
|
||||
</div>
|
||||
<div className="me-4">
|
||||
<div className="field-label">Receipt Upload</div>
|
||||
<label className="custom-file-upload">
|
||||
<Upload width={20} color={"#fff"} className="me-2"></Upload> Upload
|
||||
<input type="file" onChange={(e) => setRepairReceiptFile(e.target.files[0])}/>
|
||||
</label>
|
||||
<div className="file-name">{repairReceiptFile?.name}</div>
|
||||
</div>
|
||||
<div className="me-4">
|
||||
<div className="field-label">Next Replacement Reminder</div>
|
||||
<input type="text" placeholder="e.g., 78,000" value={repairNextReminder} onChange={e => setRepairNextReminder(e.target.value)}/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{error && <div className="col-md-12 mb-4 alert alert-danger" role="alert">
|
||||
{error}
|
||||
</div>}
|
||||
<div className="list row mb-5">
|
||||
<div className="col-md-12 col-sm-12 col-xs-12">
|
||||
<button className="btn btn-default btn-sm float-right" onClick={() => redirectTo()}> Cancel </button>
|
||||
<button className="btn btn-primary btn-sm float-right" onClick={() => saveRepair()}> Add Repair Record </button>
|
||||
</div>
|
||||
</div>
|
||||
</Tab>
|
||||
</Tabs>
|
||||
<div className="list-func-panel">
|
||||
<button className="btn btn-primary" onClick={() => triggerShowDeleteModal()}><Archive size={16} className="me-2"></Archive>Archive</button>
|
||||
|
||||
@@ -311,7 +311,7 @@ const ViewVehicle = () => {
|
||||
<td className="td-checkbox"><input type="checkbox" checked={selectedItemsMonthly.includes(doc?.url)} onClick={() => toggleItemMonthly(doc?.url)}/></td>
|
||||
<td className="td-index">{index + 1}</td>
|
||||
<td>
|
||||
<PencilSquare size={16} className="clickable me-2" onClick={() => goToEdit(currentVehicle?.id)}></PencilSquare>
|
||||
<PencilSquare size={14} className="clickable me-2" onClick={() => navigate(`/vehicles/${currentVehicle?.id}/inspections/monthly/edit?fileName=${encodeURIComponent(doc?.name)}&date=${encodeURIComponent(doc?.inspectionDate || '')}`)} />
|
||||
<a className="btn btn-link btn-sm" href={doc?.url} target="_blank">{doc?.name}</a>
|
||||
</td>
|
||||
<td>{doc?.inspectionDate}</td>
|
||||
@@ -345,7 +345,7 @@ const ViewVehicle = () => {
|
||||
<td className="td-checkbox"><input type="checkbox" checked={selectedItemsYearly.includes(doc?.url)} onClick={() => toggleItemYearly(doc?.url)}/></td>
|
||||
<td className="td-index">{index + 1}</td>
|
||||
<td>
|
||||
<PencilSquare size={16} className="clickable me-2" onClick={() => goToEdit(currentVehicle?.id)}></PencilSquare>
|
||||
<PencilSquare size={14} className="clickable me-2" onClick={() => navigate(`/vehicles/${currentVehicle?.id}/inspections/yearly/edit?fileName=${encodeURIComponent(doc?.name)}&date=${encodeURIComponent(doc?.inspectionDate || '')}`)} />
|
||||
<a className="btn btn-link btn-sm" href={doc?.url} target="_blank">{doc?.name}</a>
|
||||
</td>
|
||||
<td>{doc?.inspectionDate}</td>
|
||||
@@ -378,7 +378,10 @@ const ViewVehicle = () => {
|
||||
<tr key={repair.id}>
|
||||
<td className="td-checkbox"><input type="checkbox" checked={selectedItemsRepair.includes(repair?.id)} onClick={() => toggleItemRepair(repair?.id)}/></td>
|
||||
<td className="td-index">{index + 1}</td>
|
||||
<td>{REPAIR_PART_NAME_TEXT[repair?.part_name] || repair?.part_name || repair?.repair_description}</td>
|
||||
<td>
|
||||
<PencilSquare size={14} className="clickable me-2" onClick={() => navigate(`/vehicles/${currentVehicle?.id}/repairs/edit/${repair?.id}`)} />
|
||||
{REPAIR_PART_NAME_TEXT[repair?.part_name] || repair?.part_name || repair?.repair_description}
|
||||
</td>
|
||||
<td>{repair?.repair_date}</td>
|
||||
<td>{repair?.mileage_at_replacement}</td>
|
||||
<td>{repair?.quantity}</td>
|
||||
@@ -514,31 +517,37 @@ const ViewVehicle = () => {
|
||||
</Tab>
|
||||
|
||||
<Tab eventKey="documents" title="Documents & Records">
|
||||
<h6 className="text-primary">Yearly Inspection</h6>
|
||||
<div className="d-flex justify-content-between align-items-center mb-3">
|
||||
<h6 className="text-primary mb-0">Yearly Inspection</h6>
|
||||
<button className="btn btn-primary btn-sm" onClick={() => navigate(`/vehicles/${currentVehicle?.id}/inspections/yearly/add`)}>+ Add Yearly Inspection</button>
|
||||
</div>
|
||||
{tableYearly}
|
||||
<h6 className="text-primary">Monthly Inspection</h6>
|
||||
<div className="d-flex justify-content-between align-items-center mb-3 mt-4">
|
||||
<h6 className="text-primary mb-0">Monthly Inspection</h6>
|
||||
<button className="btn btn-primary btn-sm" onClick={() => navigate(`/vehicles/${currentVehicle?.id}/inspections/monthly/add`)}>+ Add Monthly Inspection</button>
|
||||
</div>
|
||||
{tableMonthly}
|
||||
</Tab>
|
||||
|
||||
<Tab eventKey="repairRecords" title="Repair & Maintenance">
|
||||
<div className="d-flex justify-content-between align-items-center mb-3 mt-4">
|
||||
<h6 className="text-primary mb-0">Repair & Maintenance Record</h6>
|
||||
<button className="btn btn-primary btn-sm" onClick={() => navigate(`/vehicles/${currentVehicle?.id}/repairs/add`)}>+ Add Repair Record</button>
|
||||
</div>
|
||||
{tableRepair}
|
||||
</Tab>
|
||||
</Tabs>
|
||||
<div className="list-func-panel">
|
||||
{(currentTab === 'documents' || currentTab === 'repairRecords') && (
|
||||
<input className="me-2 with-search-icon" type="text" placeholder="Search" value={keyword} onChange={(e) => setKeyword(e.currentTarget.value)} />
|
||||
)}
|
||||
{currentTab === 'documents' && (
|
||||
<button className="btn btn-primary" onClick={() => download()}><Download size={16} className="me-2"></Download>Download</button>
|
||||
<>
|
||||
<input className="me-2 with-search-icon" type="text" placeholder="Search" value={keyword} onChange={(e) => setKeyword(e.currentTarget.value)} />
|
||||
<Export
|
||||
columns={columnsRepair}
|
||||
data={filteredRepairs}
|
||||
filename={`vehicle-${currentVehicle?.vehicle_number}-repairs`}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
{currentTab === 'repairRecords' && (
|
||||
<Export
|
||||
columns={columnsRepair}
|
||||
data={filteredRepairs}
|
||||
filename={`vehicle-${currentVehicle?.vehicle_number}-repairs`}
|
||||
/>
|
||||
{currentTab !== 'documents' && (
|
||||
<button className="btn btn-primary ms-2" onClick={() => goToEdit(currentVehicle?.id)}><PencilSquare className="me-2" size={16}></PencilSquare>Edit</button>
|
||||
)}
|
||||
<button className="btn btn-primary ms-2" onClick={() => goToEdit(currentVehicle?.id)}><PencilSquare className="me-2" size={16}></PencilSquare>Edit</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -5,6 +5,9 @@ const getAll = (vehicle) => {
|
||||
const createNewVehicleRepair = (data) => {
|
||||
return http.post('/vehicle-repairs', data);
|
||||
};
|
||||
const updateVehicleRepair = (id, data) => {
|
||||
return http.put(`/vehicle-repairs/${id}`, data);
|
||||
};
|
||||
const deleteVehicleRepair = (id) => {
|
||||
return http.delete(`/vehicle-repairs/${id}`);
|
||||
};
|
||||
@@ -12,5 +15,6 @@ const deleteVehicleRepair = (id) => {
|
||||
export const VehicleRepairService = {
|
||||
getAll,
|
||||
createNewVehicleRepair,
|
||||
updateVehicleRepair,
|
||||
deleteVehicleRepair
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user