added sfloader for file uploads.
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -5,3 +5,4 @@ wwwroot/images/
|
|||||||
cartracker.db
|
cartracker.db
|
||||||
wwwroot/documents/
|
wwwroot/documents/
|
||||||
wwwroot/temp/
|
wwwroot/temp/
|
||||||
|
wwwroot/imports/
|
||||||
|
|||||||
@@ -11,6 +11,7 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<PackageReference Include="CsvHelper" Version="30.0.1" />
|
||||||
<PackageReference Include="LiteDB" Version="5.0.17" />
|
<PackageReference Include="LiteDB" Version="5.0.17" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
|||||||
@@ -9,6 +9,9 @@ using System.Linq.Expressions;
|
|||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using CarCareTracker.External.Implementations;
|
using CarCareTracker.External.Implementations;
|
||||||
using CarCareTracker.Helper;
|
using CarCareTracker.Helper;
|
||||||
|
using CsvHelper;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
namespace CarCareTracker.Controllers
|
namespace CarCareTracker.Controllers
|
||||||
{
|
{
|
||||||
@@ -157,6 +160,56 @@ namespace CarCareTracker.Controllers
|
|||||||
}
|
}
|
||||||
return PartialView("_Gas", computedResults);
|
return PartialView("_Gas", computedResults);
|
||||||
}
|
}
|
||||||
|
[HttpGet]
|
||||||
|
public IActionResult GetBulkImportModalPartialView(string mode)
|
||||||
|
{
|
||||||
|
return PartialView("_BulkDataImporter", mode);
|
||||||
|
}
|
||||||
|
[HttpPost]
|
||||||
|
public IActionResult ImportToVehicleIdFromCsv(int vehicleId, string mode, string fileName)
|
||||||
|
{
|
||||||
|
var fullFileName = _fileHelper.GetFullFilePath(fileName);
|
||||||
|
if (vehicleId == default || string.IsNullOrWhiteSpace(fullFileName))
|
||||||
|
{
|
||||||
|
return Json(false);
|
||||||
|
}
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using (var reader = new StreamReader(fullFileName))
|
||||||
|
{
|
||||||
|
var config = new CsvHelper.Configuration.CsvConfiguration(System.Globalization.CultureInfo.InvariantCulture);
|
||||||
|
config.MissingFieldFound = null;
|
||||||
|
config.HeaderValidated = null;
|
||||||
|
using (var csv = new CsvReader(reader, config))
|
||||||
|
{
|
||||||
|
if (mode == "gas")
|
||||||
|
{
|
||||||
|
var records = csv.GetRecords<GasRecordImport>().ToList();
|
||||||
|
if (records.Any())
|
||||||
|
{
|
||||||
|
foreach (GasRecordImport gasRecordToInsert in records)
|
||||||
|
{
|
||||||
|
var convertedGasRecord = new GasRecord()
|
||||||
|
{
|
||||||
|
VehicleId = vehicleId,
|
||||||
|
Date = gasRecordToInsert.Date,
|
||||||
|
Mileage = gasRecordToInsert.Odometer,
|
||||||
|
Gallons = gasRecordToInsert.FuelConsumed,
|
||||||
|
Cost = gasRecordToInsert.Cost
|
||||||
|
};
|
||||||
|
_gasRecordDataAccess.SaveGasRecordToVehicle(convertedGasRecord);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Json(true);
|
||||||
|
} catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Error Occurred While Bulk Inserting");
|
||||||
|
return Json(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
[HttpPost]
|
[HttpPost]
|
||||||
public IActionResult SaveGasRecordToVehicleId(GasRecordInput gasRecord)
|
public IActionResult SaveGasRecordToVehicleId(GasRecordInput gasRecord)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ namespace CarCareTracker.External.Implementations
|
|||||||
using (var db = new LiteDatabase(dbName))
|
using (var db = new LiteDatabase(dbName))
|
||||||
{
|
{
|
||||||
var table = db.GetCollection<GasRecord>(tableName);
|
var table = db.GetCollection<GasRecord>(tableName);
|
||||||
var gasRecords = table.Find(Query.EQ(nameof(GasRecord.VehicleId), vehicleId)).OrderBy(x => x.Date);
|
var gasRecords = table.Find(Query.EQ(nameof(GasRecord.VehicleId), vehicleId)).OrderBy(x => x.Date).ThenBy(x=>x.Mileage);
|
||||||
return gasRecords.ToList() ?? new List<GasRecord>();
|
return gasRecords.ToList() ?? new List<GasRecord>();
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
{
|
{
|
||||||
public interface IFileHelper
|
public interface IFileHelper
|
||||||
{
|
{
|
||||||
|
string GetFullFilePath(string currentFilePath);
|
||||||
public string MoveFileFromTemp(string currentFilePath, string newFolder);
|
public string MoveFileFromTemp(string currentFilePath, string newFolder);
|
||||||
public bool DeleteFile(string currentFilePath);
|
public bool DeleteFile(string currentFilePath);
|
||||||
}
|
}
|
||||||
@@ -12,6 +13,21 @@
|
|||||||
{
|
{
|
||||||
_webEnv = webEnv;
|
_webEnv = webEnv;
|
||||||
}
|
}
|
||||||
|
public string GetFullFilePath(string currentFilePath)
|
||||||
|
{
|
||||||
|
if (currentFilePath.StartsWith("/"))
|
||||||
|
{
|
||||||
|
currentFilePath = currentFilePath.Substring(1);
|
||||||
|
}
|
||||||
|
string oldFilePath = Path.Combine(_webEnv.WebRootPath, currentFilePath);
|
||||||
|
if (File.Exists(oldFilePath))
|
||||||
|
{
|
||||||
|
return oldFilePath;
|
||||||
|
} else
|
||||||
|
{
|
||||||
|
return string.Empty;
|
||||||
|
}
|
||||||
|
}
|
||||||
public string MoveFileFromTemp(string currentFilePath, string newFolder)
|
public string MoveFileFromTemp(string currentFilePath, string newFolder)
|
||||||
{
|
{
|
||||||
string tempPath = "temp/";
|
string tempPath = "temp/";
|
||||||
|
|||||||
10
Models/GasRecord/GasRecordImport.cs
Normal file
10
Models/GasRecord/GasRecordImport.cs
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
namespace CarCareTracker.Models
|
||||||
|
{
|
||||||
|
public class GasRecordImport
|
||||||
|
{
|
||||||
|
public DateTime Date { get; set; }
|
||||||
|
public int Odometer { get; set; }
|
||||||
|
public decimal FuelConsumed { get; set; }
|
||||||
|
public decimal Cost { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -16,7 +16,7 @@
|
|||||||
<button class="nav-link active" id="garage-tab" data-bs-toggle="tab" data-bs-target="#garage-tab-pane" type="button" role="tab" aria-selected="true"><i class="bi bi-car-front me-2"></i>Garage</button>
|
<button class="nav-link active" id="garage-tab" data-bs-toggle="tab" data-bs-target="#garage-tab-pane" type="button" role="tab" aria-selected="true"><i class="bi bi-car-front me-2"></i>Garage</button>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-item ms-auto" role="presentation">
|
<li class="nav-item ms-auto" role="presentation">
|
||||||
<button class="nav-link" id="help-tab" data-bs-toggle="tab" data-bs-target="#help-tab-pane" type="button" role="tab" aria-selected="false"><i class="bi bi-gear me-2"></i>Help</button>
|
<button class="nav-link" id="help-tab" data-bs-toggle="tab" data-bs-target="#help-tab-pane" type="button" role="tab" aria-selected="false"><i class="bi bi-question-circle me-2"></i>Help</button>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
<div class="tab-content" id="homeTab">
|
<div class="tab-content" id="homeTab">
|
||||||
|
|||||||
@@ -71,6 +71,12 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="modal fade" id="bulkImportModal" tabindex="-1" role="dialog">
|
||||||
|
<div class="modal-dialog" role="document">
|
||||||
|
<div class="modal-content" id="bulkImportModalContent">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<script>
|
<script>
|
||||||
function GetVehicleId() {
|
function GetVehicleId() {
|
||||||
return { vehicleId: @Model.Id};
|
return { vehicleId: @Model.Id};
|
||||||
|
|||||||
55
Views/Vehicle/_BulkDataImporter.cshtml
Normal file
55
Views/Vehicle/_BulkDataImporter.cshtml
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
@model string
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title">Import Data from CSV</h5>
|
||||||
|
<button type="button" class="btn-close" onclick="hideBulkImportModal()" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<form>
|
||||||
|
<div class="form-group">
|
||||||
|
<div class="form-group">
|
||||||
|
<div class="col-12">
|
||||||
|
<div class="alert alert-warning" role="alert">
|
||||||
|
In order for this utility to function properly, your CSV file MUST be formatted exactly like the provided sample.
|
||||||
|
Dates must be supplied in a string.
|
||||||
|
Numbers must be supplied as numbers without currency formatting.
|
||||||
|
</div>
|
||||||
|
<div class="alert alert-danger" role="alert">
|
||||||
|
Failure to format the data correctly can cause data corruption. Please make sure you make a copy of the local database before proceeding.
|
||||||
|
</div>
|
||||||
|
@if (Model == "gas")
|
||||||
|
{
|
||||||
|
<a class="btn btn-link" href="/defaults/gassample.csv" target="_blank">Download Sample</a>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mt-2">
|
||||||
|
<div class="col-12">
|
||||||
|
<label for="csvFileUploader">Upload CSV File</label>
|
||||||
|
<input onChange="uploadFileAsync(this)" type="file" multiple accept=".csv" class="form-control-file" id="csvFileUploader">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-secondary" onclick="hideBulkImportModal()">Cancel</button>
|
||||||
|
<button type="button" onclick="importFromCsv()" class="btn btn-primary">Import</button>
|
||||||
|
</div>
|
||||||
|
<script>
|
||||||
|
var uploadedFile = "";
|
||||||
|
function importFromCsv() {
|
||||||
|
var vehicleId = GetVehicleId().vehicleId;
|
||||||
|
var mode = "@Model";
|
||||||
|
$.post('/Vehicle/ImportToVehicleIdFromCsv', { vehicleId: vehicleId, mode: mode, fileName: uploadedFile }, function (data) {
|
||||||
|
if (data) {
|
||||||
|
successToast("Data Imported Successfully");
|
||||||
|
hideBulkImportModal();
|
||||||
|
if (mode == "gas") {
|
||||||
|
getVehicleGasRecords();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
errorToast("An error has occurred, please double check the data and try again.");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@@ -12,8 +12,14 @@
|
|||||||
<span class="ms-2 badge bg-success">@($"Total Fuel Consumed: {Model.Sum(x=>x.Gallons)}")</span>
|
<span class="ms-2 badge bg-success">@($"Total Fuel Consumed: {Model.Sum(x=>x.Gallons)}")</span>
|
||||||
<span class="ms-2 badge bg-success">@($"Total Cost: {Model.Sum(x => x.Cost).ToString("C")}")</span>
|
<span class="ms-2 badge bg-success">@($"Total Cost: {Model.Sum(x => x.Cost).ToString("C")}")</span>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div class="btn-group">
|
||||||
<button onclick="showAddGasRecordModal()" class="btn btn-primary btn-md mt-1 mb-1"><i class="bi bi-pencil-square me-2"></i>Add Gas Record</button>
|
<button onclick="showAddGasRecordModal()" class="btn btn-primary btn-md mt-1 mb-1"><i class="bi bi-pencil-square me-2"></i>Add Gas Record</button>
|
||||||
|
<button type="button" class="btn btn-md btn-primary btn-md mt-1 mb-1 dropdown-toggle dropdown-toggle-split" data-bs-toggle="dropdown" aria-expanded="false">
|
||||||
|
<span class="visually-hidden">Toggle Dropdown</span>
|
||||||
|
</button>
|
||||||
|
<ul class="dropdown-menu">
|
||||||
|
<li><a class="dropdown-item" href="#" onclick="showBulkImportModal('gas')">Import via CSV</a></li>
|
||||||
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -22,11 +22,11 @@
|
|||||||
@if (!string.IsNullOrWhiteSpace(Model.ImageLocation))
|
@if (!string.IsNullOrWhiteSpace(Model.ImageLocation))
|
||||||
{
|
{
|
||||||
<label for="inputImage">Replace picture(optional)</label>
|
<label for="inputImage">Replace picture(optional)</label>
|
||||||
<input onChange="uploadFileAsync()" type="file" accept=".png,.jpg,.jpeg" class="form-control-file" id="inputImage">
|
<input onChange="uploadFileAsync(this)" type="file" accept=".png,.jpg,.jpeg" class="form-control-file" id="inputImage">
|
||||||
} else
|
} else
|
||||||
{
|
{
|
||||||
<label for="inputImage">Upload a picture(optional)</label>
|
<label for="inputImage">Upload a picture(optional)</label>
|
||||||
<input onChange="uploadFileAsync()" type="file" accept=".png,.jpg,.jpeg" class="form-control-file" id="inputImage">
|
<input onChange="uploadFileAsync(this)" type="file" accept=".png,.jpg,.jpeg" class="form-control-file" id="inputImage">
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
3
wwwroot/defaults/gassample.csv
Normal file
3
wwwroot/defaults/gassample.csv
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
Date,Odometer,FuelConsumed,Cost
|
||||||
|
5/8/2020,204836,8.331,16.24
|
||||||
|
5/30/2020,205056,11.913,25.72
|
||||||
|
@@ -90,9 +90,10 @@ function saveVehicle(isEdit) {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
function uploadFileAsync() {
|
function uploadFileAsync(event) {
|
||||||
let formData = new FormData();
|
let formData = new FormData();
|
||||||
formData.append("file", $("#inputImage")[0].files[0]);
|
formData.append("file", event.files[0]);
|
||||||
|
sloader.show();
|
||||||
$.ajax({
|
$.ajax({
|
||||||
url: "/Files/HandleFileUpload",
|
url: "/Files/HandleFileUpload",
|
||||||
data: formData,
|
data: formData,
|
||||||
@@ -101,6 +102,7 @@ function uploadFileAsync() {
|
|||||||
contentType: false,
|
contentType: false,
|
||||||
type: 'POST',
|
type: 'POST',
|
||||||
success: function (response) {
|
success: function (response) {
|
||||||
|
sloader.hide();
|
||||||
if (response.trim() != '') {
|
if (response.trim() != '') {
|
||||||
uploadedFile = response;
|
uploadedFile = response;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -96,6 +96,17 @@ function editVehicle(vehicleId) {
|
|||||||
function hideEditVehicleModal() {
|
function hideEditVehicleModal() {
|
||||||
$('#editVehicleModal').modal('hide');
|
$('#editVehicleModal').modal('hide');
|
||||||
}
|
}
|
||||||
|
function showBulkImportModal(mode) {
|
||||||
|
$.get(`/Vehicle/GetBulkImportModalPartialView?mode=${mode}`, function (data) {
|
||||||
|
if (data) {
|
||||||
|
$("#bulkImportModalContent").html(data);
|
||||||
|
$("#bulkImportModal").modal('show');
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
function hideBulkImportModal(){
|
||||||
|
$("#bulkImportModal").modal('hide');
|
||||||
|
}
|
||||||
function deleteVehicle(vehicleId) {
|
function deleteVehicle(vehicleId) {
|
||||||
Swal.fire({
|
Swal.fire({
|
||||||
title: "Confirm Deletion?",
|
title: "Confirm Deletion?",
|
||||||
|
|||||||
Reference in New Issue
Block a user