Added method to upload and delete documents from service records.

This commit is contained in:
ivancheahhh
2024-01-02 15:15:25 -07:00
parent 30df01ba96
commit 3a3f216ddf
12 changed files with 178 additions and 17 deletions

2
.gitignore vendored
View File

@@ -3,3 +3,5 @@ bin/
obj/ obj/
wwwroot/images/ wwwroot/images/
cartracker.db cartracker.db
wwwroot/documents/
wwwroot/temp/

View File

@@ -7,31 +7,52 @@ using static System.Net.Mime.MediaTypeNames;
using System.Drawing; using System.Drawing;
using System.Linq.Expressions; using System.Linq.Expressions;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using CarCareTracker.Helper;
namespace CarCareTracker.Controllers namespace CarCareTracker.Controllers
{ {
public class FilesController : Controller public class FilesController : Controller
{ {
private readonly ILogger<FilesController> _logger; private readonly ILogger<FilesController> _logger;
private readonly IVehicleDataAccess _dataAccess;
private readonly IWebHostEnvironment _webEnv; private readonly IWebHostEnvironment _webEnv;
private readonly IFileHelper _fileHelper;
public FilesController(ILogger<FilesController> logger, IWebHostEnvironment webEnv) public FilesController(ILogger<FilesController> logger, IFileHelper fileHelper, IWebHostEnvironment webEnv)
{ {
_logger = logger; _logger = logger;
_webEnv = webEnv; _webEnv = webEnv;
_fileHelper = fileHelper;
} }
[HttpPost] [HttpPost]
public IActionResult HandleFileUpload(IFormFile file) public IActionResult HandleFileUpload(IFormFile file)
{ {
var fileName = UploadImage(file); var fileName = UploadFile(file);
return Json(fileName); return Json(fileName);
} }
private string UploadImage(IFormFile fileToUpload) [HttpPost]
public IActionResult HandleMultipleFileUpload(List<IFormFile> file)
{ {
string uploadDirectory = "images/"; List<UploadedFiles> uploadedFiles = new List<UploadedFiles>();
foreach (IFormFile fileToUpload in file)
{
var fileName = UploadFile(fileToUpload);
uploadedFiles.Add(new UploadedFiles { Name = fileToUpload.FileName, Location = fileName});
}
return Json(uploadedFiles);
}
[HttpPost]
public ActionResult DeleteFiles(string fileLocation)
{
var result = _fileHelper.DeleteFile(fileLocation);
return Json(result);
}
private string UploadFile(IFormFile fileToUpload)
{
string uploadDirectory = "temp/";
string uploadPath = Path.Combine(_webEnv.WebRootPath, uploadDirectory); string uploadPath = Path.Combine(_webEnv.WebRootPath, uploadDirectory);
if (!Directory.Exists(uploadPath)) if (!Directory.Exists(uploadPath))
Directory.CreateDirectory(uploadPath); Directory.CreateDirectory(uploadPath);

View File

@@ -7,6 +7,7 @@ using static System.Net.Mime.MediaTypeNames;
using System.Drawing; using System.Drawing;
using System.Linq.Expressions; using System.Linq.Expressions;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using CarCareTracker.Helper;
namespace CarCareTracker.Controllers namespace CarCareTracker.Controllers
{ {
@@ -14,13 +15,13 @@ namespace CarCareTracker.Controllers
{ {
private readonly ILogger<HomeController> _logger; private readonly ILogger<HomeController> _logger;
private readonly IVehicleDataAccess _dataAccess; private readonly IVehicleDataAccess _dataAccess;
private readonly IWebHostEnvironment _webEnv; private readonly IFileHelper _fileHelper;
public HomeController(ILogger<HomeController> logger, IVehicleDataAccess dataAccess, IWebHostEnvironment webEnv) public HomeController(ILogger<HomeController> logger, IVehicleDataAccess dataAccess, IFileHelper fileHelper)
{ {
_logger = logger; _logger = logger;
_dataAccess = dataAccess; _dataAccess = dataAccess;
_webEnv = webEnv; _fileHelper = fileHelper;
} }
public IActionResult Index() public IActionResult Index()
@@ -41,6 +42,8 @@ namespace CarCareTracker.Controllers
{ {
try try
{ {
//move image from temp folder to images folder.
vehicleInput.ImageLocation = _fileHelper.MoveFileFromTemp(vehicleInput.ImageLocation, "images/");
//save vehicle. //save vehicle.
var result = _dataAccess.SaveVehicle(vehicleInput); var result = _dataAccess.SaveVehicle(vehicleInput);
return Json(result); return Json(result);

View File

@@ -8,6 +8,7 @@ using System.Drawing;
using System.Linq.Expressions; using System.Linq.Expressions;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using CarCareTracker.External.Implementations; using CarCareTracker.External.Implementations;
using CarCareTracker.Helper;
namespace CarCareTracker.Controllers namespace CarCareTracker.Controllers
{ {
@@ -18,12 +19,14 @@ namespace CarCareTracker.Controllers
private readonly INoteDataAccess _noteDataAccess; private readonly INoteDataAccess _noteDataAccess;
private readonly IServiceRecordDataAccess _serviceRecordDataAccess; private readonly IServiceRecordDataAccess _serviceRecordDataAccess;
private readonly IWebHostEnvironment _webEnv; private readonly IWebHostEnvironment _webEnv;
private readonly IFileHelper _fileHelper;
public VehicleController(ILogger<HomeController> logger, IVehicleDataAccess dataAccess, INoteDataAccess noteDataAccess, IServiceRecordDataAccess serviceRecordDataAccess, IWebHostEnvironment webEnv) public VehicleController(ILogger<HomeController> logger, IFileHelper fileHelper, IVehicleDataAccess dataAccess, INoteDataAccess noteDataAccess, IServiceRecordDataAccess serviceRecordDataAccess, IWebHostEnvironment webEnv)
{ {
_logger = logger; _logger = logger;
_dataAccess = dataAccess; _dataAccess = dataAccess;
_noteDataAccess = noteDataAccess; _noteDataAccess = noteDataAccess;
_fileHelper = fileHelper;
_serviceRecordDataAccess = serviceRecordDataAccess; _serviceRecordDataAccess = serviceRecordDataAccess;
_webEnv = webEnv; _webEnv = webEnv;
} }
@@ -70,6 +73,8 @@ namespace CarCareTracker.Controllers
[HttpPost] [HttpPost]
public IActionResult SaveServiceRecordToVehicleId(ServiceRecordInput serviceRecord) public IActionResult SaveServiceRecordToVehicleId(ServiceRecordInput serviceRecord)
{ {
//move files from temp.
serviceRecord.Files = serviceRecord.Files.Select(x => { return new UploadedFiles { Name = x.Name, Location = _fileHelper.MoveFileFromTemp(x.Location, "documents/") }; }).ToList();
var result = _serviceRecordDataAccess.SaveServiceRecordToVehicle(serviceRecord.ToServiceRecord()); var result = _serviceRecordDataAccess.SaveServiceRecordToVehicle(serviceRecord.ToServiceRecord());
return Json(result); return Json(result);
} }
@@ -82,7 +87,6 @@ namespace CarCareTracker.Controllers
public IActionResult GetServiceRecordForEditById(int serviceRecordId) public IActionResult GetServiceRecordForEditById(int serviceRecordId)
{ {
var result = _serviceRecordDataAccess.GetServiceRecordById(serviceRecordId); var result = _serviceRecordDataAccess.GetServiceRecordById(serviceRecordId);
//retrieve uploaded files.
//convert to Input object. //convert to Input object.
var convertedResult = new ServiceRecordInput { Id = result.Id, var convertedResult = new ServiceRecordInput { Id = result.Id,
Cost = result.Cost, Cost = result.Cost,
@@ -90,7 +94,8 @@ namespace CarCareTracker.Controllers
Description = result.Description, Description = result.Description,
Mileage = result.Mileage, Mileage = result.Mileage,
Notes = result.Notes, Notes = result.Notes,
VehicleId = result.VehicleId VehicleId = result.VehicleId,
Files = result.Files
}; };
return PartialView("_ServiceRecordModal", convertedResult); return PartialView("_ServiceRecordModal", convertedResult);
} }

57
Helper/FileHelper.cs Normal file
View File

@@ -0,0 +1,57 @@
namespace CarCareTracker.Helper
{
public interface IFileHelper
{
public string MoveFileFromTemp(string currentFilePath, string newFolder);
public bool DeleteFile(string currentFilePath);
}
public class FileHelper: IFileHelper
{
private readonly IWebHostEnvironment _webEnv;
public FileHelper(IWebHostEnvironment webEnv)
{
_webEnv = webEnv;
}
public string MoveFileFromTemp(string currentFilePath, string newFolder)
{
string tempPath = "temp/";
if (!currentFilePath.StartsWith("/temp/")) //file is not in temp directory.
{
return currentFilePath;
}
if (currentFilePath.StartsWith("/")) {
currentFilePath = currentFilePath.Substring(1);
}
string uploadPath = Path.Combine(_webEnv.WebRootPath, newFolder);
string oldFilePath = Path.Combine(_webEnv.WebRootPath, currentFilePath);
if (!Directory.Exists(uploadPath))
Directory.CreateDirectory(uploadPath);
string newFileUploadPath = oldFilePath.Replace(tempPath, newFolder);
if (File.Exists(oldFilePath))
{
File.Move(oldFilePath, newFileUploadPath);
}
string newFilePathToReturn = "/" + currentFilePath.Replace(tempPath, newFolder);
return newFilePathToReturn;
}
public bool DeleteFile(string currentFilePath)
{
if (currentFilePath.StartsWith("/"))
{
currentFilePath = currentFilePath.Substring(1);
}
string filePath = Path.Combine(_webEnv.WebRootPath, currentFilePath);
if (File.Exists(filePath))
{
File.Delete(filePath);
}
if (!File.Exists(filePath)) //verify file no longer exists.
{
return true;
} else
{
return false;
}
}
}
}

View File

@@ -9,5 +9,6 @@
public string Description { get; set; } public string Description { get; set; }
public decimal Cost { get; set; } public decimal Cost { get; set; }
public string Notes { get; set; } public string Notes { get; set; }
public List<UploadedFiles> Files { get; set; }
} }
} }

View File

@@ -9,7 +9,7 @@
public string Description { get; set; } public string Description { get; set; }
public decimal Cost { get; set; } public decimal Cost { get; set; }
public string Notes { get; set; } public string Notes { get; set; }
public List<string> Files { get; set; } = new List<string>(); public List<UploadedFiles> Files { get; set; } = new List<UploadedFiles>();
public ServiceRecord ToServiceRecord() { return new ServiceRecord { Id = Id, VehicleId = VehicleId, Date = DateTime.Parse(Date), Cost = Cost, Mileage = Mileage, Description = Description, Notes = Notes }; } public ServiceRecord ToServiceRecord() { return new ServiceRecord { Id = Id, VehicleId = VehicleId, Date = DateTime.Parse(Date), Cost = Cost, Mileage = Mileage, Description = Description, Notes = Notes, Files = Files }; }
} }
} }

8
Models/UploadedFiles.cs Normal file
View File

@@ -0,0 +1,8 @@
namespace CarCareTracker.Models
{
public class UploadedFiles
{
public string Name { get; set; }
public string Location { get; set; }
}
}

View File

@@ -1,5 +1,6 @@
using CarCareTracker.External.Implementations; using CarCareTracker.External.Implementations;
using CarCareTracker.External.Interfaces; using CarCareTracker.External.Interfaces;
using CarCareTracker.Helper;
var builder = WebApplication.CreateBuilder(args); var builder = WebApplication.CreateBuilder(args);
@@ -8,6 +9,7 @@ builder.Services.AddControllersWithViews();
builder.Services.AddSingleton<IVehicleDataAccess, VehicleDataAccess>(); builder.Services.AddSingleton<IVehicleDataAccess, VehicleDataAccess>();
builder.Services.AddSingleton<INoteDataAccess, NoteDataAccess>(); builder.Services.AddSingleton<INoteDataAccess, NoteDataAccess>();
builder.Services.AddSingleton<IServiceRecordDataAccess, ServiceRecordDataAccess>(); builder.Services.AddSingleton<IServiceRecordDataAccess, ServiceRecordDataAccess>();
builder.Services.AddSingleton<IFileHelper, FileHelper>();
var app = builder.Build(); var app = builder.Build();

View File

@@ -7,6 +7,7 @@
<div class="form-group"> <div class="form-group">
<div class="row"> <div class="row">
<div class="col-md-6 col-12"> <div class="col-md-6 col-12">
<input type="text" id="workAroundInput" style="height:0px; width:0px; display:none;">
<label for="serviceRecordDate">Date</label> <label for="serviceRecordDate">Date</label>
<div class="input-group"> <div class="input-group">
<input type="text" id="serviceRecordDate" class="form-control" value="@Model.Date"> <input type="text" id="serviceRecordDate" class="form-control" value="@Model.Date">
@@ -22,6 +23,26 @@
<div class="col-md-6 col-12"> <div class="col-md-6 col-12">
<label for="serviceRecordNotes">Notes(optional)</label> <label for="serviceRecordNotes">Notes(optional)</label>
<textarea id="serviceRecordNotes" class="form-control" rows="5">@Model.Notes</textarea> <textarea id="serviceRecordNotes" class="form-control" rows="5">@Model.Notes</textarea>
@if (Model.Files.Any())
{
<div>
<label>Uploaded Documents</label>
@foreach (UploadedFiles filesUploaded in Model.Files)
{
<div class="d-flex justify-content-between">
<a type="button" class="btn btn-link" href="@filesUploaded.Location" target="_blank">@filesUploaded.Name</a>
<button type="button" class="btn btn-sm btn-outline-danger" onclick="deleteServiceRecordFile('@filesUploaded.Location', this)"><i class="bi bi-trash"></i></button>
</div>
}
<label for="serviceRecordFiles">Upload more documents</label>
<input onChange="uploadServiceRecordFilesAsync()" type="file" multiple accept=".png,.jpg,.jpeg,.pdf,.xls,.xlsx,.docx" class="form-control-file" id="serviceRecordFiles">
</div>
}
else
{
<label for="serviceRecordFiles">Upload documents(optional)</label>
<input onChange="uploadServiceRecordFilesAsync()" type="file" multiple accept=".png,.jpg,.jpeg,.pdf,.xls,.xlsx,.docx" class="form-control-file" id="serviceRecordFiles">
}
</div> </div>
</div> </div>
</div> </div>
@@ -36,12 +57,25 @@
@if (Model.Id == 0) @if (Model.Id == 0)
{ {
<button type="button" id="addServiceRecordButton" class="btn btn-primary" onclick="saveServiceRecordToVehicle()">Add New Service Record</button> <button type="button" id="addServiceRecordButton" class="btn btn-primary" onclick="saveServiceRecordToVehicle()">Add New Service Record</button>
} else if (Model.Id > 0) }
else if (Model.Id > 0)
{ {
<button type="button" id="editServiceRecordButton" class="btn btn-primary" onclick="saveServiceRecordToVehicle(true)">Edit Service Record</button> <button type="button" id="editServiceRecordButton" class="btn btn-primary" onclick="saveServiceRecordToVehicle(true)">Edit Service Record</button>
} }
</div> </div>
<script> <script>
var uploadedFiles = [];
getUploadedFilesFromModel();
function getUploadedFilesFromModel() {
@foreach (UploadedFiles filesUploaded in Model.Files)
{
@:uploadedFiles.push({ name: "@filesUploaded.Name", location: "@filesUploaded.Location" });
}
}
function deleteServiceRecordFile(fileLocation, event) {
event.parentElement.remove();
uploadedFiles = uploadedFiles.filter(x => x.location != fileLocation);
}
function getAndValidateServiceRecordValues() { function getAndValidateServiceRecordValues() {
var serviceDate = $("#serviceRecordDate").val(); var serviceDate = $("#serviceRecordDate").val();
var serviceMileage = $("#serviceRecordMileage").val(); var serviceMileage = $("#serviceRecordMileage").val();
@@ -51,7 +85,7 @@
var vehicleId = GetVehicleId().vehicleId; var vehicleId = GetVehicleId().vehicleId;
//validation //validation
var hasError = false; var hasError = false;
if (serviceDate.trim() == '' || !isDate(serviceDate)) { //eliminates whitespace. if (serviceDate.trim() == '') { //eliminates whitespace.
hasError = true; hasError = true;
$("#serviceRecordDate").addClass("is-invalid"); $("#serviceRecordDate").addClass("is-invalid");
} else { } else {
@@ -83,7 +117,8 @@
mileage: serviceMileage, mileage: serviceMileage,
description: serviceDescription, description: serviceDescription,
cost: serviceCost, cost: serviceCost,
notes: serviceNotes notes: serviceNotes,
files: uploadedFiles
} }
} }
</script> </script>

View File

@@ -1,6 +1,10 @@
@model List<ServiceRecord> @model List<ServiceRecord>
<div class="row"> <div class="row">
<div class="d-flex flex-row-reverse"> <div class="d-flex justify-content-between">
<div class="d-flex align-items-center">
<span class="badge bg-primary">@($"Total: {Model.Sum(x=>x.Cost).ToString("C")}")</span>
<span class="ms-2 badge bg-success">@($"# of Service Records: {Model.Count()}")</span>
</div>
<div> <div>
<button onclick="showAddServiceRecordModal()" class="btn btn-primary btn-md mt-1 mb-1"><i class="bi bi-pencil-square"></i>Add Service Record</button> <button onclick="showAddServiceRecordModal()" class="btn btn-primary btn-md mt-1 mb-1"><i class="bi bi-pencil-square"></i>Add Service Record</button>
</div> </div>

View File

@@ -81,6 +81,7 @@ function hideAddServiceRecordModal() {
$('#serviceRecordModal').modal('hide'); $('#serviceRecordModal').modal('hide');
} }
function deleteServiceRecord(serviceRecordId) { function deleteServiceRecord(serviceRecordId) {
$("#workAroundInput").show();
Swal.fire({ Swal.fire({
title: "Confirm Deletion?", title: "Confirm Deletion?",
text: "Deleted Service Records cannot be restored.", text: "Deleted Service Records cannot be restored.",
@@ -99,6 +100,8 @@ function deleteServiceRecord(serviceRecordId) {
errorToast("An error has occurred, please try again later."); errorToast("An error has occurred, please try again later.");
} }
}); });
} else {
$("#workAroundInput").hide();
} }
}); });
} }
@@ -120,4 +123,24 @@ function saveServiceRecordToVehicle(isEdit) {
errorToast("An error has occurred, please try again later."); errorToast("An error has occurred, please try again later.");
} }
}) })
}
function uploadServiceRecordFilesAsync() {
let formData = new FormData();
var files = $("#serviceRecordFiles")[0].files;
for (var x = 0; x < files.length; x++) {
formData.append("file", files[x]);
}
$.ajax({
url: "/Files/HandleMultipleFileUpload",
data: formData,
cache: false,
processData: false,
contentType: false,
type: 'POST',
success: function (response) {
if (response.length > 0) {
uploadedFiles.push.apply(uploadedFiles, response);
}
}
});
} }